Saved Cards and Merchant-Initiated Charges

Collect a buyer's card or bank account with a zero-amount checkout, then charge it from your server whenever you need to, without the buyer present.

Use this flow when you charge a buyer later or on your own schedule: pay after delivery, usage billing, or a deposit taken after a booking. The buyer saves a payment method once, and your server charges it with a merchant-initiated charge. If you bill the same amount on a fixed schedule, Subscriptions do the scheduling and retries for you.

Before you start

  • Ask Payment Labs to enable merchant-initiated charges on your account. Without it, Charge Checkout returns 403 FORBIDDEN.
  • Get a token with Create Access Token, the same login payors use, from a credential with the Merchant Write permission. See Permissions.
  • Know how to create a checkout and show it to the buyer. See Hosted Checkout Page and Iframe.

The flow uses two checkouts: a zero-amount checkout that saves the instrument, and a new checkout, with the amount to charge, for each later charge. You never charge the zero-amount checkout itself.

Step 1: Create a zero-amount checkout

Call Create Checkout with "amount": 0:

curl -X POST https://api-sandbox.paymentlabs.io/checkouts \
  -H "Authorization: Bearer $PAYMENT_LABS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "alias": "Save a payment method",
    "amount": 0,
    "currency": "USD",
    "buyer": { "email": "[email protected]", "referenceId": "customer-7731" },
    "termsOfServiceUrl": "https://shop.example.com/terms",
    "privacyPolicyUrl": "https://shop.example.com/privacy"
  }'

A zero-amount checkout behaves differently from a normal one:

  • The buyer must sign in or create an account, so the instrument can be saved to them. authenticationOptions defaults to AUTHENTICATED_ONLY, and any other value returns 400 VALIDATION_ERROR.
  • Only cards and bank accounts are offered. Apple Pay and Google Pay are not.
  • Instruments the buyer saved before are not listed. The buyer always enters the details they want you to keep.

Step 2: Let the buyer save the instrument

Send the buyer to links.checkout, embed links.embeddable, or render the checkout with the Payin SDK. The page asks the buyer to save the details instead of to pay. Nothing is charged.

When the buyer finishes, the checkout becomes PAID with a zero-amount transaction, and succeededCheckoutInstrumentId holds the saved instrument's ID (SI-…). Wait for checkout-status-changed or read the checkout with Get Checkout, and store buyerId and succeededCheckoutInstrumentId with your customer record.

📘

A buyer who signs in and ticks the option to save their card during a normal, paid checkout also leaves a saved instrument you can charge later. The zero-amount checkout is for when you have nothing to charge yet.

Step 3: Create a checkout for the charge

When you are ready to charge, create a new checkout with the amount and the same buyer.email. Payment Labs matches the buyer by email, so the new checkout belongs to the buyer who saved the instrument.

curl -X POST https://api-sandbox.paymentlabs.io/checkouts \
  -H "Authorization: Bearer $PAYMENT_LABS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "alias": "Invoice 2291",
    "referenceId": "invoice-2291",
    "amount": 120.00,
    "currency": "USD",
    "buyer": { "email": "[email protected]", "referenceId": "customer-7731" },
    "termsOfServiceUrl": "https://shop.example.com/terms",
    "privacyPolicyUrl": "https://shop.example.com/privacy"
  }'

Step 4: Pick the instrument

Call List Available Checkout Instruments on the new checkout. It returns the buyer's saved instruments that can pay this checkout. Use the id of an instrument with "status": "ACTIVE", normally the one you stored in step 2.

curl https://api-sandbox.paymentlabs.io/checkouts/S-01JBXC6R8T0V2X4Z6B8D0F2H4K/checkout-instruments/available \
  -H "Authorization: Bearer $PAYMENT_LABS_TOKEN"
[
  {
    "id": "SI-01JBX8B1C3D5E7F9G1H3J5K7M9",
    "buyerId": "U-01JBX6M2N8P4Q6R8S0T2V4W6Y8",
    "instrumentTypeGroup": "PaymentCard",
    "status": "ACTIVE",
    "isDefault": true,
    "isOnFile": true,
    "data": { "brand": "Visa", "lastFour": "4242", "expirationMonth": 12, "expirationYear": 2028 }
  }
]

To see a buyer's instruments without a checkout, for example to list their saved payment methods in your account page, use List Buyer Available Checkout Instruments. If you did not store the buyerId, find it by email or by your referenceId with Search Buyers.

Step 5: Charge the checkout

Call Charge Checkout on the new checkout:

curl -X PUT https://api-sandbox.paymentlabs.io/checkouts/S-01JBXC6R8T0V2X4Z6B8D0F2H4K/charge \
  -H "Authorization: Bearer $PAYMENT_LABS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "checkoutInstrumentId": "SI-01JBX8B1C3D5E7F9G1H3J5K7M9" }'

Send checkoutInstrumentId or terminalId, never both; terminalId charges a card reader instead (see POS Terminals).

The response is the new checkout transaction. The charge runs asynchronously, so the transaction is normally still PENDING:

{
  "id": "ST-01JBXD1A3C5E7G9J1K3M5N7P9Q",
  "checkoutId": "S-01JBXC6R8T0V2X4Z6B8D0F2H4K",
  "status": "PENDING",
  "amount": { "value": 120, "currency": "USD", "formattedValue": "$120.00", "digits": 2, "wholeValue": 12000 },
  "isMerchantInitiated": true
}

Step 6: Confirm the result

Wait for checkout-status-changed, or poll Get Checkout until the status leaves PROCESSING. PAID means the money was taken. FAILED carries the reason in errorTransaction; see Checkout Statuses and Failure Reasons.

After a FAILED result you can call Charge Checkout again on the same checkout, with the same or another instrument, until its expiresAt passes. Do not retry a declined card in a tight loop: wait, or ask the buyer for another payment method. Never charge again while the checkout is PROCESSING or UNKNOWN.

Errors and edge cases

ResponseCauseWhat to do
400 VALIDATION_ERRORNeither checkoutInstrumentId nor terminalId was sent, or details.checkoutInstrumentId reports CHECKOUT_INSTRUMENT_NOT_FOUND (not one of this buyer's instruments) or CHECKOUT_INSTRUMENT_NOT_ACTIVE.Send one instrument from step 4, created with the same buyer.email.
403 FORBIDDENThe checkout belongs to another account, or merchant-initiated charges are not enabled on your account.Check the checkout ID, or ask Payment Labs to enable merchant-initiated charges.
404 NOT_FOUNDThe checkout does not exist.Check the checkout ID.
409 VALIDATION_ERRORThe checkout has no buyer, or your account can no longer charge this checkout.Create the checkout again with buyer.email; contact Payment Labs if it persists.
423 NOT_EDITABLEThe checkout has expired, is already paid, or has a charge in progress or unconfirmed.Create a new checkout, or wait for the charge in progress to finish.

Next steps


What’s Next

Did this page help you?