Ir para o conteúdo
Plataforma

Avalia Sales

Um sistema de vendas reconstruído,
com cobrança por gateway de pagamento e identificação das placas por QR Code e NFC.

O Avalia Sales parte de um sistema de vendas em Laravel/PHP. Esta implementação reescreve a base em .NET, React e PostgreSQL, em servidor próprio, com o gateway Pagar.me. É a infraestrutura de uma rede de revendas que vende produtos e serviços pelo celular, com estoque, comissões e financeiro numa única plataforma.

Pedro Fernandes, responsável pelo projeto, desenvolvimento e operação da plataforma, de ponta a ponta.

Em produção em sales.avaliaone.com.br, com o código aberto.

Início do revendedor no computador: placas em estoque, placas no ar, vendas e recebido no mês, gráfico dos últimos 30 dias e vendas de hoje Nova venda no celular do vendedor: catálogo com placas e serviços, preço e pedido mínimo
Telas da plataforma com dados de demonstração.

Arquitetura: API e Worker em .NET, telas em React, dados no PostgreSQL

A API responde sem esperar as integrações - o Worker processa filas, eventos de pagamento e e-mails com retentativa.

Como as partes da plataforma se conectam, como está no código

Navegador

Telas App do vendedor e painéis de gestão.
  • React
  • TypeScript
  • TanStack Query
  • Tailwind CSS

Aplicação

API Regras de negócio, autenticação e avisos.
  • ASP.NET Core
  • EF Core
  • SignalR
Worker Filas, pagamentos e e-mails com retentativa.
  • .NET Worker Service

Dados e serviços

PostgreSQL Dados isolados por revenda, razão e filas.
  • PostgreSQL
Integrações Pagamento, e-mail, mapas, assinatura e ERP.
  • Pagar.me
  • Resend
  • Google
  • D4Sign
  • Protheus

Atualização em tempo realPostgreSQL (LISTEN/NOTIFY)APISignalRtela do vendedor

Servidor próprio
  • Linux
  • Docker Compose
  • Nginx
  • Let's Encrypt
  • GitHub Actions
  • Sentry

Antes e depois da atualização

Comparação entre o sistema anterior e a implementação atual
CamadaAntesDepois
AplicaçãoLaravel 12, PHP 8.3ASP.NET Core e Worker Service em C#, em camadas
TelasBlade, Alpine.js e TailwindReact e TypeScript, com tipos gerados do contrato OpenAPI
Banco de dadosMySQLPostgreSQL com row-level security por revenda
HospedagemHospedagem compartilhadaServidor próprio com Docker Compose, Nginx e TLS
PublicaçãoScript no servidor, disparado por cron após o pushGitHub Actions com testes, análise do SonarCloud, aprovação manual e health check

Decisões de engenharia e evidências

Cada uma tem registro (ADR) ou documento no repositório.

  1. Revendas isoladas em duas camadas: aplicação e banco de dados

    Filtro do EF Core e row-level security no PostgreSQL: se a aplicação falhar, o banco recusa.

    Ver contexto e evidência
    Contexto
    Várias revendas usam o mesmo banco, cada uma com clientes, vendas e dinheiro próprios.
    Decisão
    Filtro global do EF Core pela revenda do login e row-level security forçada no PostgreSQL. A aplicação conecta com um papel sem permissão para ignorar a regra.
    Motivo
    Uma consulta que esqueça o filtro não alcança dados de outra revenda: o banco recusa.
    Evidência

    ADR 0001Testes de isolamento

    Um teste reprova a publicação se uma tabela com revenda nascer sem a regra.

    Vale em todas as etapas do fluxo de venda

  2. Pagamento confirmado na origem, no gateway, e aplicado uma vez

    A venda só fica paga depois de o Worker reconsultar a cobrança no Pagar.me, uma vez por evento.

    Ver contexto e evidência
    Contexto
    O webhook do Pagar.me pode chegar repetido, fora de ordem ou forjado, e a rede pode cair no meio da cobrança.
    Decisão
    A cobrança é gravada antes de chamar o Pagar.me, numa transação que recusa uma segunda cobrança viva da mesma venda. O webhook só registra o evento. O Worker reconsulta a cobrança no gateway e trava a linha do evento (FOR UPDATE SKIP LOCKED) antes da baixa. Uma vez por dia reconsulta também as cobranças pagas, para pegar estorno e chargeback feitos no painel do Pagar.me.
    Motivo
    A venda só muda de situação pelo estado confirmado no Pagar.me, e o mesmo aviso não é aplicado duas vezes.

    No fluxo: etapas 3 e 4

  3. Aritmética monetária em inteiros e razão append-only

    Valores em centavos como inteiros, sem ponto flutuante, e razão só de inserção: correção entra como estorno.

    Ver contexto e evidência
    Contexto
    Split, comissão, estorno parcial e fechamento de mês precisam fechar ao centavo.
    Decisão
    Valores inteiros em centavos. O razão só recebe inserção: correção entra como estorno. O mês fechado é travado por trigger, e o banco nega UPDATE e DELETE no razão ao papel da aplicação.
    Motivo
    Qualquer saldo pode ser refeito a partir das linhas, e nenhuma correção apaga o que já foi lançado.

    No fluxo: etapa 5

  4. Gravação repetida responde igual, sem efeito duplo

    Toda gravação POST autenticada aceita Idempotency-Key: a mesma chave devolve a resposta gravada, sem cobrar nem lançar de novo.

    Ver contexto e evidência
    Contexto
    Duplo clique, rede lenta e nova tentativa do navegador reenviam a mesma gravação.
    Decisão
    Um middleware guarda a resposta por 24 horas, por usuário e rota. A mesma chave com outro corpo responde 422, e a execução simultânea responde 409. O front manda uma chave por chamada.
    Motivo
    Repetir o pedido não cobra, não comissiona e não envia e-mail duas vezes.

    No fluxo: etapa 3

  5. Migração incremental a partir do sistema em Laravel/PHP

    Rotas trocadas uma a uma, dados importados numa transação e virada concluída em 24/10/2026, sem mudar o endereço das placas vendidas.

    Ver contexto e evidência
    Contexto
    O sistema anterior estava em uso, com placas impressas cujo endereço do QR Code não pode mudar.
    Decisão
    As rotas do sistema anterior passaram a redirecionar para a nova plataforma uma a uma (padrão strangler fig). Os dados foram convertidos a partir do backup e importados numa transação única, depois de um ensaio. As placas estão hoje na plataforma, e o endereço antigo (/q/) repassa para o novo com 302, sem cache. O histórico financeiro das vendas também é importável, para a DRE dos meses de origem.
    Motivo
    A operação continuou durante a troca, e uma importação com erro é desfeita por inteiro.

    No fluxo: etapa 6, o endereço da placa

A aplicação em funcionamento: uma venda em seis etapas

No celular do vendedor, com leitura do QR Code da placa e cobrança pelo Pagar.me. As etapas 3 a 6 aplicam as decisões de engenharia acima.

Nova venda

  • Placa de avaliaçãoQR Code e NFCR$ 120,00
  • Perfil no GoogleServiço da operadoraR$ 180,00

TotalR$ 300,00

Cobrar R$ 300,00

Passo 1 de 6

Simulação com valores de exemplo.

  1. Uma cobrança para a venda inteira. Cancelar um item devolve só a parte dele.

  2. BarcodeDetector nativo, com fallback em WebAssembly.

  3. A cobrança é gravada antes de chamar o Pagar.me, e o cartão é tokenizado no navegador.

  4. O webhook do Pagar.me é gravado e conferido na origem. A tela recebe por SignalR.

  5. Valores em centavos no razão, split do Pagar.me por recebedor e comissão paga em repasse. Caixa e DRE saem do razão, com a receita da placa reconhecida em 12 partes.

  6. Redirecionamento configurável sem reimprimir a placa - gravação NFC pelo Web NFC.

Quality Assurance

Verificações do repositório e da produção a cada publicação. A contagem de testes indica os casos executados, não a cobertura.

Integrações e o estado de cada uma

Integrações, uso e estado
ServiçoUsoEstado
Pagar.mePix, cartão, marketplace com split por recebedor, estorno total ou parcial e webhook com reconsultaEm produção
ResendE-mails transacionais com fila e retentativaEm produção
Google Places API e Maps APILocalização do estabelecimento e mapa de vendasEm produção
BrasilAPIRazão social e endereço por CNPJ e CEPEm produção
GotenbergContrato e distrato em PDF, num contêiner sem redeEm produção
SentryErros com a versão publicada, monitor do site e do WorkerEm produção
Cloudflare R2Cópia diária do banco, cifrada, fora do servidorEm produção
Cloudflare TurnstileCaptcha no cadastro públicoEm produção
SonarCloudAnálise de código e cobertura dos testes no CINo CI
D4SignAssinatura eletrônica do contrato de revendaSandbox
ProtheusPedido de placas pago gerado como pedido de venda no ERPStandby/Endpoint

Aprofundamento

Documentação versionada com o código, para análise aprofundada.

Camadas do código e responsabilidades
Camadas do código e a responsabilidade de cada uma
ProjetoResponsabilidade
DomainEntidades e regras de negócio, sem dependência de pacote
ApplicationCasos de uso e as interfaces das integrações
InfrastructureEF Core, migrations, clientes HTTP e simuladores
Api e WorkerASP.NET Core e o processamento em segundo plano

Retentativa automática de HTTP só em métodos seguros, para uma cobrança não ser reenviada por engano.

Arquitetura (SDD)

White label com contraste calculado

Cada revenda aplica logo e cor nas telas. A plataforma escolhe a cor do texto do botão e escurece o destaque até a leitura passar no critério do WCAG.

Experimente a cor de uma revenda
Cor da revenda

Botão com texto branco.

Revenda Aurora

VendasClientesPlacas

Padaria SolPaga

Ótica LumeVer ficha

Nova venda

Acesso

Plataforma em operação. Solicite uma demonstração ou implante a partir do código no GitHub.

Solicitar demonstração Acessar repositório