> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Merchant-side card binding

> Bind a card independently on the merchant page and manage its token for subsequent payments.

Use `@waffo/payment-sdk` to bind a card independently on your page and securely convert the card data into a token. This flow does not require a regular payment in the same operation.

> [!NOTE]
>
> This flow spans both the **server-side** (calling the Generate / Inquiry / Remove APIs) and the **frontend** (submitting card information via `@waffo/payment-sdk`).

## When to use this flow

* You need to bind a card before deciding when to charge it.
* You want to design the card-binding UI on your own page.
* You do not use Waffo Checkout, or do not want to bind the card as part of the current payment.
* You are building a merchant-managed subscription in which your system owns the billing schedule, subscription status, and failed-payment retries, and initiates each MIT.

If you already use Waffo Checkout and want the first successful payment to create a token for subsequent MIT payments, use [Waffo Checkout card binding](https://waffo.com/docs/en/developer-docs/integration/tokenization/checkout-card-binding.md).

## Core flow

1. **Phase 1: bind the card** — the merchant backend obtains `tokenSessionId`, and the frontend submits card information through `@waffo/payment-sdk`. The returned `tokenId` has the `UNVERIFIED` status. This phase does not trigger 3DS.
2. **Phase 2: zero-amount payment verification** — the backend creates a zero-amount CIT with the token. This payment may require 3DS. After it succeeds, the token becomes `VERIFIED`.
3. **Subsequent payments** — pass the `tokenId` as `paymentInfo.userPaymentAccessToken` in `order/create`.

```mermaid theme={null}
sequenceDiagram
    actor User
    participant Frontend as Merchant frontend
    participant Backend as Merchant backend
    participant Waffo

    Backend->>Waffo: POST /api/v1/tokenization/generate
    Waffo-->>Backend: Generate response<br/>data.tokenSessionId
    Backend-->>Frontend: tokenSessionId
    User->>Frontend: Enter card information
    Frontend->>Waffo: sdk.tokenizationSubmit()<br/>Encrypted card data
    Waffo-->>Frontend: tokenizationSubmit response<br/>tokenRequestId
    Waffo-->>Backend: TOKENIZATION_NOTIFICATION<br/>tokenId + UNVERIFIED
    Backend->>Waffo: POST /api/v1/order/create<br/>orderAmount=0 + userPaymentAccessToken
    Waffo-->>Backend: Order Create response<br/>data.orderStatus + data.orderAction
    Backend-->>User: Redirect for payment verification
    opt 3DS is required
        User->>Waffo: Complete 3DS
    end
    Waffo-->>Backend: PAYMENT_NOTIFICATION Webhook<br/>zero-amount CIT result
    opt Query the zero-amount CIT result
        Backend->>Waffo: POST /api/v1/order/inquiry
        Waffo-->>Backend: Order Inquiry response<br/>data.orderStatus
    end
    Waffo-->>Backend: TOKENIZATION_NOTIFICATION<br/>tokenStatus=VERIFIED
    Backend->>Backend: Store the latest token status
    Note over Backend,Waffo: Token=VERIFIED and merchant is allowlisted for MIT
    Backend->>Waffo: POST /api/v1/order/create<br/>userPaymentAccessToken + merchantInitiatedMode
    Waffo-->>Backend: Order Create response<br/>data.orderStatus
    Waffo-->>Backend: PAYMENT_NOTIFICATION Webhook<br/>MIT result
    opt Query the MIT result
        Backend->>Waffo: POST /api/v1/order/inquiry
        Waffo-->>Backend: Order Inquiry response<br/>data.orderStatus
    end
```

## Card binding flow

**1. Merchant backend calls the Generate API**

Call [POST /api/v1/tokenization/generate](https://waffo.com/docs/api-reference/waffo-tokenization-api/tokenization-generate.md), passing parameters such as `tokenRequestId`, `merchantUserId`, and `tokenType: "CARD"`. On success, it returns `tokenSessionId`.

```json theme={null}
{
  "tokenRequestId": "TOKEN_202609170001",
  "merchantUserId": "USER_001",
  "tokenType": "CARD",
  "merchantInfo": {
    "merchantId": "YOUR_MERCHANT_ID"
  },
  "notifyUrl": "https://merchant.example.com/webhooks/tokenization"
}
```

**2. Frontend submits card information**

Use the `tokenizationSubmit` method of `@waffo/payment-sdk` to encrypt the card data and submit it to the Waffo server:

```typescript theme={null}
import WaffoSDK from '@waffo/payment-sdk';

const sdk = new WaffoSDK('your-client-api-key', {
  env: 'prod',
  locale: 'en'
});

const result = await sdk.tokenizationSubmit('tokenSessionId', {
  tokenData: {
    pan: '4111111111111111',
    name: 'John Doe',
    expiry: '12/2028',    // MM/YYYY
    cvv: '123'            // optional
  },
  billingAddress: {        // optional
    countryCode: 'USA',
    region: 'CA',
    city: 'San Francisco',
    postalCode: '94102',
    address: '123 Main St'
  }
});
```

The merchant frontend passes plaintext card data to the SDK, which encrypts the data before transmission. With merchant-side card binding, the merchant does not need PCI DSS certification as long as its backend neither retains nor transmits plaintext card data.

**3. Handle the card binding result**

```typescript theme={null}
if (result.success) {
  const { tokenRequestId } = result.data;
  // Binding was submitted; wait for the Webhook to obtain tokenId
}
```

If the Generate request includes `notifyUrl`, Waffo sends `TOKENIZATION_NOTIFICATION` when card binding completes, the Token status changes, or the card summary is updated. Process notifications idempotently using `result.tokenId`, and store the latest `tokenStatus` and Token data. The notification is not limited to the initial binding result.

**4. Create a zero-amount verification payment**

The SDK card-binding phase does not trigger 3DS. After receiving an `UNVERIFIED` `tokenId`, call [`POST /api/v1/order/create`](https://waffo.com/docs/api-reference/order-create/create-new-order), set `orderAmount` to `0`, and pass the token as `paymentInfo.userPaymentAccessToken`.

```json theme={null}
{
  "paymentRequestId": "VERIFY_202609170001",
  "merchantOrderId": "VERIFY_ORDER_202609170001",
  "orderCurrency": "HKD",
  "orderAmount": "0",
  "orderDescription": "Verify bound card",
  "orderRequestedAt": "2026-09-17T02:00:00.000Z",
  "notifyUrl": "https://merchant.example.com/webhooks/payment",
  "successRedirectUrl": "https://merchant.example.com/card/verified",
  "failedRedirectUrl": "https://merchant.example.com/card/verification-failed",
  "merchantInfo": {
    "merchantId": "YOUR_MERCHANT_ID"
  },
  "userInfo": {
    "userId": "USER_001",
    "userEmail": "user@example.com",
    "userTerminal": "WEB"
  },
  "paymentInfo": {
    "productName": "ONE_TIME_PAYMENT",
    "userPaymentAccessToken": "TOKEN_xxxxxxxxxxxx"
  }
}
```

The zero-amount verification transaction is a CIT. Do not set `paymentInfo.merchantInitiatedMode`. If the response has `orderStatus: AUTHORIZATION_REQUIRED`, parse `orderAction` and send the user to complete 3DS. Read the payment result from `PAYMENT_NOTIFICATION`. After payment succeeds, confirm that the token becomes `VERIFIED` through `TOKENIZATION_NOTIFICATION` or Tokenization Inquiry.

## Token status

After card binding succeeds, the Token starts in `UNVERIFIED`. Create a zero-amount [`ONE_TIME_PAYMENT`](https://waffo.com/docs/api-reference/order-create/create-new-order) CIT with that token. The payment may require the user to complete 3DS. After it succeeds, the token becomes `VERIFIED`.

```mermaid theme={null}
stateDiagram-v2
    [*] --> UNVERIFIED: Card binding succeeds
    UNVERIFIED --> VERIFIED: CIT succeeds
    UNVERIFIED --> EXPIRED: Card expires
    VERIFIED --> EXPIRED: Card expires
    UNVERIFIED --> SUSPENDED: Waffo suspends use
    VERIFIED --> SUSPENDED: Waffo suspends use
```

* `UNVERIFIED`: The Token exists but has not completed a successful CIT. You cannot use it for scheduled or unscheduled MIT; Waffo returns `A0045`.
* **`VERIFIED`**: A successful CIT is complete, and the Token can be used for subsequent MIT payments.
* `EXPIRED`: The Token status becomes `EXPIRED` when the card reaches its expiry date.
* `SUSPENDED`: Waffo has suspended use of the Token. The public contract does not define the exact trigger conditions.

## Initiate a subsequent MIT

> [!WARNING]
>
> Before initiating MIT, confirm that the Token is `VERIFIED` and your merchant account is allowlisted by Waffo for MIT. Scheduled and unscheduled MIT are unavailable before approval.

Call [`POST /api/v1/order/create`](https://waffo.com/docs/api-reference/order-create/create-new-order). Use the same `merchantUserId` from the Generate API as `userInfo.userId`, and set both `paymentInfo.userPaymentAccessToken` and `paymentInfo.merchantInitiatedMode`.

```json theme={null}
{
  "paymentRequestId": "MIT_202609170001",
  "merchantOrderId": "MIT_ORDER_202609170001",
  "orderCurrency": "HKD",
  "orderAmount": "49.00",
  "orderDescription": "Scheduled merchant-initiated payment",
  "orderRequestedAt": "2026-09-17T03:00:00.000Z",
  "notifyUrl": "https://merchant.example.com/webhooks/payment",
  "merchantInfo": {
    "merchantId": "YOUR_MERCHANT_ID"
  },
  "userInfo": {
    "userId": "USER_001",
    "userEmail": "user@example.com",
    "userTerminal": "WEB"
  },
  "paymentInfo": {
    "productName": "ONE_TIME_PAYMENT",
    "userPaymentAccessToken": "TOKEN_xxxxxxxxxxxx",
    "merchantInitiatedMode": "scheduled"
  }
}
```

* `scheduled`: Charge on a pre-agreed fixed schedule.
* `unscheduled`: Merchant-initiated charge without a fixed schedule.

  MIT does not require the user to enter card information or complete 3DS again. Receive the result through `PAYMENT_NOTIFICATION`. If the notification is not delivered, call [`POST /api/v1/order/inquiry`](https://waffo.com/docs/api-reference/order-inquiry/order-inquiry) to retrieve the order status. Pass either `paymentRequestId` or `acquiringOrderId` in the request body, for example:

  ```json theme={null}
  {
    "paymentRequestId": "MIT_202609170001"
  }
  ```

You can use `tokenId` only with Waffo's `ONE_TIME_PAYMENT` product. Do not pass it to Waffo's `SUBSCRIPTION` product.

## Token API usage

| Action | API | Description |
| - | - | - |
| Start card binding | [POST /api/v1/tokenization/generate](https://waffo.com/docs/api-reference/waffo-tokenization-api/tokenization-generate.md) | Create a card binding session and obtain the `tokenSessionId` required by the frontend SDK |
| Inquiry | [POST /api/v1/tokenization/inquiry](https://waffo.com/docs/api-reference/waffo-tokenization-api/tokenization-inquiry.md) | Retrieve one Token by `tokenId`, or retrieve a user's Token list by `merchantUserId` |
| Remove | [POST /api/v1/tokenization/remove](https://waffo.com/docs/api-reference/waffo-tokenization-api/tokenization-remove.md) | Remove one Token by `tokenId`, or remove all Tokens for a user by `merchantUserId` |

## Security mechanisms

* The merchant frontend passes plaintext card data to the SDK, which encrypts it before transmission; the merchant backend does not handle the plaintext card number
* All API requests and responses use SHA256WithRSA signature verification
* The first zero-amount CIT supports 3DS verification; the SDK card-binding phase does not trigger 3DS


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.