Low-Level Payments (core-js + REST)
Advanced: direct card tokenization, 3DS, payment instruments, and server-side payment processing via @tagadapay/core-js + REST API
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 |
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
- A AsherPay 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/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:
- Routes to the right PSP — based on your payment flow rules (card brand, currency, amount, BIN range, etc.)
- Attaches 3DS data — if a
threedsSessionIdis provided, the authentication result is forwarded to the PSP - Handles retries — if the primary PSP declines, the payment flow can cascade to a backup PSP
- Normalizes the response — regardless of which PSP handled it, you get the same response format
- 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:
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 }; }; } ```