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

# Low-Level Payments (core-js + REST)

> Advanced: direct card tokenization, 3DS, payment instruments, and server-side payment processing via @tagadapay/core-js + REST API

# Low-Level Payments (core-js + REST)

<Warning>
  **Most developers don't need this page.** If you're building a checkout, start with the [Headless SDK](/developer-tools/headless-sdk/introduction) — it wraps everything below into a single `tagada.payment.pay()` call with built-in session management, APM discovery, and 3DS handling.

  This page is for **advanced use cases** where you need direct control over payment instruments, 3DS sessions, and the charge lifecycle.
</Warning>

## Which Approach Should I Use?

|                            | [Headless SDK](/developer-tools/headless-sdk/payment-setup) (recommended) | This page (advanced)                                        |
| -------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------- |
| **Package**                | `@tagadapay/headless-sdk`                                                 | `@tagadapay/core-js` + REST API                             |
| **Pay a checkout session** | `tagada.payment.pay({ checkoutSessionId, tagadaToken })`                  | 4 separate API calls (tokenize → instrument → 3DS → charge) |
| **Checkout sessions**      | Built-in (`useCheckout()`, cart, promos, shipping)                        | Not included — you manage sessions yourself                 |
| **APM discovery**          | `tagada.payment.getPaymentSetup()`                                        | Manual REST call                                            |
| **3DS handling**           | Automatic (inside `pay()`)                                                | Manual (create session + start challenge)                   |
| **Best for**               | Stores, checkouts, e-commerce flows                                       | Mobile apps, embedded forms, MIT charges, platforms         |

<Tip>
  **Rule of thumb:** If your customer is checking out on a web page, use the **Headless SDK**. If you're building a native mobile app, a payment platform, or need server-initiated (MIT) charges, use **core-js + REST** as described below.
</Tip>

***

## When to Use This

| Use case                   | Example                                                                 |
| -------------------------- | ----------------------------------------------------------------------- |
| **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                      |
| **MIT / server charges**   | Subscription renewals, metered billing, retry logic — no browser needed |
| **Auth + capture**         | Authorize now, capture later (hotels, car rentals, pre-orders)          |

### When NOT to Use This

| Instead of...                                       | Use...                                                                                                  |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Building a web checkout                             | [Headless SDK](/developer-tools/headless-sdk/payment-setup) — handles sessions, APMs, 3DS automatically |
| 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);
    ```

    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 };
  };
}
```

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