Skip to main content

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.

Alternative payment methods

APMs split into two operational categories from a code standpoint:
CategoryExamplesBrowser workServer work
Wallet APMsApple Pay, Google PayYES — open native sheet, tokenizeCharge call (server-to-server)
Redirect APMsKlarna, iDEAL, Bancontact, Twint, Blik, Affirm, Multibanco, PayPalNONE — just a button clickCharge call returns a redirect URL, browser navigates to it
Within wallet APMs there’s a further split: native (you tokenize directly via TagadaPay’s vault) vs through-processor (Stripe Payment Request, Airwallex Wallet, etc.). This page explains all three paths.

Mental model: method × provider

Every APM in the system has two coordinates:
CoordinateQuestion it answersExamples
methodWhat does the customer see?apple_pay, google_pay, klarna, ideal, bancontact
providerWhich infrastructure handles it?apple_pay (native), google_pay (native), stripe, airwallex, tagada
The same method can ship through different providers, with different code paths. Example: a merchant might offer Apple Pay through native tokenization in one country, and through Stripe Payment Request in another. You discover this via paymentSetup.get(storeId):
{
  "apple_pay": {
    "enabled": true,
    "method": "apple_pay",
    "provider": "apple_pay",      // ← native path
    "metadata": { "country": "FR" }
  },
  "apple_pay:stripe": {
    "enabled": true,
    "method": "apple_pay",
    "provider": "stripe",          // ← through-processor path
    "publishableKey": "pk_live_xxx"
  },
  "klarna:stripe": {
    "enabled": true,
    "method": "klarna",
    "provider": "stripe",
    "type": "apm",
    "processorId": "proc_stripe_eu"
  }
}
Your client code reads the provider field to decide which browser SDK (if any) to load.

Wallet APMs — native path

This is the simplest, most efficient path. The customer’s wallet talks directly to TagadaPay’s vault. No third-party processor SDK loads in the browser.

Apple Pay (native)

import { startApplePaySession, isApplePayAvailable } from '@tagadapay/core-js';

if (!isApplePayAvailable()) return;  // device doesn't support Apple Pay

startApplePaySession(
  {
    basisTheoryApiKey: paymentSetup.apple_pay.metadata.basisTheoryApiKey,
    countryCode: 'FR',
    storeName: 'Acme SAS',
  },
  {
    currency: 'EUR',
    totalAmountMinor: 4999,
    lineItems: [
      { label: 'Subtotal', amount: '49.99' },
    ],
  },
  {
    onSuccess: async (tagadaToken, contacts) => {
      // tagadaToken is the same shape as a card token —
      // pass it to your server, then go through the standard charge flow.
      await fetch('/api/charge', {
        method: 'POST',
        body: JSON.stringify({
          tagadaToken,
          paymentMethod: 'apple_pay',
          customerData: {
            email: contacts.shippingContact?.emailAddress,
            firstName: contacts.shippingContact?.givenName,
            lastName: contacts.shippingContact?.familyName,
          },
        }),
      });
    },
    onError: (err) => console.error(err),
    onCancel: () => console.log('user cancelled'),
  },
);
Server side it’s identical to a card charge — just pass paymentMethod: 'apple_pay':
const { paymentInstrument } = await charging.paymentInstruments.createFromToken({
  tagadaToken, storeId, customerData,
});
const { payment } = await charging.payments.process({
  paymentInstrumentId: paymentInstrument.id,
  storeId,
  amount: 4999,
  currency: 'EUR',
  paymentMethod: 'apple_pay',
  mode: 'purchase',
});

Google Pay (native)

import { startGooglePaySession, isGooglePayAvailable } from '@tagadapay/core-js';

if (!isGooglePayAvailable()) return;

startGooglePaySession(
  {
    basisTheoryApiKey: paymentSetup.google_pay.metadata.basisTheoryApiKey,
    merchantId: paymentSetup.google_pay.metadata.merchantId,
    merchantName: 'Acme SAS',
    countryCode: 'FR',
    sandboxed: false,  // true for test mode
  },
  { currency: 'EUR', totalAmountMinor: 4999 },
  {
    onSuccess: async (tagadaToken, payerInfo) => {
      // Same as Apple Pay — pass tagadaToken to your server with paymentMethod: 'google_pay'
    },
  },
);
Both wallet primitives are session-free. They never call TagadaPay’s backend during the sheet/tokenize phase — only Apple/Google + BasisTheory. The first TagadaPay call happens on your server when you create the payment instrument. This is why partners can use them without a checkoutSession.

Wallet APMs — through-processor (Stripe Express)

When paymentSetup exposes apple_pay:stripe or google_pay:stripe, the wallet runs through Stripe’s Payment Request API. You load Stripe.js, Stripe handles the wallet UI and tokenization, and you pass the resulting Stripe PaymentMethod to TagadaPay for the charge.
import { StripeExpressCheckout } from '@tagadapay/core-js';
//                ^^^^^^^^^^^^^^^^^^^^
// Loads Stripe.js, opens the appropriate sheet (Apple Pay or Google Pay), and returns a Stripe PaymentMethod.

const checkout = new StripeExpressCheckout({
  publishableKey: paymentSetup['express_checkout:proc_stripe_eu'].publishableKey,
  amount: 4999,
  currency: 'EUR',
  country: 'FR',
});

const result = await checkout.show({
  requestPayerName: true,
  requestPayerEmail: true,
});

if (result.status === 'success') {
  // result.paymentMethodId is a Stripe PaymentMethod (pm_xxx)
  // Pass it to your server.
  await fetch('/api/charge-stripe-express', {
    method: 'POST',
    body: JSON.stringify({
      stripePaymentMethodId: result.paymentMethodId,
      processorId: paymentSetup['express_checkout:proc_stripe_eu'].processorId,
    }),
  });
}
Server-side, the charge call references the Stripe PaymentMethod via the processor:
const { payment } = await charging.payments.process({
  storeId,
  customerId: customer.id,
  processorId: 'proc_stripe_eu',
  amount: 4999,
  currency: 'EUR',
  paymentMethod: 'apple_pay',  // logical method name
  externalPaymentMethodId: stripePaymentMethodId,  // Stripe pm_xxx
  mode: 'purchase',
});

When does each path apply?

paymentSetup[...] shapeBrowser code
apple_pay with provider: 'apple_pay'startApplePaySession() (native)
apple_pay:stripe with provider: 'stripe'StripeExpressCheckout.show()
google_pay with provider: 'google_pay'startGooglePaySession() (native)
google_pay:stripe with provider: 'stripe'StripeExpressCheckout.show()
express_checkout:proc_xxxStripeExpressCheckout.show() (multiple methods bundled)
A merchant’s paymentSetup may include several of these at once. Render the buttons accordingly:
const setup = await tagada.paymentSetup.get(storeId);

if (setup.apple_pay?.enabled && setup.apple_pay.provider === 'apple_pay') {
  renderNativeApplePayButton();
} else if (setup['apple_pay:stripe']?.enabled) {
  renderStripeExpressButton();
}

Redirect APMs

For Klarna, iDEAL, Bancontact, Twint, Blik, Affirm, Multibanco, PayPal: the browser does nothing beyond clicking. All processor differences are absorbed server-side.

Initiate from your server

const { payment } = await charging.payments.process({
  storeId,
  customerId: customer.id,                              // not paymentInstrumentId
  processorId: setup['klarna:stripe'].processorId,      // proc_stripe_eu
  paymentMethod: 'klarna',                               // universal method name
  amount: 4999,
  currency: 'EUR',
  mode: 'purchase',
  returnUrl: 'https://your-platform.com/checkout/return?order=order_42',
  metadata: { yourOrderId: 'order_42' },
});

// payment.status === 'requires_action'
// payment.requireAction === 'redirect'
// payment.requireActionData.metadata.redirect.redirectUrl === 'https://...'

Hand the redirect URL to the browser

// Your /api/charge response:
return Response.json({
  paymentId: payment.id,
  redirectUrl: payment.requireActionData.metadata.redirect.redirectUrl,
});
// Your client:
const { redirectUrl } = await fetch('/api/charge', { method: 'POST', body: ... }).then(r => r.json());
window.location.href = redirectUrl;
The customer completes the APM flow on the processor’s page (Klarna, iDEAL, etc.), then is sent to your returnUrl. The final payment status comes through a webhook (payment.succeeded / payment.failed).

Same method, different processors

The exact same client code initiates Klarna whether the merchant’s paymentSetup routes through Stripe, Airwallex, or our native APM processor:
// All three of these work identically on the client side
processorId: 'proc_stripe_eu'    // Klarna via Stripe
processorId: 'proc_airwallex_eu' // Klarna via Airwallex
processorId: 'proc_tagada_apm'   // Klarna via Tagada native
The processor decision is data-driven (read from paymentSetup), not code-driven. Your code just passes through the processorId.

Discovering what each merchant has enabled

Use paymentSetup.get(storeId) to fetch the full method × provider config for a merchant. See Payment setup config for the full shape.
const setup = await tagada.paymentSetup.get('store_xxx');

const enabledMethods = Object.entries(setup)
  .filter(([_, entry]) => entry.enabled)
  .map(([key, entry]) => ({
    key,                       // e.g. 'apple_pay:stripe'
    method: entry.method,      // 'apple_pay'
    provider: entry.provider,  // 'stripe'
    isExpress: entry.express,
    processorId: entry.processorId,
  }));

console.log(enabledMethods);
// → [
//     { key: 'card', method: 'card', provider: 'tagada', ... },
//     { key: 'apple_pay', method: 'apple_pay', provider: 'apple_pay', isExpress: true, ... },
//     { key: 'klarna:stripe', method: 'klarna', provider: 'stripe', ... },
//   ]

Recurring with APMs

MethodRecurring support
Card✅ MIT supported, see payments page
Apple Pay✅ Token saved as a normal payment instrument; subsequent charges via MIT
Google Pay✅ Same as Apple Pay
Klarna⚠️ Requires Klarna’s subscription product, supported via Stripe processor only
iDEAL❌ One-shot only
Bancontact❌ One-shot only
PayPal✅ Vaulted via PayPal Reference Transactions, supported via Tagada native processor
Pass recurring: true when calling payments.process for any APM that supports it — we’ll set the right processor flags so the issuer/wallet records the consent.

Quick decision table

Customer clicks "Apple Pay" button


What does paymentSetup.apple_pay.provider say?

        ├── 'apple_pay' ──────► startApplePaySession (core-js)
        │                       Browser → BasisTheory → tagadaToken
        │                       Server  → payments.process({ paymentInstrumentId })

        └── 'stripe'    ──────► StripeExpressCheckout (core-js)
                                Browser → Stripe.js → Stripe PaymentMethod
                                Server  → payments.process({ externalPaymentMethodId })

Customer clicks "Klarna" button


Server: payments.process({ paymentMethod: 'klarna', customerId, processorId, returnUrl })


Server returns { redirectUrl }


Browser: window.location = redirectUrl


Klarna page → customer authorizes → returns to your returnUrl


Webhook: payment.succeeded