> ## 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.

# Card tokenization & 3DS

> Tokenize a card in the browser with @tagadapay/core-js, create a payment instrument on your server, handle the 3DS challenge, charge

# Card tokenization & 3DS

**Time**: \~20 minutes | **Difficulty**: Intermediate | **Type**: part of [Examples](/developer-tools/examples/introduction)

<Info>
  **Finished project:** [`core-js-tokenization`](https://github.com/TagadaPay/examples/tree/main/core-js-tokenization). Clone it if you want the working demo first — or keep reading to understand each hop of the flow.
</Info>

This is the **"you already have the order"** path: your cart lives in Medusa, WooCommerce, or your own order API, and TagadaPay only tokenizes and charges. The demo walks the entire flow with a UI that narrates every step — tokenize, create the instrument, detect SCA, run the 3DS challenge in a modal, charge, retry.

<Warning>
  If TagadaPay should manage the cart, upsells, and funnel, this is the wrong page — use the [Headless SDK](/developer-tools/headless-sdk/introduction) instead. Two minutes on [Choose how you accept payments](/developer-tools/payments/choose-your-integration) settles it.
</Warning>

***

## The flow — who runs what

The whole point of this example is the **client / server split**. Raw card data stays in the browser; your API key stays on the server. Four hops:

| # | Step                             | Where                              | Call                                          |
| - | -------------------------------- | ---------------------------------- | --------------------------------------------- |
| 1 | Tokenize the card                | Browser                            | `tokenizeCard()` → `TagadaToken`              |
| 2 | Create the payment instrument    | **Your server**                    | `POST /payment-instruments/create-from-token` |
| 3 | 3DS session + challenge (if SCA) | Server creates, browser challenges | `startChallenge()` modal                      |
| 4 | Charge                           | **Your server**                    | `POST /payments/process`                      |

```tsx theme={null}
// 1 — browser: card data goes to Basis Theory, never to you
const { tagadaToken } = await tokenizeCard({ cardNumber, expiryDate, cvc, cardholderName });

// 2 — your server: turn the token into a reusable instrument
const { paymentInstrument } = await createPaymentInstrument(
  { tagadaToken, storeId },
  process.env.TAGADAPAY_API_KEY,   // server-side only
);
```

When the instrument requires Strong Customer Authentication, the demo creates a 3DS session server-side and runs the challenge client-side — a modal pops, the customer authenticates, and you charge with the session attached:

```tsx theme={null}
// 3 — browser: the modal appears here
const completion = await startChallenge({
  acsChallengeUrl: threedsSession.acsChallengeUrl,
  creq: threedsSession.creq,
  threeDSVersion: threedsSession.messageVersion,
});

// 4 — your server: charge, referencing the 3DS session
await processPayment({
  amount: 2999,                    // integer cents
  currency: 'USD',
  storeId,
  paymentInstrumentId: paymentInstrument.id,
  threedsSessionId: threedsSession.id,
});
```

<Tip>
  `src/api/paymentBackend.ts` in the demo is written to be **copied to your server** — every function is annotated with a Next.js Server Action and an Express route version. The demo calls them from the browser only so you can watch the flow in one page; in production those calls move behind your auth.
</Tip>

***

## Run it

```bash theme={null}
git clone https://github.com/TagadaPay/examples.git
cd examples/core-js-tokenization
pnpm install && pnpm dev
```

Open `http://localhost:5173`, paste a store id, and use a [sandbox test card](/developer-tools/node-sdk/sandbox-testing). The right-hand sidebar keeps a history of tokens and store ids in `localStorage` so you can replay flows quickly, and a failed 3DS can be retried from the beginning.

## The minimal variant

[`core-js-card-payment`](https://github.com/TagadaPay/examples/tree/main/core-js-card-payment) is the same flow with everything non-essential removed: one page, one card form, one charge, plus a `/return` route that shows how to resume after a processor redirect. Read `core-js-tokenization` to learn; copy `core-js-card-payment` when you just want the skeleton.

***

## Next

<CardGroup cols={2}>
  <Card title="Finished project on GitHub" icon="github" href="https://github.com/TagadaPay/examples/tree/main/core-js-tokenization">
    Clone `core-js-tokenization` and watch every hop in the UI
  </Card>

  <Card title="Apple Pay & Google Pay" icon="wallet" href="/developer-tools/examples/apple-google-pay">
    Same charge path, wallet buttons instead of a card form
  </Card>

  <Card title="core-js reference" icon="book" href="/developer-tools/payments/core-js-payments">
    The full API this demo is built on — every endpoint, every field
  </Card>

  <Card title="Sandbox testing" icon="flask" href="/developer-tools/node-sdk/sandbox-testing">
    Test cards, forced 3DS, simulated declines
  </Card>
</CardGroup>
