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. Each one just needs its API keys:
ProcessorWhat you provide
StripeSecret key + Publishable key
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 },  // Stripe — 40%
      { processorId: 'proc_cko', weight: 30 },  // Checkout.com — 30%
      { processorId: 'proc_aw',  weight: 30 },  // 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 },  // Stripe
      { processorId: 'proc_cko', weight: 50 },  // 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 },  // 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 },  // Stripe — 40%
      { processorId: checkout.id,  weight: 30 },  // Checkout.com — 30%
      { processorId: airwallex.id, weight: 30 },  // 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)

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