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

# Accept a payment with your own cart

> Tokenize a card in the browser with @tagadapay/core-js, then charge it from your server. Use this when you already have the order.

# Accept a payment with your own cart

<Warning>
  This page is **not** the Headless SDK. Use it when **you already have the order** (Medusa, WooCommerce, a custom order API) and only need to tokenize and charge. Native apps and partner server-to-server flows live here too.

  If TagadaPay should manage the cart, upsells, and funnel, use the [Headless SDK](/developer-tools/headless-sdk/introduction) instead (`processPayment()`, not this page).

  Not sure? [Choose how you accept payments](/developer-tools/payments/choose-your-integration).
</Warning>

## Which Approach Should I Use?

|                        | [Headless SDK](/developer-tools/headless-sdk/payment-setup)         | This page (core-js + REST)                                        |
| ---------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------- |
| **Package**            | `@tagadapay/headless-sdk` (+ `core-js` peer for cards)              | `@tagadapay/core-js` + REST / Node `payments.process`             |
| **Who owns the cart?** | Tagada checkout session                                             | You (Medusa, Woo, your backend)                                   |
| **Pay**                | `tagada.payment.processPayment({ checkoutSessionId, tagadaToken })` | Tokenize → instrument → 3DS → `POST /payments/process`            |
| **Checkout sessions**  | Built-in (`useCheckout()`, cart, promos, shipping)                  | Not used — you already have an order                              |
| **3DS handling**       | Automatic inside `processPayment()`                                 | Manual (create session + start challenge)                         |
| **Best for**           | Custom UI, TagadaPay manages the cart                               | Your own commerce backend, mobile, later charges (MIT), platforms |

<Tip>
  **Rule of thumb:** You already have the order → this page. TagadaPay manages the cart → Headless. Hosted on TagadaPay → Plugin SDK. Charge a saved card later → Node `payments.process()`. See [Choose how you accept payments](/developer-tools/payments/choose-your-integration).
</Tip>

***

## When to Use This

| Use case                   | Example                                                                                           |
| -------------------------- | ------------------------------------------------------------------------------------------------- |
| **Own commerce backend**   | Medusa, custom Woo, your own order API — tokenize in the browser, charge with `/payments/process` |
| **Mobile apps**            | Native iOS/Android apps that collect card data via your own UI                                    |
| **Embedded checkout**      | Injecting a payment form into a third-party page or iframe                                        |
| **Marketplace / platform** | Collecting card details on behalf of sub-merchants                                                |
| **Later charges (MIT)**    | Subscription renewals, metered billing, retries — merchant-initiated, customer not present        |
| **Auth + capture**         | Authorize now, capture later (hotels, car rentals, pre-orders)                                    |

### When NOT to Use This

| Instead of...                                            | Use...                                                                                                  |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Building a web checkout where TagadaPay manages the cart | [Headless SDK](/developer-tools/headless-sdk/payment-setup) — sessions, APMs, 3DS                       |
| You just want a hosted checkout page                     | [Merchant Quick Start](/developer-tools/node-sdk/merchant-quickstart) — 7 API calls, zero frontend code |
| You want a custom-branded page (HTML or Plugin SDK)      | [Funnel Pages](/developer-tools/node-sdk/custom-checkout) — deploy pages as plugins                     |
| You need server-side recurring billing                   | [Subscriptions Guide](/developer-tools/node-sdk/subscriptions) — managed subscription lifecycle         |

***

## The Flow

Every payment follows 4 steps:

```
1. Tokenize card    →  Client-side, via @tagadapay/core-js
2. Create instrument →  Server-side, POST to TagadaPay API
3. 3DS authenticate  →  Client + Server (if required by the card issuer)
4. Process payment   →  Server-side, POST to TagadaPay API
```

```
┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  1. Tokenize │────▶│ 2. Instrument│────▶│   3. 3DS     │────▶│  4. Charge   │
│  (client)    │     │  (server)    │     │ (client+srv) │     │  (server)    │
└──────────────┘     └──────────────┘     └──────────────┘     └──────────────┘
  Card details         TagadaToken          Challenge if         Amount + currency
  → Secure Vault       → Payment Inst.      required             → PSP routing
  → TagadaToken        + Customer                                → Transaction
```

***

## Prerequisites

* A TagadaPay account with an **API key** (Bearer token)
* A **store** with at least one **payment flow** configured (connects to your PSP: Stripe, NMI, Airwallex, etc.)
* Install the tokenization SDK:

```bash theme={null}
npm install @tagadapay/core-js
```

<Note>
  The tokenization SDK runs **client-side** (browser). Steps 2–4 happen **server-side** with your API key. Never expose your API key in the browser.
</Note>

***

## Step 1: Tokenize the Card (Client-Side)

The card number never touches your server. It goes directly to a PCI-compliant secure vault (BasisTheory) and returns a token.

<Tabs>
  <Tab title="React">
    ```typescript theme={null}
    import { useCardTokenization } from '@tagadapay/core-js/react';

    function CheckoutForm() {
      const { tokenizeCard, isLoading, error } = useCardTokenization({
        environment: 'production', // 'production' | 'development' | 'local'
      });

      async function handleSubmit() {
        const { tagadaToken, rawToken } = await tokenizeCard({
          cardNumber: '4242424242424242',
          expiryDate: '12/28',
          cvc: '123',
          cardholderName: 'Jane Doe',
        });

        // tagadaToken → base64 string, send to your server
        // rawToken.metadata.auth.scaRequired → true if 3DS is needed
        await sendToServer(tagadaToken, rawToken);
      }
    }
    ```
  </Tab>

  <Tab title="Vanilla JavaScript">
    ```javascript theme={null}
    import { Tokenizer } from '@tagadapay/core-js';

    const tokenizer = new Tokenizer({ environment: 'production' });
    await tokenizer.initialize();

    const tagadaToken = await tokenizer.tokenizeCard({
      cardNumber: '4242424242424242',
      expiryDate: '12/28',
      cvc: '123',
      cardholderName: 'Jane Doe',
    });

    // tagadaToken → base64 string, send to your server
    ```

    For the raw token with SCA metadata:

    ```javascript theme={null}
    const rawToken = await tokenizer.tokenizeCardRaw({
      cardNumber: '4242424242424242',
      expiryDate: '12/28',
      cvc: '123',
    });

    if (rawToken.metadata?.auth?.scaRequired) {
      console.log('3DS will be required');
    }
    ```
  </Tab>

  <Tab title="Apple Pay / Google Pay">
    ```typescript theme={null}
    import { useCardTokenization } from '@tagadapay/core-js/react';

    const { tokenizeApplePay, tokenizeGooglePay } = useCardTokenization({
      environment: 'production',
    });

    // Apple Pay
    const appleResult = await tokenizeApplePay(applePayToken);

    // Google Pay
    const { tagadaToken } = await tokenizeGooglePay(googlePayToken);
    ```

    **Merchant-scoped Apple Pay domains (BYOK / platform integrator):** the
    payment-setup config attaches a domain-keyed `applePayByok` map to the
    `apple_pay` method's metadata for these domains. Resolve the current
    hostname with `resolveApplePayByok` and pass the resulting
    `merchantRegistrationId` to **both** the merchant session and the tokenize
    call — Apple encrypts the wallet token to that registration's certificate:

    ```typescript theme={null}
    import { resolveApplePayByok } from '@tagadapay/core-js';

    const byok = resolveApplePayByok(applePayMethod.metadata, window.location.hostname);

    // Merchant session (inside onvalidatemerchant)
    const session = await validateApplePayMerchant(
      byok?.displayName ?? storeName,
      window.location.host,
      byok?.merchantRegistrationId,
    );

    // Tokenization (inside onpaymentauthorized)
    const appleResult = await tokenizeApplePay(applePayToken, byok?.merchantRegistrationId);
    ```

    A `null` result from `resolveApplePayByok` means the domain uses the legacy
    platform configuration — omit the argument and behavior is unchanged.
    Alternatively, `startApplePaySession` from `@tagadapay/core-js` drives the
    whole sheet (session + tokenization) and accepts `merchantRegistrationId` in
    its config.

    See the [Apple & Google Pay example](https://github.com/TagadaPay/examples/tree/main/apple-google-tokenization) for the full integration including wallet button setup.
  </Tab>
</Tabs>

**What's in a TagadaToken?** It's a base64-encoded JSON envelope containing the provider token, card metadata (last4, brand, BIN, expiry), SCA flags, and issuer information. It's safe to send over the network — no raw card data.

***

## Step 2: Create a Payment Instrument (Server-Side)

Send the `tagadaToken` from your client to your server, then call TagadaPay to create a reusable payment instrument:

```bash theme={null}
curl -X POST https://app.tagadapay.com/api/public/v1/payment-instruments/create-from-token \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tagadaToken": "eyJ0eXBlIjoiY2FyZC...",
    "storeId": "store_abc123",
    "customerData": {
      "email": "jane@example.com",
      "firstName": "Jane",
      "lastName": "Doe"
    }
  }'
```

Response:

```json theme={null}
{
  "paymentInstrument": {
    "id": "pi_a1b2c3d4",
    "type": "card",
    "customerId": "cus_x1y2z3",
    "card": {
      "last4": "4242",
      "brand": "visa",
      "expMonth": 12,
      "expYear": 2028
    }
  },
  "customer": {
    "id": "cus_x1y2z3",
    "email": "jane@example.com"
  }
}
```

<Tip>
  If the customer already exists, pass `customerId` instead of `customerData`. The instrument will be attached to the existing customer.
</Tip>

Save the `paymentInstrument.id` — you'll use it for every charge on this card. The instrument is reusable for future payments (subscriptions, repeat purchases).

***

## Step 3: 3DS Authentication (If Required)

3DS (3D Secure) is required by many card issuers, especially in Europe (SCA regulation). The tokenization step tells you if it's needed via `rawToken.metadata.auth.scaRequired`.

3DS has two parts: creating a session (server + client) and handling the challenge (client).

### 3a. Create a 3DS Session

**Client-side:** Create a local session with the 3DS provider SDK:

```typescript theme={null}
import { useThreeds } from '@tagadapay/core-js/react';

const { createSession, startChallenge } = useThreeds({
  environment: 'production',
});

const session = await createSession(
  {
    id: paymentInstrument.id,
    token: rawToken.id,
    type: 'card',
    card: {
      last4: '4242',
      bin: '424242',
      expirationMonth: 12,
      expirationYear: 2028,
    },
  },
  {
    amount: 2999,       // in cents
    currency: 'USD',
    customerInfo: {
      name: 'Jane Doe',
      email: 'jane@example.com',
    },
  },
);
```

**Server-side:** Persist the session to TagadaPay so the payment processor can use it:

```bash theme={null}
curl -X POST https://app.tagadapay.com/api/public/v1/threeds/create-session \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "basis_theory",
    "storeId": "store_abc123",
    "paymentInstrumentId": "pi_a1b2c3d4",
    "sessionData": {
      "sessionId": "bt_session_xyz",
      "metadata": {}
    }
  }'
```

Response:

```json theme={null}
{
  "id": "threeds_abc123",
  "externalSessionId": "bt_session_xyz",
  "provider": "basis_theory",
  "status": "created",
  "paymentInstrumentId": "pi_a1b2c3d4"
}
```

### 3b. Handle the Challenge (After Payment — See Step 4)

If the issuer requires a challenge (password, SMS, biometric), it comes back in the payment response. You handle it client-side — see Step 4b below.

***

## Step 4: Process the Payment (Server-Side)

### 4a. Send the charge request

```bash theme={null}
curl -X POST https://app.tagadapay.com/api/public/v1/payments/process \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 2999,
    "currency": "USD",
    "storeId": "store_abc123",
    "paymentInstrumentId": "pi_a1b2c3d4",
    "threedsSessionId": "threeds_abc123",
    "initiatedBy": "customer",
    "mode": "purchase"
  }'
```

| Field                 | Type     | Description                                                                                            |
| --------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `amount`              | `number` | Amount in **cents** (2999 = \$29.99)                                                                   |
| `currency`            | `string` | ISO currency code (USD, EUR, GBP, etc.)                                                                |
| `storeId`             | `string` | Your store ID                                                                                          |
| `paymentInstrumentId` | `string` | From Step 2                                                                                            |
| `threedsSessionId`    | `string` | From Step 3 (optional if 3DS not required)                                                             |
| `initiatedBy`         | `string` | `"customer"` (CIT) or `"merchant"` (MIT)                                                               |
| `mode`                | `string` | `"purchase"` (charge immediately), `"auth"` (authorize only), or `"capture"` (capture a previous auth) |
| `reasonType`          | `string` | For MIT only: `"recurring"`, `"unscheduled"`, or `"installment"`                                       |
| `paymentFlowId`       | `string` | Optional — forces a specific payment flow (PSP routing rule)                                           |

**If payment succeeds immediately:**

```json theme={null}
{
  "payment": {
    "id": "pay_xyz789",
    "amount": 2999,
    "currency": "USD",
    "status": "succeeded",
    "requireAction": "none"
  }
}
```

**If 3DS challenge is required:**

```json theme={null}
{
  "payment": {
    "id": "pay_xyz789",
    "status": "pending",
    "requireAction": "threeds_auth",
    "requireActionData": {
      "type": "threeds_auth",
      "metadata": {
        "threedsSession": {
          "externalSessionId": "bt_session_xyz",
          "acsChallengeUrl": "https://acs.issuer.com/challenge",
          "acsTransID": "acs_trans_123",
          "messageVersion": "2.2.0"
        }
      }
    }
  }
}
```

### 4b. Handle the 3DS Challenge (Client-Side)

When the payment response has `requireAction: "threeds_auth"`, show the challenge to the customer:

```typescript theme={null}
const challengeResult = await startChallenge({
  sessionId: threedsSession.externalSessionId,
  acsChallengeUrl: threedsSession.acsChallengeUrl,
  acsTransactionId: threedsSession.acsTransID,
  threeDSVersion: threedsSession.messageVersion,
});

if (challengeResult.success) {
  // 3DS passed — poll for final payment status
}
```

The SDK opens a modal with the issuer's authentication page. Once the customer completes it, poll for the final status.

### 4c. Poll for Final Status

After 3DS completes, the PSP processes the payment asynchronously. Poll until you get a terminal status:

```bash theme={null}
curl https://app.tagadapay.com/api/public/v1/payments/pay_xyz789 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Poll every 1–2 seconds. Terminal statuses: `succeeded`, `failed`, `declined`.

***

## CIT vs MIT (Customer vs Merchant Initiated)

|                   | CIT (Customer Initiated)             | MIT (Merchant Initiated)                           |
| ----------------- | ------------------------------------ | -------------------------------------------------- |
| **When**          | Customer is present and clicks "Pay" | Server charges without customer present            |
| **Examples**      | First purchase, one-click reorder    | Subscription renewal, metered billing, retry       |
| **3DS**           | Required if issuer demands it        | Typically exempt (uses stored credential)          |
| **`initiatedBy`** | `"customer"`                         | `"merchant"`                                       |
| **`reasonType`**  | Not needed                           | `"recurring"`, `"unscheduled"`, or `"installment"` |

### CIT Example (Customer Paying Now)

```json theme={null}
{
  "amount": 2999,
  "currency": "USD",
  "storeId": "store_abc123",
  "paymentInstrumentId": "pi_a1b2c3d4",
  "initiatedBy": "customer",
  "mode": "purchase"
}
```

### MIT Example (Subscription Renewal)

```json theme={null}
{
  "amount": 2999,
  "currency": "USD",
  "storeId": "store_abc123",
  "paymentInstrumentId": "pi_a1b2c3d4",
  "initiatedBy": "merchant",
  "reasonType": "recurring",
  "mode": "purchase"
}
```

### MIT Example (Auth + Capture)

```json theme={null}
// Step 1: Authorize
{
  "amount": 2999,
  "currency": "USD",
  "storeId": "store_abc123",
  "paymentInstrumentId": "pi_a1b2c3d4",
  "initiatedBy": "merchant",
  "reasonType": "unscheduled",
  "mode": "auth"
}

// Step 2: Capture later
{
  "paymentId": "pay_xyz789",
  "mode": "capture"
}
```

***

## Void and Refund

### Void (Cancel an Authorization)

```bash theme={null}
curl -X POST https://app.tagadapay.com/api/public/v1/payments/void \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "paymentId": "pay_xyz789", "storeId": "store_abc123" }'
```

### Refund (Full or Partial)

```bash theme={null}
curl -X POST https://app.tagadapay.com/api/public/v1/payments/refund \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "paymentId": "pay_xyz789",
    "storeId": "store_abc123",
    "amount": 1000
  }'
```

Omit `amount` for a full refund.

***

## What TagadaPay Does Under the Hood

When you call `/payments/process`, TagadaPay:

1. **Routes to the right PSP** — based on your payment flow rules (card brand, currency, amount, BIN range, etc.)
2. **Attaches 3DS data** — if a `threedsSessionId` is provided, the authentication result is forwarded to the PSP
3. **Handles retries** — if the primary PSP declines, the payment flow can cascade to a backup PSP
4. **Normalizes the response** — regardless of which PSP handled it, you get the same response format
5. **Records the transaction** — for analytics, dispute management, and reconciliation

You don't need to integrate with Stripe, NMI, Airwallex, etc. individually. Configure the PSP connections in the [CRM dashboard](https://app.tagadapay.com), set up payment flow routing rules, and TagadaPay handles the rest.

***

## API Reference

| Endpoint                                                                                                                  | Method | Purpose                            |
| ------------------------------------------------------------------------------------------------------------------------- | ------ | ---------------------------------- |
| [`/payment-instruments/create-from-token`](/api-reference/payment-instruments/create-payment-instrument-from-tagadatoken) | POST   | Create instrument from TagadaToken |
| [`/payment-instruments/{id}`](/api-reference/payment-instruments/get-payment-instrument-details)                          | GET    | Get instrument details             |
| [`/customers/{id}/payment-instruments`](/api-reference/payment-instruments/list-customer-payment-instruments)             | GET    | List customer's instruments        |
| [`/threeds/create-session`](/api-reference/3ds-authentication/create-3ds-authentication-session)                          | POST   | Create 3DS session                 |
| [`/payments/process`](/api-reference/payments/process-a-payment)                                                          | POST   | Process a payment (CIT or MIT)     |
| [`/payments/{id}`](/api-reference/payments/get-payment-by-id)                                                             | GET    | Get payment status                 |
| [`/payments/void`](/api-reference/payments/void-a-payment)                                                                | POST   | Void an authorization              |
| [`/payments/refund`](/api-reference/payments/refund-a-payment)                                                            | POST   | Full or partial refund             |

***

## Working Examples

Complete working examples with React, TypeScript, and Tailwind CSS are available on GitHub:

<CardGroup cols={2}>
  <Card title="Card Tokenization + Payment" icon="credit-card" href="https://github.com/TagadaPay/examples/tree/main/core-js-tokenization">
    Full 4-step payment flow with 3DS
  </Card>

  <Card title="Apple Pay & Google Pay" icon="apple" href="https://github.com/TagadaPay/examples/tree/main/apple-google-tokenization">
    Wallet tokenization integration
  </Card>
</CardGroup>

**Test card:** `4242 4242 4242 4242` (any future expiry, any CVC)

***

## SDK Reference

### `@tagadapay/core-js` Package

| Export    | Import Path                  | What it provides                                            |
| --------- | ---------------------------- | ----------------------------------------------------------- |
| **Core**  | `@tagadapay/core-js`         | `Tokenizer` class, `createTagadaToken`, `decodeTagadaToken` |
| **React** | `@tagadapay/core-js/react`   | `useCardTokenization`, `useThreeds` hooks                   |
| **3DS**   | `@tagadapay/core-js/threeds` | `ThreedsManager`, `ThreedsModal` for manual 3DS             |

### Key Types

```typescript theme={null}
interface CardPaymentMethod {
  cardNumber: string;
  expiryDate: string;   // "MM/YY"
  cvc: string;
  cardholderName?: string;
}

interface CardTokenResponse {
  id: string;
  type: string;
  data: { number?: string; expiration_month?: number; expiration_year?: number };
  metadata?: {
    auth?: { scaRequired?: boolean };
    tokenizedAt?: string;
  };
}

interface TagadaToken {
  type: 'card' | 'apple_pay' | 'google_pay';
  token: string;
  provider: string;
  nonSensitiveMetadata: {
    last4?: string;
    brand?: string;
    bin?: string;
    expiryMonth?: number;
    expiryYear?: number;
    funding?: string;
    fingerprint?: string;
    authentication?: 'sca_required' | 'optional';
    issuer?: { name?: string; country?: string };
  };
}
```

## Test your integration

In sandbox, any 16-digit card works. Use this one:

|        |                       |
| ------ | --------------------- |
| Card   | `4242 4242 4242 4242` |
| Expiry | Any future date       |
| CVC    | Any 3 digits          |

Confirm the payment succeeds, then list it with `tagada.payments.list()`. To exercise a 3DS return, use a card your processor flags for authentication and confirm the shopper lands back on the `returnUrl` you passed.

Full sandbox setup (processor, payment flow, store): [Sandbox testing](/developer-tools/node-sdk/sandbox-testing).

<CardGroup cols={2}>
  <Card title="npm Package" icon="npm" href="https://www.npmjs.com/package/@tagadapay/core-js">
    @tagadapay/core-js on npm
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/introduction">
    Full REST API documentation
  </Card>
</CardGroup>
