# O status da assinatura mora no Stripe, não na sua coluna booleana

> 

Apps SaaS no começo adoram um flag local `IsActive`. Parece rápido. Também é o jeito clássico de passar uma sexta reconciliando um cliente que pagou, uma linha que ainda diz plano grátis e um webhook que você já processou duas vezes.

## Status é uma máquina de estados, não um checkbox

Assinaturas no Stripe passam por estados de verdade: `incomplete`, `incomplete_expired`, `trialing`, `active`, `past_due`, `canceled`, `unpaid` e às vezes `paused`. Cada um significa algo diferente para o acesso.

`incomplete` não é "quase ativo". Significa que a primeira fatura não passou. `past_due` não é cancelado. Retentativas ainda podem funcionar. `paused` depois de um trial sem método de pagamento não é a mesma coisa que pausar a cobrança enquanto as faturas continuam sendo geradas.

Se o app guarda um booleano e atualiza "quando parece certo", você inventa uma segunda fonte da verdade. Essa segunda fonte diverge.

## Espelhe o provedor, não invente a sua

O padrão pragmático é chato e confiável:

1. Persista o id da assinatura no provedor e a string de status bruta.
2. Derive o acesso ao produto desse status (e das suas regras de entitlement), não de um flag mantido à mão.
3. Trate o webhook como o caminho de escrita que atualiza o status. Trate o admin como leitor, não como um livro-razão rival.

Em .NET isso costuma significar mapear o enum de status do Stripe para o seu domínio uma vez, e recusar enums paralelos que quase batem. O "quase" é onde mora o bug.

## Webhook sem idempotência cobra duas vezes

Retentativas acontecem. Rede falha. Seu handler vai ver o mesmo `event.id` mais de uma vez. Sem uma constraint única nesse id (ou uma tabela de claim equivalente), você aplica efeito colateral duas vezes: libera acesso duas vezes, manda email duas vezes ou, pior, muda billing duas vezes.

Verifique assinaturas contra o body cru. Use chaves de idempotência em chamadas API que não sejam GET. Mantenha o handler magro: atualize a projeção local, enfileire trabalho se precisar, responda 200.

## Stripe doméstico, Paddle global, um modelo de acesso

Se a cobrança caiu via Stripe em casa ou via Merchant of Record no exterior, o produto ainda precisa de uma resposta só para "essa conta pode usar o recurso hoje?" Deixe detalhes do provedor na borda. Deixe a lógica de entitlement num lugar só, lendo um status normalizado.

Uma fundação code-first que já liga webhooks de billing, projeção de status e uma única checagem de entitlement economiza a tarde que você gastaria depurando por que produção diz pago e o banco diz grátis. Essa tarde sempre custa mais do que a template parecia na página de preço.
