Integração com a Paytix
A cada acontecimento de uma venda (pedido criado, pagamento aprovado, reembolso, carrinho abandonado, assinatura renovada), a Paytix envia um POST com JSON assinado para a URL que você cadastrar. Esta página documenta o que o seu sistema precisa para receber esses eventos: o formato de cada payload, os cabeçalhos, a verificação de assinatura e a política de retentativa.
Comece aqui
- Publique um endpoint
httpsque aceitePOSTe responda200. Só HTTPS é aceito. - Na tela Webhooks do app, cadastre a URL e escolha os eventos que interessam.
- Copie o segredo de assinatura (
whsec_…) que aparece no webhook criado e guarde-o como variável de ambiente no seu servidor. - Verifique a assinatura de toda chamada antes de confiar no conteúdo: código pronto na seção Assinatura e segurança.
- Clique em Testar e confirme que chegou um evento com
"test": true.
O mesmo formato serve n8n, Make, Zapier e qualquer sistema próprio. Todos recebem o payload canônico descrito aqui. As integrações nativas (Utmify, Meta, TikTok) ficam em Integrações e não precisam de webhook.
A entrega
Cada evento é um POST com corpo JSON (UTF-8) e estes cabeçalhos:
| Campo | Tipo | Descrição |
|---|---|---|
X-Paytix-Event | string | O nome do evento (ex.: order.paid). Dá para rotear sem abrir o corpo. |
X-Paytix-Event-Id | uuid | Id único do evento, igual ao campo id do corpo. Repete em toda retentativa: use-o para descartar duplicata. |
X-Paytix-Signature | string | Assinatura HMAC no formato t=<unix>,v1=<hex>, detalhado na seção seguinte. |
Content-Type | string | application/json |
User-Agent | string | Paytix-Webhooks/2.0 |
Regras da entrega
- Responda 2xx em até 10 segundos. O que passar disso conta como falha. Grave o evento e responda na hora; processe depois, fora da requisição.
- Redirecionamento (3xx) não é seguido e conta como falha: cadastre a URL final.
- A resposta do seu servidor fica no log da tela Webhooks, truncada em 2 KB.
Retentativas
Falhou, a Paytix tenta de novo com espera crescente: 10 s, 1 min, 5 min, 30 min, 2 h, 6 h e 12 h (±20% de variação), num total de 7 tentativas em cerca de 21 horas. Erro 4xx não é retentado (endpoint que responde 400 vai responder 400 de novo), com duas exceções: 408 e 429. 5xx, timeout e falha de rede sempre retentam. Esgotadas as tentativas, a entrega aparece como “desistiu” na tela de webhooks, com botão de reenviar.
Saúde do destino: 10 falhas seguidas marcam o webhook como “falhando”; 30 falhas seguidas o desligam e o vendedor recebe um e-mail. Reative-o na tela de webhooks depois de corrigir o endpoint.
Duplicatas e ordem
A mesma entrega pode chegar mais de uma vez, e eventos diferentes podem chegar fora de ordem: uma retentativa de order.created pode aterrissar depois do order.paid. Deduplique pelo id e decida o estado final pelo par occurred_at + status, nunca pela ordem de chegada.
Assinatura e segurança
Toda entrega é assinada com o segredo (whsec_…) do seu webhook, no mesmo formato que a Stripe usa. O cabeçalho X-Paytix-Signature traz t=<timestamp unix>,v1=<assinatura>, onde v1 é o HMAC-SHA256 da string {t}.{corpo cru}. Para verificar:
- Separe
tev1do cabeçalho. - Calcule o HMAC-SHA256 de
{t}.{corpo}com o segredo, sobre o corpo cru, byte a byte, antes de qualquer parse. - Compare com
v1em tempo constante. - Recuse se
testiver a mais de 5 minutos do seu relógio. Isso protege contra reenvio de uma chamada capturada.
Node.js
const crypto = require("crypto");
// segredo: o que aparece em "Segredo de assinatura", na tela de webhooks
function confere(corpoCru, cabecalho, segredo) {
const partes = Object.fromEntries(
cabecalho.split(",").map((p) => p.split("="))
);
const esperado = crypto
.createHmac("sha256", segredo)
.update(partes.t + "." + corpoCru)
.digest("hex");
// fora de 5 minutos, recuse: protege contra reenvio do mesmo pedido
if (Math.abs(Date.now() / 1000 - Number(partes.t)) > 300) return false;
return crypto.timingSafeEqual(
Buffer.from(esperado), Buffer.from(partes.v1)
);
}
// no Express, use express.raw(). O corpo tem que ser o texto original
app.post("/paytix", express.raw({ type: "*/*" }), (req, res) => {
const ok = confere(
req.body.toString(),
req.header("X-Paytix-Signature"),
process.env.PAYTIX_WEBHOOK_SECRET
);
if (!ok) return res.sendStatus(401);
res.sendStatus(200); // responda 2xx rápido; processe depois
});PHP
<?php
$corpoCru = file_get_contents("php://input");
$cabecalho = $_SERVER["HTTP_X_PAYTIX_SIGNATURE"] ?? "";
$segredo = getenv("PAYTIX_WEBHOOK_SECRET");
parse_str(str_replace(",", "&", $cabecalho), $partes);
$esperado = hash_hmac("sha256", $partes["t"] . "." . $corpoCru, $segredo);
// fora de 5 minutos, recuse
if (abs(time() - (int) $partes["t"]) > 300) { http_response_code(401); exit; }
if (!hash_equals($esperado, $partes["v1"])) { http_response_code(401); exit; }
http_response_code(200);As chamadas não saem de um IP fixo: a autenticidade vem da assinatura, não da origem. Endereço novo no seu firewall não é sinal de problema.
O envelope
Todo evento tem a mesma casca. O que muda é o conteúdo de data.
| Campo | Tipo | Descrição |
|---|---|---|
id | uuid | Identificador único do evento. Igual ao X-Paytix-Event-Id e estável entre retentativas. |
event | string | O nome do evento. O catálogo está na seção seguinte. |
version | número | Versão do formato. Hoje, sempre 1; mudança incompatível virá com número novo. |
occurred_at | data UTC | Quando o fato aconteceu. Não muda entre retentativas. |
sent_at | data UTC | Quando esta entrega saiu. Muda a cada retentativa. |
test | booleano | true quando o evento veio do botão “Testar”. Não dispare efeito real para ele. |
data | objeto | O conteúdo: eventos order.* preenchem data.order; cart.abandoned preenche data.cart; subscription.* preenche data.subscription. Os demais blocos (items, buyer, seller, coupon, tracking) existem em todos. |
Datas são sempre UTC, em ISO 8601. Valores monetários são números JSON em reais (ex.: 297, 267.3), não em centavos. Campo sem valor vem null, nunca some do payload.
Catálogo de eventos
| Evento | Conteúdo | Quando dispara |
|---|---|---|
order.created | pedido | Pedido criado aguardando pagamento: o Pix ou boleto foi gerado. Cartão aprovado na hora não passa por aqui. |
order.paid | pedido | Pagamento aprovado pelo gateway. É o evento para entregar o produto. |
order.refused | pedido | Pagamento recusado (cartão). O código da recusa vai em refused_reason. |
order.refunded | pedido | Reembolso feito. Revogue o acesso ao produto. |
order.chargeback | pedido | Chargeback: o comprador contestou a compra no banco. |
cart.abandoned | carrinho | O comprador preencheu o e-mail e ficou ~30 minutos sem pagar. |
subscription.created | assinatura | Assinatura criada, com a primeira cobrança confirmada pelo gateway. |
subscription.renewed | assinatura | Cobrança recorrente aprovada. A assinatura segue ativa. |
subscription.charge_failed | assinatura | Cobrança recorrente falhou. A assinatura entra em carência (past_due). Não é cancelamento. |
subscription.canceled | assinatura | Assinatura cancelada. É este evento que revoga o acesso. |
Ao cadastrar o webhook você escolhe quais desses eventos recebe. Evento fora da sua lista não é enviado.
Eventos de pedido (order.*)
Um pedido é uma cobrança no gateway: o produto principal e os order bumps são linhas do mesmo pedido, em items. Upsell é uma cobrança separada, então chega como um pedido novo (com items[].kind = "upsell") que herda o tracking do pedido de origem. O data.order.id é estável: o order.refunded de amanhã traz o mesmo id do order.paid de hoje.
data.order
| Campo | Tipo | Descrição |
|---|---|---|
id | uuid | Id do pedido na Paytix. |
status | string | pending | paid | failed | refunded | chargeback. Acompanha o evento: order.created chega com pending, order.paid com paid, e assim por diante. |
payment_method | string | pix | credit_card | boleto |
installments | número | Parcelas. 1 em Pix e boleto. |
currency | string | Sempre "BRL". |
amounts.subtotal | número | Valor antes do cupom (total + discount). |
amounts.discount | número | Desconto do cupom aplicado ao pedido. |
amounts.total | número | O que o comprador pagou: principal + order bumps, já com desconto. |
amounts.gateway_fee | número | null | Taxa do gateway, em reais. Vem null enquanto o gateway não informa: a taxa costuma nascer segundos depois do pagamento, então no order.paid o comum é null; o valor aparece nos eventos seguintes do pedido. |
amounts.seller_net | número | total − gateway_fee. Enquanto a taxa não é conhecida, é igual ao total. |
created_at | data UTC | Quando o pedido foi criado. |
paid_at | data UTC | null | Quando foi pago. Preenchido também em order.refunded e order.chargeback. |
refused_at | data UTC | null | Quando a última tentativa foi recusada. |
refunded_at | data UTC | null | Quando foi reembolsado. |
refused_reason | string | null | Código da recusa do cartão (ex.: insufficient_funds). Só em order.refused. |
gateway | string | pagarme | mercadopago. O gateway do vendedor que processou. |
gateway_order_id | string | null | Id do pedido no gateway, para conciliação. |
checkout_url | string | A página de pagamento do produto. |
data.items[]
| Campo | Tipo | Descrição |
|---|---|---|
kind | string | main | bump | upsell. Bump é item do mesmo pedido; upsell chega num pedido próprio. |
sale_id | uuid | Id da linha de venda. Cada item tem o seu. |
product_id / product_name | uuid / string | O produto vendido. |
offer_id / offer_name | uuid | null / string | null | A oferta aplicada, quando houver. |
quantity | número | Hoje, sempre 1. |
unit_price / total | número | Em reais, já com o desconto do cupom aplicado. |
data.buyer, data.seller e data.coupon
| Campo | Tipo | Descrição |
|---|---|---|
buyer.name / buyer.email | string | O comprador, como preencheu no checkout. |
buyer.phone | string | null | Telefone com DDD, só dígitos. |
buyer.document | string | null | CPF, só dígitos. |
buyer.ip | string | null | IP do comprador na página de pagamento. |
buyer.country | string | Hoje, sempre "BR". |
seller.id / seller.name | uuid / string | A conta Paytix e o nome de fornecedor exibido no checkout. |
coupon | objeto | null | { code, discount } quando um cupom foi usado. |
data.tracking
A atribuição capturada na primeira visita à página de pagamento. Campo que não veio na URL chega null.
| Campo | Tipo | Descrição |
|---|---|---|
session_id | string | null | Id da sessão de checkout. Liga o pedido aos eventos de funil. |
utm_source … utm_term | string | null | Os cinco parâmetros UTM da URL. |
src / sck | string | null | Parâmetros de rastreio da Utmify. |
fbclid / gclid / ttclid | string | null | Ids de clique de Meta, Google e TikTok. |
fbp / fbc | string | null | Cookies do pixel da Meta. São eles que casam a conversão com o clique. |
referrer / landing_url | string | null | De onde o comprador veio e a URL exata em que caiu. |
device_type | string | null | mobile | desktop |
user_agent | string | null | O navegador do comprador. |
Exemplo de order.paid
{
"id": "9d3f1c8a-1b2c-4d5e-8f90-1a2b3c4d5e6f",
"event": "order.paid",
"version": 1,
"occurred_at": "2026-08-25T14:32:11Z",
"sent_at": "2026-08-25T14:32:12Z",
"test": false,
"data": {
"order": {
"id": "8c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"status": "paid",
"payment_method": "pix",
"installments": 1,
"currency": "BRL",
"amounts": {
"subtotal": 346.9,
"discount": 29.7,
"total": 317.2,
"gateway_fee": null,
"seller_net": 317.2
},
"created_at": "2026-08-25T14:30:07+00:00",
"paid_at": "2026-08-25T14:32:11+00:00",
"refused_at": null,
"refunded_at": null,
"refused_reason": null,
"gateway": "pagarme",
"gateway_order_id": "or_x9k2m4n8",
"checkout_url": "https://paytix.com.br/pay/curso-de-trafego"
},
"items": [
{
"kind": "main",
"sale_id": "8c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"product_id": "1f2e3d4c-5b6a-7988-9a0b-1c2d3e4f5a6b",
"product_name": "Curso de Tráfego",
"offer_id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
"offer_name": "Turma de agosto",
"quantity": 1,
"unit_price": 267.3,
"total": 267.3
},
{
"kind": "bump",
"sale_id": "4e5f6a7b-8c9d-0e1f-2a3b-4c5d6e7f8a9b",
"product_id": "0b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e",
"product_name": "Planilha de Métricas",
"offer_id": null,
"offer_name": null,
"quantity": 1,
"unit_price": 49.9,
"total": 49.9
}
],
"buyer": {
"name": "João Silva",
"email": "[email protected]",
"phone": "11999999999",
"document": "12345678900",
"ip": "189.10.20.30",
"country": "BR"
},
"seller": {
"id": "2b3c4d5e-6f70-8192-a3b4-c5d6e7f8a9b0",
"name": "Minha Marca"
},
"coupon": {
"code": "PROMO10",
"discount": 29.7
},
"tracking": {
"session_id": "s_1756130000_ab12cd34",
"utm_source": "facebook",
"utm_medium": "cpc",
"utm_campaign": "lancamento",
"utm_content": "criativo-3",
"utm_term": null,
"src": null,
"sck": null,
"fbclid": "IwAR123",
"gclid": null,
"ttclid": null,
"fbp": "fb.1.1756130000.123456",
"fbc": "fb.1.1756130000.IwAR123",
"referrer": "https://facebook.com/",
"landing_url": "https://paytix.com.br/pay/curso-de-trafego?utm_source=facebook",
"device_type": "mobile",
"user_agent": "Mozilla/5.0 …"
},
"subscription": null
}
}Carrinho abandonado (cart.abandoned)
Dispara quando o comprador preenche o e-mail no checkout e passa cerca de 30 minutos sem pagar. Não existe pedido ainda: data.order vem null e o conteúdo fica em data.cart. Se ele pagar dentro da janela, o evento não dispara.
data.cart
| Campo | Tipo | Descrição |
|---|---|---|
id | uuid | Id da sessão de checkout abandonada. |
status | string | Sempre "abandoned". |
payment_method | string | null | O meio de pagamento selecionado quando o comprador parou. |
amounts | objeto | subtotal, discount e total, sem gateway_fee: não houve cobrança. |
initiated_at | data UTC | Quando o comprador começou a preencher. |
abandoned_at | data UTC | Quando foi marcado como abandonado. |
checkout_url | string | O link para levar o comprador de volta ao checkout. |
Diferenças em relação aos eventos de pedido: items[].sale_id vem null (não existe venda), buyer.ip vem null, e no tracking os campos fbp, fbc, referrer e user_agent vêm null.
Exemplo
{
"id": "9d3f1c8a-1b2c-4d5e-8f90-1a2b3c4d5e6f",
"event": "cart.abandoned",
"version": 1,
"occurred_at": "2026-08-25T14:32:11Z",
"sent_at": "2026-08-25T14:32:12Z",
"test": false,
"data": {
"order": null,
"cart": {
"id": "6d7e8f9a-0b1c-2d3e-4f5a-6b7c8d9e0f1a",
"status": "abandoned",
"payment_method": "pix",
"currency": "BRL",
"amounts": {
"subtotal": 297,
"discount": 0,
"total": 297
},
"initiated_at": "2026-08-25T13:58:20+00:00",
"abandoned_at": "2026-08-25T14:32:11+00:00",
"checkout_url": "https://paytix.com.br/pay/curso-de-trafego"
},
"items": [
{
"kind": "main",
"sale_id": null,
"product_id": "1f2e3d4c-5b6a-7988-9a0b-1c2d3e4f5a6b",
"product_name": "Curso de Tráfego",
"offer_id": null,
"offer_name": null,
"quantity": 1,
"unit_price": 297,
"total": 297
}
],
"buyer": {
"name": "João Silva",
"email": "[email protected]",
"phone": "11999999999",
"document": "12345678900",
"ip": null,
"country": "BR"
},
"seller": {
"id": "2b3c4d5e-6f70-8192-a3b4-c5d6e7f8a9b0",
"name": "Minha Marca"
},
"coupon": null,
"tracking": {
"session_id": "s_1756130000_ab12cd34",
"utm_source": "facebook",
"utm_medium": "cpc",
"utm_campaign": "lancamento",
"utm_content": "criativo-3",
"utm_term": null,
"src": null,
"sck": null,
"fbclid": "IwAR123",
"gclid": null,
"ttclid": null,
"fbp": null,
"fbc": null,
"referrer": null,
"landing_url": "https://paytix.com.br/pay/curso-de-trafego?utm_source=facebook",
"device_type": "mobile",
"user_agent": null
},
"subscription": null
}
}Eventos de assinatura (subscription.*)
Nesses eventos data.order vem null e o conteúdo fica em data.subscription. O subscription.id é estável entre criar, renovar e cancelar: é a chave certa para liberar e revogar acesso. Importante: subscription.charge_failed abre a carência (past_due), não é cancelamento; quem revoga é o subscription.canceled. O tracking vem vazio nesses eventos, e buyer traz só nome e e-mail.
data.subscription
| Campo | Tipo | Descrição |
|---|---|---|
id | uuid | Id da assinatura na Paytix. É o mesmo em todos os eventos dela. |
status | string | trialing | active | past_due | cancelled. O estado no momento do evento. |
gateway / gateway_subscription_id | string / string | null | Onde a recorrência roda e o id dela lá. |
frequency | string | weekly | monthly | quarterly | semiannual | annual |
payment_method | string | Hoje, credit_card. |
amount | número | Valor de cada cobrança, em reais. |
current_period_start / current_period_end | data UTC | O período vigente já pago. |
trial_ends_at | data UTC | null | Fim do período de teste, quando houver. |
cancelled_at | data UTC | null | Quando foi cancelada. |
card | objeto | null | { brand, last4 } do cartão salvo. Nunca o número. |
Exemplo de subscription.renewed
{
"id": "9d3f1c8a-1b2c-4d5e-8f90-1a2b3c4d5e6f",
"event": "subscription.renewed",
"version": 1,
"occurred_at": "2026-08-25T14:32:11Z",
"sent_at": "2026-08-25T14:32:12Z",
"test": false,
"data": {
"order": null,
"items": [
{
"kind": "main",
"sale_id": null,
"product_id": "1f2e3d4c-5b6a-7988-9a0b-1c2d3e4f5a6b",
"product_name": "Mentoria Mensal",
"offer_id": "9a2b3c4d-5e6f-7081-9a2b-3c4d5e6f7081",
"offer_name": "Pro",
"quantity": 1,
"unit_price": 97,
"total": 97
}
],
"buyer": {
"name": "João Silva",
"email": "[email protected]",
"phone": null,
"document": null,
"ip": null,
"country": "BR"
},
"seller": {
"id": "2b3c4d5e-6f70-8192-a3b4-c5d6e7f8a9b0",
"name": "Minha Marca"
},
"coupon": null,
"tracking": {
"session_id": null,
"utm_source": null,
"utm_medium": null,
"utm_campaign": null,
"utm_content": null,
"utm_term": null,
"src": null,
"sck": null,
"fbclid": null,
"gclid": null,
"ttclid": null,
"fbp": null,
"fbc": null,
"referrer": null,
"landing_url": null,
"device_type": null,
"user_agent": null
},
"subscription": {
"id": "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d",
"status": "active",
"gateway": "pagarme",
"gateway_subscription_id": "sub_x1y2z3w4",
"frequency": "monthly",
"payment_method": "credit_card",
"amount": 97,
"currency": "BRL",
"current_period_start": "2026-08-25T14:32:11+00:00",
"current_period_end": "2026-09-25T14:32:11+00:00",
"trial_ends_at": null,
"cancelled_at": null,
"plan": {
"offer_id": "9a2b3c4d-5e6f-7081-9a2b-3c4d5e6f7081",
"name": "Pro",
"slug": "mentoria-pro",
"code": "visitas_50k"
},
"card": {
"brand": "visa",
"last4": "4242"
}
}
}
}Eventos de teste
O botão Testar da tela Webhooks envia um evento com a forma exata do real, mas com "test": true, valor de R$ 1 e o pedido de id fixo paytix-teste-conexao. Testar de novo repete o mesmo id, para ferramenta que evolui pedido por id atualizar a mesma linha em vez de criar outra. Trate test antes de qualquer efeito: evento de teste não deve entregar produto nem disparar automação real.
API de leitura
O webhook avisa quando algo acontece. A API responde quando você pergunta, e é o caminho mais curto para um SaaS: no login do seu sistema, pergunte se aquele e-mail tem assinatura ativa e em qual plano, sem guardar estado nenhum do lado de cá.
Autenticação
Crie uma chave em Configurações → Chaves de API e mande em Authorization: Bearer ptx_…. Ela aparece uma única vez: a Paytix guarda só o SHA-256. Chame do servidor: a API não responde a requisição de navegador, de propósito, para a chave não vazar na aba de rede de quem abrir a sua página.
GET /subscriptions
curl "https://qefuqiimonxnutinyvcu.supabase.co/functions/v1/api-v1/[email protected]" \ -H "Authorization: Bearer ptx_sua_chave"
| Parâmetro | Tipo | Descrição |
|---|---|---|
email | string | E-mail do comprador, como ele digitou no checkout. |
product_id | uuid | Só as assinaturas deste produto. |
product_slug | string | O mesmo, pelo endereço do checkout. |
status | string | Um ou mais, separados por vírgula: trialing, active, past_due, pending, cancelled. |
limit | número | Até 100. O padrão é 50, do mais novo para o mais antigo. |
A resposta:
{
"data": [
{
"id": "3f1c…",
"status": "active",
"active": true,
"plan": {
"offer_id": "9a2b…",
"code": "visitas_10k",
"name": "Starter",
"slug": "linkboom-starter"
},
"product": {
"id": "7d4e…",
"name": "Linkboom",
"slug": "linkboom"
},
"buyer": {
"email": "[email protected]",
"name": "Cliente Exemplo"
},
"amount": 47,
"currency": "BRL",
"frequency": "monthly",
"interval_count": 1,
"payment_method": "credit_card",
"card": {
"brand": "visa",
"last4": "4242"
},
"trial_ends_at": null,
"current_period_start": "2026-08-01T12:00:00Z",
"current_period_end": "2026-09-01T12:00:00Z",
"cancel_at_period_end": false,
"cancelled_at": null,
"activated_at": "2026-08-01T12:00:04Z",
"created_at": "2026-08-01T12:00:00Z"
}
]
}| Campo | Tipo | Descrição |
|---|---|---|
active | booleano | Se o cliente pode usar o software hoje. Verdadeiro em trialing, active e past_due. A carência de uma cobrança recusada vai até o cancelamento, e cortar no primeiro erro derruba cliente bom por cartão vencido. |
plan.code | string | O código que você escreveu na aba Planos (visitas_10k). É por ele que se aplica a cota: não muda se você renomear o plano. |
plan.offer_id | uuid | A oferta. Use quando não houver código. |
current_period_end | data | Até quando o ciclo pago vale. |
POST /subscriptions/{id}/upgrade
"Esse cliente estourou a cota, sobe para o próximo plano." Quem sabe que o limite acabou é o seu software; a Paytix executa: acha o degrau seguinte da escada que você ordenou em Produtos → Planos, troca o plano e cobra a diferença proporcional aos dias que faltam do ciclo.
curl -X POST "https://qefuqiimonxnutinyvcu.supabase.co/functions/v1/api-v1/subscriptions/3f1c.../upgrade" \
-H "Authorization: Bearer ptx_sua_chave" \
-H "Content-Type: application/json" \
-d '{"plan_code": "visitas_50k"}' # opcional: sem isso, sobe um degrau| Resposta | Tipo | Descrição |
|---|---|---|
plan.code | string | O plano em que o cliente ficou. |
prorated_amount | número | O que foi cobrado agora. Zero quando não houve cobrança; veja Mercado Pago abaixo. |
effective_at | data | Quando o plano novo passou a valer: agora. |
next_charge_at | data | Quando entra o valor cheio. |
Os "não" possíveis, todos com corpo JSON:
| Erro | HTTP | O que fazer |
|---|---|---|
auto_upgrade_disabled | 409 | Desligado no produto (scope: "product") ou pelo cliente na área de compras dele (scope: "buyer"). Você decide: travar, avisar ou degradar. |
no_next_plan | 409 | Ele já está no plano mais alto da escada. |
plan_not_found | 404 | Nenhum plano ativo com esse código neste produto. |
not_an_upgrade | 409 | O plano pedido não é mais caro. Auto-upgrade não desce de plano. |
subscription_inactive | 409 | A assinatura está cancelada ou ainda pendente. |
upgrade_failed | 502 | O gateway recusou, e a mensagem vem junto (cartão sem limite, por exemplo). O plano não mudou. |
No Mercado Pago não há cobrança da diferença: todo cartão salvo lá exige o CVV a cada cobrança, e ninguém digita nada num upgrade automático. O plano sobe na hora e o valor cheio entra na renovação, e o prorated_amount volta zero.
Cobrar mais caro sem autorização prévia é venda casada: a regra aparece no checkout antes da compra e o cliente pode desligar em /compras. Diga isso também na sua página de preços.
Limites
120 chamadas por minuto, por chave. Passou disso, 429. Chave inválida ou revogada responde 401. Guarde o resultado em cache pelo tempo da sessão do seu usuário em vez de consultar a cada página.

