Docs

O que o simulador faz por trás de cada chamada, o painel que mostra isso, e cada configuração.

Por que não só o sandbox

O sandbox do Asaas não entrega webhook no localhost. Isso obriga a um túnel, um deploy de homologação ou payloads forjados na mão, e nenhum deles testa o que importa: o prazo de crédito, os juros de atraso, o split, a retentativa do webhook. E no sandbox, um pagamento no cartão cai em 32 dias reais.

Este simulador entrega webhooks na sua máquina e deixa você mover o tempo. Diferente de um mock de respostas fixas, ele guarda estado: saldos, extratos, filas e agendamentos.

As regras que segue

  • Uma máquina de estados por forma de pagamento. Pix vai direto a RECEIVED; boleto cai em D+1 útil; cartão de crédito em D+32; débito em D+3. Uma cobrança não paga passa antes por OVERDUE.
  • Dinheiro de verdade. netValue = value − taxa, a taxa é debitada no razão, e o extrato fecha com o saldo (um invariante testado).
  • Juros e multa. A multa uma vez, no primeiro dia de atraso; os juros pro rata die sobre o mês comercial de 30 dias.
  • Split entre contas. Cada chave de API é uma conta, com walletId e saldo próprios. Um split move dinheiro e lança nos dois extratos.
  • Webhooks como o Asaas manda. O header asaas-access-token; só HTTP 200 conta; 15 tentativas na tabela do Asaas; SEQUENTIALLY com bloqueio real da fila; interrupted no fim.
  • Cartão. Tokenização (o número do cartão nunca é guardado), cartões de teste, pré-autorização e captura, chargeback. O parcelamento no cartão cobra o cartão.
  • Cobrança recorrente no cartão. No vencimento o cartão salvo da assinatura é tentado às 8h, 14h e 20h, e mais três vezes no dia seguinte (o relato do próprio Asaas sobre o cronograma, blog.asaas.com, 2024). Cada recusa é um PAYMENT_CREDIT_CARD_CAPTURE_REFUSED com a cobrança ainda PENDING; depois OVERDUE, sem novas tentativas. Um cartão novo na assinatura vale para as cobranças pendentes. Um cartão salvo pode aprovar mais N cobranças e depois recusar.
  • Recorrência. A assinatura cria a primeira cobrança na criação; as seguintes nascem 40 dias antes do vencimento. No parcelamento, a sobra do arredondamento vai para a última parcela, e no cartão a parcela n cai em D+32×n.
  • Pix que paga. GET /v3/payments/{id}/pixQrCode devolve um BR Code EMV de verdade, com CRC16 e a imagem do QR: a câmera do celular lê.
  • Onboarding de subconta. Criar a subconta, listar os documentos pendentes, enviar um arquivo, aprovar.

Notificações ao pagador

O que o Asaas avisa a quem paga, entregue de verdade na sua máquina.

  • Todo evento de cobrança (criada, alterada, vencendo, vencida, recebida) manda ao pagador um e-mail no Mailpit, um template de WhatsApp pela Cloud API do mock do WhatsApp e um SMS no mock de SMS, com o remetente ASAAS.
  • O SMS segue o interruptor smsEnabledForCustomer da notificação e só sai para quem tem celular cadastrado.
  • Um parcelamento é avisado uma vez: a criação da primeira parcela conta o plano inteiro (R$ 3.000,00 em 5x de R$ 600,00); lembretes e avisos de pagamento seguem por parcela.
  • Nada para ligar, do jeito que você rodar (bun, docker run, o seu compose, make setup): o Mailpit, o mock do WhatsApp e o mock de SMS são achados sozinhos, pelo nome do serviço no compose, nesta máquina ou no host do Docker, na subida e a cada 10 s, o mais próximo primeiro. O mock de SMS é procurado na porta 8899, pelo serviço sms ou sms-mock do compose, e só é aceito quando responde service: "sms" no GET /health. O endereço em que o mock do WhatsApp responde de volta é achado do mesmo jeito: fica o primeiro que ele verifica.
  • O gerador também é achado sozinho na porta 45450 (no boot e a cada 10 s, no serviço generator ou generator-mock do compose, nesta máquina ou no host do Docker): aceita quem responder service: "generator" no GET /health, liberando a aba Integrações › Gerador no painel para popular a conta selecionada com clientes reais e cobranças.
  • Tudo sobe configurado: no boot e a cada tick, o número remetente recebe o webhook deste servidor e os templates do pagador são criados no mock do WhatsApp, pelas chamadas da própria Meta.
  • Todo cliente nasce com as oito notificações padrão do Asaas (GET /v3/customers/{id}/notifications, alteradas com PUT /v3/notifications/batch), aqui com e-mail, SMS e WhatsApp ligados (o Asaas começa com o WhatsApp desligado). O aviso de vencimento sai N dias antes (10 por padrão) e no dia do vencimento; a cobrança vencida é avisada de novo a cada N dias (7 por padrão) enquanto não é paga; a cobrança de assinatura não é avisada na criação.
  • O notificationDisabled do cliente e cada configuração são respeitados, um canal com falha nunca trava a cobrança, e o painel reenvia quando você pede.

O painel

Abra http://localhost:45445. Não é o painel do Asaas: é o do simulador, e mostra o que o Asaas não deixa ver.

  • Clientes: criar, editar, remover e restaurar; abrir um para ver tudo o que foi cobrado dele.
  • Uma cobrança: o link da fatura, simular o pagamento, confirmar recebimento em dinheiro, receber, vencer, estornar, apagar, restaurar, e as notificações dela.
  • Integrações, por conta: chaves de API, webhooks com a lista de eventos, o log de requisições (cada chamada à /v3 com o que foi enviado e o que voltou, erros incluídos), o log de webhooks com a resposta do seu endpoint e os eventos que nenhum webhook levou, os canais de e-mail e WhatsApp, e o gerador (Gerador).
  • Cartões de teste: aceitar qualquer cartão bem formado ou só os da lista; adicionar um cartão com o desfecho que ele força.
  • Notas fiscais (NFS-e): agendar a nota de uma cobrança, emitir, cancelar e cadastrar as informações fiscais da conta. A prefeitura responde no próximo tick (SYNCHRONIZED → AUTHORIZED, ou ERROR sem informações fiscais), ou na hora com os botões do simulador; o pdfUrl e o xmlUrl de uma nota autorizada abrem o documento. Uma assinatura com invoiceSettings ganha a nota de cada cobrança.
  • Perfis: uma conta raiz por tenant da sua aplicação, criada e apagada sem reiniciar. Zerar esvazia um perfil e mantém o id e as chaves: tudo, ou só as cobranças, as notas e os logs, mantendo clientes e subcontas; cancelar antes envia PAYMENT_DELETED, SUBSCRIPTION_DELETED e INVOICE_CANCELED aos seus webhooks antes da limpeza.

Configuração

VariávelPadrãoSignificado
PORT45445Porta HTTP
ASAAS_API_KEYgerada, impressa no bootA chave da conta principal
WEBHOOK_LOCALHOST_REWRITE(vazio)Host que substitui localhost nas URLs de webhook; use host.docker.internal
CLOCK_MODEREALREAL (segue a máquina; o relógio não deixa pular), VIRTUAL_FLOWING ou VIRTUAL_FROZEN
ADMIN_ENABLEDtrueAs rotas /_admin/* e o painel
INVOICE_BASE_URLhttp://localhost:<PORT>Para onde apontam invoiceUrl e bankSlipUrl, como o navegador alcança este servidor
MAILPIT_URL(sem valor: achado sozinho)O Mailpit dos e-mails ao pagador; vazio, desativa o e-mail
WHATSAPP_MOCK_URL(sem valor: achado sozinho)O mock do WhatsApp dos templates ao pagador; vazio, desativa o WhatsApp
SMS_MOCK_URL(sem valor: achado sozinho)O mock de SMS das mensagens ao pagador; vazio, desativa o SMS
GENERATOR_URL(sem valor: achado sozinho)O gerador para popular o mock; vazio, desativa o gerador
WHATSAPP_PHONE_NUMBER_ID5511400000000O número remetente no mock do WhatsApp
WHATSAPP_WABA_ID100000000000001A WABA cuja biblioteca guarda os templates do pagador
NOTIFY_CALLBACK_BASE_URL(sem valor: achado sozinho)Este servidor como o mock do WhatsApp o alcança
MAILPIT_UI_URL, WHATSAPP_UI_URLhttp://localhost:8025, http://localhost:3010As duas telas, como o navegador as abre a partir de Integrações
SMS_UI_URLhttp://localhost:8899A tela do mock de SMS, onde as mensagens ao pagador são lidas. O painel do Asaas não tem aba de SMS
GENERATOR_UI_URLhttp://localhost:45450A tela do gerador, como o navegador a abre a partir de Integrações
NOTIFY_FROM_EMAILnotificacoes@asaas.localhostRemetente dos e-mails
CARD_CAPTURE_SCHEDULE0@08:00,0@14:00,0@20:00,1@08:00,1@14:00,1@20:00Quando a cobrança recorrente no cartão é tentada: dias depois do vencimento @ hora local. O painel troca em tempo de execução
DATABASE_PATH/data/asaas.dbMonte um volume em /data para persistir
TZAmerica/Sao_PauloFuso horário

Fidelidade: provada, não prometida

As regras de negócio foram capturadas do sandbox real do Asaas e são conferidas campo a campo por uma suíte de paridade. tools/capture.ts roda os cenários contra o Asaas e grava as respostas em tests/golden/; a suíte as reproduz contra o simulador e compara tudo o que não é legitimamente volátil, até o conjunto exato de chaves de cada objeto.

A captura derrubou 20 regras deduzidas da documentação, entre elas: a assinatura cria a primeira cobrança na hora; a taxa do parcelamento no cartão sai do total e a divisão trunca; a parcela n cai em D+32×n; o campo do desconto é limitDate, que a especificação não declara.

bun run test:parity
ASAAS_SANDBOX_API_KEY='$aact_hmlg_…' bun run capture   # capture de novo com a sua chave do sandbox

O script recusa qualquer chave que não comece com $aact_hmlg_. Detalhes em progress.md.

Desenvolvimento

bun install
bun run codegen        # spec OpenAPI → TypeBox e o manifesto de operações
bun run db:generate    # migrations a partir do schema Drizzle
bun run dev
bun test
bun run e2e            # sobe o servidor, paga um Pix, espera o PAYMENT_RECEIVED
bun run spec:diff      # o Asaas mudou a API?

Leia o AGENTS.md antes de contribuir: domínio puro, nada de Date.now(), dinheiro em centavos inteiros.

Licença

MIT, veja a LICENSE.

Sem vínculo com o Asaas. Um simulador independente da API pública deles, para desenvolvimento local e testes. "Asaas" é marca de seus donos. Não use para mover dinheiro de verdade: nenhum pagamento aqui é real.