Mode 3A - Saved cards (tokenized payments)
Charge a card the customer already saved, with no new card entry. This is what makes subscriptions and one-click repeat purchases possible.
23.1 The flow
1. Customer pays once through mode 1 or mode 2
2. Customer explicitly ticks "save my card for next time"
3. ROKI stores the processor's own token - never the card number
4. Your backend receives the payment_method.saved webhook with an opaque pm_*
(or looks it up later with GET /payment-methods)
5. Later, your backend charges that pm_* with sk_*
6. ROKI verifies the method belongs to you, in the right environment, and is
still usable, then charges the stored token
7. You get the same payment.approved / payment.failed webhooks as any other payment
The card is saved only when the customer agrees. There is no way to save one silently.
23.1.0 Saved cards must be enabled for the account first
Before writing any code for this mode, confirm the merchant account has it enabled. Card-on-file is not on by default: ROKI authorises it per merchant, manually. That is deliberate - storing a credential that can be charged without the customer present is a fraud and chargeback question, not a feature flag - and other gateways gate it the same way.
What matters for an integration is how the refusal arrives, and that depends on where you meet it:
| Situation | What you get |
|---|---|
| Sandbox not provisioned | 422 sandbox_terminal_unavailable, saying so plainly (16.3) |
| Mode 3A not enabled, and you list or charge | 422 with code: "tokenized_charge_disabled" on GET /payment-methods, POST /payment-methods/{id}/charge and POST /payments/token-charge, before any other check. Since 2026-09-23 |
| Mode 3A not enabled, and the customer saves a card | Nothing, as observed on 2026-08-14: the payment succeeds and no card is stored. Not re-measured since |
Branch on code, not on the errors key. The 422 tokenized_charge_disabled carries an
errors object whose key changes with the route - customer on the list, payment_method_id on
charge, payment_token on token-charge - so it looks like a validation error on that field. It is
not: no value of that field will fix it. DELETE /payment-methods/{id} is not gated.
Before 2026-09-23 the list answered 200 with data: [] on accounts without the feature, so an
empty list could not tell "not enabled" from "no cards yet". It can now.
On an account without the module enabled, the refusal is silent: saveCard: true is accepted, the
payment is approved, GET /payment-methods stays empty and no payment_method.saved event
arrives. Nothing anywhere reports it. If you get that result, ask ROKI to enable card-on-file for
the merchant before looking for a bug in your code.
So do not debug your code. Check first:
# One call answers it - no payment needed:
curl "https://aura.roki.systems/api/connect/v1/payment-methods?customer[identity_number]=0801199000000" \
-H "Authorization: Bearer sk_test_..."
A 422 with code: "tokenized_charge_disabled" means the account does not have mode 3A, not that
your request was wrong. Ask ROKI to enable it for the merchant, and only then build mode 3A. A 200 -
even with an empty data - means it is enabled.
23.1.1 How that agreement is actually enabled - verified 2026-08-14
The flow above says "the customer ticks a box" without saying where the box comes from. That matters, because the two modes differ and only one of them works today.
Mode 2 (embedded components): the merchant enables it. The SDK accepts a saveCard option and
translates it into save_card=1 on the iframe URL. The checkbox the customer sees is yours;
ROKI only receives the intent:
const payment = roki.createPaymentComponent({
customer: { identity_number: '0801199000000', email: 'buyer@example.com' },
saveCard: true, // el comercio habilita el guardado; la casilla la dibujas vos
});
Send customer.identity_number: that is the key GET /payment-methods looks the card up by.
Mode 1 (hosted checkout): nothing you send at creation turns it on. There is no request field that makes the save option appear on the checkout page - it is an account authorisation, not a parameter (23.1.0).
What this means for an integration: if you need saved cards, originate them from mode 2. Do not promise a customer that paying through a payment link will save their card.
23.2 Getting the pm_*
From the webhook - the envelope is the one in 14.3, so the event id is a plain UUID with no
prefix:
{
"id": "3f0c1a7e-9d84-4b2e-8c51-6a2f0b7d19c4",
"type": "payment_method.saved",
"created_at": "2026-08-12 12:05:00",
"data": {
"id": "pm_7k2n9xqf31ab",
"card_brand": "Visa",
"last_four": "4242",
"exp_month": 11,
"exp_year": 2027,
"is_default": true
}
}
Or on demand:
curl -G https://aura.roki.systems/api/connect/v1/payment-methods \
-H "Authorization: Bearer sk_test_..." \
--data-urlencode "customer[identity_number]=0801199012345"
Identify the customer with customer[identity_number] (preferred) or customer[email] - the same
identifiers you send when creating payments. On an account with mode 3A enabled, a customer with no
saved cards is a normal 200 with {"data": []}, not a 404. Without mode 3A the call answers
422 tokenized_charge_disabled (23.1.0).
23.3 Charging
POST /api/connect/v1/payment-methods/pm_7k2n9xqf31ab/charge
Authorization: Bearer sk_test_...
Idempotency-Key: order-2002-charge
{ "amount": 500.00, "currency_code": "HNL", "external_reference": "order-2002" }
Idempotency-Key is mandatory here, not merely recommended as it is on payment creation.
Omitting it returns 422. Use a unique key per charge attempt so a network retry can never charge
twice.
Note currency_code takes the alphabetic code ("HNL") on this endpoint, while payment creation
takes the numeric one ("340").
POST /payments/token-charge is an equivalent alias that takes the saved card in the body as
payment_token, which suits subscription renewals:
{ "payment_token": "pm_7k2n9xqf31ab", "amount": 100.00,
"currency_code": "HNL", "external_reference": "sub-aug-2026" }
Never call either endpoint from a browser. Both need sk_*.
23.4 Reversing and revoking
The charge response returns an id - that is the transaction UUID. Use it with the ordinary
/payments/{transaction_id}/void and /refund endpoints from section 13. There is no separate
reversal path for token charges.
To remove a saved card:
curl -X DELETE https://aura.roki.systems/api/connect/v1/payment-methods/pm_7k2n9xqf31ab \
-H "Authorization: Bearer sk_test_..."
A revoked method fails later charges with a distinct error rather than silently succeeding.
23.5 What to think about before enabling it
A saved card charged without the customer present is a different risk profile from a checkout they just completed. Before shipping recurring billing: define what happens when a card expires or is reissued, decide how many times you retry a declined renewal and after which decline codes you stop, and give the customer a way to see and remove their saved cards. None of that is enforced by the API.
