Upsells e downsells em um clique

Salve um cartão durante uma compra avulsa e cobre-o de novo com um único clique — sem redigitar o cartão, sem CVV — para oferecer upsells e downsells pós-compra.

Um upsell em um clique é a oferta que você mostra logo depois de uma compra bem-sucedida — um adicional, um upgrade, um combo — e que o cliente aceita com um único clique. Um downsell é a alternativa mais barata que você mostra quando ele recusa. Os dois cobram o cartão que o cliente acabou de usar, sem pedir que ele o digite de novo e sem CVV.

A Tokeflow suporta isso com duas flags em POST /api/v1/transactions:

  • save_payment_instrument: true na primeira cobrança salva o cartão como um instrumento de pagamento e guarda a credencial do lado do provedor que o torna cobrável de novo.
  • credential_on_file: true em uma cobrança posterior cobra esse instrumento salvo como uma cobrança com o cliente presente — sem dados de cartão, sem CVV. Ela também pode pagar uma sessão de checkout depois que o merchant fizer o opt-in no checkout com cartão salvo.

O cartão em si nunca é armazenado pela Tokeflow nem por você. O que fica guardado é a credencial reutilizável do próprio provedor para aquele cartão, no conector que aprovou a primeira cobrança. As cobranças em um clique seguintes vão para esse mesmo conector.

Cobranças em um clique são iniciadas pelo cliente. Elas usam a mesma mecânica de credencial armazenada das renovações de assinatura, mas o pedido é registrado com recurrence: "unscheduled" e os metadados da transação carregam stored_credential_usage: "customer_initiated", para que os relatórios distingam um upsell de uma renovação agendada.

Antes de começar

  • Um registro de cliente. As duas flags exigem customer_id — o instrumento pertence a um cliente, e uma cobrança em um clique é recusada para qualquer outro cliente.
  • Somente pagamentos com cartão. credit_card ou debit_card. PIX, boleto e wallets não têm credencial armazenada para reutilizar.
  • Um provedor que suporte cobranças com credencial armazenada. A primeira cobrança precisa ser aprovada em um conector cujo provedor consiga cobrar um cartão salvo depois. Verifique o catálogo de provedores antes de depender do um clique em um conector específico.
  • Bridge SDK 2.6.0 ou mais recente no navegador, para emitir um ciphertext que pode ser salvo (veja o passo 1).

Passo 1 — Emita um ciphertext que possa ser salvo

Um token de cartão emitido pelo Bridge SDK tem uma finalidade (purpose). Um ciphertext one_time é de uso único e expira; só um ciphertext instrument_enrollment pode ser salvo como instrumento de pagamento. Qual finalidade você recebe depende de onde a tokenização acontece:

Onde o SDK está rodandoComo pedir um ciphertext cadastrável
Dentro de uma sessão de checkout (checkoutSessionId definido)Passe { saveCard: true }. O servidor deriva a finalidade a partir da oferta — uma oferta recorrente sempre cadastra; uma oferta avulsa cadastra apenas quando saveCard está definido. Qualquer purpose enviado é ignorado aqui.
Standalone (sem sessão de checkout)Passe { purpose: 'instrument_enrollment' }. Sem isso, o SDK emite um ciphertext one_time.
// Dentro de uma sessão de checkout: o cliente marcou "salvar meu cartão"
const token = await tokeflow.cards.tokenize({ card: cardElement }, { saveCard: true });

// Tokenização standalone
const token = await tokeflow.cards.tokenize(
  { card: cardElement },
  { purpose: 'instrument_enrollment' },
);

console.log(token.purpose); // 'instrument_enrollment' — confira antes de depender disso

Confira token.purpose na resposta. Pedir uma finalidade que não se sustenta não falha na tokenização — o problema aparece depois, como um 400 na cobrança, quando save_payment_instrument recusa um ciphertext one_time. Veja Tokenização → Opções.

Obtenha o consentimento explícito do cliente para manter o cartão em arquivo antes de tokenizar com qualquer uma das opções.

Passo 2 — A primeira cobrança salva o cartão

Envie o ciphertext ao seu servidor e crie a transação com save_payment_instrument: true. A Tokeflow cadastra o cartão para o cliente antes de rotear a cobrança e então cobra o ciphertext que você enviou (o único que ainda carrega o CVV). Depois que o provedor aprova, a credencial reutilizável dele fica guardada nesse 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
  }'

A resposta é o objeto de transação de sempre. O campo a guardar é payment_instrument_id — o pi_… que você vai cobrar no passo 3.

RegraDetalhe
Exigecard_ciphertext_id emitido com purpose=instrument_enrollment, mais customer_id.
Não combina comcheckout_session_id — dentro de uma sessão de checkout, a flag save_card da sessão governa o cadastro. payment_instrument_id — você está salvando um cartão novo, não cobrando um salvo.
Cartão já em arquivoO instrumento existente para esse cartão e cliente é reutilizado; nenhuma duplicata é criada.
Ciphertext one_time400 validation_error indicando a correção. Nada é cobrado e nada é silenciosamente rebaixado a um cartão não salvo.
Primeira cobrança recusadaO instrumento existe, mas ainda não guarda credencial de provedor; por isso uma cobrança credential_on_file posterior é recusada com 400 até que uma cobrança com o portador presente ou uma verificação de cartão seja aprovada.

Já coleta cartões por uma sessão de checkout? Nada muda lá: tokenize com saveCard: true e cobre a sessão normalmente. A própria sessão cadastra o cartão, e o payment_instrument_id resultante vem na resposta da transação.

Passo 3 — Cobre o cartão salvo com um clique

Quando o cliente aceita o upsell (ou o downsell), seu servidor cria uma nova transação contra o instrumento com credential_on_file: true. Sem ciphertext, sem CVV, sem sessão 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
  }'

Exemplo de resposta — 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"
}

Um downsell é a mesma chamada com o valor da oferta alternativa e seu próprio external_order_id e chave de idempotência. Cada oferta aceita é sua própria transação e seu próprio pedido.

O que uma cobrança credential_on_file faz de diferente

ComportamentoDetalhe
ConectorFixado no conector ativo que guarda a credencial utilizável mais recente do instrumento — o que aprovou a primeira cobrança, a menos que ele tenha sido desativado desde então ou não possa receber esta cobrança (moeda, parcelas). Regras de roteamento e cascata não se aplicam.
CapturaSempre capturada em uma etapa. capture: false é recusado com 400.
PedidoCriado automaticamente com recurrence: "unscheduled".
Metadadosstored_credential_usage: "customer_initiated" é adicionado ao que você enviar.
RespostaO objeto de transação de sempre, com payment_instrument_id preenchido. Mesma idempotência, mesmo envelope e mesmos webhooks de qualquer cobrança.

Regras

RegraDetalhe
Exigepayment_instrument_id e customer_id. O instrumento deve pertencer a esse cliente e estar active.
Não combina comcard_ciphertext_id (envie o ciphertext sozinho para uma cobrança com o portador presente), save_payment_instrument ou capture: false.
Com uma sessão de checkoutPermitido apenas quando o merchant fez o opt-in no checkout com cartão salvo — veja Pague uma sessão de checkout com um cartão salvo.
Pré-requisitoO instrumento deve guardar uma credencial de provedor obtida em uma cobrança com o portador presente ou uma verificação de cartão bem-sucedida em um conector cujo provedor suporte cobranças com credencial armazenada. Caso contrário: 400 validation_error.
payment_instrument_id sozinhoRecusado com 400. Um cartão salvo é cobrado com credential_on_file: true ou com um card_ciphertext_id novo (CVV redigitado).

Pague uma sessão de checkout com um cartão salvo

A mesma credencial armazenada pode pagar uma sessão de checkout: um cliente recorrente escolhe um cartão que salvou antes e paga sem digitá-lo de novo. Isso é o checkout com cartão salvo, e ele vem desativado por padrão.

  1. Faça o opt-in do merchant. Defina saved_card_checkout_enabled: true com PATCH /merchants/:id. Ao ativá-lo, você declara que seu servidor autentica o cliente antes de identificá-lo em uma sessão.
  2. Ofereça apenas os cartões que podem ser cobrados. No navegador, o contexto de checkout informa savedCardCheckoutEnabled e, para cada cartão salvo, uma indicação reusable.
  3. Cobre a sessão a partir do seu servidor com checkout_session_id, payment_instrument_id e credential_on_file: true. A sessão fornece o valor, a moeda e o cliente; em uma oferta recorrente, a assinatura começa no conector que guarda a credencial do cartão.
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"
  }'

Sem o opt-in, qualquer uso de um cartão salvo em uma sessão de checkout que não traga um card_ciphertext_id novo — a cobrança acima, ou um trial de valor zero iniciado a partir do cartão salvo — é recusado com 400. Colete o cartão de novo e cobre a sessão com o novo ciphertext. Regras completas: Checkout com cartão salvo.

Trate as recusas

Uma recusa não é um erro HTTP: a resposta é 201 Created com status: "failed" e uma entrada na timeline explicando o motivo. Ramifique pelo status, como em qualquer cobrança, e leia o decline_reason da tentativa para escolher o que mostrar ao cliente.

  • error_code é AUTHENTICATION_REQUIRED — o emissor exige autenticação forte do cliente para este cartão. Uma cobrança com credencial armazenada não consegue executar um desafio 3-D Secure. Peça ao cliente para digitar o cartão de novo e cobre esse ciphertext novo da forma normal.
  • Recusas brandas (saldo insuficiente, limites) — mostre o downsell, ou ofereça outro cartão salvo da carteira do cliente (GET /payment-instruments?customer_id=…&status=active).
  • 400 com uma mensagem sobre credencial armazenada ausente — a primeira cobrança nunca foi aprovada em um conector capaz. Volte para uma tokenização nova; essa cobrança vai preencher a credencial.

Envie uma Idempotency-Key distinta para cada oferta que o cliente aceitar. Reutilizar a chave da compra original devolve a transação original em vez de cobrar o upsell. Veja Idempotência e limites de taxa.

Boas práticas

  • Cobre apenas em um clique explícito. credential_on_file é para um cliente que está presente e acabou de escolher a oferta. Cobranças que o cliente não iniciou pertencem às assinaturas.
  • Autentique o cliente primeiro. O id do instrumento não é um segredo, mas o endpoint seu que dispara a cobrança só pode agir em nome do cliente dono dele.
  • Mostre o que será cobrado — valor, moeda e a bandeira e os quatro últimos dígitos do cartão (card_brand, last4 no instrumento) — antes do clique.
  • Deixe o cliente revogar um cartão salvo a partir da conta dele (DELETE /payment-instruments/:id ou o Portal do Cliente).

Relacionados

Nesta página