**Produto gratuito (preço `0`).** Sem cobrança, sem taxa, sem lançamento no razão e sem provedor: cria uma compra `COMPLETED` com `pricePaid: "0.00"`, `paymentMethod: FREE` e concede o acesso na hora (curso: matrícula; evento: RSVP `going`; plano gratuito: assinatura). `paymentMethod` é **opcional** nesse caso (e ignorado) e o preço é sempre o do servidor. Exige login e perfil na comunidade. Uma por pessoa e produto (garantido pelo banco): repetir responde **200** com a compra que já existe; a primeira responde **201** com `data.free = true`. Não exige informações comerciais aprovadas nem as vendas ligadas. `INSTALLMENT` com preço 0 é 400 (`param = productId`); `addOnProductIds` com produto gratuito, 400. Mais de 10 tentativas em 10 minutos: 429. Esgotado (capacidade ou estoque): 409 com `data.soldOut = true`; quem tem uma oferta da fila de espera aberta usa a vaga reservada. O e-mail de confirmação sai pela fila. Produto de curso ou de evento é vendido na página do próprio curso/evento, sem exigir loja nem link direto. **Produto físico (`kind: physical`)** exige `shippingAddress` (400 com `param = shippingAddress.<campo>`); o CEP/código postal é validado pelo país. O endereço fica na compra e só o comprador (`GET /api/checkout/purchases`) e a equipe (detalhe da venda) o leem. **Renovação:** produto de curso ou evento com `accessDuration` pode ser comprado de novo enquanto a compra anterior tem data de fim; o novo fim é contado a partir do antigo e a vaga que a pessoa já tem não é tomada outra vez. **Compra paga de curso/evento** matricula/confirma o comprador quando o pagamento é confirmado, consumindo uma só vaga; reembolso com revogação ou chargeback cancela a matrícula/RSVP e devolve a vaga.
**Opção de plano com teste (`trialDays > 0`).** Na primeira vez de cada pessoa naquele plano, o teste começa sem cobrança: responde **201** com `data.trial = true`, `data.trialEnd`, `data.subscriptionId` e `amount: "0.00"`; a compra fica `COMPLETED` com `pricePaid: "0.00"` e o `paymentMethod` escolhido, e a assinatura nasce `TRIALING`. Em `trialEnd` a renovação normal cobra: o cartão salvo ou, em PIX/boleto, a cobrança de renovação enviada antes do vencimento, com a régua de inadimplência de sempre; paga, a assinatura vira `ACTIVE`. Cartão (`CREDIT_CARD`/`DEBIT_CARD`) exige `cardToken` (salvo no provedor, sem cobrança) ou um cartão padrão válido já salvo; `requiresCardForTrial` no produto exige cartão. Sem cartão: 400 (`param = paymentMethod` ou `cardToken`; também quando o cartão salvo é antigo e o provedor não informa mais os dados que o identificam: mande um `cardToken` novo). Teste não aceita cupom: 400 (`param = couponCode`). O teste vale uma vez por plano (qualquer opção de cobrança dele) para o mesmo e-mail (todas as contas com ele, sem contar a `+etiqueta` nem, no Gmail, os pontos) e para o mesmo cartão (em qualquer conta: o cartão é reconhecido pelos seis primeiros e quatro últimos dígitos e pela validade); cancelar ou excluir a assinatura não devolve o teste. Quem já teve, ou tem salvo nesta conta um cartão que já teve teste no plano, paga desde o primeiro dia, pelo checkout normal. Dois checkouts simultâneos começam um teste só: o outro, e o cartão enviado agora que já foi usado num teste do plano, respondem 409 (`param = productId`, `monetization.purchase.trialAlreadyUsed`).
`productId` de um adicional (produto com pré-requisito) responde 400 com `param = productId` para **qualquer pessoa**, inclusive quem tem o plano; o adicional só entra com `addOnProductIds` junto da opção do plano ou por `POST /api/subscriptions/{id}/add-ons`. Opção de cobrança (`SUBSCRIPTION`) que não está em nenhum plano não excluído: 400 com `param = productId`. As duas recusas vêm antes de gravar qualquer coisa ou falar com o provedor. A opção de um plano Somente com link Estoque: produto avulso com `hasStock` e sem unidade responde 409 com `param = productId` e a mensagem "Esgotado" traduzida, sem gravar nada e sem chamar o provedor. A unidade é reservada no pedido, de forma atômica, e volta quando o pedido falha, o PIX expira, o boleto vence ou um reembolso revoga o acesso. Um comprador não segura mais de uma unidade: se o perfil já tem PIX ou boleto em aberto desse produto avulso com estoque, o checkout responde 409 (`param = productId`, mensagem "já existe um pagamento em aberto") com `data.openPayment` (o mesmo formato do plano) e não reserva outra unidade. continua vendável. Assinaturas que já existem não são tocadas. As vendas da comunidade desligadas — o interruptor `payoutEnabled` falso (pausa do dono, ou o desligamento do sistema quando o recebedor saiu de `active`) — recusam 400 com `param = productId` (mensagem `monetization.salesDisabled`), antes de gravar qualquer coisa ou chamar o provedor. Em BRL, recebedor conhecido fora de `active` (pelo histórico que o webhook, a conciliação e a leitura do SuperAdmin mantêm) recusa do mesmo jeito; BRL sem recebedor aprovado segue recusando como sempre (`monetization.payment.recipientNotActive`). Primeira recusa depois de achar o produto: 403 quando a comunidade não tem informações comerciais `APPROVED`, para **todo** produto (antes só quando `requiresApproval` era verdadeiro), antes de gravar qualquer coisa — inclusive o `saveDocument` — ou chamar o provedor. Produto avulso (`ONE_TIME`/`INSTALLMENT`) só é comprável quando aparece num space Loja (`storefront`) que o comprador enxerga, ou quando tem `directLinkEnabled`. Fora disso responde 403 com `errors[0].param = productId`, antes de criar a compra ou chamar o provedor. Assinaturas não mudam. Também antes de gravar qualquer coisa: 403 (`param = X-CommunityId`) para quem não tem perfil na comunidade; 400 (`param = paymentMethod` ou `installments`) quando o método não está em `allowedPaymentMethods` ou as parcelas passam de `maxInstallments`; 400 (`param = customerData`) sem nome e e-mail do comprador (os da conta valem quando o corpo não traz). CPF/CNPJ: sem `customerData.document`, vale o salvo na conta (`document` de `GET /api/auth/me`). Campo opcional `saveDocument` (boolean): `true` grava `customerData.document` na conta, substituindo o salvo (ex.: comprar como empresa); CPF/CNPJ com dígito verificador errado responde 400 (`param = customerData.document`) sem criar nada. Sem `saveDocument`, o documento e o telefone do pedido são gravados no primeiro pagamento confirmado só se a conta não tiver (telefone existente nunca é trocado). Plano já assinado: 409 (`param = productId`, mensagem "Você já assina este plano") quando o comprador tem assinatura `ACTIVE`, `TRIALING` ou `PAST_DUE` de qualquer opção do mesmo plano (grupo), sua ou da empresa em que trabalha; `data: { subscriptionId, subscriptionStatus, productId, paidByCompany }` para o app levar a Cobrança (`paidByCompany: true`: o plano é pago pela empresa). PIX ou boleto do mesmo plano ainda em aberto: 409 (`param = productId`) sem criar outro pedido, com `data.openPayment: { purchaseId, orderId, productId, paymentMethod, status, amount, currency, qrCode, pixCode, expiresAt, boletoUrl, boletoBarcode, boletoDueAt }` para mostrar o mesmo QR (ou cancelar em `POST /checkout/{purchaseId}/cancel`). O pedido aberto é consultado no provedor de pagamento antes: pago vira o 409 de plano assinado; expirado ou falho deixa seguir. Outro plano da comunidade não é bloqueado. A resposta 201 traz os campos opcionais `thankYouMessage` e `redirectUrl` do produto. O valor cobrado (`amount`) é o preço, mais a taxa da plataforma quando `feePayer = BUYER`. Quando o provedor recusa o pagamento, a compra fica `FAILED` e a resposta é 402 com `errors[].param = payment` e uma mensagem traduzida: saldo ou limite insuficiente, cartão vencido, dados do cartão a conferir, ou a recusa genérica. O texto do provedor não é repassado. Provedor fora do ar responde 502. Parcelas (`installments`, 1–12, até `maxInstallments`) só valem no cartão de crédito: PIX e boleto são à vista, e a resposta e a compra trazem `installments = 1`. Assinatura `YEARLY` vende o ano em parcelas, com os juros de parcelamento da Memberfy pagos por quem o produto diz (`installmentInterestPayer`), e a renovação anual cobra o ano de novo no cartão salvo, nas mesmas parcelas da compra, com a tabela vigente. A resposta ganha os campos opcionais `installmentInterestPayer`, `installmentInterestPercent`, `installmentInterest`, `totalWithInterest` e `installmentAmount` (`amount` já é o total cobrado). O cartão com que uma assinatura é paga vira o cartão padrão do membro (`/payment-methods`), para a renovação. Adicional (produto com `prerequisiteGroupIds`) só é vendido a quem tem assinatura ACTIVE ou TRIALING num dos grupos exigidos, inclusive por link direto: sem isso, 403 com `errors[0].param = productId` (`monetization.purchase.prerequisiteRequired`), antes de criar a compra ou chamar o provedor. `addOnProductIds` (array de UUID, opcional) compra a opção de plano e os adicionais da lista do plano num pedido só, numa cobrança (um PIX, um boleto ou uma transação no cartão, nas parcelas da opção). É o que permite comprar o adicional sem ainda ter o plano. Cada adicional vira uma compra própria ligada à do plano e, pago o pedido, uma assinatura cobrada junto com a do plano (`billedWithId`), no ritmo da opção (o mensal, ou 12 × o mensal no anual). A taxa fixa da plataforma entra uma vez, na compra do plano. Recusas com 400 e `param = addOnProductIds`, antes de gravar: adicional fora da lista do plano, que não é `SUBSCRIPTION` `MONTHLY` à venda, repetido, já assinado pelo comprador, ou opção que não aceita adicional (intervalo sem fator, ou com teste). Com o campo, a resposta ganha `items` (`[{ purchaseId, productId, title, amount, feeBreakdown }]`, o plano primeiro), `amount` passa a ser o total do pedido e `feeBreakdown` a soma; `purchaseId` continua sendo o da compra do plano. `couponCode` (opcional) aplica o cupom antes da taxa: a taxa da plataforma, o valor cobrado (`pricePaid`), o ledger e um reembolso seguem o valor com desconto. Com cupom, a resposta ganha `coupon` (`code`, `type`, `discountValue`, `duration`, `durationInMonths`), `discountAmount` (do pedido) e `items` mesmo sem adicionais, cada item com `baseAmount` (antes do desconto) e `discountAmount`. O uso do cupom (`CouponUsage`, `currentUses`) só é registrado quando o pagamento é confirmado; até lá o pedido em aberto segura um dos `maxUses`. Na renovação, `ONCE` vale só na primeira cobrança, `REPEATING` enquanto o período começa dentro de `durationInMonths` desde o início da assinatura, `FOREVER` sempre. Recusas com `param = couponCode`, antes de cobrar: 404 cupom inexistente; 400 inativo, fora da validade, sem usos, limite por pessoa, só primeira compra, pedido abaixo do mínimo, produto fora da lista, cupom `FREE_TRIAL`, ou desconto que deixa um item entre 0 e 1,00. **Cupom que zera o total** de um produto avulso ou de uma opção de plano sem período de teste, sem adicionais (para a pessoa ou, com `companyId`, para a empresa): segue a compra gratuita acima (`free: true`, uso do cupom registrado na hora, uma por comprador e produto). Num plano, só cupom `FOREVER` zera (a compra gratuita não salva cartão) e as renovações não cobram; cupom de 100% `ONCE` ou `REPEATING` num plano: 400 `param = couponCode` (`monetization.coupon.fullDiscountNeedsForever`). Depois de 10 códigos recusados em 15 minutos, 429. **Compra para a empresa:** com `companyId`, o pedido, a assinatura, o cartão e o cliente no processador são da empresa (a compra traz `companyId` e nenhuma pessoa); o admin que comprou fica registrado. Os e-mails de cobrança vão para o e-mail de cobrança e para cada admin da empresa. A resposta 201 ecoa `companyId`. Regras e recusas em `companyId` do corpo. Google Analytics: `gaClientId` e `gaSessionId` (opcionais) são os ids do GA4 do navegador; com eles, o `purchase` que o servidor envia ao GA da comunidade (quando ela tem o segredo do Measurement Protocol) cai no mesmo usuário e sessão. O `transaction_id` é o `purchaseId` desta resposta. Esses ids valem só para o `purchase` desta compra: renovação e estorno vão com um id derivado da transação, nunca com o do navegador.