Pular para o conteúdo

BillingLedger

Cobrança, pagamentos e conciliação em ledger

.NET 9 C# MassTransit SQS/SNS PostgreSQL EF Core AWS CDK Serilog
Interface do projeto BillingLedger
Arquitetura
Distribuída orientada a eventos
Contextos
billing · payments · ledger (schemas separados)
Testes
39/39 testes de domínio no Billing
Infra
AWS CDK · ECS Fargate · SNS/SQS + DLQ

Visão geral

Sistema backend para emissão de cobranças, recebimento de pagamentos e conciliação em ledger. Modela o ciclo de vida de uma fatura (Draft → Issued → Paid/Cancelled/Overdue), recebe notificações externas de pagamento via webhook, processa o evento de forma assíncrona e registra os efeitos financeiros em um livro-caixa imutável.

O problema é típico de ambientes financeiros: separar as responsabilidades de faturamento, pagamento e contabilização sem acoplar tudo em uma única transação síncrona.

Arquitetura

Não é microservices puro nem monólito simples. A classificação precisa é: sistema distribuído orientado a eventos, em monorepo com bounded contexts separados e banco físico compartilhado com schemas isolados por contexto.

  • Billing.Api API pública e contexto de faturamento. Aggregate root Invoice com regras encapsuladas.
  • Payments.Worker Processamento de pagamentos com idempotência por (Provider, ExternalPaymentId).
  • Ledger.Worker Materialização contábil: lançamentos de débito e crédito.
  • Contracts Contratos de integração versionados (V1) com EventId, CorrelationId e SchemaVersion.
  • SharedKernel / BuildingBlocks Primitivas, eventos de domínio e abstrações técnicas compartilhadas.

Fluxo de emissão, pagamento e conciliação

  1. Um usuário autenticado cria uma invoice pela API; o aggregate nasce em Draft e um AuditLog é gravado.
  2. Ao emitir, a invoice muda para Issued e gera InvoiceIssuedDomainEvent.
  3. Um interceptor do EF Core converte o evento de domínio em uma linha na tabela infra.outbox_messages — na mesma transação da mudança de estado.
  4. O OutboxDispatcherService publica o contrato InvoiceIssuedV1.
  5. O Ledger.Worker consome o evento e cria um lançamento de débito.
  6. O provedor externo chama POST /api/payments/webhook; a API valida a assinatura HMAC-SHA256 e publica PaymentReceivedV1.
  7. O Payments.Worker grava o PaymentAttempt com idempotência e publica PaymentConfirmedV1.
  8. O Billing marca a invoice como Paid e emite InvoicePaidV1; o Ledger cria o lançamento de crédito e o saldo da invoice tende a zero.

Pontos fortes

  • Domínio bem representado: aggregate Invoice, regras de transição, domain events e Money como value object.
  • Mensageria real na AWS com MassTransit sobre SQS/SNS.
  • Outbox implementado exatamente no ponto mais sensível: a mudança de estado da invoice.
  • Idempotência consciente, com índices únicos e guards nos consumers.
  • Infraestrutura como código em CDK: VPC, RDS, Redis, ECS, ECR, SNS/SQS, DLQ e CloudWatch.
  • Segurança baseline sólida: JWT + RBAC, webhook com HMAC, rate limiting, correlation id validado e segredos no Secrets Manager.

Limites e decisões conscientes

O que este projeto não faz, e por quê. Descrever isso com precisão vale mais do que exagerar o escopo.

  • A arquitetura é layered + DDD-inspired, não Clean Architecture rigorosa: a Billing.Api concentra tudo no mesmo assembly e os controllers chamam repositórios diretamente.
  • As pastas Application/Handlers existem, mas estão vazias — não há pipeline formal de casos de uso com MediatR.
  • A transição Overdue e o contrato InvoiceOverdueV1 existem no domínio, mas não há job agendado nem consumer correspondente implementado.
  • Parte da arquitetura está mais madura no desenho do que na execução completa.