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

# Multi-PSP Routing & Vault

> Connect Stripe, NMI, Checkout.com, Airwallex — vault cards once, route and cascade transactions automatically

# Multi-PSP Routing & Vault

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

***

## The Problem

You're locked into one payment processor. If it declines, you lose the sale:

```
Customer → Stripe → decline → lost sale
```

With TagadaPay, you connect **all your processors** and route intelligently:

```
Customer → TagadaPay → Stripe        (40% traffic)
                     → Checkout.com  (30% traffic)
                     → Airwallex     (30% traffic)
                     → NMI           (fallback if all fail)
```

Card data is vaulted **once**. Transactions flow to the best processor automatically. If one declines, the next one picks up — **zero extra code on your side**.

<Info>
  **TagadaPay sits above every PSP.** It's not a Stripe plugin or a Checkout.com add-on. You connect any combination of Stripe, NMI, Checkout.com, Airwallex, Adyen, and more — then define routing rules. Each processor is just a connection with API keys.
</Info>

***

## How It Works

```
┌─────────────────────────────────────────────────────────────┐
│                      Your Checkout                          │
│   (Plugin SDK, headless, or any frontend)                   │
│                          ↓                                  │
│                    Card tokenized                           │
│              (PCI-compliant vault, one time)                 │
│                          ↓                                  │
│        ┌──────── Payment Flow ─────────┐                    │
│        │  strategy: cascade            │                    │
│        │  40% → Stripe                 │                    │
│        │  30% → Checkout.com           │  ← sticky: reuse  │
│        │  30% → Airwallex              │    last successful │
│        │  fallback #1 → NMI            │    processor for   │
│        │  fallback #2 → Stripe (retry) │    returning       │
│        └───────────────────────────────┘    customers       │
│                          ↓                                  │
│                   Transaction OK                            │
└─────────────────────────────────────────────────────────────┘
```

Three concepts to understand:

| Concept          | What it is                                                                                          |
| ---------------- | --------------------------------------------------------------------------------------------------- |
| **Processor**    | A PSP connection (Stripe, NMI, Checkout.com, Airwallex, etc.) with its own API keys                 |
| **Payment Flow** | A routing rule: which processors to use, traffic weights, fallback order                            |
| **Vault**        | Card data is tokenized once and reusable across **all** processors — you never see raw card numbers |

***

## Step 1: Connect Your Processors

Add each processor in the [TagadaPay dashboard](https://app.tagadapay.com) → Settings → Processors, or connect them via the SDK:

| Processor        | What you provide                                                                        |
| ---------------- | --------------------------------------------------------------------------------------- |
| **Stripe**       | Secret key + Publishable key **or** use [Stripe Connect OAuth](#stripe-connect-via-api) |
| **NMI**          | API key (or username/password)                                                          |
| **Checkout.com** | Secret key + Public key + Processing Channel ID                                         |
| **Airwallex**    | Client ID + API Key                                                                     |

After connecting, list them via the SDK to get their IDs:

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

const tagada = new Tagada('your-api-key');

const { processors } = await tagada.processors.list();

console.log(processors);
// [
//   { id: 'proc_s1',  name: 'Stripe',       type: 'stripe',      enabled: true },
//   { id: 'proc_nmi', name: 'NMI',          type: 'nmi',         enabled: true },
//   { id: 'proc_cko', name: 'Checkout.com', type: 'checkout',    enabled: true },
//   { id: 'proc_aw',  name: 'Airwallex',    type: 'airwallex',   enabled: true },
// ]
```

<Tip>
  You can connect **unlimited** processor accounts — even multiple Stripe accounts (US, EU, etc.). TagadaPay is fully gateway-agnostic.
</Tip>

***

## Step 2: Create a Payment Flow

A payment flow defines **how** transactions are routed across your 4 processors:

```ts theme={null}
const flow = await tagada.paymentFlows.create({
  data: {
    name: 'Global 4-PSP Cascade',
    strategy: 'cascade',
    fallbackMode: true,
    maxFallbackRetries: 2,
    threeDsEnabled: true,
    stickyProcessorEnabled: true,
    pickProcessorStrategy: 'weighted',
    processorConfigs: [
      { processorId: 'proc_s1',  weight: 40, disabled: false, nonStickable: false },  // Stripe — 40%
      { processorId: 'proc_cko', weight: 30, disabled: false, nonStickable: false },  // Checkout.com — 30%
      { processorId: 'proc_aw',  weight: 30, disabled: false, nonStickable: false },  // Airwallex — 30%
    ],
    fallbackProcessorConfigs: [
      { processorId: 'proc_nmi', orderIndex: 0 },  // NMI — 1st fallback
      { processorId: 'proc_s1',  orderIndex: 1 },  // Stripe — 2nd fallback (retry)
    ],
  },
});

console.log(flow.id); // 'flow_xyz'
```

Here's what each field does:

| Field                               | What it does                                              |
| ----------------------------------- | --------------------------------------------------------- |
| `strategy: 'cascade'`               | Use multiple processors (vs `'simple'` for one)           |
| `fallbackMode: true`                | If the primary processor declines, try the next one       |
| `maxFallbackRetries: 2`             | Try up to 2 fallback processors before giving up          |
| `pickProcessorStrategy: 'weighted'` | Distribute traffic by weight (40/30/30 split)             |
| `stickyProcessorEnabled: true`      | Returning customers reuse their last successful processor |
| `threeDsEnabled: true`              | Enable 3D Secure when required by the card issuer         |
| `processorConfigs`                  | Primary processors with traffic weights                   |
| `fallbackProcessorConfigs`          | Backup processors tried in `orderIndex` order on decline  |

<Info>
  **Yes, a processor can appear in both lists.** Stripe is the primary at 40% *and* the 2nd fallback. If Checkout.com or Airwallex declines, NMI tries first, then Stripe retries — sometimes a retry on the same PSP with a fresh attempt succeeds.
</Info>

***

## Step 3: Vault a Card (Tokenize Once)

Card data is tokenized **client-side** via `@tagadapay/core-js`. The raw card number never reaches your server.

```ts theme={null}
// Client-side (browser)
import { useCardTokenization } from '@tagadapay/core-js/react';

const { tokenizeCard } = useCardTokenization({ environment: 'production' });

const { tagadaToken } = await tokenizeCard({
  cardNumber: '4242424242424242',
  expiryDate: '12/28',
  cvc: '123',
  cardholderName: 'Jane Doe',
});

// Send tagadaToken to your server →
```

Then, **server-side**, create a reusable payment instrument:

```ts theme={null}
// Server-side (Node.js)
const { paymentInstrument, customer } = await tagada.paymentInstruments.createFromToken({
  tagadaToken: tagadaToken,  // base64 string from client
  storeId: 'store_xxx',
  customerData: {
    email: 'jane@example.com',
    firstName: 'Jane',
    lastName: 'Doe',
  },
});

console.log(paymentInstrument.id); // 'pi_abc123'
// This instrument is now vaulted — reusable across ALL processors
```

<Info>
  **One vault, all processors.** The payment instrument is PSP-agnostic. TagadaPay translates it to the right format for whichever processor the flow selects — Stripe, NMI, Checkout.com, Airwallex, or any other. You tokenize once, charge anywhere.
</Info>

***

## Step 4: Charge — TagadaPay Routes Automatically

```ts theme={null}
const { payment } = await tagada.payments.process({
  amount: 4999,             // $49.99 in cents
  currency: 'USD',
  storeId: 'store_xxx',
  paymentInstrumentId: paymentInstrument.id,
  paymentFlowId: flow.id,   // uses the 4-PSP cascade flow
  initiatedBy: 'customer',
  mode: 'purchase',
});

console.log(payment.status);       // 'captured'
console.log(payment.processorId);  // 'proc_cko' (Checkout.com won the route)
```

What happened under the hood:

1. TagadaPay's weighted algorithm picked **Checkout.com** (30% weight, this time)
2. If Checkout.com declined → automatically retried on **NMI** (fallback #1)
3. If NMI also declined → retried on **Stripe** (fallback #2)
4. The winning processor is recorded in `payment.processorId`

**You wrote zero retry logic. Zero PSP-specific code. One API call for 4 processors.**

***

## Routing Strategies Explained

### Weighted Distribution

Split traffic by percentage. Great for load balancing or gradually migrating to a new processor.

```ts theme={null}
processorConfigs: [
  { processorId: 'proc_s1',  weight: 40 },  // Stripe — 40%
  { processorId: 'proc_cko', weight: 30 },  // Checkout.com — 30%
  { processorId: 'proc_aw',  weight: 30 },  // Airwallex — 30%
]
```

### Lowest Capacity

Sends traffic to whichever processor has processed the fewest transactions today. Auto-balances across all 4 PSPs.

```ts theme={null}
pickProcessorStrategy: 'lowestCapacity',
processorConfigs: [
  { processorId: 'proc_s1' },   // Stripe
  { processorId: 'proc_cko' },  // Checkout.com
  { processorId: 'proc_aw' },   // Airwallex
  { processorId: 'proc_nmi' },  // NMI
]
```

### Automatic (Round Robin)

Equal distribution — each processor gets roughly the same number of transactions.

```ts theme={null}
pickProcessorStrategy: 'automatic',
```

### Sticky Processor

When enabled, a returning customer is always routed to the processor that last succeeded for them. Reduces declines on stored-credential transactions.

```ts theme={null}
stickyProcessorEnabled: true,
```

***

## Multiple Flows for Different Scenarios

You can create different flows for different situations. Example: a standard flow and a high-value flow using different processor mixes:

```ts theme={null}
// Standard flow: spread across Stripe + Checkout.com, NMI as fallback
const standardFlow = await tagada.paymentFlows.create({
  data: {
    name: 'Standard',
    strategy: 'cascade',
    fallbackMode: true,
    maxFallbackRetries: 1,
    threeDsEnabled: true,
    stickyProcessorEnabled: true,
    pickProcessorStrategy: 'weighted',
    processorConfigs: [
      { processorId: 'proc_s1',  weight: 50, disabled: false, nonStickable: false },  // Stripe
      { processorId: 'proc_cko', weight: 50, disabled: false, nonStickable: false },  // Checkout.com
    ],
    fallbackProcessorConfigs: [
      { processorId: 'proc_nmi', orderIndex: 0 },
    ],
  },
});

// High-value flow: Airwallex primary (best rates for large amounts),
// all others as fallbacks
const highValueFlow = await tagada.paymentFlows.create({
  data: {
    name: 'High Value (>$500)',
    strategy: 'cascade',
    fallbackMode: true,
    maxFallbackRetries: 3,
    threeDsEnabled: true,
    stickyProcessorEnabled: false,
    pickProcessorStrategy: 'weighted',
    processorConfigs: [
      { processorId: 'proc_aw', weight: 100, disabled: false, nonStickable: false },  // Airwallex — all traffic
    ],
    fallbackProcessorConfigs: [
      { processorId: 'proc_s1',  orderIndex: 0 },  // Stripe — 1st fallback
      { processorId: 'proc_cko', orderIndex: 1 },  // Checkout.com — 2nd
      { processorId: 'proc_nmi', orderIndex: 2 },  // NMI — last resort
    ],
  },
});

// Route based on order amount
const flowId = orderTotal > 50000
  ? highValueFlow.id
  : standardFlow.id;

await tagada.payments.process({
  amount: orderTotal,
  currency: 'USD',
  storeId: 'store_xxx',
  paymentInstrumentId: 'pi_abc123',
  paymentFlowId: flowId,
  initiatedBy: 'customer',
  mode: 'purchase',
});
```

You can also override per funnel step — see the [Step Config Guide](/developer-tools/node-sdk/step-config-guide#payment-flow-override).

***

## Recurring Charges (MIT)

For subscriptions and server-initiated charges, use `initiatedBy: 'merchant'`. The vaulted instrument works the same way — TagadaPay handles the stored-credential handshake with each PSP.

```ts theme={null}
await tagada.payments.process({
  amount: 2999,
  currency: 'USD',
  storeId: 'store_xxx',
  paymentInstrumentId: 'pi_abc123',
  paymentFlowId: flow.id,
  initiatedBy: 'merchant',
  reasonType: 'recurring',
  mode: 'purchase',
});
```

No customer present, no 3DS challenge, same routing intelligence.

***

## Comparison: Single PSP vs TagadaPay

|                         | Single PSP (e.g. Stripe alone) | TagadaPay (Stripe + NMI + Checkout.com + Airwallex) |
| ----------------------- | ------------------------------ | --------------------------------------------------- |
| **Processors**          | 1                              | Unlimited, any combination                          |
| **Add a new PSP**       | New integration, new code      | Dashboard → add API keys → done                     |
| **Fallback on decline** | You build it                   | Automatic cascade                                   |
| **Traffic splitting**   | You build it                   | `weight: 40` / `weight: 30` / `weight: 30`          |
| **Sticky routing**      | You build it                   | `stickyProcessorEnabled: true`                      |
| **Card vault**          | PSP-specific tokens            | PSP-agnostic, works across all processors           |
| **3DS**                 | Per-PSP implementation         | Unified, automatic                                  |
| **Lines of code**       | Hundreds (per PSP)             | \~20 (for all PSPs combined)                        |

***

## Full Example: 4 PSPs, End to End

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

const tagada = new Tagada('your-api-key');

// 1. List all connected processors
const { processors } = await tagada.processors.list();
const stripe    = processors.find(p => p.type === 'stripe')!;
const nmi       = processors.find(p => p.type === 'nmi')!;
const checkout  = processors.find(p => p.type === 'checkout')!;
const airwallex = processors.find(p => p.type === 'airwallex')!;

// 2. Create a 4-PSP cascade flow
const flow = await tagada.paymentFlows.create({
  data: {
    name: '4-PSP Global Cascade',
    strategy: 'cascade',
    fallbackMode: true,
    maxFallbackRetries: 2,
    threeDsEnabled: true,
    stickyProcessorEnabled: true,
    pickProcessorStrategy: 'weighted',
    processorConfigs: [
      { processorId: stripe.id,    weight: 40, disabled: false, nonStickable: false },  // Stripe — 40%
      { processorId: checkout.id,  weight: 30, disabled: false, nonStickable: false },  // Checkout.com — 30%
      { processorId: airwallex.id, weight: 30, disabled: false, nonStickable: false },  // Airwallex — 30%
    ],
    fallbackProcessorConfigs: [
      { processorId: nmi.id,    orderIndex: 0 },  // NMI — 1st fallback
      { processorId: stripe.id, orderIndex: 1 },  // Stripe — 2nd fallback
    ],
  },
});

// 3. Vault a card (after client-side tokenization via @tagadapay/core-js)
const { paymentInstrument } = await tagada.paymentInstruments.createFromToken({
  tagadaToken: 'eyJ0eXBlIjoiY2FyZC...',  // base64 from client
  storeId: 'store_xxx',
  customerData: { email: 'jane@example.com', firstName: 'Jane' },
});

// 4. Charge — TagadaPay picks the best processor from the flow
const { payment } = await tagada.payments.process({
  amount: 4999,
  currency: 'USD',
  storeId: 'store_xxx',
  paymentInstrumentId: paymentInstrument.id,
  paymentFlowId: flow.id,
  initiatedBy: 'customer',
  mode: 'purchase',
});

console.log(`Payment ${payment.status} via ${payment.processorId}`);
// → "Payment captured via proc_cko" (Checkout.com won this round)
```

***

## Stripe Connect via API

If your merchants use **Stripe**, you can programmatically connect their Stripe accounts via OAuth instead of manually entering API keys.

### How It Works

```
┌────────────────────────────────────────────────────────────────┐
│ 1. Your server calls processors.initiateStripeConnect()       │
│    → returns a Stripe OAuth URL                               │
│                                                                │
│ 2. Redirect the merchant to that URL in their browser         │
│    → merchant authorizes on Stripe                            │
│                                                                │
│ 3. Stripe redirects to TagadaPay's callback                   │
│    → TagadaPay saves the connected account credentials        │
│    → merchant is redirected to your redirectUrl                │
│                                                                │
│ 4. Processor is now live — ready to accept payments           │
└────────────────────────────────────────────────────────────────┘
```

### Step-by-Step

**1. Create a Stripe processor (placeholder)**

```ts theme={null}
const { processor } = await tagada.processors.create({
  processor: {
    name: 'Stripe EU (pending connect)',
    type: 'stripe',
    enabled: false,
    supportedCurrencies: ['EUR', 'GBP'],
    baseCurrency: 'EUR',
  },
});
```

**2. Initiate the OAuth flow**

```ts theme={null}
const { url } = await tagada.processors.initiateStripeConnect({
  processorId: processor.id,
});

// Redirect the merchant to `url` in their browser
console.log('Send merchant to:', url);
```

TagadaPay handles the OAuth callback automatically — no URL configuration needed.

You can optionally pass `redirectUrl` to control where the merchant lands after authorizing:

```ts theme={null}
const { url } = await tagada.processors.initiateStripeConnect({
  processorId: processor.id,
  redirectUrl: 'https://myapp.com/settings?stripe=connected',
});
```

**3. After the merchant authorizes**, TagadaPay automatically:

* Exchanges the authorization code for access tokens
* Saves the connected Stripe account ID and credentials on the processor
* Enables the processor
* Redirects the merchant to your `redirectUrl` (or the default processors page)

**4. Verify the connection**

```ts theme={null}
const proc = await tagada.processors.retrieve(processor.id);
console.log(proc.enabled); // true
console.log(proc.options); // { accountId: 'acct_...', ... }

// Get full account details
const details = await tagada.processors.getStripeAccountDetails(
  proc.options.accountId as string
);
console.log(details.charges_enabled);  // true
console.log(details.country);          // 'FR'
```

### Disconnect a Stripe Account

```ts theme={null}
await tagada.processors.disconnectStripe({
  accountId: 'acct_1234567890',
  processorId: processor.id,
});
// Processor is now disabled, OAuth access revoked
```

<Tip>
  **When to use Stripe Connect vs direct API keys?**

  * Use **Stripe Connect** when you're a platform onboarding merchants who have their own Stripe accounts
  * Use **direct API keys** when you own the Stripe account and just need to plug it in
</Tip>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Subscriptions & Rebilling" icon="rotate" href="/developer-tools/node-sdk/subscriptions">
    Trials, auto-retry, manual rebill, processor migration for recurring billing
  </Card>

  <Card title="Node SDK Quick Start" icon="node-js" href="/developer-tools/node-sdk/quick-start">
    Full SDK setup with stores, products, and funnels
  </Card>

  <Card title="Headless Payments (Frontend)" icon="credit-card" href="/developer-tools/payments/headless-payments">
    Build your own checkout UI with client-side card tokenization and 3DS
  </Card>

  <Card title="Scripts & Pixels per Step" icon="chart-mixed" href="/developer-tools/node-sdk/step-config-guide">
    Add tracking pixels and custom scripts to funnel steps
  </Card>
</CardGroup>
