- Hosting panel (Namecheap cPanel) → Backup → Download a Full MySQL Database Backup.
- Save to
SJ Master Backup/DB-Snapshots/{date}-prod.sql.gz.
- Verify file size is reasonable (not 0 bytes, not absurdly small).
- For a quick logical backup via WP-CLI:
wp db export prod-{date}.sql --add-drop-table.
- 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.
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.
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.
Six events, all in sj-crm-stripe/sj-crm-stripe.php → handle_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.
Use only if Stripe webhook fails or for comped accounts.
- Network Admin → Sites → Add New.
- Site Address: customer’s chosen slug.
- Site Title: business name.
- Admin Email: customer’s email.
- Save.
- Edit the new site → Plugins tab → activate sj-crm and any modules they paid for.
- Add row to
wpp3_sj_crm_tenants with the new blog_id and Stripe customer ID (if any).
- Email the customer their login link.
For comped accounts, still create a Stripe customer (no card) so future upgrades work cleanly.
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.
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).