---
title: Payment Connectors
description: Connect hosted payment gateways to QuotaStack while keeping money movement, subscription state, credits, usage, and entitlements under explicit ownership.
order: 7
---

# 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.

> **Mental Model:** A payment connector is a **verified bridge between a cashier and your billing ledger**. The selected provider owns money movement and recurring collection; QuotaStack owns product mapping, subscription state, credits, metering, and entitlements.

## 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

## Diagrams

### Ownership and confirmation

The tenant application asks QuotaStack to create checkout for a mapped target. The selected provider hosts payment collection and sends a signed event to QuotaStack. QuotaStack verifies and deduplicates that event, grants nothing for non-success outcomes, and applies a mapped top-up or subscription effect exactly once only after payment succeeds.

```mermaid
flowchart LR
    A[Tenant application] -->|connection + target| Q[QuotaStack connector]
    Q -->|mapped product| P[Selected payment provider]
    P -->|hosted checkout| C[Customer]
    P -->|signed payment event| I[QuotaStack event inbox]
    I -->|verify + deduplicate| D{Payment succeeded?}
    D -->|No| N[Record state; grant nothing]
    D -->|Yes, top-up| T[Grant mapped package exactly once]
    D -->|Yes, subscription| S[Activate or renew locked period]
    T --> L[QuotaStack credit ledger]
    S --> L
```

### Initiator scenarios

Four scenarios make the initiator boundary explicit. For a connector top-up, the tenant starts checkout and QuotaStack grants only after Dodo’s signed success. For the first connector subscription, the tenant starts checkout and Dodo creates the mandate and first payment before QuotaStack activates. For recurring renewal, Dodo—not QuotaStack—automatically attempts the charge; QuotaStack only reacts to its signed result. In a manual gateway integration, the tenant application verifies its own provider result and calls the public grant or renew endpoint.

```mermaid
flowchart TB
    subgraph T["1 · Connector top-up — tenant invokes"]
      T1[Tenant: create checkout] --> T2[QuotaStack: route mapped package]
      T2 --> T3[Dodo: collect payment]
      T3 -->|signed payment.succeeded| T4[QuotaStack: grant top-up exactly once]
    end
    subgraph S["2 · First connector subscription — tenant invokes"]
      S1[Tenant: create checkout] --> S2[QuotaStack: route prepaid variant]
      S2 --> S3[Dodo: create mandate + take first payment]
      S3 -->|signed payment.succeeded| S4[QuotaStack: grant + activate]
    end
    subgraph R["3 · Recurring renewal — Dodo invokes charge"]
      R1[Dodo: schedule and attempt charge] --> R2[Dodo: signed payment result]
      R2 -->|succeeded| R3[QuotaStack: grant + advance one locked period]
      R2 -->|failed| R4[QuotaStack: record state; grant nothing]
    end
    subgraph M["4 · Manual gateway integration — tenant app invokes"]
      M1[Tenant-owned gateway] --> M2[Tenant app verifies payment]
      M2 --> M3[Tenant app calls QS top-up grant or subscription renew]
    end
```

## 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_package` for a one-time credit purchase
- `plan_variant` for 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:

1. QuotaStack made a checkout and got its hosted URL.
2. 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:

1. It verifies the signature against the raw body.
2. During setup, QuotaStack records the Dodo business ID.
3. Each signed callback must have a `business_id` that matches it.
4. QuotaStack rejects a mismatch before the durable inbox.
5. It saves the event and sends a fast reply.
6. It checks that the event ID and payment ID are new.
7. It finds the link, user, product map, checkout, and bound plan.
8. 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:

```http
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`](/docs/concepts/topups-and-wallets) 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:

1. Dodo hosts checkout and takes the first charge.
2. Proof of payment makes or finds one pending QuotaStack subscription.
3. QuotaStack applies each first grant with a stable key.
4. The plan turns on only after all grants work.
5. Dodo sets the next charge dates and owns retries.
6. Each paid renewal gets one lock for that plan and term.
7. 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: false` asks to stop at the next charge date.
- `cancel_immediately: true` asks 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](/docs/cookbook/dodo-payment-connector). 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.

## Related

- [Dodo Payment Connector](/docs/cookbook/dodo-payment-connector) — Configure Dodo, map products, and create hosted checkout.
- [Topups & Wallets](/docs/concepts/topups-and-wallets) — Understand manual and connector-managed paid credit grants.
- [Subscriptions](/docs/concepts/subscriptions) — Understand manual and connector-managed prepaid renewal.
- [Webhooks](/docs/concepts/webhooks) — Consume QuotaStack's normalized outbound billing events.
