Aller au contenu

Cria ou executa checkout

POST/api/checkout

Auth
Jeton requis
Clé d’API:
Clé d’API non acceptée
X-CommunityId
X-CommunityId Envoyez le X-CommunityId de la communauté où l’opération a lieu.
Les descriptions des endpoints viennent directement de la spécification de l’API et sont en portugais pour l’instant. L’interface autour est traduite.

**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.

En-têtes

NomTypeDescription
X-CommunityIdfacultatifstring (uuid)ID da comunidade em que a operação acontece. Obrigatório na maioria dos endpoints com escopo de comunidade.

Corps de la requête application/json

NomTypeDescription
productIdobligatoirestring (uuid)
paymentMethodfacultatifCREDIT_CARD | DEBIT_CARD | PIX | BOLETO | BANK_TRANSFERObrigatório quando o produto tem preço; omitido (ou ignorado) num produto gratuito.
ageDeclarationfacultatifbooleanDeclaração de idade: "Declaro ter 18 anos ou mais, ou estar autorizado pelo meu responsável legal". Obrigatório num pedido que custa dinheiro: `true` registra a declaração na compra; sem o campo ou com `false`, a resposta é 400 `param = ageDeclaration` (`data.purchaseAge = 18`) sem criar nada. Produto gratuito (ou cupom de 100%) não pede.
shippingAddressfacultatifobjectObrigatório para produto `physical`. `postalCode` é validado pelo `country` (no Brasil, CEP `00000-000`).
shippingAddress.namefacultatifstring
shippingAddress.streetfacultatifstring
shippingAddress.numberfacultatifstring
shippingAddress.complementfacultatifstring
shippingAddress.districtfacultatifstring
shippingAddress.cityfacultatifstring
shippingAddress.statefacultatifstring
shippingAddress.postalCodefacultatifstring
shippingAddress.countryfacultatifstringmin 2, max 2
shippingAddress.phonefacultatifstring
installmentsfacultatifintegerSó no cartão de crédito. · ≥ 1, ≤ 12
couponCodefacultatifstringmin 3, max 50
cardTokenfacultatifstringToken do cartão gerado no navegador pela tokenização do checkout. Obrigatório no cartão sem cartão salvo. No início de um teste (`trialDays`) é salvo sem cobrança.
customerDatafacultatifobject
customerData.namefacultatifstringmin 3, max 255
customerData.emailfacultatifstring (email)
customerData.documentfacultatifstringCPF ou CNPJ, só dígitos. · min 11, max 14
saveDocumentfacultatifboolean
addOnProductIdsfacultatifstring (uuid)[]
companyIdfacultatifstring (uuid)Opcional. Compra para a empresa: quem paga e assina é a empresa, não a pessoa. Só o admin da empresa com vínculo vivo (403, `param = companyId`); empresa encerrada, 409; empresa de outra comunidade ou inexistente, 404; produto (e adicionais) fora das ofertas da empresa, 400. Exige CNPJ válido em `customerData.document` (ou o salvo na empresa): sem ele, 400 com `param = customerData.document`. O processador cobra a empresa com `customerData` ou, no que faltar, `billing.legalName`, `billing.email` e `billing.taxId` dela, e esses dados ficam gravados na empresa para a próxima compra. Produto gratuito (preço 0): 400 (`monetization.company.freeNotSupported`; a equipe concede). Cupom que zera o total (plano, só com cupom `FOREVER`, ou produto avulso das ofertas): segue a compra gratuita, sem CNPJ e sem processador, e a compra e a assinatura são da empresa. Sem período de teste (a empresa paga desde o primeiro dia). O cartão pago vira o cartão da empresa.
gaClientIdfacultatifstringOpcional. O client_id do Google Analytics (cookie `_ga`) do navegador do comprador, gravado no pedido para o `purchase` que o servidor envia ao GA da comunidade quando o pagamento confirma. Fora do formato (letras, dígitos, `.`, `_`, `-`, até 100) é ignorado, nunca recusa a compra. · max 100
gaSessionIdfacultatifstringOpcional, só com `gaClientId`. O session_id do GA4 (só dígitos); fora do formato é ignorado.
quantityfacultatifintegerOpcional, só para um tipo de ingresso de evento (`productId` = o tipo): quantos ingressos no pedido, dentro do mínimo e do máximo do tipo (fora: 422 `tickets.quantityOutOfRange`; padrão: o mínimo do tipo). O preço é o do lote vigente vezes a quantidade, gravado no pedido (`quantity`, `ticketBatchName`, `unitPrice`) e mantido enquanto o pagamento está pendente. Um pedido, uma cobrança: a parte fixa da taxa da plataforma é cobrada uma vez por pedido e o percentual sobre o total. Lote que virou durante a compra: 409 `tickets.batchChanged`; vendas encerradas: 409 `tickets.salesEnded`; tipo ou evento sem vaga para a quantidade: 409 (esgotado). Ingresso gratuito: um por pessoa (422 `tickets.freeQuantityOne`). Em produto que não é ingresso, só 1 (422 `tickets.quantityOnlyTickets`). O comprador leva um ingresso e atribui os outros em `POST /api/events/{id}/my-tickets/{ticketId}/assign`. · ≥ 1, ≤ 50
registrationAnswersfacultatifobject[]Opcional, num ingresso de evento com perguntas da inscrição: as respostas de quem compra (as perguntas do tipo escolhido), validadas antes do pedido (422, um erro por campo, `param = registrationAnswers.<fieldId>`, e as obrigatórias que faltam em `data.missing`) e gravadas com o pedido. As perguntas do tipo vêm antes da compra em `GET /api/events/{id}/my-registration?ticketTypeId=`. Sem elas a compra segue e a pessoa completa em "Minha inscrição".
registrationAnswers[].fieldIdobligatoirestring (uuid)
registrationAnswers[].valuefacultatifobjectTexto, número, lista de escolhas, `true` no consentimento, ou, no `DOCUMENT`, `{ kind: CPF | PASSPORT | OTHER, number }` (CPF com dígito verificador; uma string sozinha é lida como CPF).
analyticsSessionIdfacultatifstringOpcional. O `sessionToken` do analytics da Memberfy (o que `POST /api/analytics/events` devolve), para o funil ligar o pedido à visita. Só vale token emitido para esta comunidade e ainda válido; qualquer outro valor é ignorado, nunca recusa a compra. Sem ele, um pedido feito pelo app usa a sessão mais recente do comprador nas 24 h anteriores. · max 64

Réponses

StatutDescription
201Operação realizada com sucesso.
400Requisição inválida — falha de validação.
401Token ausente, inválido ou expirado; token de outra comunidade (o `X-CommunityId` ou o `communityId` do corpo não é o do token); token antigo ou de uma sessão encerrada (troca de senha, convite aceito). Nesses casos a mensagem pede para entrar de novo (`auth.signInAgain`).
403Autenticado, mas sem permissão para esta operação.
404Recurso não encontrado.
409Produto esgotado (`param = productId`), ou os outros conflitos descritos acima.
429Códigos de cupom recusados demais em pouco tempo.
500Erro interno do servidor.

Exemple curl

curl -X POST "https://api.memberfy.net/api/checkout" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-CommunityId: $COMMUNITY_ID" \
  -H "Content-Type: application/json" \
  -d '{"productId":"d4f7a2b9-6c1e-4a3d-9b8f-2e5c7a1d3f60"}'