ROKIConnect

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 authorised 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, because it is not an error:

Situation What you get
Sandbox not provisioned 422 sandbox_terminal_unavailable, saying so plainly (16.3)
Card saving not authorised Nothing. The payment returns 201/202, the charge succeeds, and no card is stored

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:

# Pay once with saveCard, then immediately:
curl "https://aura.roki.systems/api/connect/v1/payment-methods?customer[identity_number]=0801199000000" \
  -H "Authorization: Bearer sk_test_..."

An empty data after a successful save attempt means the account is not authorised, not that your request was wrong. Ask ROKI to enable card-on-file for the merchant, and only then build mode 3A.

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:

{
  "id": "evt_01HPM01",
  "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. A customer with no saved cards is a normal 200 with {"data": []}, not a 404.

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.