Skip to main content

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

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:
ConceptWhat it is
ProcessorA PSP connection (Stripe, NMI, Checkout.com, Airwallex, etc.) with its own API keys
Payment FlowA routing rule: which processors to use, traffic weights, fallback order
VaultCard 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 → Settings → Processors, or connect them via the SDK:
ProcessorWhat you provide
StripeSecret key + Publishable key or use Stripe Connect OAuth
NMIAPI key (or username/password)
Checkout.comSecret key + Public key + Processing Channel ID
AirwallexClient ID + API Key
After connecting, list them via the SDK to get their IDs:
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 },
// ]
You can connect unlimited processor accounts — even multiple Stripe accounts (US, EU, etc.). TagadaPay is fully gateway-agnostic.

Step 2: Create a Payment Flow

A payment flow defines how transactions are routed across your 4 processors:
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:
FieldWhat it does
strategy: 'cascade'Use multiple processors (vs 'simple' for one)
fallbackMode: trueIf the primary processor declines, try the next one
maxFallbackRetries: 2Try up to 2 fallback processors before giving up
pickProcessorStrategy: 'weighted'Distribute traffic by weight (40/30/30 split)
stickyProcessorEnabled: trueReturning customers reuse their last successful processor
threeDsEnabled: trueEnable 3D Secure when required by the card issuer
processorConfigsPrimary processors with traffic weights
fallbackProcessorConfigsBackup processors tried in orderIndex order on decline
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.

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.
// 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:
// 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
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.

Step 4: Charge — TagadaPay Routes Automatically

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.
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.
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.
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.
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:
// 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.

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.
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)
Processors1Unlimited, any combination
Add a new PSPNew integration, new codeDashboard → add API keys → done
Fallback on declineYou build itAutomatic cascade
Traffic splittingYou build itweight: 40 / weight: 30 / weight: 30
Sticky routingYou build itstickyProcessorEnabled: true
Card vaultPSP-specific tokensPSP-agnostic, works across all processors
3DSPer-PSP implementationUnified, automatic
Lines of codeHundreds (per PSP)~20 (for all PSPs combined)

Full Example: 4 PSPs, End to End

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)
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
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:
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
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

await tagada.processors.disconnectStripe({
  accountId: 'acct_1234567890',
  processorId: processor.id,
});
// Processor is now disabled, OAuth access revoked
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

Next Steps

Subscriptions & Rebilling

Trials, auto-retry, manual rebill, processor migration for recurring billing

Node SDK Quick Start

Full SDK setup with stores, products, and funnels

Headless Payments (Frontend)

Build your own checkout UI with client-side card tokenization and 3DS

Scripts & Pixels per Step

Add tracking pixels and custom scripts to funnel steps