Stripe Connect e Cobrança de Assinaturas
Dois fluxos de dinheiro em uma única conta Stripe. Um marketplace cobra o comprador uma vez e distribui o valor para vários vendedores (Connect, cobranças e transferências separadas). Uma assinatura de plataforma cobra desses vendedores uma taxa recorrente fora de sessão (Billing). Eles compartilham um cliente, um par de webhooks e um tipo de dinheiro.
A percepção que molda tudo: a cobrança do comprador e os pagamentos dos vendedores falham de forma independente. Quando a liquidação é executada, o dinheiro já foi movido, portanto uma transferência rejeitada nunca deve falhar a cobrança ou o webhook, e deve permanecer reexecutável posteriormente sem pagar ninguém duas vezes.
Quando usar
- Um pagamento é dividido entre vários vendedores, com a plataforma ficando com uma parte.
- Os vendedores integram-se ao Connect e recebem pagamentos com atraso (garantia, reservas, KYC).
- A plataforma cobra de seus próprios vendedores uma taxa recorrente em um cartão salvo.
- Uma conta conectada parece saudável mas nunca recebeu nenhum dinheiro.
Quando NÃO usar
| Não isso | Use em vez disso |
|---|---|
| Checkout de vendedor único, sem divisão | Stripe Checkout / Payment Element diretamente |
| Integração e pagamentos do PayPal Marketplace | Uma habilidade do PayPal; apenas as interfaces são compartilhadas |
| UI de cartão, estilização do Payment Element | Seu sistema de design; esta habilidade é server-side |
Arquitetura
comprador ──cobrança (conta da plataforma, transfer_group = orderId)──▶ saldo da plataforma
│ fan-out de liquidação (webhook OU leitura síncrona; reivindicação arrendada)
┌────────────┴──┬────────────────────────┬────────────────┐
▼ ▼ ▼ ▼
transferência:vendedor transferência:parceiro garantia reserva contínua
(source_transaction = a cobrança) (release_at) (% retido)
│ │ cron de liberação de garantia
▼ ▼
saldo conectado ◀── retentativa de transferências saldo a pagar ──▶ pagamento (KYC/risco)
(pernas não financiadas)
plataforma ──PaymentIntent off_session (cliente + PM salvo) ──▶ fatura de assinatura
dunning: 3 tentativas, 3 dias de intervalo, depois suspender
Fatos críticos
- Cobranças e transferências separadas, nunca cobranças de destino. Uma cobrança usa um
destination; um carrinho de vários vendedores precisa de N transferências sob umtransfer_group, entãoapplication_fee_amounté inutilizável. - Uma transferência sem
source_transactionusa o saldo disponível e é rejeitada enquanto a cobrança ainda está sendo liquidada; em uma conta de plataforma jovem, isso é toda transferência. Esta única flag é o que permite que uma divisão seja financiada enquanto a cobrança ainda está sendo liquidada. - Transferências Stripe transfronteiriças funcionam apenas dentro de US/CA/UK/EEA/CH. Uma plataforma em outro lugar paga contas conectadas apenas em seu próprio país. O Stripe aplica isso no momento da transferência, não na integração, então os templates aplicam a regra de região na integração.
- Eventos do Connect carregam um segredo de assinatura diferente dos eventos da plataforma. Dois endpoints, dois segredos, uma URL — verifique contra ambos.
- O país de uma conta conectada é imutável — país errado significa reintegração, não um ajuste.
- Dinheiro nunca é um ponto flutuante — strings
numeric(19,4)de ponta a ponta, aritméticabigint, unidades menores apenas na borda do SDK.
Regras rígidas
Nunca deixe uma transferência com falha falhar a liquidação. Registre a perna como não financiada e continue. A cobrança já foi bem-sucedida, então o webhook também deve ser bem-sucedido; o cron de retentativa financia a perna depois.
Nunca trate uma inserção de webhook duplicada como "já processada". A inserção é uma reivindicação; a retentativa após uma entrega que falhou no meio do processamento executa novamente os manipuladores, então cada evento é executado até a conclusão exatamente uma vez.
Nunca reenvie uma transferência sem perguntar ao Stripe se ela já existe. Uma perna registrada como não financiada porque a resposta foi perdida paga duas vezes assim que a janela de idempotência de 24h do Stripe passa.
Nunca derive exclusividade de liquidação de uma flag de status escrita no final. Dois chamadores alcançam a liquidação rotineiramente; uma reivindicação arrendada atômica é o que faz com que o inventário e as transferências aconteçam uma vez.
Nunca reverta mais do que a margem restante de uma transferência. Reversões empilhadas (taxa de gateway, depois um reembolso) são rejeitadas além do valor original.
Início rápido
- Modele o dinheiro — money.md, data-model.md, store.md.
- Cliente, segredos duplos de webhook, env — stripe-adapter.md.
- Integre contas; verifique a regra de região primeiro — connect-accounts.md.
- Receba eventos de forma idempotente — webhooks.md.
- Liquide: reivindique, faça fan-out, garantia, reserva — settlement.md.
- Converja: retente pernas, reconcilie taxas — reconciliation.md.
- Cobre dos inquilinos — subscriptions.md; execute com operations.md.
Adapte ao seu aplicativo com adaptation.md; o registro da auditoria está em provenance.md.
Contrato de Adaptação
| Ponto de Adaptação | A habilidade fornece | O host fornece |
|---|---|---|
| Entidades de domínio | Tenant (org de vendedor), Order, VendorOrder, Partner | Seu próprio vocabulário |
| Escopo do inquilino | Um tenantId derivado do servidor | org / workspace / vendedor |
| Guarda de autenticação | Uma assinatura de adaptador por rota | Clerk, NextAuth, Supabase, customizado |
| Acesso a dados | Um contrato PaymentsStore + SQL | Seu ORM ou SDK |
| Auditoria + notificações | Locais de chamada, nomes de eventos, formas de payload | Sua tabela de auditoria / outbox |
| Trabalho em segundo plano | Quatro crons e sua cadência | Seu agendador |
| Validação | Uma forma de esquema por corpo de rota | zod / valibot / yup |
| UI | Nada — esta habilidade é server-side | Tudo |
Diretório de Referências
| Cenário | Palavras-chave | Referência |
|---|---|---|
| Por que este modelo de dinheiro | cobranças e transferências separadas, cobrança de destino, application_fee_amount, transfer_group, razão | architecture.md |
| Tabelas, colunas, RLS | esquema, migração, payment_intents, transfers, escrow_holds, webhook_events | data-model.md |
| O contrato de acesso a dados | store, repositório, reivindicação atômica, atualização condicional, ORM | store.md |
| Valores e arredondamento | numeric, bigint, unidades menores, distribuir, maior resto, centavos | money.md |
| Código do cliente e adaptador | stripeClient, apiVersion, constructEvent, whsec_, PaymentIntent, refund, payout | stripe-adapter.md |
| Integração, estado da conta | Express, Accounts v2, account.updated, charges_enabled, acct_, país, região | connect-accounts.md |
| Recebendo eventos com segurança | idempotência, duplicata, repetição, corpo bruto, assinatura, invalid_signature | webhooks.md |
| Dividindo um pedido pago | liquidação, fan-out, source_transaction, garantia, reserva, voo único | settlement.md |
| Corrigindo o que não caiu | retentativa, não financiado, backoff, adotar, taxa de gateway, clawback, reversão, margem | reconciliation.md |
| Taxas recorrentes da plataforma | SetupIntent, off_session, cartão salvo, dunning, fatura, past_due, suspender | subscriptions.md |
| Configuração, env, crons, depuração | STRIPE_SECRET_KEY, endpoint de webhook, stripe listen, cron, "vendedor nunca pagou" | operations.md |
| Adaptando a este aplicativo | adaptar, sonda do host, renomear, ORM, guarda de autenticação, portar | adaptation.md |
| O registro de auditoria | proveniência, desvio, mantido, adicionado, não verificado | provenance.md |