npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@autra-io/card-sdk-web

v0.1.0

Published

Web SDK for Autra's hosted card page: tokenization and subscription card enrollment without the card data ever touching your site.

Readme

@autra-io/card-sdk-web

SDK web da página hospedada de cartão da Autra. Seu site abre a página da Autra (modal com iframe, popup ou redirect), o cliente digita o cartão lá, e o site recebe só o resultado: final do cartão, bandeira, os tokens do cartão salvo e, em assinaturas, a adesão aprovada. Número, validade e CVV nunca passam pelo seu site nem pelo seu backend.

Read in English

Sumário

Recursos

  • Três modos: modal (iframe sobre a página), popup e redirect
  • API híbrida: Promise para o resultado final, eventos para os estados intermediários (ready, submitting, enrollment_denied, closed)
  • Canal postMessage endurecido: targetOrigin exato, validação de event.origin e event.source, envelope versionado, dedupe e trava depois do resultado final
  • Erros tipados (AutraCardError) com 12 códigos, os mesmos da página
  • ESM + CJS + .d.ts, sideEffects: false, sem dependências de runtime
  • Bundle ESM abaixo de 15 KB minificado+gzip (verificado no CI)

Requisitos

  • Tenant com a feature card_sdk_web (e subscriptions, para assinaturas). Peça a habilitação à Autra.
  • A origem do seu site cadastrada e ativa (veja o passo 0).
  • Um backend com as suas credenciais OAuth2 da Autra, com o IP na allow-list do tenant. Sessões de cartão são criadas só pelo backend.
  • Um navegador moderno. Node.js >= 20 apenas para construir este pacote.

Instalação

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

Como funciona

Seu backend                   Seu site + SDK                  Página da Autra
     |                               |                               |
     |-- POST /card-sessions ------->| (Autra devolve hostedCardUrl)  |
     |--- hostedCardUrl ------------>|                               |
     |                               |--- card.open() (iframe) ----->|
     |                               |------- host_hello ----------->|
     |                               |<--------- ready --------------|
     |                               |<------- submitting -----------|  cliente digita o cartão
     |                               |<-------- result --------------|  final, bandeira, tokens
     |<== webhook subscription.activated / acquiring.card_enrollment.completed ==|

O site nunca vê o cartão. O resultado no navegador serve para a interface; a confirmação de verdade é o webhook (ou o GET) no seu backend.

Passo 0: cadastre a origem do seu site

A página só aceita ser embutida (e só conversa) com as origens que o tenant cadastrou, por ambiente. O cadastro é feito no painel da Autra, que chama:

POST /v1/tenants/card-sdk/origins
Authorization: Bearer <token do painel>
Content-Type: application/json

{ "origin": "https://loja.exemplo.com.br", "environment": "production" }
  • Formato exato: https://<host>[:porta], minúsculas, sem caminho e sem barra final. Fora disso a API recusa (422); ela nunca corrige o valor.
  • GET /v1/tenants/card-sdk/origins lista; DELETE .../{id} revoga (a revogação vale na hora, inclusive para sessões já abertas).
  • Cadastre uma origem por site/subdomínio e por ambiente (sandbox e production).

Fluxo completo de assinatura

Todas as chamadas /v1/acquiring/* são do seu backend (https://api.autra.io, Authorization: Bearer <access token>, header X-Merchant-Document com o CPF/CNPJ do estabelecimento).

1. Backend: crie o plano (uma vez)

POST /v1/acquiring/subscription-plans
X-Merchant-Document: 60116920000100

{
  "name": "Plano Mensal Ouro",
  "amount": 49.9,
  "periodicity": "MONTHLY",
  "allowedMethods": ["CARD"],
  "enrollmentCharge": "FIRST_DUE_DATE",
  "firstDue": { "rule": "FIXED_DAY", "billingDay": 10 }
}

2. Backend: crie a assinatura do cliente

POST /v1/acquiring/subscriptions
X-Merchant-Document: 60116920000100

{
  "planId": "2814b0fd-25b7-476c-affe-34f30fe46d6d",
  "externalRef": "cliente-123-plano-ouro",
  "customer": { "name": "Maria da Silva", "document": "12345678909", "email": "[email protected]" }
}

A assinatura nasce PENDING_ENROLLMENT. Guarde o id.

3. Backend: abra a sessão de cartão de adesão

POST /v1/acquiring/card-sessions

{
  "documentId": "60116920000100",
  "purpose": "ENROLLMENT",
  "channel": "web",
  "origin": "https://loja.exemplo.com.br",
  "subscriptionId": "0b3c9f7e-2a1d-4c6e-9f10-6a2b3c4d5e6f"
}

Resposta (resumida):

{
  "hostedCardSessionId": "03294670-67f0-4dca-a121-cf0e0fb2a92f",
  "hostedCardUrl": "https://cards.autra.io/tokenize/sessions/03294670-67f0-4dca-a121-cf0e0fb2a92f#token=...",
  "expiresAt": "2026-10-01T13:15:00Z",
  "purpose": "ENROLLMENT",
  "channel": "web",
  "embedModes": ["iframe", "popup", "redirect"],
  "allowedOrigin": "https://loja.exemplo.com.br",
  "subscriptionId": "0b3c9f7e-2a1d-4c6e-9f10-6a2b3c4d5e6f"
}

Devolva ao front só hostedCardUrl (e, se quiser, embedModes e hostedCardSessionId). A sessão vale 15 minutos e é de uso único. Nunca registre a hostedCardUrl em log: o fragmento #token= é a credencial da página.

A adesão faz uma validação de valor zero (não cobra) e salva o cartão.

4. Front: abra a página

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

button.addEventListener('click', async () => {
  const { hostedCardUrl } = await fetch('/api/assinatura/cartao', { method: 'POST' }).then((r) =>
    r.json(),
  );

  const card = new AutraCard({ hostedCardUrl, mode: 'modal' });
  card.on('ready', () => hideSpinner());
  card.on('submitting', () => showMessage('Validando o 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.
    track('card_denied', { reasonText });
  });

  try {
    const result = await card.open();
    // { purpose: 'ENROLLMENT', card: { last4, brand }, token: { slugStoredCard },
    //   enrollment: { status: 'APPROVED', subscriptionId, paymentId, brandReferenceId } }
    showMessage(`Assinatura ativada no cartão final ${result.card.last4}`);
  } catch (error) {
    if (error instanceof AutraCardError && error.indeterminate) {
      // O cartão já tinha sido enviado: pode ter sido aprovado. Vale o webhook.
      return showMessage('Estamos confirmando o seu cartão…');
    }
    if (error instanceof AutraCardError && error.code === 'canceled') return;
    showError(error instanceof AutraCardError ? error.code : 'unknown_error');
  }
});

Buscar a sessão dentro do clique, como acima, funciona para modal e redirect. No modo popup o window.open() precisa acontecer no gesto do usuário sem await antes: crie a sessão antes do clique (ela vale 15 minutos) e chame card.open() direto no handler.

5. Backend: confirme pelo webhook

Com a adesão aprovada, a assinatura vira ACTIVE e a Autra emite subscription.activated (domínio subscriptions). A adesão em si também gera acquiring.card_enrollment.completed (ou .failed) no domínio acquiring. Libere o serviço a partir do webhook ou de GET /v1/acquiring/subscriptions/{id}, não do resultado do navegador.

Daí em diante a Autra cobra sozinha: gera a fatura 3 dias antes de cada vencimento e cobra o cartão salvo, sem CVV (webhooks invoice.* e subscription.*).

Só salvar o cartão (TOKENIZE)

Para salvar um cartão sem assinatura (ex.: carteira do cliente, one-click), crie a sessão com purpose: "TOKENIZE" (o padrão) e channel: "web":

POST /v1/acquiring/card-sessions

{ "documentId": "60116920000100", "purpose": "TOKENIZE", "channel": "web", "origin": "https://loja.exemplo.com.br" }
const card = new AutraCard({ hostedCardUrl, mode: 'modal' });
const { card: saved, token } = await card.open();
// saved = { last4: '1111', first4: '4111', brand: 'VISA' }
// token = { slugToken, slugStoredCard, tokenExpirationDate }
await fetch('/api/cartoes', { method: 'POST', body: JSON.stringify(token) });

Seu backend usa slugToken/slugStoredCard em tokenData nas rotas de pagamento. Trate-os como dados sensíveis: só no backend, nunca em log.

Modos de abertura

| mode | Quando usar | embedModes da sessão | Observações | | ---------- | ---------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------- | | modal | Padrão. Overlay com iframe sobre o seu site | iframe | Exige a origem cadastrada e ativa; uma modal por vez | | popup | Quando o site não pode ter iframe | popup | open() dentro do clique, sem await antes; senão popup_blocked | | redirect | Mobile web/webviews onde popup e iframe falham | redirect | returnUrl obrigatório, na mesma origem cadastrada; a Promise fica pendente (a aba navega) |

Escolha um modo presente em embedModes. A API chama de iframe o que o SDK chama de modal.

Retorno do modo redirect

A página devolve o cliente para returnUrl com o resultado no fragmento (#autra_card_status=result&autra_card=...). Na página de retorno:

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

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

No modo redirect o resultado não traz token (slugToken/slugStoredCard): o fragmento fica no histórico do navegador do cliente, então só vão purpose, card (final e bandeira) e enrollment. Seu backend recebe o cartão salvo e a assinatura ativada pelos webhooks acquiring.card_enrollment.completed e subscription.activated.

sessionId é obrigatório: passe o hostedCardSessionId que o seu backend criou para este cliente (guarde-o na sessão do seu site antes do redirect). Sem ele a função devolve null e avisa no console fora de produção; fragmento de outra sessão vira message_invalid. Com undefined no lugar do hash, lê window.location.hash e limpa o fragmento da barra de endereço.

O fragmento é forjável. Qualquer pessoa monta um link com #autra_card_status=result&.... Use o retorno só para a interface; liberar serviço, marcar pagamento ou ativar conta só pelo webhook (subscription.activated, acquiring.card_enrollment.completed) ou pelo GET no backend.

Referência da API

new AutraCard(options)

| Opção | Tipo | Obrigatório | Notas | | --------------- | -------------------------------------- | ------------- | --------------------------------------------------------------------------------------- | | hostedCardUrl | string | sim | A hostedCardUrl devolvida pelo seu backend, sem alteração. HTTPS | | mode | "modal" | "popup" | "redirect" | sim | Veja Modos de abertura | | returnUrl | string | em redirect | HTTPS, na origem cadastrada | | timeoutMs | number | não | Teto local do fluxo. Padrão 900000 (15 min, o TTL da sessão) | | allowedHosts | string[] | não | Hosts aceitos na hostedCardUrl. Padrão ['cards.autra.io', 'cards.sandbox.autra.io'] |

Só valida a entrada, de forma síncrona: lança AutraCardError com configuration_error para entrada inválida (inclusive hostedCardUrl fora de allowedHosts). Não mexe no DOM nem na rede. http:// só é aceito para localhost/127.0.0.1 e ainda exige o host em allowedHosts.

card.on(evento, callback) / card.off(evento, callback)

| Evento | Payload | Quando | | ------------------- | ------------------------------ | --------------------------------------------------------------------- | | ready | { purpose?, embedModes } | A página carregou e está pronta | | submitting | undefined | O cliente enviou o cartão | | enrollment_denied | { reasonCode?, reasonText? } | O emissor recusou a adesão. Não é final: pode vir mais de uma vez | | closed | undefined | A modal/popup foi fechada (qualquer desfecho) |

on() devolve uma função que cancela a inscrição.

card.open(): Promise<CardResult>

Abre a página e resolve com o resultado:

type CardResult = {
  purpose: 'TOKENIZE' | 'ENROLLMENT';
  card: { last4: string; first4?: string; brand?: string; nickname?: string };
  token?: { slugToken?: string; slugStoredCard?: string; tokenExpirationDate?: string };
  enrollment?: {
    status: 'APPROVED';
    paymentId?: string;
    brandReferenceId?: string;
    subscriptionId?: string;
  };
};

Ou rejeita com AutraCardError. Uso único: a segunda chamada rejeita com configuration_error (crie outra sessão). Em ENROLLMENT, o resultado sempre traz enrollment.status === 'APPROVED'.

card.close()

Fecha a página (avisa a página com host_cancel). Sem efeito se nada estiver aberto.

  • Antes de submitting: fecha na hora e o open() rejeita com canceled (indeterminate: false).
  • Depois de submitting (cartão a caminho do core): esconde a modal e espera até 10 s pelo desfecho da página. Se chegar result ou error, o open() entrega esse desfecho; senão rejeita com canceled e indeterminate: true, porque o core pode ter aprovado (ou cobrado) o cartão. Nesse caso vale o webhook.

AutraCard.readRedirectResult(hash, { sessionId })

Lê o resultado do modo redirect. sessionId é obrigatório. Devolve { type: 'result', sessionId, result }, { type: 'error', sessionId?, error } ou null (carregamento normal, sem resultado, ou sem sessionId). Nunca lança. Também exportada como readRedirectResult. O resultado é forjável: só para a interface.

AutraCardError

class AutraCardError extends Error {
  readonly code: AutraCardErrorCode;
  readonly retryable: boolean; // criar uma nova sessão e tentar de novo faz sentido?
  readonly sessionId?: string;
  readonly reasonCode?: string; // só em enrollment_denied
  readonly reasonText?: string;
  readonly indeterminate: boolean; // true: o cartão já tinha sido enviado; confirme pelo webhook
}

AUTRA_CARD_SDK_VERSION

Versão publicada do pacote (também enviada à página no host_hello).

Erros

| code | Origem | O que fazer | | --------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | configuration_error | SDK | Entrada inválida (URL não HTTPS, mode, returnUrl, timeoutMs), open() repetido ou outra modal aberta | | session_expired | Página (410 HOSTED_CARD_SESSION_EXPIRED) | A sessão passou de 15 min ou já foi usada: crie outra. No modal/popup costuma aparecer como timeout (veja abaixo) | | origin_not_allowed | Página | Origem não cadastrada/ativa: veja o passo 0. No modal/popup aparece como timeout | | message_invalid | SDK | Mensagem fora do contrato (versão, formato). Atualize o SDK | | validation_error | Página | Dados do cartão inválidos e o cliente desistiu | | tokenization_failed | Página | Falha ao salvar o cartão: crie outra sessão e tente de novo | | enrollment_denied | Página (422 CARD_ENROLLMENT_DENIED) | O emissor recusou e o cliente desistiu. reasonCode/reasonText trazem o motivo | | enrollment_pending_review | Página (503 CARD_ENROLLMENT_PENDING_REVIEW) | Resultado incerto: não abra outra adesão; aguarde o webhook | | provider_unavailable | Página (503 HOSTED_CARD_PROVIDER_UNAVAILABLE) | Instabilidade transitória: tente de novo em instantes | | canceled | Cliente ou close() | O cliente fechou a página/popup ou o site chamou close(). Com indeterminate: true (depois de submitting), o cartão pode ter sido aprovado: vale o webhook | | timeout | SDK | A página não respondeu ready em 15 s (sessão expirada/inválida, origem não cadastrada, página fora do ar) ou o fluxo passou de timeoutMs. Com indeterminate: true, vale o webhook | | popup_blocked | SDK | O navegador bloqueou o popup: chame open() direto no clique, ou use modal |

Segurança e PCI

  • O site nunca vê o cartão. Número, validade e CVV são digitados na página da Autra, em outra origem; o SDK só recebe final (4 dígitos), bandeira e tokens. Isso tira o seu site do escopo de dados de cartão do PCI DSS (a Autra continua responsável pela página).
  • O SDK só aceita hostedCardUrl HTTPS, num host de allowedHosts (padrão: só os da Autra), e deriva a origem da página com new URL(hostedCardUrl).origin.
  • Nunca repasse uma hostedCardUrl vinda do cliente (query string, postMessage, corpo de requisição): use sempre a que o seu backend recebeu de POST /v1/acquiring/card-sessions. Não amplie allowedHosts em produção.
  • canceled/timeout com indeterminate: true não significam falha: o cartão já tinha sido enviado. Confirme pelo webhook antes de pedir outro cartão ao cliente.
  • O retorno do modo redirect é forjável: use readRedirectResult só para a interface.
  • Todo postMessage usa essa origem exata como targetOrigin, nunca '*'. Toda mensagem recebida é validada por event.origin e event.source (a própria janela do iframe/popup), por versão do envelope e por correlationId; depois do resultado final, nada mais é aceito.
  • Os campos do resultado são copiados de uma lista fechada; last4/first4 só passam com exatamente 4 dígitos.
  • O page token vai no fragmento da hostedCardUrl; o SDK não lê, não guarda e não registra. Não logue a hostedCardUrl no seu backend nem no front.
  • Nenhuma credencial da API da Autra chega ao navegador: sessões são criadas só pelo seu backend.
  • Veja SECURITY.md.

Solução de problemas

| Sintoma | Causa provável | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 403 CARD_SDK_WEB_DISABLED ao criar a sessão | O tenant não tem a feature card_sdk_web | | 422 CARD_SDK_ORIGIN_NOT_REGISTERED / _INVALID | origin não cadastrada/ativa no ambiente, ou fora do formato https://host[:porta] (sem barra final) | | 409 SUBSCRIPTION_NOT_AWAITING_ENROLLMENT | A assinatura já está ativa/cancelada ou o plano não aceita CARD | | Modal em branco e depois timeout | A página se recusou a ser embutida: a origem do site (esquema, host e porta) não bate com a cadastrada na sessão | | timeout no modal/popup sem motivo aparente | Sessão expirada/inválida ou origem não cadastrada: sem origem confiável, a página não tem canal para avisar o SDK e mostra o erro só na tela. Crie uma sessão nova e confira a origem | | Popup abre mas o fluxo termina em canceled/timeout | O seu site manda Cross-Origin-Opener-Policy: same-origin, que corta o window.opener. Use same-origin-allow-popups, ou os modos modal/redirect | | configuration_error: host "…" is not allowed | hostedCardUrl fora de allowedHosts: use a URL devolvida pelo seu backend, sem alteração | | Testar em http://localhost | A página real exige origens HTTPS (cadastro e frame-ancestors): exponha o seu site local por um túnel HTTPS (ex.: ngrok, Cloudflare Tunnel) e cadastre essa origem no ambiente sandbox | | popup_blocked | Houve await entre o clique e card.open(); crie a sessão antes do clique | | open() nunca resolve no modo redirect | Esperado: a aba navega; leia o resultado na returnUrl com readRedirectResult() | | session_expired logo ao abrir | Sessão reaproveitada ou criada há mais de 15 min: crie uma nova a cada abertura | | configuration_error no construtor | hostedCardUrl alterada, sem HTTPS ou sem o id da sessão no caminho |

Licença

Apache-2.0: veja LICENSE e NOTICE.