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:
| Category | Examples | Browser work | Server work |
|---|
| Wallet APMs | Apple Pay, Google Pay | YES — open native sheet, tokenize | Charge call (server-to-server) |
| Redirect APMs | Klarna, iDEAL, Bancontact, Twint, Blik, Affirm, Multibanco, PayPal | NONE — just a button click | Charge 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:
| Coordinate | Question it answers | Examples |
|---|
| method | What does the customer see? | apple_pay, google_pay, klarna, ideal, bancontact |
| provider | Which 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[...] shape | Browser 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_xxx | StripeExpressCheckout.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
| Method | Recurring 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