Save and reuse payment methods

Use the Onerway Web SDK to save card credentials during checkout and reuse tokenId for future purchases.

Overview

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

Integration complexity
Integration type
Web SDK (embedded)
Core output
tokenId (card-on-file token)

Best-fit scenarios

  • Support one-click repeat purchases after the first payment
  • Save customer card credentials without storing sensitive card data
  • Complete card-on-file setup and payment in one checkout session

Key capabilities

  • Enable card-on-file flow with subProductType=TOKEN
  • Support one-step and two-step bind-and-pay UX
  • Control saved method display with hideTokenList
  • Support 3DS with redirectUrl handling
  • One transaction triggers two webhooks (BIND_CARD + SALE)

TOKEN notification model

In TOKEN mode, the system separates notifications for card-on-file result and payment result:

  1. Card-on-file webhook: txnType = "BIND_CARD", usually orderAmount = "0.00", returns tokenId
  2. Payment webhook: txnType = "SALE", returns the first payment result and the same tokenId
SDK callbacks are for frontend interaction only. Do not use them as the basis for fulfillment or accounting.

Integration steps

Implement in this order:

  1. Create a TOKEN transaction on the server
  2. Initialize SDK on the client (TOKEN mode)
  3. Choose bind-and-pay UX mode (buttonSeparation)
  4. Control saved method visibility (hideTokenList)
  5. Handle 3DS redirect
  6. Define responsibility boundaries (Must / Must not)
  7. Receive and process webhooks idempotently
Before saving payment methods, clearly disclose and collect customer consent on the frontend (usage purpose, future charge scenarios, and card-on-file cancellation path). On the server, store only required identifiers such as tokenId; never store full PAN/CVV.

Create a TOKEN transaction on the server Server-side

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:

  • transactionId
  • redirectUrl

Initialize SDK on the client (TOKEN mode) Client-side

const 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);
  }
});

Choose bind-and-pay UX mode (buttonSeparation) Client-side

ValueUser experienceRecommended scenario
trueSave method first, then confirm payment (two-step)Stronger confirmation flow and clearer risk messaging
falseComplete save + pay directly after card entry (one-step)Shortest path and higher conversion priority

Control saved method visibility (hideTokenList) Client-side

hideTokenList controls whether SDK displays the saved payment method list (for example, masked card tails):

ValueBehaviorRecommended scenario
falseShow saved methodsRepeat-purchase flows where users should quickly pay with existing methods
trueHide saved methodsScenarios 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:

  1. Show clear copy: "Saved payment methods are not displayed on this page"
  2. Set user expectation that card details need to be entered again
  3. Provide a payment-method management entry in account settings (or track as a TODO in product requirements)

Handle 3DS redirect Client-side Server-side

When SDK callback returns status = "R":

  1. Read redirectUrl
  2. Redirect immediately to the issuer's 3DS verification page
  3. After verification, user returns to returnUrl
  4. Update order and card-on-file result based on webhook final status
status = "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):

RoleMustMust not
ClientShow status, handle 3DS redirect, report callback paramsMust not fulfill, account, or activate service based only on SDK callback
ServerVerify webhook signature, process idempotently, handle out-of-order events, update final order statusMust not update business state without signature verification

Receive and process webhooks idempotently Server-side

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;
}
In an initial TOKEN transaction, BIND_CARD and SALE can arrive in any order. Use transactionId for idempotency and out-of-order tolerance.

Key fields and config quick reference

FieldWhere it appearsPurpose
merchantCustIdRequest, webhookMerchant-side customer unique ID for linking tokenId and future purchases
subProductTypeRequest + SDK configMust be TOKEN
transactionIdCreate response, callback, webhookTransaction unique ID and idempotency key
redirectUrlCreate response / SDK callbackSDK init URL or 3DS redirect
hideTokenListSDK configControls saved method list visibility
tokenIdSDK callback, BIND_CARD webhook, SALE webhookReusable token for future token-based payments
txnTypewebhookDistinguishes BIND_CARD and SALE
statusSDK callback, webhookTransaction status field; webhook is final

Test checklist

Best practices

  1. You may temporarily store tokenId from SDK callback, but use webhook final status for accounting and fulfillment.
  2. Keep webhook processing order fixed: signature verification -> idempotency check -> business handling -> return transactionId.
  3. Model "card-on-file success" and "first payment success" as separate events for clearer reconciliation and troubleshooting.
  4. Use merchantCustId as customer primary key and keep a traceable mapping to tokenId.
  5. Before production launch, test 3DS challenges, retries, duplicate notifications, out-of-order delivery, and network jitter.