Pular para o conteúdo

Cadastro de cartão

Para cobrar uma assinatura no cartão, o motor precisa de um cartão guardado na conexão de gateway da assinatura. Esse cartão é digitado pelo pagador numa página hospedada pela Revtriever — o número nunca passa pelo seu servidor nem pelo seu front. Você só abre uma sessão e redireciona.

Hoje o cadastro de cartão pelo motor funciona com conexões Asaas. Em outra conexão, abrir a sessão devolve 422 engine.card_tokenization_unsupported.

seu sistema motor página da Revtriever pagador
│ POST …/card-capture-sessions │ │ │
│─────────────────────────────▶│ │ │
│◀──── { captureUrl, … } ──────│ │ │
│──────────────── redireciona o pagador para captureUrl ──────────────────────────────────────▶│
│ │◀──── GET /public/card-capture/… ─│ (empresa, volta, prazo) │
│ │◀──── POST /public/card-capture/… ─│ (cartão + titular) │
│ │ tokeniza no gateway, guarda token │ │
│◀── webhook subscription.card_saved │── volta para returnUrl ───▶│
│ │ ?card=saved │
  1. Cadastre as origens de volta (uma vez): PUT /v1/integration/card-capture-return-origins com as origens do seu app para onde o pagador pode voltar.
  2. Abra uma sessão para a assinatura: POST /v1/subscriptions/{subscriptionId}/card-capture-sessions, opcionalmente com returnUrl.
  3. Redirecione o pagador para o captureUrl devolvido (https://app.revtriever.com/c/cct_…). Redirecione na mesma aba ou abra uma nova — não use iframe: a página recusa ser enquadrada.
  4. O pagador vê o nome e a marca da sua empresa, digita o cartão e os dados do titular (nome, e-mail, CPF/CNPJ, CEP, número do endereço e telefone — o gateway exige para análise antifraude) e salva.
  5. Na volta, a página redireciona para a sua returnUrl com ?card=saved (ou ?card=canceled se o pagador desistir). Sem returnUrl, a página só mostra a confirmação.
  6. Confirme pelo servidor, não pela URL: o evento subscription.card_saved chega no seu webhook, e o campo card da assinatura passa a mostrar bandeira, finais e validade.
Janela do terminal
# 1. uma vez: de onde o pagador pode voltar
curl -X PUT $MOTOR/integration/card-capture-return-origins -H "$AUTH" \
-H "Content-Type: application/json" \
-d '{ "origins": ["https://app.suaempresa.com.br"] }'
# 2. a cada cadastro: abre a sessão
curl -X POST $MOTOR/subscriptions/0199c1b0-…/card-capture-sessions -H "$AUTH" \
-H "Idempotency-Key: cartao-4512-1" -H "Content-Type: application/json" \
-d '{ "returnUrl": "https://app.suaempresa.com.br/assinatura?id=4512" }'
{
"token": "cct_Yw3kX9c2ZpQv8Lr1TtB0mN5sHf7JdA4eGu6WiKoRyEl",
"expiresAt": "2026-09-29T12:15:00.000Z",
"captureUrl": "https://app.revtriever.com/c/cct_Yw3kX9c2ZpQv8Lr1TtB0mN5sHf7JdA4eGu6WiKoRyEl",
"returnUrl": "https://app.suaempresa.com.br/assinatura?id=4512"
}

?card=saved na volta é só um aviso de navegação — qualquer um pode digitar essa URL. A fonte de verdade é o evento subscription.card_saved (ou o card da assinatura).

A returnUrl só é aceita se:

  • for https (sem usuário ou senha embutidos);
  • a origem dela (esquema + host + porta) estiver na lista cadastrada da sua conta.

Caminho e query são livres dentro de uma origem cadastrada. Fora disso, abrir a sessão devolve 422 engine.card_capture_return_url_not_allowed e nenhuma sessão é criada. A lista é por conta, guardada no servidor, e só muda por API key — a página nunca redireciona para um destino que não veio da sua sessão. Cada item da lista precisa ser uma origem exata (https://app.suaempresa.com.br, sem caminho, query ou curinga); até 10.

  • O link é um segredo. Quem tiver o captureUrl pode cadastrar um cartão naquela assinatura. Entregue só ao titular do cartão — redirecionando na sessão dele, não por canal aberto — e não registre o link em log.
  • Uso único e 15 minutos. O token vale uma vez: salvo o cartão, o link morre. Vencido, usado ou desconhecido, a página mostra “link expirado ou já usado” e o pagador precisa de um novo — abra outra sessão. O token aparece só na resposta da criação; guardamos apenas o hash.
  • 3 tentativas. Cada recusa do gateway gasta uma tentativa e a página mostra quantas restam; a terceira recusa queima o link. Falha do gateway (fora do ar) não gasta tentativa. Há também limite por IP e por link (resposta 429).
  • Sem cobrança de validação. Não há verificação com valor zero nem cobrança de teste: “cartão válido” quer dizer que o gateway aceitou tokenizar. O banco só é consultado de verdade na primeira cobrança.
  • O que fica guardado: só o token do gateway, cifrado, mais bandeira, últimos 4 dígitos e validade. Número e CVV não são gravados nem registrados em log.
  • Idempotência: repetir o POST com a mesma Idempotency-Key devolve o mesmo token, sem abrir outra sessão.
  • Salvo o cartão, a assinatura passa a usar cartão a partir da próxima fatura gerada (paymentMethod: "card", paymentMethodSource: "override"). Trocar o cartão é abrir uma sessão nova: o cartão anterior é aposentado e a assinatura tem sempre um cartão ativo.
  • A fatura de cartão espera o vencimento (paymentDueDate) e é cobrada no dia, no cartão salvo — não é cobrada na geração.
  • Recusa na cobrança não trava nada: a mesma fatura sai no gateway como cobrança de cartão comum, com link de pagamento, e o motor emite charge.failed (reason: "saved_card_declined", fallback: "payment_link"). Daí em diante, o caminho de sempre: vencimento, invoice.overdue e, se você tem a Recuperação, a régua.
  • Cartão vencido (pela validade informada) não é usado: a fatura sai com link.
  • O cartão vale na conexão onde foi cadastrado. Se a assinatura trocar de gateway, o card da assinatura vira null e é preciso um cadastro novo na conexão nova.

Uma fatura overdue não é final. O motor continua conciliando com o gateway as faturas vencidas por 90 dias após o vencimento: se o pagamento atrasado cair, a fatura vira paid e sai invoice.paid — mesmo depois de você já ter recebido invoice.overdue. Trate invoice.overdue como “venceu sem pagamento até agora”, não como perda.

A tool subscription_card_capture_link abre a sessão e devolve só { captureUrl, expiresAt } — os mesmos cuidados valem: o link é segredo de uso único, para o titular do cartão. Veja MCP.