Payment Connectors
Connect hosted payment gateways to QuotaStack while keeping money movement, subscription state, credits, usage, and entitlements under explicit ownership.
Quick take
- Each checkout explicitly names one connection and one mapped QuotaStack target
- Multiple sandbox or live connections can coexist, but there is no automatic gateway failover
- Only a verified successful provider payment can grant paid credits or advance a paid subscription period
- Hosted-checkout redirects are for customer experience — never proof of payment
- Moving an active subscription to another gateway requires cancellation plus a new checkout and mandate
Payment Connectors
A payment connector lets QuotaStack start a hosted checkout. It turns a proven payment into a top-up or prepaid plan change. It is not a new credit ledger. It also does not make QuotaStack the seller of record.
Dodo Payments is the first provider. The same model can work with more providers. You can keep more than one provider account. Your credits, usage, and access rules do not change.
Who owns each task
| Task | Owner |
|---|---|
| Checkout, tax, payment method, SCA, and each charge | Payment provider |
| Proof that a charge was paid | Signed provider event or provider API |
| Product map, billing state, and plan history | QuotaStack |
| Credits, held funds, usage, and access rules | QuotaStack |
| Charge dates and retries | Payment provider |
| New paid terms and credit grants | QuotaStack, after proof of payment |
QuotaStack never gets card or bank details. The provider hosts checkout and takes each charge. QuotaStack locks the provider keys at rest. It keeps provider IDs so you can match records. Core billing gets a small set of payment facts.
Pick a connection
A connection is one provider account in one QuotaStack environment. You may add many connections. Each checkout must name one connection_id.
A checkout also names one QuotaStack target:
topup_packagefor a one-time credit purchaseplan_variantfor a fixed prepaid plan
QuotaStack gets the provider product from a checked map. The checkout call cannot change the product, amount, cash type, or credit count.
QuotaStack does not move a charge to a new provider. If checkout times out, repeat the identical POST body with the same Idempotency-Key. QuotaStack can then return the saved result. Use the returned checkout ID and poll its status. Never use a new idempotency key or another provider while the first result is not known. That could charge the user twice.
QuotaStack never autonomously attempts a charge. The tenant application starts one-time and first-subscription checkout. For an existing recurring subscription, Dodo schedules and attempts the charge; QuotaStack only reacts to the signed result.
What counts as paid
There are two kinds of success:
- QuotaStack made a checkout and got its hosted URL.
- The provider later said the charge was paid.
Only the second event can start a paid state. Return and cancel URLs help the user. They are not proof of payment.
When a provider calls back, QuotaStack does this work:
- It verifies the signature against the raw body.
- During setup, QuotaStack records the Dodo business ID.
- Each signed callback must have a
business_idthat matches it. - QuotaStack rejects a mismatch before the durable inbox.
- It saves the event and sends a fast reply.
- It checks that the event ID and payment ID are new.
- It finds the link, user, product map, checkout, and bound plan.
- It changes credits or plan state only after it can prove payment.
Other events may change payment or review state. They do not grant paid credits. If proof or the product map is missing, QuotaStack grants nothing. The event stays in view. An admin can fix it and retry.
Top-up flow
For a mapped top-up pack, your app starts checkout:
POST /v1/payment-checkouts
Idempotency-Key: checkout:topup:order_123
After proof of payment, QuotaStack saves the amount and cash type. It then grants the credits in the mapped pack. Your app does not turn cash into credits with its own rule.
You can still use POST /v1/topups/grant in a manual flow. In that flow, your webhook checks the payment and calls QuotaStack. A connector uses its own paid grant path. It does not call the public grant route.
Prepaid plan flow
For a mapped prepaid plan variant:
- Dodo hosts checkout and takes the first charge.
- Proof of payment makes or finds one pending QuotaStack subscription.
- QuotaStack applies each first grant with a stable key.
- The plan turns on only after all grants work.
- Dodo sets the next charge dates and owns retries.
- Each paid renewal gets one lock for that plan and term.
- QuotaStack grants once, then starts the new term.
Plan events can sync states such as active, on hold, canceled, or expired. Only payment.succeeded can prove a paid credit grant.
Connector plans block manual POST /v1/subscriptions/{id}/renew. They also block a local planned downgrade. Both calls get 409 Conflict. The QuotaStack job cannot start a paid term without proof from the provider.
Cancel or change a provider
Use the normal plan cancel route. For a bound plan, QuotaStack first asks its provider to cancel:
cancel_immediately: falseasks to stop at the next charge date.cancel_immediately: trueasks to stop now.
When the provider accepts, the local state moves to canceling. A signed last event sets the end state.
You cannot edit a live plan to use a new connection. The payment rule belongs to the provider that made it. To move, cancel the old plan. Then start a new checkout and payment rule. The first release does not move mandates, split fees, or retry on a new provider.
One ledger for all providers
All provider connections feed the same QuotaStack tools:
- one credit ledger and burn order;
- one plan history;
- one usage and access system;
- one payment ID space per connection.
Do not turn on provider credits, meters, or access rules. Do not map a Dodo-managed trial. Do not map a QuotaStack plan variant with a free trial. Those options would make two tools own the same facts. The provider product sets the cash price. The QuotaStack target sets what the user gets.
Mix manual and connector flows
You can use a manual flow for some goods and a connector for others. Keep each path clear:
| Flow | Who checks payment? | How state changes |
|---|---|---|
| Manual top-up | Your webhook code | Your server calls POST /v1/topups/grant |
| Manual prepaid renew | Your payment code | Your server calls POST /v1/subscriptions/{id}/renew |
| Connector top-up | QuotaStack callback code | QuotaStack grants the mapped pack |
| Connector prepaid renew | QuotaStack callback code | QuotaStack runs one locked renew |
Never call a manual grant or renew route as a backup for a connector charge. That would make a second path for the same paid change.
Safe retry and repair
Provider events can come twice, late, or out of order. The connector uses:
- event IDs to ignore the same event twice;
- payment IDs to apply each paid change once;
- stable keys for top-ups and paid terms;
- a set retry count for short faults;
- a failed-event list for bad setup or maps;
- repair jobs for old checkouts and live links.
Turning off new connector use does not drop events for work in flight. Signed events for open checkouts and bound plans still run. This keeps a paid charge from being left with no result.
Try Dodo
Follow the Dodo payment connector cookbook. It shows how to set up test and live links, map goods, and start checkout.
Common mistakes
Don't treat checkout creation or a return redirect as payment proof
A customer can abandon checkout, a payment can remain processing, and redirect parameters are client-controlled. Financial effects wait for a verified provider event.
Don't let both the provider and QuotaStack own credits or usage meters
Two ledgers will drift. Connector products collect money; QuotaStack remains the sole authority for credit blocks, metering, and entitlements.
Don't silently move an active subscription between gateways
Mandates and recurring payment methods belong to a provider. The MVP requires an explicit cancellation and a new checkout on the new connection.
Loading…