Assinaturas com cartão (SDK Web)

Recorrência no cartão de ponta a ponta — plano, assinatura, adesão do cartão com a SDK web, cobranças e webhooks.

Assinaturas com cartão — guia de implementação

O estabelecimento cria um plano e uma assinatura por cliente. O cliente cadastra o cartão uma vez numa página segura da Autra, aberta dentro do site. Depois disso, a Autra cobra sozinha a cada vencimento, sem pedir o cartão de novo.

O site nunca recebe o número do cartão, a validade ou o CVV. Eles são digitados na página da Autra, em cards.autra.io, e o site recebe só os 4 últimos dígitos e a bandeira.

A integração tem duas partes:

  • Backend do estabelecimento: cria o plano, a assinatura e a sessão de cartão, e recebe os webhooks. Todas as chamadas à API saem do backend, nunca do navegador.
  • Site do estabelecimento: usa a SDK web @autra-io/card-sdk-web para abrir a página de cartão e mostrar o resultado ao cliente.

O fluxo completo, do plano à primeira cobrança:

  1. O backend cria o plano, uma única vez.
  2. O backend cria a assinatura do cliente. Ela nasce aguardando o cartão e ainda não é cobrada.
  3. O backend abre uma sessão de cartão ligada a essa assinatura e recebe um link da página.
  4. O site abre a página com a SDK e o cliente digita o cartão.
  5. A Autra valida o cartão sem cobrar nada, salva o cartão e ativa a assinatura.
  6. O backend recebe o webhook de assinatura ativada e libera o acesso do cliente.
  7. A Autra gera cada fatura 3 dias antes do vencimento e cobra o cartão salvo no dia.

Antes de começar

Cinco itens precisam estar prontos antes da primeira chamada. Os três primeiros são feitos pela Autra, a pedido do estabelecimento.

ItemQuem fazComo confirmar
Módulo de assinaturas e SDK web habilitados no tenantAutraAs rotas de plano param de responder FEATURE_DISABLED e a sessão web para de responder CARD_SDK_WEB_DISABLED
Estabelecimento credenciado para cartãoAutraA sessão de cartão para de responder ACQUIRING_NOT_READY
Endereço do site cadastrado como origem permitidaAutra, com o endereço enviado pelo estabelecimentoA sessão web devolve iframe em embedModes
Credenciais de API (clientId e clientSecret) e IP do backend liberadoAutra entrega as credenciais; o estabelecimento informa os IPs de saídaPOST /v1/oauth/token devolve um token
URL do backend para receber webhooksEstabelecimentoCadastro com PUT /v1/banking/webhooks/{domain}, na seção de webhooks

Origem do site. É o endereço exato em que a página será aberta, no formato https://loja.exemplo.com.br. Vai sem caminho, sem barra no fim e em letras minúsculas. Cada subdomínio é uma origem diferente, e produção e sandbox são cadastradas separadamente.

Token de acesso. O backend gera o token com o fluxo OAuth 2.0 client_credentials e o envia em todas as chamadas:

curl -X POST https://api.autra.io/v1/oauth/token \
  -H "Authorization: Basic <base64(clientId:clientSecret)>" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials"

Toda chamada às rotas de assinatura e de cartão leva também o header X-Merchant-Document, com o CPF ou CNPJ do estabelecimento, só números.

Referência: Gerar token de acesso.

Passo a passo no backend

São três chamadas, sempre a partir do backend, em https://api.autra.io.

1. Criar o plano (uma vez)

O plano define quanto cobrar, de quanto em quanto tempo e quando vence a primeira cobrança. Ele é reaproveitado por todos os clientes daquele produto.

Referência: Criar plano de assinatura.

curl -X POST https://api.autra.io/v1/acquiring/subscription-plans \
  -H "Authorization: Bearer <token>" \
  -H "X-Merchant-Document: 60116920000100" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Plano Mensal Ouro",
    "amount": 49.90,
    "periodicity": "MONTHLY",
    "allowedMethods": ["CARD"],
    "enrollmentCharge": "FIRST_DUE_DATE",
    "firstDue": { "rule": "FIXED_DAY", "billingDay": 10 },
    "cardRetryDays": [1, 3, 7]
  }'
CampoRegra
nameÚnico por estabelecimento, até 120 caracteres
amountValor em reais, maior que zero, até 2 casas decimais
periodicityWEEKLY, MONTHLY ou ANNUAL
allowedMethodsUse ["CARD"]. A cobrança automática desta versão é só no cartão
enrollmentChargeUse FIRST_DUE_DATE: o cadastro do cartão não cobra, e a primeira cobrança acontece no primeiro vencimento
firstDueFIXED_DAY com billingDay de 1 a 31, ou DAYS_AFTER_ENROLLMENT com offsetDays de 0 a 365
cardRetryDaysOpcional. Dias após o vencimento para tentar de novo depois de uma recusa. O padrão é [1, 3, 7]

Valor e periodicidade não mudam depois de criados. Para mudar o preço, crie outro plano e arquive o antigo. As assinaturas existentes continuam no plano delas.

2. Criar a assinatura do cliente

A assinatura liga um cliente a um plano. Ela nasce em PENDING_ENROLLMENT e só é cobrada depois que o cliente cadastrar o cartão. Guarde o id devolvido.

Referência: Criar assinatura.

curl -X POST https://api.autra.io/v1/acquiring/subscriptions \
  -H "Authorization: Bearer <token>" \
  -H "X-Merchant-Document: 60116920000100" \
  -H "Content-Type: application/json" \
  -d '{
    "planId": "2814b0fd-25b7-476c-affe-34f30fe46d6d",
    "externalRef": "cliente-123-plano-ouro",
    "customer": {
      "document": "12345678909",
      "name": "Maria da Silva",
      "email": "[email protected]"
    }
  }'
  • externalRef é o identificador do estabelecimento para essa assinatura e serve de chave contra duplicidade. Repetir a chamada com o mesmo externalRef, plano e cliente devolve a mesma assinatura, sem criar outra.
  • firstDueDate é opcional, no formato AAAA-MM-DD. Sem ele, vale a regra do plano. O dia do primeiro vencimento vira o dia fixo das cobranças seguintes.
  • metadata é opcional, até 4 KiB, e volta como foi enviado. Não coloque dado sensível nele.

3. Abrir a sessão de cartão da adesão

A sessão gera o link da página onde o cliente digita o cartão. Crie uma sessão nova a cada tentativa: ela vale 15 minutos e é de uso único.

Referência: Create hosted card session.

curl -X POST https://api.autra.io/v1/acquiring/card-sessions \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "documentId": "60116920000100",
    "purpose": "ENROLLMENT",
    "channel": "web",
    "origin": "https://loja.exemplo.com.br",
    "subscriptionId": "<id da assinatura do passo 2>"
  }'

A resposta traz os campos que o site vai usar:

CampoUso
hostedCardUrlLink da página de cartão. Entregue ao site exatamente como veio
hostedCardSessionIdIdentificador da sessão. Necessário no modo redirect
embedModesModos de abertura permitidos: iframe, popup e redirect
expiresAtFim da validade da sessão, 15 minutos depois da criação

O origin precisa ser igual, caractere por caractere, à origem cadastrada. A hostedCardUrl carrega uma credencial temporária da página: não grave esse link em log.

Passo a passo no site

O site pede ao próprio backend uma sessão e abre a página de cartão com a SDK. A SDK tem cerca de 5 KB e nenhuma dependência.

4. Instalar a SDK

npm install @autra-io/card-sdk-web

5. Abrir a página de cartão

import { AutraCard, AutraCardError } from '@autra-io/card-sdk-web';

botaoAssinar.addEventListener('click', async () => {
  // Seu backend cria a sessão do passo 3 e devolve a hostedCardUrl.
  const { hostedCardUrl } = await fetch('/api/assinatura/cartao', { method: 'POST' }).then((r) => r.json());

  const card = new AutraCard({ hostedCardUrl, mode: 'modal' });
  card.on('ready', () => esconderCarregando());
  card.on('submitting', () => mostrarMensagem('Validando seu cartão…'));
  card.on('enrollment_denied', ({ reasonText }) => {
    // Não é o fim: a página mostra o motivo e o cliente pode tentar outro cartão.
  });

  try {
    const result = await card.open();
    mostrarMensagem(`Assinatura ativa no cartão final ${result.card.last4}`);
  } catch (error) {
    if (error instanceof AutraCardError && error.indeterminate) {
      // O cartão já tinha sido enviado e pode ter sido aprovado. Vale o webhook.
      return mostrarMensagem('Estamos confirmando seu cartão…');
    }
    if (error instanceof AutraCardError && error.code === 'canceled') return;
    mostrarErro(error instanceof AutraCardError ? error.code : 'erro_desconhecido');
  }
});

O resultado de card.open() traz os 4 últimos dígitos, a bandeira e a confirmação da adesão com o subscriptionId. Ele serve para a tela do cliente. A liberação do acesso deve vir do webhook no backend.

Modos de abertura

Use um modo que apareça em embedModes na resposta da sessão. O modo modal da SDK corresponde a iframe na API.

ModoQuando usarCuidados
modalPadrão. A página abre por cima do siteExige a origem cadastrada e ativa
popupQuando o site não pode ter iframeChame card.open() direto no clique, sem await antes. Crie a sessão antes do clique
redirectWeb no celular ou webviews onde modal e popup falhamExige returnUrl na origem cadastrada. O resultado volta no endereço de retorno

No modo redirect, a página de retorno lê o resultado assim:

const outcome = AutraCard.readRedirectResult(location.hash, { sessionId: hostedCardSessionId });
if (outcome?.type === 'result') mostrarMensagem(`Cartão final ${outcome.result.card.last4} salvo`);
else if (outcome?.type === 'error') mostrarErro(outcome.error.code);

O resultado no endereço de retorno pode ser forjado por qualquer pessoa. Use-o só para a tela. Ative a conta do cliente apenas pelo webhook ou por uma consulta do backend.

Erros da SDK

Quando card.open() falha, o erro traz um code:

codeO que fazer
canceledO cliente fechou a página. Com indeterminate: true, o cartão pode ter sido aprovado: aguarde o webhook
timeoutA página não carregou em 15 segundos. Causas comuns: sessão vencida ou origem não cadastrada. Crie outra sessão
session_expiredA sessão passou de 15 minutos ou já foi usada. Crie outra
origin_not_allowedA origem do site não está cadastrada ou ativa
enrollment_deniedO emissor recusou o cartão e o cliente desistiu. reasonText traz o motivo
enrollment_pending_reviewResultado incerto. Não abra outra adesão e aguarde o webhook
provider_unavailableInstabilidade momentânea. Tente de novo em instantes
tokenization_failedFalha ao salvar o cartão. Crie outra sessão e tente de novo
validation_errorDados do cartão inválidos e o cliente desistiu
popup_blockedO navegador bloqueou o popup. Chame open() direto no clique ou use modal
configuration_errorParâmetro inválido na SDK, open() repetido ou outra modal já aberta
message_invalidMensagem fora do formato esperado. Atualize a SDK

Depois da adesão

A partir da adesão aprovada, a Autra cobra sozinha e avisa o backend por webhook a cada mudança.

6. Cadastrar os webhooks

Cadastre a URL do backend nos domínios subscriptions e acquiring, uma chamada para cada um:

Referência: Criar ou atualizar webhook.

curl -X PUT https://api.autra.io/v1/banking/webhooks/subscriptions \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://backend.exemplo.com.br/webhooks/autra",
    "eventTypes": ["*"],
    "enabled": true
  }'

A resposta do primeiro cadastro traz o secret, que só aparece nessa vez. Guarde-o num cofre de senhas. Todo webhook chega com o header X-Autra-Signature: sha256=<hex>, que é o HMAC-SHA256 do corpo bruto com esse secret. Recuse o webhook se a assinatura não bater. O guia Outbound Webhooks tem exemplos de validação em várias linguagens.

Se a entrega falhar, a Autra tenta de novo por até 16 vezes, em cerca de 60 minutos. O backend deve responder 2xx rápido e tratar eventos repetidos sem efeito duplicado.

EventoQuando chegaAção sugerida
subscription.activatedCartão aprovado na adesãoLiberar o acesso do cliente
invoice.created3 dias antes de cada vencimentoAvisar o cliente da cobrança
invoice.paidCobrança aprovadaRegistrar o pagamento
invoice.payment_failedTentativa recusada; há nova tentativa agendadaPedir ao cliente que confira o cartão
invoice.overdueAs 3 tentativas falharamTratar a inadimplência
subscription.past_dueA assinatura entrou em atrasoRestringir o acesso, se for a política
subscription.reactivatedA fatura em atraso foi pagaDevolver o acesso
subscription.suspended e subscription.canceledO estabelecimento suspendeu ou cancelouAtualizar o cadastro
acquiring.card_enrollment.completed ou .failedFim da sessão de cartão da adesãoRegistro da adesão, independente do navegador

Os webhooks trazem subscriptionId e externalRef para localizar o cliente. O documento do cliente vem mascarado.

Como as cobranças funcionam

Cada vencimento gera uma fatura, criada 3 dias antes. No dia do vencimento, a Autra cobra o cartão salvo, sem pedir CVV.

  • Recusa: cada fatura tem até 3 tentativas. São o dia do vencimento e as duas primeiras datas de cardRetryDays, que pelo padrão são 1 e 3 dias depois.
  • Atraso: se as 3 falham, a fatura fica OVERDUE e a assinatura fica PAST_DUE. Nenhum período novo é cobrado até essa fatura ser paga. Quando é paga, a assinatura volta a ACTIVE sozinha.
  • Consulta: GET /v1/acquiring/subscriptions/{id}/invoices lista as faturas da assinatura, da mais recente para a mais antiga.
Status da faturaSignificado
SCHEDULEDCriada, aguardando o vencimento
OPENCobrança em andamento
RETRYINGUma tentativa falhou e há outra agendada
PAIDPaga
OVERDUEAs 3 tentativas falharam
CANCELEDCancelada junto com a assinatura

Gerenciar a assinatura

AçãoRotaEfeito
ConsultarGET /v1/acquiring/subscriptions/{id}Status, próximo vencimento e cartão
ListarGET /v1/acquiring/subscriptionsAssinaturas do estabelecimento; aceita filtro por externalRef
SuspenderPOST /v1/acquiring/subscriptions/{id}/suspendPausa as cobranças
ReativarPOST /v1/acquiring/subscriptions/{id}/reactivateVolta a cobrar uma assinatura suspensa
CancelarPOST /v1/acquiring/subscriptions/{id}/cancelEncerra de vez e cancela as faturas não cobradas

Uma cobrança já em andamento no momento da suspensão ou do cancelamento termina normalmente e pode ser debitada. Faturas pagas não são estornadas automaticamente. Se precisar devolver, use o estorno do pagamento.

Erros comuns e checklist

Os erros da API vêm no corpo {"errors":[{"code":"...","msg":"..."}]}. Estes são os que mais aparecem na implantação:

codeHTTPCausaO que fazer
FEATURE_DISABLED403Módulo de assinaturas não habilitadoPedir a habilitação à Autra
CARD_SDK_WEB_DISABLED403SDK web não habilitadaPedir a habilitação à Autra
CARD_SDK_ORIGIN_NOT_REGISTERED422O origin da sessão não está cadastradoConferir a grafia ou pedir o cadastro à Autra
CARD_SDK_ORIGIN_INVALID422Origem fora do formato https://hostRemover caminho, barra final e :443
ACQUIRING_NOT_READY409Estabelecimento ainda não credenciado para cartãoAguardar o credenciamento
SUBSCRIPTION_NOT_AWAITING_ENROLLMENT409A assinatura já tem cartão ou foi canceladaConsultar a assinatura antes de abrir a sessão
SUBSCRIPTION_NOT_FOUND404subscriptionId de outro estabelecimento ou inexistenteConferir o id e o X-Merchant-Document
EXTERNAL_REF_IN_USE409externalRef já usado com outro plano ou clienteUsar outro externalRef
PLAN_ARCHIVED422O plano está arquivadoUsar um plano ativo

A referência completa de cada rota, com todos os campos, exemplos e erros:

RotaReferência
POST /v1/oauth/tokenGerar token de acesso
POST /v1/acquiring/subscription-plansCriar plano de assinatura
GET /v1/acquiring/subscription-plansListar planos do estabelecimento
GET /v1/acquiring/subscription-plans/{id}Consultar plano
PATCH /v1/acquiring/subscription-plans/{id}Renomear ou arquivar plano
POST /v1/acquiring/subscriptionsCriar assinatura
GET /v1/acquiring/subscriptionsListar assinaturas
GET /v1/acquiring/subscriptions/{id}Consultar assinatura
GET /v1/acquiring/subscriptions/{id}/invoicesListar faturas da assinatura
POST /v1/acquiring/subscriptions/{id}/suspendSuspender assinatura
POST /v1/acquiring/subscriptions/{id}/reactivateReativar assinatura suspensa
POST /v1/acquiring/subscriptions/{id}/cancelCancelar assinatura
POST /v1/acquiring/card-sessionsCreate hosted card session
PUT /v1/banking/webhooks/{domain}Criar ou atualizar webhook
Eventos e validação da assinaturaOutbound Webhooks

Checklist para produção

  • Módulo de assinaturas e SDK web habilitados no tenant
  • Estabelecimento credenciado para cartão
  • Origem de produção do site cadastrada
  • Credenciais de produção no backend e IPs de saída liberados
  • Webhooks cadastrados em subscriptions e acquiring, com a assinatura X-Autra-Signature validada
  • Acesso do cliente liberado pelo webhook subscription.activated, e não pelo navegador
  • Tratamento de eventos repetidos sem efeito duplicado
  • Uma adesão de teste feita de ponta a ponta com cartão real