Zodiac Tech CRM Docs

Payments failing

Fix failed payments and billing states — AutoPay problems, provider configuration, webhook delivery, reconciliation alerts and the grace-to-suspension lifecycle.

admin super-admin Medium ⏱ 20 min Updated 2026-10-05

Payments failing

A payment failed, AutoPay will not set up, or a workspace got restricted/suspended over billing. The commercial billing stack has two sides: the workspace (paying, fixing a failed method) and the super admin (provider configuration, webhook health, reconciliation). Identify which side the problem is on before changing anything.

Who this is for: workspace admins getting billing right, and super admins running commercial billing.

Problem

A charge did not go through, a subscription state changed against expectations, or the tenant portal shows a payment problem that will not clear.

What you see

Symptom Where
Payment-failed messaging and a retry/re-check flow Tenant Admin → Billing (/admin/billing)
Redirect to /admin/subscription-required (reason=suspended) Tenant users after suspension
Restriction to the billing page only Tenant during restriction (e.g. trial expired)
AutoPay shows unapproved, or REQUIRES_NEW_AUTHORIZATION Admin → Billing → Agreement (/admin/billing/agreement)
payment_failures / grace_period / restricted / suspended counts rising Super Admin Billing → Commercial (/super-admin/billing/commercial)
A reconciliation alert on the billing overview Billing overview → Needs Attention (BillingReconciliationAlert, raised every 30 minutes)
Webhook deliveries absent or rejected Provider dashboard / server log

Likely causes

  • The payment method itself: card declined, expired, insufficient limit, or bank-side block.
  • AutoPay mandate not approved — a mandate that needs a new authorisation shows as REQUIRES_NEW_AUTHORIZATION and must be re-authorised, not "retried".
  • Provider configuration drift (super admin): wrong environment (sandbox vs production), wrong or rotated keys, or a webhook secret that no longer matches — the classic cause of "money moved but the CRM does not know".
  • Webhooks not delivered: provider cannot reach POST /api/webhooks/razorpay or POST /api/webhooks/cashfree (firewall, DNS, cron.php/.htaccess weirdness on shared hosts).
  • Status drift: provider and CRM disagree — exactly what the reconciliation alert exists to flag.
  • Lifecycle timing: even a fixed payment does not teleport the workspace out of suspension instantly — lifecycle jobs run on a daily schedule.

Quick fix

  1. Workspace: open Admin → Billing (/admin/billing), read the banner, and complete the offered action — usually re-authorising AutoPay or paying the open invoice. The page's "re-check" flow re-queries the provider; use it before panicking.
  2. Super admin: open Super Admin → Billing → Commercial (/super-admin/billing/commercial) → Payments (/super-admin/billing/commercial-payments) and find the failed attempt; the provider's message is attached.
  3. Clear reconciliation alerts deliberately — each one records a real disagreement between provider and CRM.

Step-by-step fix

  1. Classify the failure. Workspace-level (a person must act: pay, re-authorise) or platform-level (configuration/webhooks). If several workspaces fail at once, assume platform-level and go to step 3.
  2. Workspace actions:
    • Admin → Billing shows the current state and the required action. Pay the outstanding invoice under Billing → Invoices (/admin/billing/invoices); review history under Billing → Payments (/admin/billing/payments).
    • AutoPay: check the agreement status. REQUIRES_NEW_AUTHORIZATION means the old mandate is dead — set up AutoPay again from the billing home.
    • After acting, use the re-check action so the CRM asks the provider for fresh status rather than waiting for the next reconciliation pass.
  3. Provider configuration (super admin): Billing → Commercial Providers (/super-admin/billing/commercial-providers). Verify the enabled toggle (commercial_billing_enabled), the provider (razorpay or cashfree), the environment (sandbox vs production — check twice), keys (Razorpay key id/secret; Cashfree app id/secret/webhook secret), and grace_period_days (default 7).
  4. Webhooks: confirm the provider is configured to send webhooks to this install's POST /api/webhooks/razorpay / POST /api/webhooks/cashfree, and that they are being accepted — signatures are verified, and duplicates are ignored safely (idempotent processing, tracked for 90 days). A silent webhook endpoint means the CRM never learns the payment succeeded; the reconciliation alert then fires.
  5. Understand the lifecycle: when a payment stays failed, the workspace moves grace → restriction → suspension. These transitions run on the daily schedule — grace application 07:15, restriction 07:30, and the subscription lifecycle sweep at 06:00 — so a fix applied today may not reflect until the next run. Do not "fix" it by manual database edits.
  6. Suspension behavior to expect (and to explain): logged-in users are redirected to /admin/subscription-required, and mobile/sign-in might still work while the workspace is blocked at the door — login itself stays allowed so people can reach the billing pages. Once paid and reconciled, access returns.
  7. Invoices and renewals: renewal invoices are generated on schedule (06:30) and recurring invoices processed at 07:00 (invoices:process-recurring); upcoming renewals are checked at the same time. If an invoice is missing, it may simply not be due yet — check the next renewal date first.

Do not chase single events — read the alert queue One payment failure is an incident for one customer. Several simultaneous failures, or a reconciliation alert that keeps re-raising, is a platform problem. Treat them differently.

Admin fix

  • Tenant admins can do everything on /admin/billing and sub-pages: pay, re-authorise AutoPay, use re-check, review invoices/payments.
  • If the billing page offers nothing to click, the decision is not theirs to make — the workspace owner/super admin must verify the provider configuration.

Developer fix

  • Webhooks: POST /api/webhooks/razorpay and POST /api/webhooks/cashfree; signature-verified; idempotency via processed_billing_events (retained 90 days — duplicates arriving late are ignored, not double-counted).
  • Reconciliation: runs every 30 minutes; disagreements become BillingReconciliationAlert rows shown on the billing overview's "Needs Attention".
  • Lifecycle: leadhub:subscription-lifecycle daily 06:00; ApplyGracePeriod 07:15; ApplyRestriction 07:30; ApplyRestriction/suspension behavior enforced by EnforceNotSuspended + EnforceSubscription with state from TenantSubscriptionState.
  • Suspension redirect target: /admin/subscription-required with reason=suspended; when reconciling a workspace state, check what the provider actually says before overriding anything.

Logs to check

  • storage/logs/laravel.log — webhook signature failures, provider API errors, reconciliation errors.
  • Billing → Commercial → Payments — per-attempt provider messages.
  • processed_billing_events row timestamps — confirm whether the provider's webhook reached you at all.
  • Provider dashboards (Razorpay/Cashfree) — the ground truth on charge attempts and webhook delivery.

When to escalate

  • Webhooks are delivered by the provider but rejected by the server — secret mismatch or signature bug; a developer should compare payload and signature.
  • Money collected but the workspace never reactivated after a reconciliation cycle — treat as SEV1, escalate to the super admin with the payment id.
  • Multiple workspaces failing simultaneously — platform-level (provider config, keys, webhook endpoint), escalate immediately.

Was this page helpful?