Visão geral
O sistema de assinaturas da PlugToPay opera em três camadas:
| Objeto | Responsabilidade |
|---|
| Plano | Define valor, intervalo, período de trial e regras do ciclo |
| Cupom | Desconto percentual ou fixo aplicável a N ciclos |
| Assinatura | Vincula cliente + cartão + plano e orquestra as cobranças |
O fluxo básico é: criar plano → criar assinatura → coletar cartão via Checkout → cobrar. A partir daí, a PlugToPay cobra automaticamente a cada ciclo, com retentativas em caso de falha, e dispara webhooks a cada evento relevante.
Criar assinatura
POST /api/v1/subscriptions — requer X-Client-ID + X-API-Key.
{
"plan_uuid": "018f1a2b-3c4d-7e5f-a6b7-c8d9e0f1a2b3",
"customer_token": "cus_7556...",
"card_token": "card_86123...",
"coupon_code": "VERAO30",
"billing_day": 10,
"metadata": {
"internal_ref": "user_789"
}
}
| Campo | Tipo | Obrigatório | Descrição |
|---|
plan_uuid | uuid | sim | UUID do plano existente e ativo |
customer_token | string | sim | Token do cliente (customer.token) |
card_token | string | não* | Token do cartão (card.token). *Obrigatório para planos sem trial ou com trial_period_days: 0 |
coupon_code | string | não | Código do cupom a aplicar desde o primeiro ciclo |
billing_day | inteiro (1–28) | não | Sobrepõe o billing_day do plano para esta assinatura |
metadata | objeto | não | Dados livres associados à assinatura (IDs internos, referências, etc.) |
O sistema impede a criação de uma segunda assinatura ativa do mesmo cliente no mesmo plano. Para mudar o plano, use a troca de plano em vez de cancelar e recriar.
Validação de min_card_months: se o plano tiver esse campo definido, o cartão informado deve ter validade mínima de min_card_months meses a partir de hoje. Uma assinatura anual com min_card_months: 13, por exemplo, impede o uso de um cartão que vence em 6 meses.
Resposta 201:
{
"id": "01a2b3c4-d5e6-7f8a-9b0c-1d2e3f4a5b6c",
"status": "trialing",
"plan": {
"id": "018f1a2b-3c4d-7e5f-a6b7-c8d9e0f1a2b3",
"name": "Pro Mensal",
"amount": 2990,
"interval": "monthly",
"interval_count": 1
},
"customer": {
"id": 42,
"name": "João Silva",
"email": "joao@example.com",
"token": "cus_7556..."
},
"card": {
"last_4_digits": "1111",
"brand": "visa",
"expire_month": "12",
"expire_year": "2028"
},
"coupon": {
"code": "VERAO30",
"discount_type": "percentage",
"discount_value": 30,
"max_cycles": 3
},
"amount": 2093,
"original_amount": 2990,
"discount_amount": 897,
"currency": "BRL",
"interval": "monthly",
"interval_count": 1,
"billing_day": 10,
"trial": {
"period_days": 7,
"start": "2026-06-25T12:00:00+00:00",
"end": "2026-07-02T12:00:00+00:00"
},
"current_period": {
"start": "2026-06-25T12:00:00+00:00",
"end": "2026-07-02T12:00:00+00:00"
},
"next_billing_at": "2026-07-02T12:00:00+00:00",
"paused_at": null,
"paused_until": null,
"canceled_at": null,
"cancel_at": null,
"cancel_at_period_end": false,
"plan_change_at": null,
"new_plan_id": null,
"metadata": { "internal_ref": "user_789" },
"created_at": "2026-06-25T12:00:00+00:00",
"updated_at": "2026-06-25T12:00:00+00:00"
}
Status possíveis
| Status | Descrição |
|---|
incomplete | Assinatura criada, aguardando cartão do cliente |
trialing | Em período de trial — nenhuma cobrança ainda |
active | Faturando normalmente |
past_due | Todas as retentativas esgotadas — requer ação do cliente |
paused | Faturamento suspenso temporariamente |
canceled | Encerrada |
Ciclo de faturamento
Quando next_billing_at é atingido, a PlugToPay:
- Cria um ciclo
pending para o período
- Aplica o desconto do cupom (se elegível pelo
max_cycles)
- Tenta cobrar o cartão via
CreatePaymentCardService (mesmo fluxo de pagamento avulso com cartão tokenizado)
- Sucesso → ciclo marcado como
paid, período avança, webhook subscription.payment.success
- Falha → ciclo marcado como
failed, retentativas agendadas, webhook subscription.payment.failed
Lógica de retentativas:
| Tentativa | Quando |
|---|
| 1ª (original) | Na data de vencimento |
| 2ª retry | +1 dia após a falha original |
| 3ª retry | +3 dias após a falha original |
| 4ª retry | +5 dias após a falha original |
Após a 4ª tentativa sem sucesso, a assinatura vai para past_due e o webhook subscription.past_due é disparado. Nesse estado, nenhuma nova cobrança automática ocorre — o cliente deve atualizar o cartão.
Cálculo do billing_day:
weekly: o billing_day é ignorado; o ciclo avança exatamente pelo número de semanas definido
monthly / yearly: a cobrança ocorre no billing_day do próximo mês/ano (sempre limitado a 28)
Listar assinaturas
GET /api/v1/subscriptions — requer X-Client-ID + X-API-Key.
Retorna lista paginada. Suporta filtros (consultar via parâmetros de query).
Consultar assinatura
GET /api/v1/subscriptions/{uuid} — requer X-Client-ID + X-API-Key.
Retorna o objeto completo com plan, customer, card, coupon e datas de período.
Listar ciclos
GET /api/v1/subscriptions/{uuid}/cycles — requer X-Client-ID + X-API-Key.
Retorna o histórico de ciclos de faturamento da assinatura:
[
{
"id": "01b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"subscription_id": "01a2b3c4-d5e6-7f8a-9b0c-1d2e3f4a5b6c",
"period_start": "2026-07-02T00:00:00+00:00",
"period_end": "2026-08-10T00:00:00+00:00",
"amount": 2093,
"discount_amount": 897,
"status": "paid",
"due_at": "2026-07-10T00:00:00+00:00",
"paid_at": "2026-07-10T08:45:00+00:00",
"failed_at": null,
"retry_count": 0,
"next_retry_at": null,
"gateway_transaction_id": "019ddb56-fbe3-72e1-9f3c-8d0b2faf8f73",
"created_at": "2026-07-10T00:00:00+00:00"
}
]
| Campo | Descrição |
|---|
status | pending|paid|failed|waived |
retry_count | Número de retentativas já realizadas (máx. 3) |
next_retry_at | Próxima tentativa agendada (null se esgotadas ou bem-sucedido) |
gateway_transaction_id | UUID da transação no gateway_transactions (consulta via GET /api/v1/payments/{id}) |
Coletando o cartão via Checkout
Use o Checkout Session para coletar os dados do cartão do cliente no frontend sem expor sua chave de API. O fluxo funciona em dois estágios: primeiro a assinatura é criada (ficando no estado incomplete), depois o cartão é coletado e a primeira cobrança é disparada automaticamente.
Assinaturas em planos com trial ficam em trialing ao invés de incomplete. Nesse caso, o cartão é salvo mas a cobrança só ocorre quando o trial encerrar.
Passo 1 — Criar a assinatura sem cartão
POST /api/v1/subscriptions
{
"plan_uuid": "018f1a2b-3c4d-7e5f-a6b7-c8d9e0f1a2b3",
"customer_token": "cus_7556..."
}
A resposta retorna a assinatura com status: "incomplete" e o id que será usado no próximo passo.
Passo 2 — Criar uma sessão de checkout vinculada à assinatura
POST /api/v1/checkout/sessions — requer X-Client-ID + X-API-Key.
{
"amount": 2990,
"currency": "BRL",
"merchant_order_id": "SIGNUP-user_789",
"payment_method": "card",
"subscription_id": "01a2b3c4-d5e6-7f8a-9b0c-1d2e3f4a5b6c",
"customer": {
"token": "cus_7556..."
}
}
| Campo | Descrição |
|---|
subscription_id | UUID da assinatura em incomplete ou trialing. A sessão só pode ser vinculada a assinaturas nestes dois estados. |
customer | Identifica o cliente para carregar os cartões salvos (saved_cards na resposta). |
Resposta:
{
"token": "cs_live_a1b2c3d4...",
"amount": 2990,
"currency": "BRL",
"merchant_order_id": "SIGNUP-user_789",
"payment_method": "card",
"expires_at": "2026-06-26T15:00:00+00:00",
"saved_cards": [
{
"token": "card_abc123",
"holder_name": "JOAO SILVA",
"brand": "visa",
"first_6_digits": "411111",
"last_4_digits": "1111",
"expire_month": "12",
"expire_year": "2029"
}
]
}
Passe o token (cs_live_...) ao frontend. O array saved_cards pode ser exibido para que o cliente escolha um cartão já cadastrado.
Passo 3 — Submeter o cartão pelo frontend
POST /api/v1/checkout/subscriptions/card — autenticado via X-Checkout-Token.
X-Checkout-Token: cs_live_a1b2c3d4...
{
"card": {
"holder": "JOAO SILVA",
"number": "4111111111111111",
"cvv": "123",
"expiration_month": "12",
"expiration_year": "2028",
"holder_document": "12345678901",
"holder_birthdate": "1990-01-15",
"holder_phone": "11987654321"
},
"billing_address": {
"street": "Rua das Flores",
"number": "123",
"neighborhood": "Centro",
"zip_code": "01310100",
"city": "São Paulo",
"state": "SP",
"country": "BR"
}
}
| Campo | Obrigatório | Descrição |
|---|
card.holder | sim | Nome do titular (mínimo 2 palavras) |
card.number | sim | Número do cartão (16 dígitos) |
card.cvv | sim | Código de segurança (3 ou 4 dígitos) |
card.expiration_month | sim | Mês de validade (01–12) |
card.expiration_year | sim | Ano de validade (4 dígitos) |
card.holder_document | sim | CPF ou CNPJ do titular |
card.holder_birthdate | sim | Data de nascimento (yyyy-mm-dd ou dd/mm/yyyy) |
card.holder_phone | sim | Telefone com DDD (ex: 11987654321) |
billing_address.street | sim | Logradouro |
billing_address.number | sim | Número |
billing_address.neighborhood | sim | Bairro |
billing_address.zip_code | sim | CEP (8 dígitos, somente números) |
billing_address.city | sim | Cidade |
billing_address.state | sim | UF (2 letras, ex: SP) |
billing_address.country | sim | País (ex: BR) |
O X-Checkout-Token é de uso único. Após este request ser processado, o token é invalidado automaticamente. Crie uma nova sessão se precisar tentar novamente.
Resposta — assinatura atualizada:
{
"id": "01a2b3c4-d5e6-7f8a-9b0c-1d2e3f4a5b6c",
"status": "active",
"card": {
"last_4_digits": "1111",
"brand": "visa",
"expire_month": "12",
"expire_year": "2028"
},
"next_billing_at": "2026-07-26T00:00:00+00:00"
}
Para assinaturas que estavam em incomplete, a cobrança do primeiro ciclo é disparada imediatamente. Se o cartão for aprovado, status passa para active. Se for recusado, passa para past_due e as retentativas são agendadas automaticamente.
Diagrama do fluxo completo
Seu backend PlugToPay Frontend do cliente
│ │ │
│── POST /subscriptions ───────►│ │
│◄─ { id, status: incomplete } ─│ │
│ │ │
│── POST /checkout/sessions ───►│ │
│ { subscription_id, ... } │ │
│◄─ { token: "cs_live_..." } ───│ │
│ │ │
│─── envia cs_live_... ────────────────────────────────────────►│
│ │ │
│ │◄─ POST /checkout/subscriptions/card ─│
│ │ X-Checkout-Token: cs_live_... │
│ │ { card: { ... } } │
│ │ │
│ │── tokeniza cartão ──────────► │
│ │── cobra primeira parcela ────► │
│ │ │
│ │─── { status: active, ... } ──►│
│◄──── webhook subscription.payment.success ────────────────────│
Referência rápida de rotas
Todos os endpoints requerem X-Client-ID + X-API-Key, exceto POST /api/v1/checkout/subscriptions/card que usa X-Checkout-Token.
| Método | Rota | Descrição |
|---|
POST | /api/v1/subscriptions/plans | Criar plano |
GET | /api/v1/subscriptions/plans | Listar planos |
GET | /api/v1/subscriptions/plans/{uuid} | Detalhar plano |
PUT | /api/v1/subscriptions/plans/{uuid} | Atualizar plano |
DELETE | /api/v1/subscriptions/plans/{uuid} | Deletar plano |
POST | /api/v1/subscriptions/coupons | Criar cupom |
GET | /api/v1/subscriptions/coupons | Listar cupons |
GET | /api/v1/subscriptions/coupons/{uuid} | Detalhar cupom |
PUT | /api/v1/subscriptions/coupons/{uuid} | Atualizar cupom |
DELETE | /api/v1/subscriptions/coupons/{uuid} | Deletar cupom |
POST | /api/v1/subscriptions | Criar assinatura |
GET | /api/v1/subscriptions | Listar assinaturas |
GET | /api/v1/subscriptions/{uuid} | Detalhar assinatura |
DELETE | /api/v1/subscriptions/{uuid} | Cancelar assinatura |
PATCH | /api/v1/subscriptions/{uuid}/pause | Pausar |
PATCH | /api/v1/subscriptions/{uuid}/resume | Retomar |
PATCH | /api/v1/subscriptions/{uuid}/plan | Trocar plano |
PATCH | /api/v1/subscriptions/{uuid}/card | Atualizar cartão |
POST | /api/v1/subscriptions/{uuid}/anticipate | Antecipar cobrança |
POST | /api/v1/subscriptions/{uuid}/coupon | Aplicar cupom |
GET | /api/v1/subscriptions/{uuid}/cycles | Listar ciclos |
POST | /api/v1/checkout/sessions | Criar sessão de checkout (com subscription_id) |
POST | /api/v1/checkout/subscriptions/card | Submeter cartão via checkout token |