Skip to main content
Documentação reorganizada
  • O site ganhou duas abas — Guias e Referência da API — e páginas novas: Aprovação de produção, Cobranças, Links e checkout, Assinaturas, Modo de recebimento, Erros e códigos e Limites
  • Os grupos da referência foram renomeados em português e ordenados por importância; as rotas internas do painel ficaram num grupo final Interno, documentadas por transparência
  • Textos desatualizados foram corrigidos contra a API atual (modo de recebimento, aprovação de produção, ambiente de testes, validade do token). A página Ambiente de testes mudou de endereço (/devmode redireciona)
Aprovação de produção do lojista
  • Todo lojista nasce operando só em modo teste. Para emitir em produção, o cadastro precisa ser aprovado pela equipe V4 Pay: dados da empresa completos + 3 documentos (CNPJ ou contrato social, documento do responsável, comprovante de endereço), enviados em /producao (novo). Enquanto não aprovado: sessão do painel sempre em teste, POST /auth/environment com live e POST /api-keys com environment: live respondem 409, chave v4pay_live_ existente responde 403, e toda emissão em produção (Pix, checkout, link de pagamento, assinatura, cobrança manual e saque) é barrada — tudo com codigo: PRODUCAO_NAO_LIBERADA e o bloco producao dizendo em que pé está
  • O modo teste não muda em nada; pagamento de cobrança já emitida sempre confirma (a trava é só na emissão). No checkout público, o pagador de um link indisponível vê mensagem neutra (codigo: LINK_INDISPONIVEL) — a situação cadastral do lojista não é exposta
  • Novo: GET /admin/lojistas (e detalhe, download de documento por stream, aprovar e recusar) — a fila de análise, exclusiva da equipe. O usuário devolvido pelo login e pelo perfil ganha production_status
  • PUT /auth/profile passa a aceitar address, address_number, complement, province, postal_code e company_type
Painel em domínio próprio
  • O CORS da API aceita uma lista de origens (FRONTEND_URL + FRONTEND_URLS_EXTRAS) — o domínio novo pay.v4companyamaral.com e o antigo convivem durante a migração. Integrações sem navegador (curl, n8n, webhooks) seguem como sempre
Roadmap público do produto
  • Novo: GET /roadmap — o quadro do produto em 4 colunas (backlog, faremos, estamos cozinhando, pronto), com curtidas (1 por lojista, alterna em POST /roadmap/{id}/vote) e comentários (GET/POST /roadmap/{id}/comments; o autor ou a equipe apagam). Criar, editar, mover e apagar cartões é exclusivo da equipe V4 Pay (403 com codigo: SOMENTE_ADMIN para os demais; chave de API nunca é admin)
  • O usuário devolvido pelo login e pelo perfil ganha is_admin
Identificador fim a fim do Pix
  • O end_to_end_id da cobrança passa a ser preenchido quando o pagamento real é confirmado: o aviso do Asaas não traz o E2E — ele mora na transação Pix, que a API agora consulta na confirmação (quando a chave tem a permissão necessária). Pagamentos antigos sem o campo podem ser completados pela operação
Modo conta única pronto para produção
  • No modo conta única (cobranças na conta principal, sem subconta): o walletId deixa de ser exigido (não há split); GET /receiving-account responde modo: "conta_unica" com status aprovado — não é preciso cadastrar empresa; POST /withdrawals responde 409 com codigo: REPASSE_MANUAL (o repasse é feito manualmente pelo V4 Pay enquanto o modo estiver ligado). Fora do modo, nada muda
Servidor mais resiliente e log sem dado pessoal
  • Erro inesperado em qualquer rota passa a responder 500 controlado com um código de correlação (error.id), em vez de poder derrubar o processo; a mensagem interna do erro não é mais ecoada ao cliente — erros de negócio (com status e mensagem próprios) continuam como sempre
  • Os logs do servidor deixam de registrar dados pessoais do pagador (documento, e-mail, telefone); passam a registrar identificadores (txid, lojista, modo, valor)
Webhook com secret informado
  • POST /webhook-endpoints aceita secret opcional — para o painel gerar o segredo no navegador e mostrá-lo ao lojista antes de salvar. Só no formato exato whsec_ + 32 hexadecimais (a mesma entropia do gerado pela API); fora disso, 400. Omitido, a API gera como sempre
Cliente com CPF ou CNPJ
  • Cliente, assinatura e cobrança aceitam CPF ou CNPJ no campo de documento (cpf / customer_cpf), com os dígitos verificadores conferidos — documento inválido é recusado com 400 e mensagem clara (antes o cliente gravava qualquer coisa sem validar). A API guarda só os dígitos
  • O comprovante público mascara CNPJ também: 12.***.***/0001-**
Cobrança a partir de produto
  • POST /pix ganha o modo produto: com product_id, o valor é calculado no servidor (preço do produto, menos coupon_code opcional — mesmas regras do checkout), a descrição padrão é o nome do produto e a validade padrão é 24 h. O pagador vem de customer_id ou dos campos inline (novo_cliente: true também o cadastra em clientes). success_url e cancel_url (http(s) validadas) definem o retorno pós-pagamento, e GET /checkout/{txid} passa a devolvê-las com o product_id. O modo manual continua igual
Campos que limpam de verdade
  • Apagar a URL da imagem na edição do produto passa a limpar o campo: PUT /products/{id} com image_url vazio ou nulo grava nulo. URL preenchida agora precisa ser http(s) válida — texto solto ou outro esquema é recusado com 400
  • O mesmo '' limpa também: description de produto, cupom, assinatura e link de pagamento; customer_email da assinatura; product_id do link (desvincula o produto)
Produto com sku
  • POST /products e PUT /products/{id} aceitam sku (opcional, até 64 caracteres; vazio limpa). O campo era descartado em silêncio. Sku repetido entre os seus produtos do mesmo ambiente é recusado com 409 e codigo: SKU_DUPLICADO
Assinatura nunca fica sem data de cobrança
  • Uma assinatura ativa sem next_billing_at sumia do cron de cobrança em silêncio. Agora: PUT /subscriptions/{id} aceita next_billing_at mas recusa data nula ou inválida (400); reativar uma assinatura que ficou sem data recalcula a partir de agora pelo interval; e o cron corrige sozinho as que encontrar, com registro em sem_data no resumo da execução
Cupom sem validade volta a validar
  • Correção em GET /coupons/validate: cupom sem expires_at (sem validade — válido para sempre) era recusado como “não encontrado ou expirado”. O checkout público não tinha o problema
Correções da revisão de 06/09
  • A exclusão de cupom, produto e assinatura inverteu a ordem: apaga primeiro e desvincula as referências depois. Falha no meio deixa referências órfãs — recuperáveis, avisadas no campo aviso da resposta — em vez de perder o registro com as referências já zeradas. Acima de 500 referências a exclusão é recusada (409, codigo: MUITAS_REFERENCIAS) com a sugestão de desativar em vez de apagar
  • Reenvio de webhook do Asaas não pode mais duplicar o e-mail de comprovante: o envio passou para depois de o evento ser marcado como concluído, e o timeout padrão do e-mail caiu de 10 s para 5 s
  • GET /checkout/{txid} ganha limite por IP (300 consultas por 10 minutos; o polling normal do checkout usa ~120) e responde 429 acima disso
  • Cupom que deixaria o total abaixo do mínimo de R$ 5,00 é recusado já na validação (400, codigo: VALOR_MINIMO), em vez de prometer um total que a emissão recusaria em seguida
  • Desativar cupom passou a existir de verdade: PUT /coupons/{id} aceita is_active, e cupom desativado é recusado no checkout e no /coupons/validate — é a alternativa que o 409 da exclusão sugere
QR gerado na hora
  • A imagem do QR (qr_code_url) deixa de ser guardada na cobrança e passa a ser gerada a cada resposta a partir de pix_code. Continua vindo em POST /pix, GET /pix/status, GET /checkout/{txid} e no evento charge.created, mas só em cobrança pending; nas demais vem null. Motivo: era o maior campo da cobrança e deixava o banco sem espaço para campos novos
Saldo sem teto silencioso
  • O resumo de saldo (GET /withdrawals/summary) passa a somar todas as cobranças pagas e saques. Antes, uma conta com mais de 5 mil registros tinha o saldo disponível calculado para menos, sem erro. Listagens sem paginação continuam limitadas a 5 mil, mas agora avisam quando cortam
Exclusões sem órfãos
  • DELETE /coupons/{id}, DELETE /products/{id} e DELETE /subscriptions/{id} passam a zerar as referências antes de apagar: cobranças e links perdem o coupon_id, links perdem o product_id, cobranças perdem o subscription_id. A resposta traz desvinculados com a contagem por tabela.campo. Se a limpeza falhar, nada é apagado
Comprovante por e-mail ao pagador
  • Quando a cobrança vira paga, o pagador recebe um e-mail com valor, recebedor, data, identificador fim a fim e o link do comprovante. Vale para o pagamento real e para a simulação no Dev Mode. Cobrança de teste não gera e-mail, salvo EMAIL_ENVIAR_EM_TESTE=true. Depende de RESEND_API_KEY e EMAIL_REMETENTE no servidor; sem eles nada é enviado
Recebedor congelado no comprovante
  • Quando a cobrança vira paga, a API grava nela o nome e o CNPJ do lojista (store_name, store_document). O comprovante público (GET /checkout/{txid}) passa a mostrar quem recebeu naquele dia, mesmo que o cadastro mude depois. Cobranças pagas antes desta versão continuam lendo do cadastro atual
Valor mínimo de cobrança
  • POST /pix, o checkout de link, POST /payment-links e POST /subscriptions (criar e editar) passam a recusar valor abaixo de R$ 5,00 com 400 e codigo: VALOR_MINIMO (o mínimo vem em valor_minimo), antes de chamar o motor de pagamentos. Antes o erro chegava cru, depois de o cliente já ter sido criado. A regra vale igual no Dev Mode, para a recusa não aparecer só em produção
Evento charge.created
  • Novo evento de webhook charge.created: disparado sempre que uma cobrança nasce (POST /pix, checkout de link, cron de assinatura, POST /charges), com pix_code, qr_code_url e checkout_url. Serve para automações entregarem o QR ao cliente na hora
  • O catálogo de eventos (GET /webhook-endpoints/eventos) passa a listar todos os que a API emite: charge.created, charge.paid, subscription.charged, withdrawal.completed, withdrawal.rejected
Checkout em modo teste, de ponta a ponta
  • O checkout público (/checkout/link/{slug}/pay) passa a emitir a cobrança no ambiente do link: link criado em modo teste gera cobrança de teste. Antes, sem sessão, ele assumia produção e falhava com “lojista sem subconta”
  • GET /checkout/link/{slug} e GET /checkout/{txid} devolvem environment
  • Nova rota POST /checkout/{txid}/simular-pagamento, só para cobrança de teste: o ambiente de testes do motor de pagamentos dá a cobrança como paga e o resto do fluxo (webhook, paid, aviso ao lojista) acontece de verdade. É o botão “Simular pagamento” do checkout
  • Correção: o campo qr_code_url da cobrança tinha 2048 caracteres e a imagem do QR tem ~4,4 mil; a gravação falhava. O campo cresceu para 8192 e o provisionador passa a crescer campos quando o schema pede
Webhooks com nome e eventos
  • Endpoints de webhook ganham name e events (lista; vazia = todos). O despachante só entrega a um endpoint os eventos que ele assinou. GET /webhook-endpoints/eventos lista os eventos disponíveis. As respostas passam a trazer version: v1
Assistente de primeiro acesso
  • Nova rota POST /auth/onboarding: grava as respostas do assistente do painel (origem, responsável, loja, CNPJ, endereço, segmento, faturamento) e marca onboarding_completed_at. O usuário devolvido por /auth/me e pelo login passa a trazer esses campos
  • PUT /auth/profile aceita accept_production_terms: true, que registra production_terms_accepted_at (aceite dos termos no diálogo “Ir para produção”)
Erro de configuração sem detalhe interno
  • Quando o motor de pagamentos do ambiente não está configurado no servidor, o erro devolvido por POST /receiving-account e POST /pix passa a ser uma frase curta em português, sem citar configuração interna. O diagnóstico completo continua no log do servidor
Aviso de ambiente sem motor de pagamentos configurado
  • GET /receiving-account, quando o lojista ainda não tem subconta, passa a dizer se o motor de pagamentos do ambiente está configurado no servidor (provedor_configurado). O painel usa isso para avisar antes do formulário, em vez de falhar no fim
Aprovação da conta de recebimento (KYC)
A API passa a acompanhar a aprovação da subconta no motor de pagamentos e a barrar cobrança em conta não aprovada.
  • GET /receiving-account ganha o bloco aprovacao: as quatro frentes (dados comerciais, conta bancária, documentos, geral), a data da última conferência e o link_documentos para o lojista enviar documentos e selfie no motor de pagamentos
  • status da subconta passa a ser creating | pending | approved | rejected (era active, que só dizia que a subconta existia)
  • Nova rota POST /receiving-account/approval confere no motor de pagamentos na hora
  • POST /pix devolve 409 com codigo: SUBCONTA_NAO_APROVADA (ou SUBCONTA_INEXISTENTE) em vez de 500 com o erro cru do motor de pagamentos
  • O webhook da subconta passa a escutar os 18 eventos ACCOUNT_STATUS_*; subcontas antigas são completadas
Validação da subconta no sandbox
Subconta e split validados de ponta a ponta no sandbox (conta PJ com autoaprovação).
  • O onboarding passa a garantir uma chave Pix ativa na subconta (pix_key, pix_key_status em GET /receiving-account) — sem ela o motor de pagamentos não gera QR code
  • Endereço público de webhook documentado para quem opera o servidor: sem ele a conta de recebimento nascia sem aviso de pagamento
  • fee_amount passa a gravar o valor exato do split que o motor de pagamentos devolve (split[].totalValue), calculado sobre o líquido
  • Correção: a trava de conta única respeitava o ambiente da requisição, não o da cobrança — no webhook (sem requisição) ela disparava para pagamento de teste
Modo teste com porta de entrada
O modo teste ficou acessível. Até aqui ele existia no servidor sem jeito de ligar.
  • Novo: POST /auth/environment — troca a sessão de ambiente; GET /auth/me devolve environment; POST /auth/refresh preserva o ambiente
  • POST /api-keys aceita environment; a chave nasce v4pay_test_… ou v4pay_live_…
  • GET /api-keys mostra as chaves dos dois ambientes, com o ambiente de cada uma
  • Painel: interruptor Modo teste, faixa de aviso, seletor de ambiente ao criar chave, e o cadastro da empresa que abre a subconta
  • O interruptor Dev Mode passou a se chamar Dados de exemplo, para não se confundir com o modo teste
motor de pagamentos e travas
motor de pagamentos passa a ser o único provedor de Pix. Cada lojista tem uma subconta no CNPJ dele, e a cobrança nasce lá com um split da taxa do V4 Pay. O Banco Inter foi removido.
  • Novo: POST /receiving-account e GET /receiving-account — cadastro da empresa e abertura da subconta
  • Novo: recepção do aviso de pagamento do motor de pagamentos, com validação de origem e idempotência
  • Novo: POST /pix/simulate-payment — marca uma cobrança de teste como paga
  • Novo: POST /auth/google — acesso com a conta Google
  • Novo: GET /checkout/{txid} — dados do comprovante para a página pública
  • Separação entre ambiente de teste e de produção por requisição (coluna environment em 11 coleções)
  • Correção: avisos ao lojista disparados sem await não completavam em serverless — agora completam
  • Correção: reenvio de webhook retoma evento que não concluiu, em vez de descartá-lo
  • Removido: POST /webhook/pix e /admin/webhook (eram do Inter)
Faxina e segurança
  • Removidas as sobras do Supabase; a camada de dados é 100% Appwrite
  • .env protegido no .gitignore e credenciais expostas auditadas
  • Guia de produção e serviço de Pix reescritos
Perfil e auditoria
  • Novo: PUT /auth/profile — nome da loja e CNPJ
  • Novo: GET /audit — trilha de auditoria das ações na loja