iHubGamesiHubGamesdocs

Invoices

Invoices (visão geral)

Invoice é a unidade atômica de cobrança da iHubGames: 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.ihubplay.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, percentual cru se PERCENTAGE, ex: 5 = 5%), interestRatePerMonth os juros mensais proporcionais (percentual cru ao mês).

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 iHubGames 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).