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.

Partner Quick Start

Time: ~10 minutes · Difficulty: Beginner · You will get: a successful test payment

What you’re going to build

1. Onboard a sub-merchant     →  tpa_xxx, store_xxx auto-created
2. Mint a sub-key             →  tp_sk_xxx restricted to that tpa
3. 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.

Prerequisites

RequirementHow to get it
Partner accountTalk to your account manager — partners are activated manually
Partner API keytp_sk_partner_xxx — provided once partner is activated
Node.js 18+ + modern browsernode --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.

1. Install the two SDKs

npm install @tagadapay/node-sdk @tagadapay/core-js
That’s the entire dependency footprint for partner S2S.

2. Provision a sub-merchant (server-side)

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 TPA
const 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-merchant
console.log(tpa.storeId);  // store_xxx — the auto-provisioned store
console.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.

3. Mint a sub-key for that merchant (server-side)

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 securely
console.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&rsquo;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.

5. Charge (server-side, with the sub-key)

import Tagada from '@tagadapay/node-sdk';

// Your /api/charge handler
export 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.

What you just built

Browser                          Your server                      TagadaPay
───────                          ───────────                      ─────────
                                                                   ┌─ partner key
                                       partners.accounts.create ──►│ provisions TPA + store
                                                                   ├─ stores tpa_xxx
                                       partners.apiKeys.create  ──►│ mints tp_sk_xxx

tokenizeCard(card) ──► api.basistheory.com  ─────────────────────► │ vault stores PAN
       │                                                           │ returns tagadaToken
       │ tagadaToken                                                │
       ▼                                                            │
 POST /api/charge ──► createFromToken(tagadaToken)  ──► sub-key  ──►│ creates pi_xxx
                          │                                         │
                          ▼                                         │
                      payments.process(pi)            ──► sub-key ──►│ routes to processor
                                                                    │ returns payment_xxx

Common next steps

Add Apple Pay / Google Pay

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.