Payment providers (Razorpay / Cashfree)
Configure Razorpay or Cashfree once — encrypted keys, webhook URLs, test vs live, and the health check that proves it works.
Payment providers (Razorpay / Cashfree)
Commercial billing talks to one active provider at a time. Configure it at Billing → Providers & Settings (/super-admin/billing/commercial-providers). Keys are encrypted at rest and never echoed back — this page shows the flow, not the secrets.
Who this is for
Super Admins and the person who owns the payment provider account.
- Difficulty: Advanced
- Time: 15 min
- Prerequisites: A Razorpay or Cashfree merchant account; access to set webhooks in that account; the platform's public URL.
Step 1 — Choose the provider and environment
Under Commercial Billing:
| Setting | Options | Notes |
|---|---|---|
| Enable commercial billing | On / Off | When off, AutoPay is not offered and no provider API calls are made |
| Provider | Razorpay / Cashfree | The single active provider for commercial billing |
| Environment | Sandbox / Production | Sandbox for testing; switch after go-live |
| Grace period (days) | default 7 (max 90) | How long a failed payment waits before escalating — see Payment failures & restrictions |
Test with sandbox keys first Sandbox credentials cannot move real money, and production credentials can. Complete the full agree → authorize → renew → fail-recover cycle in Sandbox before switching to Production.
Step 2 — Enter the credentials
Razorpay
The Razorpay fields use the same credentials as the platform's checkout — they are shared, not duplicated. Enter them here or on the Billing Settings page (/super-admin/billing-settings-page); both read the same stored values:
- Key ID
- Key Secret
- Webhook Secret
Cashfree
- App ID
- Secret Key
- Webhook Secret
All secret fields behave the same way: stored encrypted at rest, never rendered back, and a blank field keeps the stored value. The helper text says it exactly: "Already saved — leave blank to keep it unchanged."
No secrets are echoed — on purpose You cannot view a saved key or secret from this page. If one is lost, generate a new one at the provider and paste it in. Never paste credentials into support tickets or docs.
Step 3 — Register the webhook URLs at the provider
Both endpoints are on the API (no auth, signature-verified, throttled):
| Provider | Webhook URL |
|---|---|
| Razorpay | https://<your-domain>/api/webhooks/razorpay |
| Cashfree | https://<your-domain>/api/webhooks/cashfree |
Signature schemes (register the matching secret above):
- Razorpay — header
X-Razorpay-Signature: hex HMAC-SHA256 of the raw request body, keyed with your webhook secret. - Cashfree — header
x-webhook-signature: Base64 HMAC-SHA256 ofx-webhook-timestamp . rawBody, keyed with your secret.
How webhooks are processed Request → signature verification → idempotent event row (unique provider + event ID, SHA-256 payload hash) → queued processing → billing service. Invalid signature returns 400 and stores nothing. An unconfigured provider returns 200 and logs. Unknown event types are stored as processed with an unmapped error and never dispatched.
Canonical events understood by the platform: subscription.authorized, subscription.charged, subscription.cancelled, subscription.halted, payment.captured, payment.failed, mandate.revoked, mandate.expired.
Step 4 — Run the health check
Click Health check → confirm. The platform performs a lightweight server-side check against the selected provider with the saved credentials. Green means the keys work and the API is reachable; a failure message tells you which side is wrong.
Step 5 — Verify activation end to end
The only proof that everything is wired: a client approves an agreement, authorizes AutoPay at the provider, and the agreement turns Active — driven by the provider webhook, not by the browser coming back. If the health check is green but activation never happens, the webhook URL or webhook secret is wrong.
Expected result
The provider is enabled, keys validate, webhooks arrive, and an agreement can reach Active through a provider-confirmed authorization.
Common mistakes
- Enabling Production with Sandbox keys (or vice versa) — authorization will fail.
- Setting only the API key pair and forgetting the webhook secret — payments succeed but agreements never activate.
- Duplicating Razorpay credentials on the Commercial page thinking they are separate from checkout. They are shared.
- Lowering the grace period to 0 days. Escalation can then restrict a client the day after a failure; the default 7 exists for a reason.
- Testing webhooks without a public URL. Localhost callbacks need a tunnel; on production the webhook URL must match the live domain.
Troubleshooting
| Symptom | What to check |
|---|---|
| Health check fails | Environment, key pair, provider account status |
| Client authorizes but agreement stays pending | Webhook URL reachable + webhook secret correct |
| Webhook returns 400 | Signature mismatch — secret from the wrong environment |
| Duplicate payment records | Should be impossible by event ID uniqueness; check for two different event IDs from the provider |
| Switching providers affects old agreements | Each agreement stores its provider; existing mandates stay with their provider |
Technical details
- Page:
App\Filament\SuperAdmin\Pages\Commercial\CommercialProviders(slugbilling/commercial-providers); settings viaApp\Settings\BillingSettings— secrets#[ShouldBeEncrypted], never echoed. - Controller:
App\Http\Controllers\Api\CommercialBillingWebhookController→App\Jobs\ProcessCommercialWebhook→BillingService::handleProviderEvent(). - Provider adapters:
app/Billing/Commercial/*; managerCommercialProviderManager::healthCheck(). - Routes:
POST /api/webhooks/razorpay(namecommercial.webhook.razorpay) andPOST /api/webhooks/cashfree(namecommercial.webhook.cashfree),throttle:240,1. - Environment:
commercial_environment(sandbox | production) on the same settings page.
Related guides
Was this page helpful?
Thanks — noted. ✦