> ## Documentation Index
> Fetch the complete documentation index at: https://developers.uvvipay.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Cobrança recorrente e assinaturas

> Crie planos, assine clientes e automatize cobranças recorrentes (mensalidades, SaaS, clubes) com renovação, trial e webhooks na UvviPay.

A **cobrança recorrente** da UvviPay cobra seus clientes de forma automática e periódica — mensalidades, assinaturas de SaaS, clubes de benefícios ou qualquer serviço com pagamento repetido. Você define o **plano** uma vez, assina o cliente e a UvviPay cuida do resto: cobra o meio de pagamento (cartão ou PIX), renova o ciclo e avisa você a cada evento.

## O que é cobrança recorrente?

Em vez de gerar uma cobrança manual todo mês, você cria um **plano** (valor, moeda e frequência) e vincula clientes a ele por meio de uma **assinatura**. A partir daí, a cada ciclo a UvviPay gera uma **fatura** e cobra o método de pagamento padrão automaticamente.

Benefícios:

* **Receita previsível** — cobranças automáticas em intervalos fixos.
* **Menos trabalho manual** — renovação e retentativas sem intervenção.
* **Pagamento seguro** — cartão tokenizado (PCI) ou PIX sem dados sensíveis de cartão.
* **Acompanhamento em tempo real** — webhooks para cada mudança de status.

## Quando usar

<CardGroup cols={2}>
  <Card title="Mensalidades e SaaS" icon="repeat">
    Acesso a plataformas, aplicativos ou APIs com cobrança mensal ou anual.
  </Card>

  <Card title="Clubes e assinaturas" icon="star">
    Clubes de benefícios, comunidades, caixas mensais e programas de fidelidade.
  </Card>

  <Card title="Cursos e conteúdo" icon="graduation-cap">
    Cursos, mídia e conteúdo premium com renovação automática.
  </Card>

  <Card title="Contratos de serviço" icon="handshake">
    Manutenção, suporte e prestação de serviço com cobrança periódica.
  </Card>
</CardGroup>

## Conceitos

| Termo          | O que é                                                                                                              |
| -------------- | -------------------------------------------------------------------------------------------------------------------- |
| **Plano**      | Modelo de cobrança: valor, moeda e cadência (mensal, anual, etc.). Criado uma vez e reutilizado por vários clientes. |
| **Assinatura** | Vínculo entre um cliente e um plano. É o que "liga" a cobrança recorrente para aquele cliente.                       |
| **Fatura**     | Cobrança de um ciclo específico da assinatura. Cada renovação gera uma nova fatura.                                  |
| **Ciclo**      | Período coberto por uma fatura (ex.: 1 mês).                                                                         |
| **Trial**      | Período de teste gratuito antes da primeira cobrança.                                                                |
| **Carência**   | Dias de tolerância após uma falha de cobrança antes do cancelamento.                                                 |

## Como funciona

<Steps>
  <Step title="Crie um plano">
    Defina valor, moeda e frequência (`POST /v1/recurrency/plan`). A cadência é definida uma vez e não muda depois.
  </Step>

  <Step title="Assine o cliente">
    Envie cliente + meio de pagamento (`POST /v1/recurrency/subscription`). Sem trial, a primeira
    cobrança roda **no mesmo request** (`201` se cartão pago, trial ou PIX pendente;
    `422` / `UVV154147` se decline de cartão).
  </Step>

  <Step title="Primeira cobrança">
    Sem trial, o resultado da cobrança já vem na resposta HTTP. Em PIX pendente, aguarde
    a liquidação via webhook. Webhooks (`subscription.activated`, `subscription.past_due`, etc.)
    cobrem as transições seguintes.
  </Step>

  <Step title="Renovação automática">
    A cada ciclo, a UvviPay gera a próxima fatura e cobra o método de pagamento padrão.
  </Step>
</Steps>

```mermaid theme={null}
flowchart LR
    P["Plano<br/>(valor + cadência)"] --> S["Assinatura<br/>(cliente + meio)"]
    S --> F["Fatura do ciclo"]
    F --> C{"Cobrança"}
    C -->|Paga| A["ACTIVE +<br/>próxima fatura"]
    C -->|PIX pendente| W["PENDING + QR"]
    W -->|Pago| A
    C -->|Falha| R["Retentativas /<br/>PAST_DUE"]
    A --> F
```

## Pré-requisitos

<Steps>
  <Step title="Produto habilitado">
    O produto **Recorrência** precisa estar ativo para o seu merchant. A ativação
    é feita pela equipe UvviPay — solicite ao seu contato comercial.
  </Step>

  <Step title="Credenciais de API">
    Use `client-id` e `client-secret` no backend. Nunca exponha o
    `client-secret` no front-end. Veja [Autenticação](/authentication).
  </Step>

  <Step title="Webhooks (recomendado)">
    Registre um endpoint para receber `subscription.*` e acompanhar a ativação
    sem polling agressivo. Veja [Webhooks](/webhooks).
  </Step>
</Steps>

<Warning>
  Não envie dados de cartão a partir do navegador. Chame os endpoints de assinatura
  e troca de método de pagamento apenas do seu backend (PCI).
</Warning>

## Catálogo (planos)

| Ação                                                    | Método  | Endpoint                                  |
| ------------------------------------------------------- | ------- | ----------------------------------------- |
| [Criar](/api-reference/recurrency/plans/create)         | `POST`  | `/v1/recurrency/plan`                     |
| [Consultar](/api-reference/recurrency/plans/get)        | `GET`   | `/v1/recurrency/plan/{planId}`            |
| [Listar](/api-reference/recurrency/plans/list)          | `GET`   | `/v1/recurrency/plans`                    |
| [Atualizar](/api-reference/recurrency/plans/update)     | `PATCH` | `/v1/recurrency/plan/{planId}`            |
| [Desativar](/api-reference/recurrency/plans/deactivate) | `POST`  | `/v1/recurrency/plan/{planId}/deactivate` |
| [Ativar](/api-reference/recurrency/plans/activate)      | `POST`  | `/v1/recurrency/plan/{planId}/activate`   |

<Note>
  A cadência do plano (`interval` / `custom_interval`) é **imutável** após a
  criação. Tentativas de alteração retornam `422 CADENCE_IMMUTABLE`.
</Note>

## Assinaturas

| Ação                                                                                      | Método | Endpoint                                                      |
| ----------------------------------------------------------------------------------------- | ------ | ------------------------------------------------------------- |
| [Criar](/api-reference/recurrency/subscriptions/create)                                   | `POST` | `/v1/recurrency/subscription` (`201` / `422`)                 |
| [Consultar](/api-reference/recurrency/subscriptions/get)                                  | `GET`  | `/v1/recurrency/subscription/{subscriptionId}`                |
| [Listar](/api-reference/recurrency/subscriptions/list)                                    | `GET`  | `/v1/recurrency/subscriptions`                                |
| [Cancelar](/api-reference/recurrency/subscriptions/cancel)                                | `POST` | `/v1/recurrency/subscription/{subscriptionId}/cancel`         |
| [Trocar meio de pagamento](/api-reference/recurrency/subscriptions/update-payment-method) | `POST` | `/v1/recurrency/subscription/{subscriptionId}/payment-method` |
| [Reativar](/api-reference/recurrency/subscriptions/reactivate)                            | `POST` | `/v1/recurrency/subscription/{subscriptionId}/reactivate`     |

### Status da assinatura

| Status      | Significado                                   |
| ----------- | --------------------------------------------- |
| `PENDING`   | Criada; aguardando a primeira cobrança        |
| `TRIAL`     | Em período de teste gratuito                  |
| `ACTIVE`    | Cobrança ativa e renovando                    |
| `PAST_DUE`  | Cobrança falhou, com retentativas restantes   |
| `CANCELLED` | Encerrada (ação do usuário ou falha terminal) |

## Exemplo prático

### 1) Criar um plano mensal

Defina valor, moeda e cadência do plano:

```bash Criar plano theme={null}
curl -X POST https://api.uvvipay.com.br/v1/recurrency/plan \
  -H "Content-Type: application/json" \
  -H "client-id: SUA_CLIENT_ID" \
  -H "client-secret: SUA_CLIENT_SECRET" \
  -d '{
    "name": "Plano Mensal Pro",
    "description": "Acesso completo",
    "amount": 9900,
    "pricing_type": "FIXED_PRICE",
    "currency": "BRL",
    "interval": "MONTHLY",
    "custom_interval": null,
    "allowed_payment_types": ["credit_card"],
    "default_trial_unit": null,
    "default_trial_duration": null,
    "prevent_trial_abuse": true
  }'
```

### 2) Assinar um cliente

Envie o cliente e o meio de pagamento (`credit_card` ou `pix`) referenciando o
`subscription_plan_id`. Sem trial, a primeira cobrança roda **no mesmo request**.
Não envie campos de cartão quando `type` for `pix`.

```bash Assinar cliente theme={null}
curl -X POST https://api.uvvipay.com.br/v1/recurrency/subscription \
  -H "Content-Type: application/json" \
  -H "client-id: SUA_CLIENT_ID" \
  -H "client-secret: SUA_CLIENT_SECRET" \
  -d '{
    "subscription_plan_id": "PLAN_UUID",
    "grace_period_days": 3,
    "trial_duration": null,
    "trial_unit": null,
    "transaction_idempotency_id": "sub-create-0001",
    "customer": {
      "external_customer_id": "customer-ext-0001",
      "name": "João da Silva",
      "email": "joao@exemplo.com",
      "phone": "11987654321",
      "address": {
        "street": "Rua Exemplo",
        "street_number": "100",
        "zip_code": "01310100",
        "city": "São Paulo",
        "state": "SP",
        "country": "BR",
        "neighborhood": "Bela Vista"
      },
      "document": {
        "number": "12345678901",
        "type": "cpf"
      }
    },
    "payment_methods": {
      "number": "4111111111111111",
      "holder_name": "JOAO DA SILVA",
      "security_code": "123",
      "expiration_month": 12,
      "expiration_year": 2030,
      "main_payment_method": true,
      "type": "credit_card"
    }
  }'
```

PIX (sem trial) — `payment_methods` pode ser apenas `{ "type": "pix" }`. A resposta inclui `pix.qrcode`.

### Respostas da criação

#### `201` — cobrança aprovada (sem trial)

```json Resposta 201 (cartão pago) theme={null}
{
  "subscription_id": "550e8400-e29b-41d4-a716-446655440000",
  "customer_id": "660e8400-e29b-41d4-a716-446655440000",
  "invoice_id": "770e8400-e29b-41d4-a716-446655440000",
  "subscription_status": "ACTIVE",
  "invoice_status": "paid",
  "charge_status": "succeeded",
  "transaction_id": "880e8400-e29b-41d4-a716-446655440000",
  "failure_code": null,
  "failure_message": null,
  "pix": null
}
```

#### `201` — trial (sem cobrança no create)

Com trial, não há cobrança no create (`subscription_status: "TRIAL"`).

#### `422` / `UVV154147` — decline de negócio na cobrança sync

A assinatura **é criada** (`PAST_DUE`, invoice `OPEN`), mas a API responde `422`
(não `201` com falha embutida):

```json Resposta 422 UVV154147 theme={null}
{
  "code": "UVV154147",
  "message": "Nao foi possivel processar o pagamento. Verifique os dados e tente novamente.",
  "subscription_id": "550e8400-e29b-41d4-a716-446655440000",
  "invoice_id": "770e8400-e29b-41d4-a716-446655440000",
  "failure_code": "issuer_declined",
  "failure_message": "Recusa do banco emissor"
}
```

**Retry manual:** reenvie o **mesmo** `POST` com o **mesmo**
`transaction_idempotency_id` e o **mesmo payload completo**. O replay **não**
duplica a assinatura (invoice permanece `OPEN`) e inicia uma **nova tentativa
de cobrança**. Alterar o fingerprint do payload retorna `409` / `UVV154862`. A
primeira cobrança **não** entra em retry automático na fila (`auto_charge=false`).

Detalhes em [Criar assinatura](/api-reference/recurrency/subscriptions/create).

## Webhooks de ciclo de vida

Com um endpoint de webhook registrado, você recebe:

| Evento                       | Quando                                                      |
| ---------------------------- | ----------------------------------------------------------- |
| `subscription.created`       | Assinatura criada                                           |
| `subscription.trial_started` | Criação com status `TRIAL`                                  |
| `subscription.activated`     | Primeira cobrança paga (`ACTIVE`)                           |
| `subscription.past_due`      | Cobrança falhou com retentativas restantes                  |
| `subscription.cancelled`     | Cancelamento por usuário ou falha terminal                  |
| `subscription.reactivated`   | Reativação após cancelamento por `USER_ACTION`              |
| `subscription.completed`     | Assinatura concluída por esgotamento de ciclos              |
| `subscription.renewed`       | Cobrança paga com assinatura já `ACTIVE` e período avançado |

Exemplo de payload:

```json theme={null}
{
  "event": "subscription.activated",
  "event_id": "<subscription_events.id>",
  "id": "<subscriptionId>",
  "status": "ACTIVE",
  "plan_id": "...",
  "amount": 9900,
  "currency": "BRL",
  "next_billing_date": "...",
  "period_start_date": "...",
  "period_end_date": "...",
  "occurred_at": "...",
  "customer_id": "<customerId>",
  "external_customer_id": "<externalCustomerId>",
  "invoice_id": "...",
  "charge_attempt_id": "...",
  "transaction_id": "...",
  "attempt": null,
  "decline_kind": null,
  "failure_category": null,
  "cancellation_reason": null,
  "payment_method_id": null,
  "trial_unit": null,
  "trial_duration": null,
  "metadata": {
    "invoiceId": "...",
    "chargeAttemptId": "...",
    "transactionId": "..."
  }
}
```

A entrega é **at-least-once**: o mesmo webhook pode ser reenviado. Use `event_id` como chave de deduplicação (`id` é o id da assinatura). Campos de contexto no top-level podem ser `null` conforme o evento; `metadata` é o payload bruto do evento.

Gerencie endpoints em [Webhooks](/webhooks).

## Campos importantes

| Campo                    | Observação                                                                                           |
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
| `amount`                 | Sempre em centavos (ex.: `9900` = R$ 99,00). Mín. `1` (R$ 0,01); máx. `1000000` (R\$ 10.000,00)      |
| `default_trial_duration` | Duração do trial padrão do plano; máx. `31` dias                                                     |
| `allowed_payment_types`  | `credit_card` e/ou `pix`                                                                             |
| `grace_period_days`      | Dias de tolerância após falha antes do cancelamento terminal                                         |
| `prevent_trial_abuse`    | Bloqueia novo trial para o mesmo cliente quando ativo no plano                                       |
| `trial_unit`             | Apenas `DAY` no create subscription                                                                  |
| Request bodies           | snake\_case (`subscription_plan_id`, `payment_methods`, …)                                           |
| Responses                | snake\_case em todos os endpoints (`subscription_id`, `subscription_status`, `next_billing_date`, …) |
| Webhooks                 | payload em snake\_case (`plan_id`, `next_billing_date`, `occurred_at`, …)                            |

## Perguntas frequentes

<AccordionGroup>
  <Accordion title="Preciso armazenar os dados do cartão?">
    Não. Na criação da assinatura o cartão é tokenizado com segurança e as cobranças
    seguintes usam o token. Você nunca guarda o número do cartão.
  </Accordion>

  <Accordion title="O que acontece se uma cobrança falhar?">
    Na **primeira cobrança** (create sem trial), a API responde erro HTTP (`422` / `UVV154147`)
    com `subscription_id` e `invoice_id` (invoice `OPEN`). A assinatura fica `PAST_DUE` e **não**
    entra em retry automático na fila. Para retry manual, reenvie o mesmo `POST` com o **mesmo**
    `transaction_idempotency_id` e o **mesmo payload completo**: o replay **não** duplica a
    assinatura e inicia uma nova tentativa de cobrança. Se o fingerprint do payload mudar,
    a API responde `409` / `UVV154862`. Em **renovações**, a UvviPay pode retentar conforme a
    política de soft/hard/grace enquanto `auto_charge` estiver habilitado.
  </Accordion>

  <Accordion title="Tem período de teste (trial)?">
    Sim. Defina o trial no plano (`default_trial_unit` / `default_trial_duration`,
    máx. 31 dias) ou na assinatura (`trial_unit` / `trial_duration`). A primeira
    cobrança ocorre automaticamente ao fim do trial.
  </Accordion>

  <Accordion title="Como cancelo uma assinatura?">
    Use `POST /v1/recurrency/subscription/{id}/cancel`. As faturas em aberto são
    canceladas e a assinatura passa a `CANCELLED` por ação do usuário. É possível
    reativar depois com `.../reactivate`.
  </Accordion>

  <Accordion title="Posso trocar o meio de pagamento de um cliente?">
    Sim. Use `POST /v1/recurrency/subscription/{id}/payment-method` (apenas do
    backend) para definir cartão ou PIX como padrão antes do próximo ciclo.
    Em `payment_methods` com `type: "pix"`, não envie campos de cartão.
  </Accordion>

  <Accordion title="Consigo mudar o valor ou a frequência do plano?">
    O valor pode ser atualizado no plano, mas a **cadência é imutável** após a
    criação (`422 CADENCE_IMMUTABLE`). Para outra frequência, crie um novo plano.
  </Accordion>
</AccordionGroup>

## Boas práticas

* Trate o retorno do create como **síncrono** para o desfecho HTTP: `201` (cartão pago, trial ou PIX pendente sem trial) ou `422` / `UVV154147` (decline com ids). Em `201`, confira `subscription_status`, `charge_status` e `pix`. Em PIX pendente, não liberar o serviço até a liquidação ser confirmada.
* Use webhooks para transições futuras (renovação, `past_due`, cancelamento, etc.). Em PIX pendente, confirme a liquidação com `subscription.activated` e/ou `transaction.paid`, correlacionando pelo `subscription_id` ou `transaction_id`.
* Guarde `subscription_id` e `transaction_idempotency_id` no seu sistema.
* Em decline na primeira cobrança, reenvie o mesmo `POST` com o **mesmo** `transaction_idempotency_id` e o **mesmo payload completo** (fingerprint diferente → `409` / `UVV154862`).
* Seja idempotente no consumo de webhook — a mesma notificação pode ser reentregue.
* Reenviar o mesmo `transaction_idempotency_id` com o mesmo payload devolve a assinatura já criada (não duplica) e, se a invoice seguir `OPEN`, pode iniciar nova tentativa de cobrança.
* Troque o meio de pagamento com `.../payment-method` antes do próximo ciclo quando o cliente atualizar cartão ou PIX.
