Pular para o conteúdo principal

Integração Asaas

Provider de pagamento da plataforma. O cliente é redirecionado para a página do Asaas (invoiceUrl) para pagar via Pix, cartão ou boleto.

Arquitetura

Pré-requisito: CPF/CNPJ

O Asaas exige documento para criar o customer. Se o usuário não tem documentType + documentNumber preenchidos, o POST /orders retorna erro.

  • Coletar no cadastro ou antes do primeiro checkout
  • Validações: CPF (11 dígitos + check digits), CNPJ (14 dígitos), ou passport
  • Armazenado em User.document_type e User.document_number

Ver Atualizar Perfil.

Ambientes

AmbienteAPI Base URLCheckout URL
Sandbox (dev)https://api-sandbox.asaas.com/v3https://sandbox.asaas.com/i/...
Produçãohttps://api.asaas.com/v3https://www.asaas.com/i/...

Variáveis de ambiente

Local (.env)
# Asaas (sandbox)
ASAAS_API_KEY=$aact_hmlg_000...
ASAAS_BASE_URL=https://api-sandbox.asaas.com/v3

# URLs
FRONTEND_URL=https://dev.labanana.art
API_URL=http://localhost:8000

# Cloudflare Tunnel (webhook dev)
CLOUDFLARE_TUNNEL_TOKEN=eyJh...
TUNNEL_URL=https://localapi.labanana.art

# Webhook (opcional — valida token se configurado)
WEBHOOK_SECRET=meu-segredo
Produção (Railway Variables)
ASAAS_API_KEY=$aact_prod_000...
ASAAS_BASE_URL=https://api.asaas.com/v3
FRONTEND_URL=https://labanana.art
API_URL=https://api.labanana.art

:::danger Guardrail de produção O app recusa iniciar em ENVIRONMENT=prod se ASAAS_API_KEY está vazio. :::

Webhook

Endpoint: POST /webhooks/asaas

Autenticação opcional: se WEBHOOK_SECRET estiver configurado, o endpoint valida o token via header asaas-access-token ou query param ?token=. Requests sem token válido recebem 401.

Payload típico
{
"event": "PAYMENT_RECEIVED",
"payment": {
"id": "pay_080225913252",
"customer": "cus_G7Dvo4iphUNk",
"value": 71.9,
"netValue": 68.5,
"billingType": "PIX",
"status": "RECEIVED",
"externalReference": "uuid-da-order",
"invoiceUrl": "https://www.asaas.com/i/080225913252",
"paymentDate": "2026-04-02"
}
}

Mapeamento de status

AsaasLabanana paymentStatus
PENDINGpending
RECEIVEDapproved
CONFIRMEDapproved
OVERDUEpending
REFUNDEDrefunded
REFUND_REQUESTEDpending
CHARGEBACK_REQUESTEDpending
CHARGEBACK_DISPUTEpending
DUNNING_RECEIVEDapproved
AWAITING_RISK_ANALYSISpending

Eventos configurados no painel Asaas

  • PAYMENT_RECEIVED — pagamento confirmado (Pix, cartão)
  • PAYMENT_CONFIRMED — pagamento confirmado e creditado
  • PAYMENT_OVERDUE — boleto vencido
  • PAYMENT_REFUNDED — reembolso processado
  • PAYMENT_PARTIALLY_REFUNDED — estorno parcial

Proteção contra pagamento em order cancelada

Se o webhook recebe um evento para uma order já cancelled ou refunded, o backend:

  1. NÃO atualiza o status da order
  2. Se o pagamento foi confirmado (RECEIVED/CONFIRMED) — estorna automaticamente no Asaas → dinheiro devolvido ao cliente
  3. Se o pagamento está pendente (PENDING) — cancela/deleta a cobrança no Asaas

:::tip Por que isso importa Evita que o cliente pague um link antigo de uma order já cancelada e fique sem o dinheiro nem o produto. :::

Transições de paymentStatus

paymentStatus — transições
pendingOrder criada, aguardando pagamento
Asaas aprovaapprovedexterno
Asaas rejeitarejectedexterno
POST /cancelcancelledadmin
approvedPagamento confirmado pelo Asaas
POST /refund (total) ou /cancelrefundedadmin
POST /refund parcial (mantém approved)admin
rejectedAsaas rejeitou o pagamento
POST /pay (retry)pendingadmin
POST /cancelcancelledadmin
refundedterminalSoma dos refunds atingiu o total
cancelledterminalOrder cancelada

De-para de campos: API Asaas ↔ Labanana

1. Criar Customer — POST /v3/customers

Chamado automaticamente no POST /orders. Cria ou reutiliza customer por CPF.

Campo AsaasObrigatórioOrigem no Labanana
nameSimOrder.full_name
cpfCnpjSimUser.document_number
emailNãoUser.email
mobilePhoneNãoOrder.phone (sem +55)
externalReferenceNãostr(User.id)

2. Criar Pagamento — POST /v3/payments

Campo AsaasObrigatórioOrigem no Labanana
customerSimCustomer ID (criado acima)
billingTypeSim"UNDEFINED" (cliente escolhe no checkout)
valueSimOrder.total_cents / 100
descriptionNãoItems concatenados: "2x Caneca Arte Tropical"
externalReferenceNãostr(Order.id) — reconciliação no webhook
callback.successUrlNão{FRONTEND_URL}/order/success?external_reference={orderId}

Response:

CampoArmazenado em
id (ex: pay_xxx)OrderPayment.provider_preference_id
invoiceUrlRetornado como initPoint no OrderResponse
status / value / netValueLog

3. Reembolso — POST /v3/payments/{id}/refund

CampoObrigatórioDescrição
valueNãoValor parcial em BRL. Se omitido = total

Response: Payment com status: "REFUNDED" ou "REFUND_REQUESTED".

4. Cancelamento — DELETE /v3/payments/{id}

Cancela um pagamento pendente. Equivale a deletar a cobrança no Asaas.

Reembolso e cancelamento

Os endpoints POST /orders/{id}/refund e POST /orders/{id}/cancel encapsulam a lógica de Asaas:

  • Refund totalpaymentStatus: "refunded"
  • Refund parcialpaymentStatus continua "approved" até que a soma atinja o total
  • Cancel em order approved → emite refund total automaticamente
  • Cancel em order pending com payment no Asaas → deleta a cobrança
  • Cancel em order pending sem Asaas → cancela só no banco

Troubleshooting

ProblemaCausaSolução
Webhook não chegaTunnel não está rodandodocker compose logs tunnel
Document is requiredUser sem CPF/CNPJPATCH /users/me com documentType + documentNumber
Failed to create paymentAPI key inválidaVerificar ASAAS_API_KEY no .env
CORS error no frontendBackend retornando 500docker compose logs web — o erro real está lá
dev-pay retorna 403ENVIRONMENT não é devVerificar .env
App não inicia em prodAPI key vaziaGuardrail: prod exige ASAAS_API_KEY
Warnings de $ no docker-composeAPI key do Asaas contém $Inofensivo — pydantic lê o .env diretamente

:::info Sandbox é estável Pix e cartão funcionam normalmente em ambiente de teste. Ver Teste E2E com Cloudflare Tunnel (em breve na seção de Desenvolvimento). :::