Upsells y downsells en un clic
Guarda una tarjeta durante una compra puntual y vuelve a cobrarla con un solo clic — sin volver a introducir la tarjeta, sin CVV — para ofrecer upsells y downsells posteriores a la compra.
Un upsell en un clic es la oferta que muestras justo después de que una compra tiene éxito — un complemento, una mejora, un paquete — y que el cliente acepta con un solo clic. Un downsell es la alternativa más barata que muestras cuando la rechaza. Ambos cobran la tarjeta que el cliente acaba de usar, sin pedirle que la vuelva a escribir y sin CVV.
Tokeflow lo admite con dos banderas en POST /api/v1/transactions:
save_payment_instrument: trueen el primer cargo guarda la tarjeta como un instrumento de pago y conserva la credencial del lado del proveedor que permite volver a cobrarla.credential_on_file: trueen un cargo posterior cobra ese instrumento guardado como un cargo con el cliente presente — sin datos de tarjeta, sin CVV. También puede pagar una sesión de checkout una vez que el merchant opte por el checkout con tarjeta guardada.
La tarjeta en sí nunca la almacena Tokeflow ni tú. Lo que se conserva es la credencial reutilizable del propio proveedor para esa tarjeta, en el conector que aprobó el primer cargo. Los cargos en un clic posteriores van a ese mismo conector.
Los cargos en un clic los inicia el cliente. Usan la misma mecánica de credencial almacenada que las renovaciones de suscripción, pero el pedido se registra con recurrence: "unscheduled" y los metadatos de la transacción llevan stored_credential_usage: "customer_initiated", de modo que los informes distinguen un upsell de una renovación programada.
Antes de empezar
- Un registro de cliente. Ambas banderas requieren
customer_id— el instrumento pertenece a un cliente, y un cargo en un clic se rechaza para cualquier otro cliente. - Solo pagos con tarjeta.
credit_cardodebit_card. PIX, boleto y wallets no tienen credencial almacenada que reutilizar. - Un proveedor que admita cargos con credencial almacenada. El primer cargo debe aprobarse en un conector cuyo proveedor pueda cobrar después una tarjeta guardada. Revisa el catálogo de proveedores antes de depender del un clic para un conector concreto.
- Bridge SDK 2.6.0 o superior en el navegador, para emitir un ciphertext que pueda guardarse (ver paso 1).
Paso 1 — Emite un ciphertext que pueda guardarse
Un token de tarjeta emitido por el Bridge SDK tiene una finalidad (purpose). Un ciphertext one_time es de un solo uso y caduca; solo un ciphertext instrument_enrollment puede guardarse como instrumento de pago. Qué finalidad obtienes depende de dónde tokenizas:
| Dónde se ejecuta el SDK | Cómo pedir un ciphertext registrable |
|---|---|
Dentro de una sesión de checkout (checkoutSessionId definido) | Pasa { saveCard: true }. El servidor deriva la finalidad de la oferta — una oferta recurrente siempre registra; una oferta puntual registra solo cuando saveCard está definido. Cualquier purpose que envíes se ignora aquí. |
| Independiente (sin sesión de checkout) | Pasa { purpose: 'instrument_enrollment' }. Sin ello, el SDK emite un ciphertext one_time. |
// Dentro de una sesión de checkout: el cliente marcó "guardar mi tarjeta"
const token = await tokeflow.cards.tokenize({ card: cardElement }, { saveCard: true });
// Tokenización independiente
const token = await tokeflow.cards.tokenize(
{ card: cardElement },
{ purpose: 'instrument_enrollment' },
);
console.log(token.purpose); // 'instrument_enrollment' — compruébalo antes de confiar en elloComprueba token.purpose en la respuesta. Pedir una finalidad que no se mantiene no falla en la tokenización — aparece más tarde como un 400 en el cargo, cuando save_payment_instrument rechaza un ciphertext one_time. Consulta Tokenización → Opciones.
Obtén el consentimiento explícito del cliente para conservar la tarjeta en archivo antes de tokenizar con cualquiera de las dos opciones.
Paso 2 — El primer cargo guarda la tarjeta
Envía el ciphertext a tu servidor y crea la transacción con save_payment_instrument: true. Tokeflow registra la tarjeta para el cliente antes de enrutar el cargo y luego cobra el ciphertext que enviaste (el único que aún lleva el CVV). Cuando el proveedor aprueba, su credencial reutilizable se conserva en ese conector.
curl -X POST https://api.tokeflow.com/api/v1/transactions \
-H "Authorization: Bearer sk_live_mer_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_1001_attempt_1" \
-d '{
"payment_method": "credit_card",
"charge_type": "payment",
"country": "BR",
"amount": 9900,
"currency": "BRL",
"customer_id": "cust_2f9a71d4",
"external_order_id": "order_1001",
"card_ciphertext_id": "tok_8f3c2a1b9d4e",
"save_payment_instrument": true
}'La respuesta es el objeto de transacción habitual. El campo que debes conservar es payment_instrument_id — el pi_… que cobrarás en el paso 3.
| Regla | Detalle |
|---|---|
| Requiere | card_ciphertext_id emitido con purpose=instrument_enrollment, más customer_id. |
| No se combina con | checkout_session_id — dentro de una sesión de checkout, la bandera save_card de la sesión gobierna el registro. payment_instrument_id — estás guardando una tarjeta nueva, no cobrando una guardada. |
| Tarjeta ya en archivo | Se reutiliza el instrumento existente para esa tarjeta y cliente; no se crea un duplicado. |
Ciphertext one_time | 400 validation_error indicando la solución. No se cobra nada y nada se degrada en silencio a una tarjeta sin guardar. |
| Primer cargo rechazado | El instrumento existe pero aún no conserva credencial del proveedor, así que un cargo credential_on_file posterior se rechaza con 400 hasta que se apruebe un cargo con el titular presente o una verificación de tarjeta. |
¿Ya recoges tarjetas mediante una sesión de checkout? Ahí nada cambia: tokeniza con saveCard: true y cobra la sesión como siempre. La propia sesión registra la tarjeta, y el payment_instrument_id resultante viene en la respuesta de la transacción.
Paso 3 — Cobra la tarjeta guardada con un clic
Cuando el cliente acepta el upsell (o el downsell), tu servidor crea una nueva transacción contra el instrumento con credential_on_file: true. Sin ciphertext, sin CVV, sin sesión de checkout.
curl -X POST https://api.tokeflow.com/api/v1/transactions \
-H "Authorization: Bearer sk_live_mer_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_1001_upsell_attempt_1" \
-d '{
"payment_method": "credit_card",
"charge_type": "payment",
"country": "BR",
"amount": 4900,
"currency": "BRL",
"customer_id": "cust_2f9a71d4",
"external_order_id": "order_1001_upsell",
"payment_instrument_id": "pi_a1b2c3",
"credential_on_file": true
}'Ejemplo de respuesta — 201 Created
{
"success": true,
"data": {
"id": "tx_812",
"order_id": "ord_045",
"customer_id": "cust_2f9a71d4",
"payment_instrument_id": "pi_a1b2c3",
"amount_authorized": 4900,
"amount_captured": 4900,
"currency": "BRL",
"payment_method": "credit_card",
"charge_type": "payment",
"country": "BR",
"status": "authorized",
"timeline": [
{
"attempt_number": 1,
"is_fallback": false,
"connector_id": "conn_a1b2c3",
"provider_slug": "acquirer_a",
"status": "success",
"started_at": "2026-09-24T12:30:00.000Z",
"finished_at": "2026-09-24T12:30:01.000Z",
"error_category": null,
"error_code": null,
"decline_reason": null
}
],
"metadata": { "stored_credential_usage": "customer_initiated" },
"created_at": "2026-09-24T12:30:00.000Z",
"updated_at": "2026-09-24T12:30:01.000Z"
},
"request_id": "req_2c9e",
"timestamp": "2026-09-24T12:30:01.000Z"
}Un downsell es la misma llamada con el importe de la oferta alternativa y su propio external_order_id y clave de idempotencia. Cada oferta aceptada es su propia transacción y su propio pedido.
Qué hace distinto un cargo credential_on_file
| Comportamiento | Detalle |
|---|---|
| Conector | Fijado al conector activo que conserva la credencial utilizable más reciente del instrumento — el que aprobó el primer cargo, salvo que se haya desactivado desde entonces o no pueda aceptar este cargo (moneda, cuotas). Las reglas de enrutamiento y la cascada no se aplican. |
| Captura | Siempre capturado en un solo paso. capture: false se rechaza con 400. |
| Pedido | Creado automáticamente con recurrence: "unscheduled". |
| Metadatos | stored_credential_usage: "customer_initiated" se añade a lo que envíes. |
| Respuesta | El objeto de transacción habitual, con payment_instrument_id informado. La misma idempotencia, el mismo envelope y los mismos webhooks que cualquier cargo. |
Reglas
| Regla | Detalle |
|---|---|
| Requiere | payment_instrument_id y customer_id. El instrumento debe pertenecer a ese cliente y estar active. |
| No se combina con | card_ciphertext_id (envía el ciphertext solo para un cargo con el titular presente), save_payment_instrument ni capture: false. |
| Con una sesión de checkout | Permitido solo cuando el merchant optó por el checkout con tarjeta guardada — consulta Paga una sesión de checkout con una tarjeta guardada. |
| Requisito previo | El instrumento debe conservar una credencial del proveedor obtenida en un cargo con el titular presente o una verificación de tarjeta exitosa en un conector cuyo proveedor admita cargos con credencial almacenada. De lo contrario: 400 validation_error. |
payment_instrument_id solo | Se rechaza con 400. Una tarjeta guardada se cobra con credential_on_file: true o con un card_ciphertext_id nuevo (CVV reescrito). |
Paga una sesión de checkout con una tarjeta guardada
La misma credencial almacenada puede pagar una sesión de checkout: un cliente que regresa elige una tarjeta que guardó antes y paga sin volver a escribirla. Esto es el checkout con tarjeta guardada, y está desactivado por defecto.
- Haz que el merchant opte por la función. Define
saved_card_checkout_enabled: trueconPATCH /merchants/:id. Al activarlo declaras que tu servidor autentica al cliente antes de identificarlo en una sesión. - Ofrece solo las tarjetas que pueden cobrarse. En el navegador, el contexto de checkout informa
savedCardCheckoutEnabledy, para cada tarjeta guardada, una indicaciónreusable. - Cobra la sesión desde tu servidor con
checkout_session_id,payment_instrument_idycredential_on_file: true. La sesión aporta el importe, la moneda y el cliente; en una oferta recurrente, la suscripción se inicia en el conector que conserva la credencial de la tarjeta.
curl -X POST https://api.tokeflow.com/api/v1/transactions \
-H "Authorization: Bearer sk_live_mer_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cks_a1b2c3d4e5_saved_card" \
-d '{
"checkout_session_id": "cks_a1b2c3d4e5",
"payment_instrument_id": "pi_123",
"credential_on_file": true,
"payment_method": "credit_card",
"charge_type": "payment",
"country": "BR"
}'Sin el opt-in, cualquier uso de una tarjeta guardada en una sesión de checkout que no lleve un card_ciphertext_id nuevo — el cargo anterior, o una prueba de importe cero iniciada a partir de la tarjeta guardada — se rechaza con 400. Recopila la tarjeta de nuevo y cobra la sesión con el nuevo ciphertext. Reglas completas: Checkout con tarjeta guardada.
Gestiona los rechazos
Un rechazo no es un error HTTP: la respuesta es 201 Created con status: "failed" y una entrada en timeline que explica el motivo. Ramifica según status, como con cualquier cargo, y lee el decline_reason del intento para elegir qué mostrarle al cliente.
error_codeesAUTHENTICATION_REQUIRED— el emisor exige autenticación reforzada del cliente para esta tarjeta. Un cargo con credencial almacenada no puede ejecutar un desafío 3-D Secure. Pide al cliente que vuelva a introducir la tarjeta y cobra ese ciphertext nuevo de la forma habitual.- Rechazos blandos (fondos insuficientes, límites) — muestra el downsell, u ofrece otra tarjeta guardada de la cartera del cliente (
GET /payment-instruments?customer_id=…&status=active). 400con un mensaje sobre una credencial almacenada ausente — el primer cargo nunca se aprobó en un conector capaz. Vuelve a una tokenización nueva; ese cargo rellenará la credencial.
Envía una Idempotency-Key distinta para cada oferta que el cliente acepte. Reutilizar la clave de la compra original devuelve la transacción original en lugar de cobrar el upsell. Consulta Idempotencia y límites de tasa.
Buenas prácticas
- Cobra solo tras un clic explícito.
credential_on_filees para un cliente que está presente y acaba de elegir la oferta. Los cargos que el cliente no inició pertenecen a las suscripciones. - Autentica primero al cliente. El id del instrumento no es un secreto, pero tu endpoint que dispara el cargo solo debe actuar en nombre del cliente que lo posee.
- Muestra lo que se va a cobrar — importe, moneda y la marca y los últimos cuatro dígitos de la tarjeta (
card_brand,last4en el instrumento) — antes del clic. - Permite al cliente revocar una tarjeta guardada desde su cuenta (
DELETE /payment-instruments/:ido el Portal del cliente).
Relacionados
- Transacciones — la referencia completa de campos de ambas banderas.
- Instrumentos de pago — cómo se crean los instrumentos y cómo gestionarlos.
- Tokenización —
saveCardypurposeen el Bridge SDK. - Proveedores de pago — qué proveedores admiten cargos con credencial almacenada.