1. Onboard a sub-merchant → tpa_xxx, store_xxx auto-created2. Mint a sub-key → tp_sk_xxx restricted to that tpa3. Tokenize a card client-side→ tagadaToken (PCI-safe)4. Charge → payment_xxx ✓
By the end you’ll have a payment_xxx row visible in the TagadaPay dashboard and the test funds reflected on your sub-merchant’s balance.
Talk to your account manager — partners are activated manually
Partner API key
tp_sk_partner_xxx — provided once partner is activated
Node.js 18+ + modern browser
node --version, any evergreen browser
Test mode first. Append ?mode=test or use the test partner key (tp_sk_partner_test_xxx) for the entire flow below. Once you’ve charged a test card successfully, switch to live keys.
import Tagada from '@tagadapay/node-sdk';const partner = new Tagada(process.env.TAGADA_PARTNER_KEY!);// ↑ tp_sk_partner_live_xxx// One round-trip creates: a stub `accounts` row + a store + a TPAconst tpa = await partner.partners.accounts.create({ legalName: 'Acme SAS', country: 'FR', currency: 'EUR', externalRef: 'merchant_42', // your own merchant id — used for idempotency});console.log(tpa.id); // tpa_xxx — the sub-merchantconsole.log(tpa.storeId); // store_xxx — the auto-provisioned storeconsole.log(tpa.accountId);// acc_xxx — internal data-ownership anchor
Idempotent. Calling create() again with the same externalRef returns the existing TPA — safe to retry on network errors.
See Sub-merchant provisioning for KYB requirements, store re-pointing, and how the orphan accounts row works.
const subKey = await partner.partners.apiKeys.create(tpa.id, { label: 'merchant_42 — server charges',});console.log(subKey.secret); // tp_sk_live_xxx — RETURNED ONCE, store securelyconsole.log(subKey.id); // ak_xxx — the key id (visible later for revocation)
The secret is only ever returned once. Store it in your secret manager immediately. We keep a prefix (the first 12 chars) for display/audit but we do not store the full secret on our side — if you lose it, you must rotate.
The sub-key is scoped to one TPA. Even if it leaks, an attacker can only act on merchant_42’s data — never on your other merchants.
4. Tokenize the card (client-side, in the browser)
<!-- Your merchant’s checkout page --><form id="pay"> <input id="card" placeholder="4242 4242 4242 4242" /> <input id="exp" placeholder="12/30" /> <input id="cvc" placeholder="123" /> <button type="submit">Pay €49.99</button></form><script type="module"> import { Tokenizer } from 'https://esm.sh/@tagadapay/core-js@2'; const tokenizer = new Tokenizer({ environment: 'production' }); document.getElementById('pay').addEventListener('submit', async (e) => { e.preventDefault(); const tagadaToken = await tokenizer.tokenizeCard({ cardNumber: card.value, expiryDate: exp.value, cvc: cvc.value, }); // Send the token (and ONLY the token) to your server await fetch('/api/charge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tagadaToken, amount: 4999, currency: 'EUR' }), }); });</script>
PCI scope-out. The raw card number, expiry, and CVC never touch your server. The tagadaToken is a single-use vault reference — safe to log, safe to store. See @tagadapay/core-js.
import Tagada from '@tagadapay/node-sdk';// Your /api/charge handlerexport async function POST(req: Request) { const { tagadaToken, amount, currency } = await req.json(); // Re-create a Tagada client with the SUB-KEY for this merchant const charging = new Tagada(process.env.MERCHANT_42_SUBKEY!); // Create a reusable payment instrument from the single-use token const { paymentInstrument, customer } = await charging.paymentInstruments.createFromToken({ tagadaToken, storeId: 'store_xxx', // the storeId you got back from step 2 customerData: { email: 'jane@example.com', firstName: 'Jane', lastName: 'Doe', }, }); // Charge it const { payment } = await charging.payments.process({ paymentInstrumentId: paymentInstrument.id, storeId: 'store_xxx', amount, // 4999 = €49.99 in minor units currency, // 'EUR' paymentMethod: 'card', mode: 'purchase', // 'auth' for authorize-only, capture later }); return Response.json({ paymentId: payment.id, status: payment.status });}
That’s the complete loop. Tokenize browser-side, charge server-side, no checkout session anywhere.
The wallet APMs use the same primitives in core-js, plus a small native sheet step. Native vs Stripe routing is explained on the APM page.
Add Klarna / iDEAL / Bancontact
Redirect APMs need no client tokenization — you call payments.process with paymentMethod: 'klarna' and a returnUrl, then redirect the customer to the URL we hand back.
Add 3DS where required
For European cards or high-risk transactions, run a 3DS challenge between tokenization and charge.
Discover what each merchant has enabled
Use paymentSetup.get(storeId) to fetch the method × provider config and render the right buttons per merchant.