When you need to complete card-on-file setup and the first charge in the same checkout session, and generate a reusable tokenId, use SDK mode.
Enable this mode with subProductType: "TOKEN". It is best for embedded checkout scenarios that balance conversion and repeat-purchase efficiency.
The key difference from DIRECT is that DIRECT is for one-time payments only and does not generate a reusable saved payment method identifier, while TOKEN generates tokenId during the payment flow for future token-based payments.
Treat webhook as the final transaction status ( ).
Integration profile
Best-fit scenarios
Key capabilities
In TOKEN mode, the system separates notifications for card-on-file result and payment result:
txnType = "BIND_CARD", usually orderAmount = "0.00", returns tokenIdtxnType = "SALE", returns the first payment result and the same tokenIdImplement in this order:
buttonSeparation)hideTokenList)tokenId; never store full PAN/CVV.This step is mostly the same as one-time payment creation. Key differences:
subProductType must be "TOKEN" ( )merchantCustId is required ( ) to maintain a stable customer-to-token mapping{
"merchantNo": "800209",
"merchantTxnId": "ORDER-20260305-0001",
"merchantCustId": "CUST-10001",
"orderAmount": "75.00",
"orderCurrency": "USD",
"productType": "CARD",
"subProductType": "TOKEN",
"txnType": "SALE",
"txnOrderMsg": "{\"appId\":\"YOUR_APP_ID\",\"returnUrl\":\"https://merchant.example.com/return\",\"notifyUrl\":\"https://merchant.example.com/webhook\"}",
"billingInformation": "{\"email\":\"customer@example.com\",\"country\":\"US\"}",
"shippingInformation": "{\"email\":\"customer@example.com\",\"country\":\"US\"}",
"sign": "YOUR_SIGNATURE"
}
After creation, the server must return these SDK initialization parameters:
transactionIdredirectUrlconst pacypay = new Pacypay(transactionId, {
container: 'onerway_checkout',
locale: 'en',
environment: 'sandbox',
mode: 'CARD',
redirectUrl: redirectUrlFromServer,
config: {
subProductType: 'TOKEN',
buttonSeparation: true,
hideTokenList: false
},
onPaymentCompleted: function (result) {
const { respCode, respMsg, data } = result || {};
if (respCode !== '20000') {
console.error('Payment failed', respMsg);
return;
}
// In TOKEN mode, tokenId may already be returned even when status=R (3DS challenge required)
if (data?.tokenId) {
// You may send it to the server for temporary storage, but webhook remains the final source
console.log('tokenId:', data.tokenId);
}
if (data?.status === 'R' && data?.redirectUrl) {
window.location.replace(data.redirectUrl);
return;
}
if (data?.status === 'S') {
// Update frontend state if needed; final status must be confirmed by webhook
console.log('SDK synchronous success');
}
},
onError: function (error) {
console.error('SDK error:', error);
}
});
buttonSeparation) Client-side| Value | User experience | Recommended scenario |
|---|---|---|
true | Save method first, then confirm payment (two-step) | Stronger confirmation flow and clearer risk messaging |
false | Complete save + pay directly after card entry (one-step) | Shortest path and higher conversion priority |
hideTokenList) Client-sidehideTokenList controls whether SDK displays the saved payment method list (for example, masked card tails):
| Value | Behavior | Recommended scenario |
|---|---|---|
false | Show saved methods | Repeat-purchase flows where users should quickly pay with existing methods |
true | Hide saved methods | Scenarios requiring forced re-entry or reduced frontend exposure of saved credentials |
hideTokenList only affects frontend rendering. It does not affect tokenId generation, storage, or usability on the backend.When hideTokenList = true, apply these compensating actions:
When SDK callback returns status = "R":
redirectUrlreturnUrlstatus = "R" means additional authentication is required, not a failed transaction. Do not close the order or release inventory at this stage.Responsibility boundaries (Must / Must not):
| Role | Must | Must not |
|---|---|---|
| Client | Show status, handle 3DS redirect, report callback params | Must not fulfill, account, or activate service based only on SDK callback |
| Server | Verify webhook signature, process idempotently, handle out-of-order events, update final order status | Must not update business state without signature verification |
The server must handle both BIND_CARD and SALE webhooks (including retries and out-of-order delivery):
@PostMapping(value = "/webhook", produces = "text/plain")
public String handleWebhook(@RequestBody Map<String, Object> body) {
String transactionId = (String) body.get("transactionId");
String txnType = (String) body.get("txnType"); // BIND_CARD / SALE
String status = (String) body.get("status"); // S / F / ...
String tokenId = (String) body.get("tokenId");
if (!verifySignature(body)) return "";
if (alreadyProcessed(transactionId)) return transactionId;
if ("BIND_CARD".equals(txnType) && "S".equals(status)) {
saveToken(body.get("merchantCustId"), tokenId);
}
if ("SALE".equals(txnType)) {
updateOrderByWebhook(body); // Update order status, fulfillment, and notifications
}
markProcessed(transactionId);
return transactionId;
}
BIND_CARD and SALE can arrive in any order. Use transactionId for idempotency and out-of-order tolerance.| Field | Where it appears | Purpose |
|---|---|---|
merchantCustId | Request, webhook | Merchant-side customer unique ID for linking tokenId and future purchases |
subProductType | Request + SDK config | Must be TOKEN |
transactionId | Create response, callback, webhook | Transaction unique ID and idempotency key |
redirectUrl | Create response / SDK callback | SDK init URL or 3DS redirect |
hideTokenList | SDK config | Controls saved method list visibility |
tokenId | SDK callback, BIND_CARD webhook, SALE webhook | Reusable token for future token-based payments |
txnType | webhook | Distinguishes BIND_CARD and SALE |
status | SDK callback, webhook | Transaction status field; webhook is final |
tokenId from SDK callback, but use webhook final status for accounting and fulfillment.transactionId.merchantCustId as customer primary key and keep a traceable mapping to tokenId.