PagnovoPagnovodocs

Invoices

Invoices (visão geral)

Invoice é a unidade atômica de cobrança da Pagnovo: toda cobrança que existe — manual, vinda de checkout público ou gerada por subscription recorrente — eventualmente é uma Invoice. Ela representa "essa pessoa deve esse valor até essa data".

Cada Invoice tem uma URL pública (hostedUrl) onde o cliente final acessa, escolhe método de pagamento (PIX) e paga sem precisar de login.

3 origens possíveis:

  POST /v2/invoices ────────────────────────┐
  (cobrança avulsa, você cria diretamente)  │
                                            │
  PaymentLink + checkout do cliente ────────┼──→ Invoice ──→ cliente paga ──→ status PAID
  (URL pública pay.pagnovo.com/<slug>)            │
                                            │
  Subscription + cron de billing ───────────┘
  (gerada automaticamente a cada ciclo)

Estados da Invoice

StatusO que significaEditável?Cancelável?
DRAFTRascunho. Cliente ainda não vê. (Pouco usado — criação padrão já vai pra PENDING.)
PENDINGAguardando pagamento. hostedUrl ativa.✓ (sem attempt APPROVED)
PAIDPago. paidAt preenchido, amountPaid > 0.— (use /mark-refunded)
OVERDUEVenceu sem ser pago. Multa/juros podem aplicar. hostedUrl ainda funciona até expiresAt.
REFUNDEDMarcada como estornada (parcial ou total).
CANCELEDCancelada manualmente. hostedUrl para de aceitar pagamento.
EXPIREDPassou de expiresAt. Cliente final não consegue mais pagar.

Importante: a Invoice pode ser editada só em DRAFT ou PENDING, e somente se não existir um InvoicePaymentAttempt com status APPROVED. Se existir attempt PENDING, dá pra editar campos não-monetários, mas não amount nem dueDate (cancele os attempts primeiro).

Items

Items são opcionais — você pode criar uma Invoice só com amount e description. Mas se você enviar items[], a soma de totalPrice precisa bater exatamente com amount (ambos em centavos), senão a API rejeita com 400.

JSON
{
  "description": "Plano Pro Mensal - Outubro/2026",
  "amount": 4900,
  "dueDate": "2026-10-15T00:00:00.000Z",
  "items": [
    { "description": "Assinatura mensal", "quantity": 1, "unitPrice": 4900, "totalPrice": 4900 }
  ]
}

E também: totalPrice == quantity * unitPrice em cada item (validado no servidor). Se você mandar item com quantity 2, unitPrice 1000 e totalPrice 1500, a API rejeita.

Items aparecem renderizados no hostedUrl pra o cliente entender o que está pagando.

Multas e juros

Igual a Subscription: lateFeeType define o tipo, lateFeeValue o valor (polimórfico — centavos se FIXED, basis points se PERCENTAGE), interestRatePerMonth os juros mensais proporcionais.

Quando a Invoice entra em OVERDUE, esses campos definem quanto será somado ao amount original em cada tentativa de cobrança. Cada InvoicePaymentAttempt armazena:

  • amount — valor cobrado nessa tentativa (em centavos)
  • lateFeeApplied — quanto de multa foi somado nessa tentativa
  • interestApplied — quanto de juros foi somado nessa tentativa

Veja todos os attempts no detalhe da Invoice (GET /v2/invoices/:id).

hostedUrl

Toda Invoice tem uma URL pública no formato:

https://<INVOICE_CHECKOUT_BASE_URL>/i/<invoiceId>

O cliente final acessa essa URL sem precisar de login, vê os items da fatura, escolhe método de pagamento e paga. A hostedUrl é gerada automaticamente no momento da criação e fica disponível em todos os retornos da API.

Quando o cliente paga via PIX no checkout, a Pagnovo cria um InvoicePaymentAttempt interno, gera o BR Code, espera o callback do Bacen e, ao confirmar, marca a Invoice como PAID. Você recebe um webhook cashin.paid (detalhes) com o transaction_id correspondente.

Notificações

Cada Invoice tem um notificationConfig opcional que controla quando o sistema envia notificações automáticas (criação, lembrete, vencido, pago, cancelado). O canal disponível hoje é EMAIL (SMS e WhatsApp estão no roadmap). Se omitido, o default é enviar e-mail nos momentos chave (criação + lembrete + pago).

Pra reenviar manualmente uma notificação (cliente diz "não recebi"), use POST /v2/invoices/:id/notifications/resend:

JSON
{
  "type": "INVOICE_REMINDER",
  "channel": "EMAIL"
}

Tipos válidos: INVOICE_CREATED, INVOICE_REMINDER, INVOICE_OVERDUE, INVOICE_PAID, INVOICE_CANCELED. Canal: EMAIL (SMS e WhatsApp no roadmap).

Reenvio só funciona em Invoices com Customer associado (Invoice "pública" sem customer não tem destinatário). E só se o Customer tiver o e-mail preenchido (canal EMAIL; SMS e WhatsApp no roadmap).

Estorno parcial

Pra marcar uma Invoice paga como estornada (total ou parcial), use POST /v2/invoices/:id/mark-refunded passando refundedAmount em centavos.

Regras:

  • Invoice precisa estar em PAID.
  • refundedAmount deve ser > 0 e <= amountPaid.
  • A invoice passa pra status REFUNDED, preenche refundedAmount e refundedAt.

⚠️ Esse endpoint apenas atualiza o status da Invoice no sistema. O estorno PIX em si é responsabilidade do módulo de Refund (transação separada). Use após confirmar com seu time que o estorno bancário foi de fato executado.

Ex: invoice paga de R$ 100,00 e você quer estornar R$ 30,00: refundedAmount: 3000. Status vira REFUNDED, mas amountPaid permanece 10000 (o que o cliente pagou originalmente).