[ADMIN] How to back up the production database

  1. Hosting panel (Namecheap cPanel) → Backup → Download a Full MySQL Database Backup.
  2. Save to SJ Master Backup/DB-Snapshots/{date}-prod.sql.gz.
  3. Verify file size is reasonable (not 0 bytes, not absurdly small).
  4. For a quick logical backup via WP-CLI: wp db export prod-{date}.sql --add-drop-table.
  5. Daily automated backups also run via the host — keep 30 days of those.

Always run a manual backup before a plugin update or schema change.

[ADMIN] How to rotate the Stripe webhook signing secret

  1. Stripe Dashboard → Developers → Webhooks → pick the endpoint.
  2. Click Roll secret. Stripe shows the new secret once — copy it now.
  3. WordPress Network Admin → SJ CRM Stripe → Settings → paste new secret → Save.
  4. From Stripe, click Send test webhook on any event — it should return 200.
  5. Verify in debug.log the signature passed.
  6. Old secret remains valid for 24 hours, but rotate the WP setting immediately to keep production aligned.

[ADMIN] How to wire up screen contexts in sj-crm

For contextual Help tabs on actual CRM admin screens, map admin screen IDs to shh screen contexts via the filter shh_screen_context_for_admin.

Example mapping (in sj-crm or a small mu-plugin):

add_filter('shh_screen_context_for_admin', function($ctx, $screen) {
    $map = array(
        'toplevel_page_sj-crm'              => 'dashboard',
        'sj-crm_page_sj-crm-contacts'       => 'contacts',
        'sj-crm_page_sj-crm-businesses'     => 'businesses',
        'sj-crm_page_sj-crm-projects'       => 'projects',
        'sj-crm_page_sj-crm-items'          => 'items',
        'sj-crm_page_sj-crm-health'         => 'health',
        'sj-crm_page_sj-crm-rentals'        => 'rentals',
        'sj-crm_page_sj-crm-subscription'   => 'billing',
        'profile'                           => 'account',
    );
    return $map[$screen->id] ?? $ctx;
}, 10, 2);

Once this is wired, the contextual Help tab on each CRM screen pulls only entries tagged with that screen.

[ADMIN] Where are tenant subsites provisioned?

Tenant subsites are auto-created by sj-crm-stripe on successful checkout via the checkout.session.completed webhook.

Flow: Stripe Checkout → webhook fires → SJ_CRM_Stripe_Tenant_Provisioner::create_tenant_site()wpmu_create_blog() with slug from form → sj-crm plugins activated on the new blog → admin user assigned.

Subsites live at sjcrmapp.com/{slug}/. The blog_id and stripe_customer_id are stored together in wpp3_sj_crm_tenants.

[ADMIN] How to deploy a plugin update to production

  1. Bump version in plugin header AND in the VERSION constant. Both must match.
  2. Update projects-map.json with new version.
  3. Add changelog entry to plugin’s readme.txt or CHANGELOG.md.
  4. Run a manual DB backup (Network Admin → Tools or hosting panel).
  5. Zip the plugin folder — user does this, not the agent.
  6. Upload via SFTP or Network Admin → Plugins → Upload.
  7. Network Activate (or activate per-site if it’s a single-tenant module).
  8. Smoke-test on one subsite: load each main screen, save one record.
  9. Purge LiteSpeed Cache: LiteSpeed Cache → Toolbox → Purge All.
  10. Log in SJ Journal: what changed, version bumped, any issues.

[ADMIN] What Stripe webhook events are handled?

Six events, all in sj-crm-stripe/sj-crm-stripe.phphandle_webhook():

  • checkout.session.completed — provisions tenant site
  • invoice.paid — extends subscription period
  • invoice.payment_failed — sends dunning email, flags account
  • customer.subscription.updated — syncs plan changes
  • customer.subscription.deleted — deactivates tenant (does not delete data)
  • checkout.session.expired — cleans up unfinished signups

Webhook signing secret is in wp_options → sj_crm_stripe_webhook_secret.

[ADMIN] How to manually provision a tenant subsite

Use only if Stripe webhook fails or for comped accounts.

  1. Network Admin → Sites → Add New.
  2. Site Address: customer’s chosen slug.
  3. Site Title: business name.
  4. Admin Email: customer’s email.
  5. Save.
  6. Edit the new site → Plugins tab → activate sj-crm and any modules they paid for.
  7. Add row to wpp3_sj_crm_tenants with the new blog_id and Stripe customer ID (if any).
  8. Email the customer their login link.

For comped accounts, still create a Stripe customer (no card) so future upgrades work cleanly.

[ADMIN] What’s the difference between cancelling and deactivating a tenant?

Cancelled = subscription ended, but the subsite still exists and data is retained for 30 days. User can resubscribe to reactivate.

Deactivated = tenant flag set, login blocked at the subsite, but database rows still present. Triggered automatically by customer.subscription.deleted.

Deleted = manual super-admin action. Run after the 30-day grace period for non-resubscribed accounts. Use Network Admin → Sites → Delete.

Never delete a tenant without confirming the customer is past the grace period and has been emailed.

[ADMIN] How to investigate a failed Stripe webhook

  1. Stripe Dashboard → Developers → Webhooks → pick the endpoint.
  2. Find the failed delivery (status Failed or Pending retry).
  3. View the response body — look for PHP error or non-200 status.
  4. Cross-reference with wp-content/debug.log at the same timestamp.
  5. Common causes: signing secret mismatch, plugin deactivated, fatal error in webhook handler, 502 from server.
  6. Once fixed, click Resend in Stripe to replay the event.
  7. Verify the side effect ran (tenant created, subscription extended, etc.).

[ADMIN] Where are error logs?

WordPress debug log: wp-content/debug.log on the live server. Read-only via AI Connect Bridge: get_error_log operation.

Stripe logs: Stripe Dashboard → Developers → Logs (filter by webhook URL).

Server PHP errors: hosting control panel → Error Logs (Namecheap cPanel).

LiteSpeed Cache logs: wp-content/litespeed/debug.log when debug mode is enabled (only enable temporarily — large file).