Save a card for future transactions

How to save a customer's card on file for use in future purchases or recurring payments.

North America—United States and Canada

What is card-on-file (COF)?

Card-on-file (COF) practices are governed by card networks and are designed to enhance security and transparency for consumers. A key compliance requirement under PCI-DSS and card network rules is that merchants must obtain explicit consent from cardholders before storing and using their payment credentials for future transactions.

COF transactions occur when cardholders authorize merchants to retain their card information for future purchases or recurring payments. To build and test the COF feature, you can use sandbox test cards.

Before you begin

Note the following:

  • Cardholder consent—Merchants must obtain cardholder consent before saving a card on file.
  • Tokenization—Ensure multi-pay tokens are enabled in your Clover gateway settings. Generate a card token using the Ecommerce API or iframe, then use it to save the card.
  • Automated notifications—When a COF record is saved, Clover automatically sends email and SMS alerts for upcoming charges, failed payments, and subscription renewals.
  • COF removal on request—Cardholders can request deletion of their saved card data.
  • External token management—If not using automated flows from Clover, you can manage multi-pay tokens and customer data externally, including customer profiles, stored credentials, and payment details. For more information, see Use external multi-pay tokens with Clover Ecommerce APIs.

Workflow

Card-on-file payment flow

Card-on-file payment flow

Prerequisites

  1. Generate an Ecommerce API key (PAKMS key) or apiAccessKey.
  2. Use the PAKMS key to create a card token. You must have all the required details, such as the card number, card verification value (CVV), and expiration date.

Save a card-on-file for a new customer

  1. Send a POST request to /v1/customers.
  2. Enter the required information:
    • email—Customer's email address.
    • source—Card token to charge. Example: clv_1ABCDefgHI23jKL4m5nOPqR, generated from the create a card token /v1/tokens endpoint.
  3. Set the Authorization header as Bearer token type, and enter the test API token or OAuth-generated access_token.

Request and response example—Save card-on-file for a new customer

curl --request POST \
  --url https://scl-sandbox.dev.clover.com/v1/customers \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ab86a5e8-48f3-b3bd-8c45-d415e9867833' \
  --header 'Content-Type: application/json' \
  --data '{
    "email": "[email protected]",
    "firstName": "John",
    "lastName": "Doe",
    "source": "clv_1TSTSwVSFJxGwWKzb79Sderj"
  }'
{
  "id": "PWG98TV2ECTT2", //customerId
  "object": "customer",
  "created": 1748340989108,
  "currency": "USD",
  "email": "[email protected]",
  "name": " ",
  "sources": {
    "object": "list",
    "data": [
      "8YTCARDXSQHP0"
    ]
  },
  "shipping": {
    "name": " "
  }
}

In response, Clover:

  • Generates a unique customerId.
  • Sets the card source token as a multi-pay token for the customer.

From here, you can use either the customerId or the multi-pay card token to create a charge for submitting payments with a customer's COF. See Make subsequent payments with saved cards.


Save a new card for an existing customer

If the cardholder requests removal of the card-on-file (VOF) or if the customer's payment method changes, you must revoke the existing (COF) and add a new one. To save a new card for an existing customer, you need to complete two steps: 1. Revoke the existing card, and 2. Add the new card information.

Step 1: Revoke the existing card

  1. Send a DELETE request to /v1/customers/{customerId}sources/{cardId}.
  2. Set the Authorization header as Bearer token type, and enter the test API token or OAuth-generated access_token.
curl --request DELETE \
--url 'https://scl-sandbox.dev.clover.com/v1/customers/{customerId}/sources/{cardId}' \
--header 'accept: application/json' \
--header 'authorization: Bearer {accessToken}'\

Step 2: Add new card information to the existing customer record

  1. Send a PUT request to /v1/customers/{customerId} .
  2. Enter information in the required fields:
  • source—Payment source or card token to charge. Example: clv_1ABCDefgHI23jKL4m5nOPqR generated from the create a card token /v1/tokens endpoint.
  • customerId—Customer identifier.
  • email address—Used to notify the customers that their card data is saved to their profile.
  1. Set the Authorization header as Bearer token type, and enter the test API token or OAuth-generated access_token.
curl --request PUT \
  --url 'https://scl-sandbox.dev.clover.com/v1/customers/{customerId}' \
  --header 'accept: application/json' \
  --header 'authorization: Bearer {access_token}' \
  --header 'content-type: application/json' \
  --data '{"source":"{token}"}'

Make payments with saved cards

You can make payments using either the customerId or a specific card token.

  • Use customerId if the customer has only one saved card.
  • Use a card token if the customer has multiple cards. In this case, include the stored_credentials object.

Create a charge with customerId

  1. Send a POST request to the v1/charges endpoint to create a charge.
  2. Enter the required parameters in the request:
  • amount
  • currency
  • source—Enter the customerId generated when you save a card-on-file for a new customer.
  • x-forwarded-for—Client internet protocol (IP) address of the web browser from which the customer’s payment originates.
  1. Set the Authorization header as Bearer token type, and enter the test API token or OAuth-generated access_token.

Request example—Create a subsequent charge with a customer ID

curl --request POST \
  --url 'https://scl-sandbox.dev.clover.com/v1/charges' \
  --header 'accept: application/json' \
  --header 'authorization: Bearer {access_token}' \
  --header 'idempotency-key: {uuid4_key}' \
  --header 'content-type: application/json' \
  --header 'x-forwarded-for: {client_ip}' \
  --data '{
    "amount": 30,
    "currency": "usd",
    "source": "{customer_id}"
  }'

Create a charge with a multi-pay card token

When you use a multi-pay card token generated from the v1/customers endpoint to create a charge, for subsequent payments, you must also include the required stored_credentials values in the charge request.

  1. Send a POST request to the v1/charges endpoint to create a charge.
  2. Enter the required parameters in the request:
  • amount in cents
  • currency
  • source—Enter the customerId generated when you save a card-on-file for a new customer.
  • sequence—Indicates the sequence of the transaction for the same payment card, whether First or Subsequent.
  • is_scheduled—Indicates whether the transaction is scheduled or part of an installment.
    Note: Installments are only available in the US.
  • initiator—Indicates whether the transaction is initiated by the merchant or with the cardholder's consent.
  1. Optional. In case of installments, enter information in the installment_info object.
  • bill_pay_indicator—Indicates whether the transaction is a recurring or installment payment.
  • invoice_number—Invoice number of the installment or recurring transaction.
  • description—Description of the installment_info or recurring transaction.
  1. Set the Authorization header as Bearer token type, and enter the test API token or OAuth-generated access_token.

Request example—Create initial charge with a multi-pay card token

curl --request POST \
  --url 'https://scl-sandbox.dev.clover.com/v1/charges' \
  --header 'accept: application/json' \
  --header 'authorization: Bearer {access_token}' \
  --header 'idempotency-key: {uuid4_key}' \
  --header 'content-type: application/json' \
  --header 'x-forwarded-for: {client_ip}' \
  --data '{
    "ecomind": "ecom",
    "metadata": {
      "existingDebtIndicator": false
    },
    "stored_credentials": {
      "sequence": "FIRST",
      "is_scheduled": false,
      "initiator": "MERCHANT"
    },
    "amount": 25,
    "currency": "USD",
    "source": "clv_1TSTS6mpb4FHAoLqPM9vUC66"
  }'
{
  "id": "J2RK5QKH6WCYE",
  "amount": 25,
  "payment_method_details": "card",
  "amount_refunded": 0,
  "currency": "USD",
  "created": 1748341370775,
  "captured": true,
  "ref_num": "514700500080",
  "auth_code": "OK8320",
  "outcome": {
    "network_status": "approved_by_network",
    "type": "authorized"
  },
  "paid": true,
  "status": "succeeded",
  "source": {
    "id": "clv_1TSTS6mpb4FHAoLqPM9vUC66",
    "brand": "VISA",
    "exp_month": "12",
    "exp_year": "2030",
    "first6": "424242",
    "last4": "4242"
  },
  "ecomind": "ecom"
}

Request and response example—Pay for an order with an initial charge

curl --request POST \
  --url 'https://scl-sandbox.dev.clover.com/v1/orders/{orderId}/pay' \
  --header 'accept: application/json' \
  --header 'authorization: Bearer {access_token}' \
  --header 'idempotency-key: {uuid4_key}' \
  --header 'content-type: application/json' \
  --header 'x-forwarded-for: {client_ip}' \
  --data '{
    "ecomind": "ecom",
    "stored_credentials": {
      "sequence": "FIRST",
      "is_scheduled": false,
      "initiator": "MERCHANT"
    },
    "customer": "PWG98TV2ECTT2",
    "amount": 25,
    "currency": "USD"
  }'

{
  "id": "4F3DBC6ZNEWQJ",
  "object": "order",
  "amount": 25,
  "amount_paid": 25,
  "currency": "USD",
  "charge": "3H1K9CBHNQGCT",
  "created": 1748341777000,
  "customer": "PWG98TV2ECTT2",
  "ref_num": "514700500090",
  "auth_code": "OK2910",
  "items": [
    {
      "quantity": 1,
      "amount": 25
    }
  ],
  "source": {
    "brand": "VISA",
    "exp_month": "12",
    "exp_year": "2030",
    "first6": "424242",
    "last4": "4242"
  },
  "status": "paid",
  "status_transitions": {
    "paid": 1748341867575
  },
  "ecomind": "ecom"
}

Request example—Create a subsequent charge with a multi-pay card token

curl --request POST \
  --url 'https://scl-sandbox.dev.clover.com/v1/charges' \
  --header 'accept: application/json' \
  --header 'authorization: Bearer {access_token}' \
  --header 'idempotency-key: {uuid4_key}' \
  --header 'content-type: application/json' \
  --header 'x-forwarded-for: {client_ip}' \
  --data '{
    "ecomind": "ecom",
    "metadata": {
      "existingDebtIndicator": false
    },
    "stored_credentials": {
      "sequence": "SUBSEQUENT",
      "is_scheduled": false,
      "initiator": "CARDHOLDER"
    },
    "amount": 25,
    "currency": "USD",
    "source": "clv_1TSTS6mpb4FHAoLqPM9vUC66"
  }'
{
  "id": "PQ7XWMJ4Y4P1Y",
  "amount": 25,
  "payment_method_details": "card",
  "amount_refunded": 0,
  "currency": "USD",
  "created": 1748341529329,
  "captured": true,
  "ref_num": "514700500085",
  "auth_code": "OK9930",
  "outcome": {
    "network_status": "approved_by_network",
    "type": "authorized"
  },
  "paid": true,
  "status": "succeeded",
  "source": {
    "id": "clv_1TSTS6mpb4FHAoLqPM9vUC66",
    "brand": "VISA",
    "exp_month": "12",
    "exp_year": "2030",
    "first6": "424242",
    "last4": "4242"
  },
  "ecomind": "ecom"
}

Request example—Pay for an order with a subsequent charge

curl --request POST \
  --url 'https://scl-sandbox.dev.clover.com/v1/orders/{orderId}/pay' \
  --header 'accept: application/json' \
  --header 'authorization: Bearer {access_token}' \
  --header 'content-type: application/json' \
  --header 'x-forwarded-for: {client_ip}' \
  --data '{
    "source": "{multi_pay_token}",
    "email": "[email protected]",
    "stored_credentials": {
      "sequence": "SUBSEQUENT",
      "is_scheduled": false,
      "initiator": "CARDHOLDER"
    }
  }'
{
  "id": "XAQXQZNW25AB0",
  "amount": 35,
  "payment_method_details": "card",
  "currency": "USD",
  "charge": "ZDTE58PPN05SE",
  "created": 1588633964000,
  "ref_num": "012500500550",
  "auth_code": "OK0504",
  "items": [
    {
      "quantity": 1,
      "amount": 35,
      "description": "item1"
    }
  ],
  "status": "paid",
  "status_transitions": {
    "paid": 1588634057577
  },
  "stored_credentials": {
    "sequence": "SUBSEQUENT",
    "initiator": "CARDHOLDER"
  }
}

Possible responses

The following table describes the possible responses when running an endpoint in the Clover Ecommerce services APIs. These responses include various status codes and error messages that can help you diagnose and handle issues effectively during API integration.

MessageDescriptionVariableDescription
200stringSuccessful response. Returns the charge object with any captured value set to true.
400 Bad RequeststringchargeIndicates a card-related error. Provides the unique identifier of the failed charge.
400 Bad RequeststringcodeProvides additional information about the error to help users identify the issue.
400 Bad Requeststringdecline_codeIndicates a card issuer declined the transaction, including the reason if specified by the issuer.
400 Bad Requeststringdoc_urlURL (link) for more information about the reported error code.
400 Bad RequeststringmessageDetailed information about the error code. For card-related errors, this can provide more information to users.
400 Bad RequeststringparamLists the specific parameter related to the error. Useful for informing users about specific issues with their card information entry.
400 Bad RequeststringtypeReturned error type:

- api_connection_error
- api_error
- authentication_error
- card_error
- idempotency_error
- invalid_request_error
- rate_limit_error

Related topics