AsherPay
Guides / Direct Card Tokenization

Low-Level Payments (core-js + REST)

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

Most developers don't need this page. If you're building a checkout, start with the Headless SDK — 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.

Which Approach Should I Use?

Headless SDK (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

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.


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 — handles sessions, APMs, 3DS automatically
You just want a hosted checkout page Merchant Quick Start — 7 API calls, zero frontend code
You want a custom-branded page (HTML or Plugin SDK) Funnel Pages — deploy pages as plugins
You need server-side recurring billing Subscriptions Guide — 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

```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/AsherPay/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 AsherPay 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 AsherPay 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 AsherPay Does Under the Hood

When you call /payments/process, AsherPay:

  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, set up payment flow routing rules, and AsherPay handles the rest.


API Reference

Endpoint Method Purpose
/payment-instruments/create-from-token POST Create instrument from TagadaToken
/payment-instruments/{id} GET Get instrument details
/customers/{id}/payment-instruments GET List customer's instruments
/threeds/create-session POST Create 3DS session
/payments/process POST Process a payment (CIT or MIT)
/payments/{id} GET Get payment status
/payments/void POST Void an authorization
/payments/refund POST Full or partial refund

Working Examples

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

Full 4-step payment flow with 3DS

Wallet tokenization integration

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

@tagadapay/core-js on npm

Full REST API documentation