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 → tokenizar cartão do cliente → criar assinatura. A partir daí, a PlugToPay cobra automaticamente a cada ciclo, com retentativas em caso de falha, e dispara webhooks a cada evento relevante.
Pré-requisito: o cliente e o cartão já devem existir no vault antes de criar uma assinatura. Para tokenizar o cartão use save_card: true em qualquer pagamento avulso — o token retornado no objeto card da resposta é o card_token usado aqui.
Planos
Criar plano
POST /api/v1/subscriptions/plans — requer X-Client-ID + X-API-Key.
{
"name": "Pro Mensal",
"description": "Acesso completo à plataforma",
"amount": 2990,
"currency": "BRL",
"interval": "monthly",
"interval_count": 1,
"trial_period_days": 7,
"billing_day": 5,
"min_card_months": 3
}
| Campo | Tipo | Obrigatório | Descrição |
|---|
name | string (max 255) | sim | Nome do plano |
description | string (max 1000) | não | Descrição exibida ao cliente |
amount | inteiro (centavos) | sim | Valor do ciclo em centavos (ex: 2990 = R$ 29,90) |
currency | string (3 chars) | não | Moeda — padrão BRL |
interval | weekly|monthly|yearly | sim | Frequência de cobrança |
interval_count | inteiro (1–365) | não | Multiplicador do intervalo — padrão 1 (ex: 2 + monthly = a cada 2 meses) |
trial_period_days | inteiro (0–365) | não | Dias de trial gratuito antes da primeira cobrança |
billing_day | inteiro (1–28) | não | Dia fixo de cobrança no mês/ano. Para weekly é ignorado. Se omitido, usa o dia da criação da assinatura |
min_card_months | inteiro (1–120) | não | Validade mínima do cartão em meses a partir de hoje. Impede criação de assinaturas com cartões prestes a vencer |
billing_day é sempre limitado a 28 para garantir compatibilidade com todos os meses. Dias 29, 30 e 31 não são aceitos.
Resposta 201:
{
"id": "018f1a2b-3c4d-7e5f-a6b7-c8d9e0f1a2b3",
"name": "Pro Mensal",
"description": "Acesso completo à plataforma",
"amount": 2990,
"currency": "BRL",
"interval": "monthly",
"interval_count": 1,
"trial_period_days": 7,
"billing_day": 5,
"min_card_months": 3,
"is_active": true,
"created_at": "2026-06-25T12:00:00+00:00",
"updated_at": "2026-06-25T12:00:00+00:00"
}
Atualizar plano
PUT /api/v1/subscriptions/plans/{uuid} — requer X-Client-ID + X-API-Key.
{
"name": "Pro Mensal Plus",
"description": "Novo benefício incluído",
"billing_day": 10,
"trial_period_days": 14,
"min_card_months": 6,
"is_active": true
}
Não é possível alterar amount, currency, interval ou interval_count após a criação. Para mudar o valor ou a frequência, crie um novo plano e migre as assinaturas via troca de plano.
Listar planos
GET /api/v1/subscriptions/plans — requer X-Client-ID + X-API-Key.
Retorna a lista de planos da empresa. Planos inativos (is_active: false) são incluídos.
Deletar plano
DELETE /api/v1/subscriptions/plans/{uuid} — requer X-Client-ID + X-API-Key.
Soft-delete. Não é possível deletar um plano com assinaturas ativas.
Cupons
Criar cupom
POST /api/v1/subscriptions/coupons — requer X-Client-ID + X-API-Key.
{
"code": "VERAO30",
"name": "30% de desconto — Verão",
"description": "Promoção de verão, primeiros 3 meses",
"discount_type": "percentage",
"discount_value": 30,
"max_redemptions": 200,
"max_cycles": 3,
"valid_from": "2026-12-01",
"valid_until": "2027-02-28"
}
| Campo | Tipo | Obrigatório | Descrição |
|---|
code | string alfanumérico (max 50) | sim | Código único por empresa. Convertido automaticamente para maiúsculas |
name | string (max 255) | sim | Nome interno do cupom |
description | string (max 1000) | não | Descrição opcional |
discount_type | percentage|fixed | sim | Tipo de desconto |
discount_value | inteiro | sim | Para percentage: 1–100. Para fixed: valor em centavos |
max_redemptions | inteiro (min 1) | não | Limite total de resgates. null = ilimitado |
max_cycles | inteiro (min 1) | não | Aplica o desconto apenas nos primeiros N ciclos. null = aplica para sempre |
valid_from | date (YYYY-MM-DD) | sim | Data de início da validade |
valid_until | date após valid_from | não | Data de fim da validade. null = sem expiração |
Resposta 201:
{
"id": "019a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"code": "VERAO30",
"name": "30% de desconto — Verão",
"description": "Promoção de verão, primeiros 3 meses",
"discount_type": "percentage",
"discount_value": 30,
"max_redemptions": 200,
"redemptions_count": 0,
"max_cycles": 3,
"valid_from": "2026-12-01T00:00:00+00:00",
"valid_until": "2027-02-28T23:59:59+00:00",
"is_active": true,
"created_at": "2026-06-25T12:00:00+00:00"
}
Atualizar cupom
PUT /api/v1/subscriptions/coupons/{uuid} — requer X-Client-ID + X-API-Key.
Campos editáveis: name, description, max_redemptions, max_cycles, valid_until, is_active.
code, discount_type e discount_value são imutáveis após a criação.
Listar e deletar cupons
| Método | Rota | Descrição |
|---|
GET | /api/v1/subscriptions/coupons | Lista todos os cupons |
GET | /api/v1/subscriptions/coupons/{uuid} | Detalha um cupom |
DELETE | /api/v1/subscriptions/coupons/{uuid} | Soft-delete |
Assinaturas
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 |
|---|
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}) |
Gerenciamento
Pausar
PATCH /api/v1/subscriptions/{uuid}/pause — requer X-Client-ID + X-API-Key.
{
"paused_until": "2026-09-01"
}
paused_until é opcional. Se informado, a assinatura retoma automaticamente nessa data via cron. Caso contrário, permanece pausada até um resume explícito.
Apenas assinaturas em active ou trialing podem ser pausadas.
Retomar
PATCH /api/v1/subscriptions/{uuid}/resume — requer X-Client-ID + X-API-Key.
Corpo vazio. Recalcula next_billing_at a partir da data atual, respeitando o billing_day.
Cancelar
DELETE /api/v1/subscriptions/{uuid} — requer X-Client-ID + X-API-Key.
{
"at_period_end": true
}
at_period_end | Comportamento |
|---|
false (padrão) | Cancela imediatamente — sem cobrança do período em andamento |
true | Agenda para o fim do período atual — cliente mantém acesso até lá |
Quando at_period_end: true, a assinatura continua active mas exibe cancel_at_period_end: true e cancel_at com a data de expiração. O cancelamento é executado automaticamente pelo cron.
Trocar plano
PATCH /api/v1/subscriptions/{uuid}/plan — requer X-Client-ID + X-API-Key.
{
"new_plan_uuid": "018f9z8y-7x6w-5v4u-3t2s-1r0q9p8o7n6m",
"immediately": false
}
immediately | Comportamento |
|---|
false (padrão) | Troca agendada para o início do próximo período |
true | Troca imediata com pro-rata: crédito proporcional ao tempo não utilizado no período atual é calculado e descontado do novo valor |
A troca é classificada automaticamente como upgrade (novo valor ≥ atual) ou downgrade (novo valor < atual) e sinalizada no webhook.
Atualizar cartão
PATCH /api/v1/subscriptions/{uuid}/card — requer X-Client-ID + X-API-Key.
{
"card_token": "card_novocartao..."
}
Use quando o cartão anterior for recusado (estado past_due) ou quando o cliente adicionar um novo método de pagamento. O cartão deve pertencer ao mesmo cliente da assinatura.
Após atualizar o cartão de uma assinatura past_due, chame Antecipar para tentar cobrar imediatamente sem esperar o próximo ciclo automático.
Antecipar
POST /api/v1/subscriptions/{uuid}/anticipate — requer X-Client-ID + X-API-Key.
Corpo vazio. Cobra o próximo ciclo agora, independentemente de next_billing_at. Ideal para:
- Reativar assinaturas
past_due após atualização de cartão
- Testes de integração
- Cobranças sob demanda antecipadas pelo operador
Retorna o ciclo criado com o resultado da tentativa de cobrança.
Aplicar cupom
POST /api/v1/subscriptions/{uuid}/coupon — requer X-Client-ID + X-API-Key.
{
"coupon_code": "RETENCAO10"
}
Substitui o cupom atual (se houver). O desconto passa a valer a partir do próximo ciclo. O contador de max_cycles é resetado — os N ciclos com desconto são contados do zero.
Webhooks
Todos os eventos de assinatura seguem o mesmo envelope. Cadastre os eventos desejados em POST /api/v1/user/company/webhooks.
Eventos disponíveis
| Evento | Quando é disparado |
|---|
subscription.created | Assinatura criada |
subscription.trial_started | Assinatura criada com trial |
subscription.activated | Primeiro ciclo pago com sucesso (trial encerrou) |
subscription.paused | Assinatura pausada |
subscription.resumed | Assinatura retomada |
subscription.plan_changed | Plano alterado (imediato ou agendado) |
subscription.payment.success | Ciclo cobrado com sucesso |
subscription.payment.failed | Tentativa de cobrança falhou |
subscription.payment.retry | Retentativa agendada |
subscription.past_due | Todas as retentativas esgotadas |
subscription.canceled | Assinatura cancelada |
Payload base
Todos os eventos incluem estes campos:
{
"event": "subscription.payment.success",
"subscription_id": "01a2b3c4-d5e6-7f8a-9b0c-1d2e3f4a5b6c",
"plan_id": "018f1a2b-3c4d-7e5f-a6b7-c8d9e0f1a2b3",
"customer_id": 42,
"status": "active",
"amount": 2093,
"currency": "BRL",
"interval": "monthly",
"interval_count": 1,
"billing_day": 10,
"next_billing_at": "2026-08-10T00:00:00+00:00",
"current_period": {
"start": "2026-07-10T00:00:00+00:00",
"end": "2026-08-10T00:00:00+00:00"
},
"trial": null,
"created_at": "2026-06-25T12:00:00+00:00"
}
| Evento | Campos adicionais |
|---|
subscription.trial_started | trial_end |
subscription.canceled | at_period_end |
subscription.paused | paused_until |
subscription.plan_changed | new_plan_id, change_type (upgrade|downgrade), immediately, executed |
subscription.payment.success | cycle_id, amount_charged, transaction_id |
subscription.payment.failed | cycle_id, retry_count, next_retry_at, final |
subscription.payment.retry | cycle_id, retry_count, next_retry_at |
subscription.past_due | cycle_id, retry_count, reason |
Implemente todos os eventos de subscription.payment.* e subscription.past_due. São eles que sinalizam quando o acesso do cliente deve ser suspenso ou reativado.
Referência rápida de rotas
Todos os endpoints requerem X-Client-ID + X-API-Key.
| 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 |