The three payment modes
Whether you’re moving real money is decided by your payment-gateway keys, not by your Recurso API key. Recurso surfaces the current mode asgateway_mode:
Which mode am I in?
Ask the server.GET /version is unauthenticated and returns the mode:
none, you’re fully sandboxed on the mock gateway. If you see live,
stop and point your tests at a non-production instance. The dashboard shows a
Test mode chip driven by this same value.
Sandbox mode (mock gateway)
Run Recurso with noSTRIPE_SECRET_KEY or RAZORPAY_KEY_ID set and every
payment path is served by the mock gateway. This is the fastest way to wire up
your integration — create plans, subscribe customers, generate invoices, and
drive the full lifecycle without any provider account.
What the mock gateway does:
Orders & checkout
CreateOrder returns a mock order_… id. Checkout verification succeeds.Mandates (UPI)
CreateMandate returns a mock token and a mock authorization URL; mandate
debits succeed.Refunds
Refunds return status
processed immediately.Retries
Retry attempts succeed ~40% of the time with random decline codes — see
Testing dunning.
Forcing a payment failure
To exercise your failure handling on the checkout verify path, pass the literal signaturefail — the mock gateway will reject it:
Gateway test mode
When you’re ready to test real gateway behavior (3-D Secure, actual UPI collect requests, provider webhooks), configure test keys and the mode flips totest:
- Stripe (cards)
- Razorpay (UPI & cards)
Any future expiry and any CVC. Full list in Stripe’s testing docs.
Testing webhooks
Two directions of webhooks are worth testing: Inbound (gateway → Recurso). Stripe and Razorpay call back to/webhooks/stripe and /webhooks/razorpay; Recurso verifies the signature on
every event. To receive them on a local instance, use the Stripe CLI
(stripe listen --forward-to localhost:8080/webhooks/stripe) or a tunnel such as
ngrok for Razorpay, and set the matching webhook secret in your env.
Outbound (Recurso → your app). Register an endpoint, then use the delivery
log to inspect and replay:
Testing dunning and retries
On the mock gateway, retry attempts succeed ~40% of the time and otherwise return a random decline code (insufficient_funds, card_expired,
card_declined, processing_error). That’s enough randomness to watch the smart
retry engine schedule attempts, escalate a dunning campaign, and eventually mark
an invoice recovered or exhausted. Create a subscription, let an invoice go past
due, and observe the retry schedule and
recovered-revenue attribution. See
Smart Retry for how the bandit picks retry timing.
Idempotency in tests
Every mutating request accepts anIdempotency-Key header. Reusing the same key
returns the original result instead of creating a duplicate — so a test that
retries a flaky network call won’t double-charge or double-create:
Loading and resetting test data
The demo dataset gives you a tenant with plans, customers, subscriptions, and invoices to poke at:sk_test_12345. Emails the system “sends” land in Mailhog at
http://localhost:8025 instead of real inboxes.
A pre-launch testing checklist
1
Sandbox the happy path
On
gateway_mode: none, drive plan → customer → subscription → invoice →
payment end-to-end.2
Exercise failures
Force a verify failure (
signature: fail), let a retry cycle run, and confirm
your app handles declines and past-due states.3
Switch to gateway test mode
Add
sk_test/rzp_test keys and repeat with real test cards and UPI, in both
INR and a non-INR currency.4
Verify webhooks both ways
Confirm inbound gateway events are accepted and your outbound endpoint
receives and can replay events.
5
Go live deliberately
Only then follow Going to Production to switch to live
keys.