> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tagada.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Payment Flows for SaaS

> Route charges across multiple TPAs and processors — one vault, automatic failover

# Payment Flows for SaaS

**Time**: \~10 minutes | **Difficulty**: Intermediate

Payment flows are TagadaPay's routing engine. For SaaS, they solve the problem Stripe cannot: **run multiple processors/TPAs behind one vaulted card**, with automatic cascade and failover.

<Info>
  **Tested in production** with TPA `tpa_fb15b76112a8` (live Adyen-backed) and sandbox processor cascade. See [`mini-saas-billing`](https://github.com/TagadaPay/examples/tree/main/mini-saas-billing).
</Info>

***

## The problem with Stripe

```
Customer card → Stripe only
                  ↓ decline → lost sale
                  ↓ Stripe bans account → all cards gone
```

## The TagadaPay model

```
Customer card → vaulted once (payment instrument)
                  ↓
         ┌── Payment Flow ──┐
         │ sandbox (dev)    │  ← test cards
         │ → TPA (live)     │  ← your TagadaPay account
         │ → Stripe         │  ← optional direct processor
         │ → Checkout.com   │  ← optional
         └──────────────────┘
                  ↓
         Rebill every month — same vault, any processor
```

**If a TPA is banned:** update the payment flow to route through a different TPA or processor. Customers never re-enter their card.

***

## Create a cascade flow (backend)

This is the flow created by `npm run seed` in the SaaS demo — tested and working:

```ts theme={null}
import Tagada from '@tagadapay/node-sdk';

const tagada = new Tagada(process.env.TAGADA_API_KEY!);

const { processors } = await tagada.processors.list();
const sandbox = processors.find((p) => p.type === 'sandbox');
const router = processors.find((p) => p.type === 'tagadapay-router');

const flow = await tagada.paymentFlows.create({
  data: {
    name: 'SaaS Billing Flow',
    strategy: 'cascade',
    fallbackMode: true,
    maxFallbackRetries: 2,
    threeDsEnabled: true,
    stickyProcessorEnabled: true,
    pickProcessorStrategy: 'weighted',
    processorConfigs: [
      { processorId: sandbox!.id, weight: 100, disabled: false, nonStickable: false },
    ],
    fallbackProcessorConfigs: [
      { processorId: router!.id, orderIndex: 0 },
    ],
  },
});

console.log(flow.id);
// flow_35f3975175fe — verified in production
```

| Setting                        | What it does for SaaS                                     |
| ------------------------------ | --------------------------------------------------------- |
| `strategy: 'cascade'`          | Try primary processor; on decline, fall through to next   |
| `stickyProcessorEnabled: true` | Returning customers reuse their last successful processor |
| `threeDsEnabled: true`         | SCA for EU rebills                                        |
| `fallbackProcessorConfigs`     | Ordered list of backup processors/TPAs                    |

<Note>
  **About `threeDsEnabled`:** this toggles *whether* 3DS is requested — *how* it runs is decided by the processor the flow routes to. With hosted-3DS PSPs (Adyen, Stripe, TagadaPay TPAs) the charge simply returns `requireAction: 'redirect'` and the processor hosts the challenge; you never create a 3DS session yourself. Standalone 3DS sessions (`threeds.createSession`) exist only for gateway-style processors like NMI and are rarely needed — see [Quick Start](/developer-tools/saas-billing/quick-start#handle-3ds-usually-nothing-to-do).
</Note>

***

## Charge through a flow

Pass `paymentFlowId` on every charge — first payment and rebills:

```ts theme={null}
const { payment } = await tagada.payments.process({
  amount: 1900,
  currency: 'EUR',
  storeId: 'store_xxx',
  paymentInstrumentId: 'inst_xxx',
  customerId: 'cus_xxx',
  initiatedBy: 'customer',
  paymentFlowId: 'flow_35f3975175fe',
});

console.log(payment.status);    // 'succeeded'
console.log(payment.processorId); // which processor won the route
```

Verified output from production test:

```
Payment: pay_666f3a317d1b succeeded processor: processor_4ae47011dc96
```

***

## Add more processors to increase approval rate

Connect Stripe, Checkout.com, or Airwallex in the dashboard, then add them to the flow:

```ts theme={null}
await tagada.paymentFlows.update({
  flowId: 'flow_35f3975175fe',
  data: {
    strategy: 'cascade',
    fallbackMode: true,
    maxFallbackRetries: 3,
    processorConfigs: [
      { processorId: 'processor_sandbox', weight: 50, disabled: false, nonStickable: false },
      { processorId: 'processor_stripe', weight: 50, disabled: false, nonStickable: false },
    ],
    fallbackProcessorConfigs: [
      { processorId: 'processor_tagadapay_router', orderIndex: 0 },
      { processorId: 'processor_checkout_com', orderIndex: 1 },
    ],
  },
});
```

No frontend changes. No re-vaulting. The same `paymentInstrumentId` works across all processors.

***

## Switch TPA without re-collecting cards

When your live TPA (`tpa_xxx`) has issues:

1. Provision a new TPA (or connect a direct Stripe/Adyen processor)
2. Add it to the payment flow's `fallbackProcessorConfigs`
3. Optionally disable the old processor
4. Existing subscriptions rebill through the new route automatically

For subscription-level processor migration, use:

```ts theme={null}
await tagada.subscriptions.changeProcessor({
  subscriptionIds: ['sub_xyz'],
  processorId: 'processor_new',
});
```

See [Subscriptions & Rebilling](/developer-tools/node-sdk/subscriptions#change-processor-migrate-without-re-vaulting).

***

## Inspect your flow at runtime

```ts theme={null}
const flow = await tagada.paymentFlows.retrieveWithProcessors('flow_35f3975175fe');

console.log(flow.name);              // 'SaaS Billing Flow'
console.log(flow.strategy);          // 'cascade'
console.log(flow.processorConfigs);  // primary processors
console.log(flow.fallbackProcessorConfigs); // fallback chain
```

The SaaS demo exposes this at `GET /api/payment-flow` so your frontend can display the active routing config.

***

## Strategies reference

| Strategy          | Best for SaaS                                              |
| ----------------- | ---------------------------------------------------------- |
| `simple`          | Single processor — dev/testing                             |
| `cascade`         | **Recommended** — try primary, fall through on decline     |
| Weighted + sticky | Split traffic across processors, stick returning customers |

Full multi-PSP guide: [Multi-PSP Routing & Vault](/developer-tools/node-sdk/multi-stripe-routing).
