Saved cards: customers & tokens (COFT)

Let returning customers pay with a card they saved earlier (card-on-file tokenisation, COFT). The card is tokenised when the customer pays with save: true in request. Later payments send the token instead of the card number.


Flow

  1. Create the customer. (Returns Customer id)

    POST /api/v1/customer

    { "mobile_number": "<customer mobile number>", "email_id": "<customer email>", "country_code": "65" }
    {
      "customer_id": "cust-v1-261102112435-aa-vSD11R",
      "country_code": "65",
      "mobile_number": "<customer mobile number>",
      "email_id": "<customer email>",
      "status": "ACTIVE",
      "created_at": "2026-11-02T11:24:35.440337118Z",
      "updated_at": "2026-11-02T11:24:35.440353442Z",
      "brand_wallet_enabled": false
    }
  2. Create the order with the customer. Put customer_id in purchase_details.customer.customer_id. This works for both POST /api/pay/v1/orders (Seamless) and POST /api/checkout/v1/orders (Hosted Checkout).

    {
      "merchant_order_reference": "order-20001",
      "order_amount": { "value": 100, "currency": "SGD" },
      "integration_mode": "SEAMLESS",
      "purchase_details": {
        "customer": {
          "customer_id": "cust-v1-261102112435-aa-vSD11R",
          "email_id": "<customer email>", "first_name": "Test", "last_name": "Buyer",
          "mobile_number": "<customer mobile number>", "country_code": "65"
        }
      }
    }
  3. First payment, saving the card. POST /api/pay/v1/orders/{order_id}/payments with card_details and "save": true. Complete 3-D Secure via challenge_url, then verify with Get order.
    List saved cards. GET /api/v1/customer/{customerId}/tokens returns each token's token_id, status, expiry, acquirer and masked card details.

    {
      "payments": [{
        "merchant_payment_reference": "pay-save-0001",
        "payment_method": "CARD",
        "payment_amount": { "value": 100, "currency": "SGD" },
        "payment_option": {
          "card_details": { "name": "Test Buyer", "card_number": "<UAT test card>", "cvv": "123", "expiry_month": "12", "expiry_year": "2027", "save": true }
        }
      }]
    }
  4. List the customer's saved cards

    GET /api/v1/customer/{customerId}/tokens

    {
      "customer_id": "cust-v1-261102131717-aa-lHRqxs",
      "total_tokens": 1,
      "tokens": [{
        "token_id": "token-v1-261102132209-aa-evR7dh",
        "merchant_token_reference": "b88bdd60-d47d-4fb1-854f-0bf0d65709c1",
        "status": "ACTIVE",
        "expired_at": "2027-05-02T13:22:09.174688Z",
        "merchant_metadata": { "acquirer": "MPGS" },
        "card_data": { "last4_digit": "6000", "network_name": "VISA", "issuer_name": "VISA" }
      }]
    }

    Note: Show customers only the masked details (network and last 4 digits), and hide tokens whose status isn't ACTIVE or whose expired_at has passed.

  5. Pay with a saved card. Create a new order, then create the payment with card_token_details.token_id and token_txn_type: "ACQUIRER_TOKEN". No CVV or expiry is needed. Complete challenge_url if returned, then verify.

    {
      "payments": [{
        "merchant_payment_reference": "pay-coft-0001",
        "payment_method": "CARD",
        "payment_amount": { "value": 100, "currency": "SGD" },
        "payment_option": {
          "card_token_details": {
            "name": "Test Buyer",
            "last4_digit": "1111",
            "cvv": "123",
            "expiry_month": "12",
            "expiry_year": "2027",
            "token_id": "token-v1-261102132209-aa-evR7dh",
            "token_txn_type": "ACQUIRER_TOKEN",
            "registered_mobile_number": "<customer mobile number>"
          }
        }
      }]
    }




Did this page help you?