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

# Links de pagamento

> URLs compartilháveis que cobram sem exigir uma linha de código de checkout.

## Visão geral

Um link de pagamento é uma URL que você compartilha com o cliente e que abre uma página de checkout hospedada pela PlugToPay. Não é preciso escrever frontend: você cria o link pela API, envia o endereço e o cliente paga.

```
Lojista (API key)              PlugToPay API             Cliente (navegador)
──────────────────             ─────────────             ───────────────────
POST /payment-links      →     cria e armazena
                         ←     link + checkout_url

                                                    abre o checkout_url
                                                    ↓
                                               GET  /p/{token}
                                                    → dados públicos do link
                                                    preenche nome, e-mail e CPF/CNPJ
                                                    ↓
                                               POST /p/{token}/session
                                                    → cria a sessão de checkout
                                               POST /checkout/payments/card|pix
```

Existem dois tipos de link:

| Tipo           | Cobrança                        | Métodos aceitos |
| -------------- | ------------------------------- | --------------- |
| `one_time`     | Uma cobrança de valor fixo      | Cartão e/ou PIX |
| `subscription` | Recorrente, atrelada a um plano | Somente cartão  |

<Info>
  Um link de assinatura nunca recebe `amount` — o valor vem do plano. O `payment_method` é forçado para `card` na criação.
</Info>

<Warning>
  **Links de assinatura ainda não podem ser pagos.** A criação e o gerenciamento funcionam, mas o pagamento pelo checkout hospedado está bloqueado até a cobrança recorrente ficar pronta no servidor. Use links avulsos por enquanto.
</Warning>

### Duas URLs para o mesmo link

| Formato                                     | Quando funciona                                   |
| ------------------------------------------- | ------------------------------------------------- |
| `{CHECKOUT_URL}/pay/{token}`                | Sempre                                            |
| `{CHECKOUT_URL}/{company_slug}/{link_slug}` | Quando a empresa **e** o link têm `slug` definido |

O campo `checkout_url` da resposta já entrega o endereço correto — prefira usá-lo em vez de montar a URL manualmente.

<Info>
  A página pública de um link **nunca** expõe cartões salvos: `saved_cards` volta sempre vazio, independentemente do e-mail ou CPF que o comprador anônimo digitar. A regra é aplicada no servidor, então ninguém consegue ver ou reutilizar o cartão de outra pessoa por um link público.
</Info>

***

## Criar um link

`POST /api/v1/payment-links` — requer `X-Client-ID` + `X-API-Key`.

### Link avulso

```http theme={null}
POST /api/v1/payment-links
Content-Type: application/json
X-Client-ID: client_...
X-API-Key: sk_...
```

```json theme={null}
{
  "title": "Curso Premium",
  "description": "Acesso vitalício ao curso premium.",
  "type": "one_time",
  "amount": 29900,
  "currency": "BRL",
  "payment_method": "card",
  "slug": "curso-premium",
  "items": [
    {
      "name": "Curso Premium",
      "description": "Acesso vitalício",
      "quantity": 1,
      "unit_price": 29900,
      "image_url": "https://meusite.com/curso.png"
    }
  ],
  "success_url": "https://meusite.com/obrigado",
  "cancel_url": "https://meusite.com/cancelado",
  "expires_at": "2026-12-31T23:59:59Z"
}
```

### Link de assinatura

```json theme={null}
{
  "title": "Assinatura mensal",
  "description": "Acesso a todos os recursos por um mês.",
  "type": "subscription",
  "plan_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "currency": "BRL",
  "success_url": "https://meusite.com/bem-vindo",
  "cancel_url": "https://meusite.com/cancelado"
}
```

### Campos

| Campo                | Tipo                      | Obrigatório          | Descrição                                                                        |
| -------------------- | ------------------------- | -------------------- | -------------------------------------------------------------------------------- |
| `title`              | string (max 255)          | sim                  | Título exibido no checkout                                                       |
| `description`        | string                    | não                  | Texto de apoio exibido abaixo do título                                          |
| `type`               | `one_time\|subscription`  | sim                  | **Imutável** depois de criado                                                    |
| `amount`             | inteiro (centavos, min 1) | só em `one_time`     | Proibido em `subscription`                                                       |
| `plan_uuid`          | uuid                      | só em `subscription` | Proibido em `one_time`; precisa existir                                          |
| `currency`           | string (3 letras)         | não                  | Padrão `BRL`                                                                     |
| `payment_method`     | `card\|pix`               | não                  | Omitir aceita ambos. Sempre `card` em assinaturas                                |
| `slug`               | string (3–100)            | não                  | Só minúsculas, números e hífens: `^[a-z0-9]+(?:-[a-z0-9]+)*$`. Único por empresa |
| `items`              | array                     | não                  | Itens exibidos no resumo do pedido                                               |
| `items[].name`       | string (max 255)          | sim (por item)       | Nome do item                                                                     |
| `items[].quantity`   | inteiro (min 1)           | sim (por item)       | Quantidade                                                                       |
| `items[].unit_price` | inteiro (centavos)        | sim (por item)       | Preço unitário                                                                   |
| `items[].image_url`  | URL                       | não                  | Miniatura do item                                                                |
| `success_url`        | URL (max 2048)            | não                  | Para onde o comprador vai depois de um pagamento **aprovado**                    |
| `cancel_url`         | URL (max 2048)            | não                  | Reservado para uma desistência explícita do comprador — ver o aviso abaixo       |
| `is_active`          | booleano                  | não                  | Padrão `true`                                                                    |
| `expires_at`         | data/hora                 | não                  | Precisa ser futura. Sem valor = nunca expira                                     |

<Warning>
  `success_url` e `cancel_url` precisam usar **https** e não podem apontar para `localhost`, domínios `.local` ou IPs privados. Qualquer um desses casos devolve `422`.
</Warning>

<Info>
  **Um cartão recusado não redireciona.** A página hospedada mostra o motivo da recusa e um botão de tentar de novo, em vez de mandar o comprador para a `cancel_url` — uma recusa quase sempre se resolve com outro cartão, e tirar o comprador da página encerraria a venda. Hoje só um pagamento **aprovado** leva à `success_url`; a `cancel_url` fica guardada para uma desistência explícita, que a página ainda não oferece.
</Info>

**Resposta `201`:**

```json theme={null}
{
  "token": "pl_live_a1b2c3d4e5f6...",
  "slug": "curso-premium",
  "title": "Curso Premium",
  "description": "Acesso vitalício ao curso premium.",
  "type": "one_time",
  "amount": 29900,
  "currency": "BRL",
  "payment_method": "card",
  "plan_id": null,
  "plan_uuid": null,
  "items": [
    {
      "name": "Curso Premium",
      "description": "Acesso vitalício",
      "quantity": 1,
      "unit_price": 29900,
      "image_url": "https://meusite.com/curso.png"
    }
  ],
  "success_url": "https://meusite.com/obrigado",
  "cancel_url": "https://meusite.com/cancelado",
  "is_active": true,
  "expires_at": "2026-12-31T23:59:59+00:00",
  "checkout_url": "https://checkout.plugtopay.com/minha-empresa/curso-premium",
  "created_at": "2026-07-09T12:00:00+00:00"
}
```

O `slug` da empresa é gerado a partir do nome no cadastro e pode ser alterado depois em `PUT /user/company`.

***

## Gerenciar links

Todas as rotas abaixo aceitam `X-Client-ID` + `X-API-Key`.

### Listar

```http theme={null}
GET /api/v1/payment-links?per_page=20&page=1
```

Resposta paginada no formato `{ "data": [...], "meta": { ... } }`.

### Buscar um link

```http theme={null}
GET /api/v1/payment-links/pl_live_a1b2c3d4e5f6
```

### Atualizar

Todos os campos são opcionais. **`type` não pode ser alterado.** O `slug` pode ser definido pela primeira vez ou trocado — é assim que se muda a `checkout_url` do formato por token para o formato legível.

```http theme={null}
PUT /api/v1/payment-links/pl_live_a1b2c3d4e5f6
Content-Type: application/json
```

```json theme={null}
{
  "title": "Curso Premium — turma 2",
  "slug": "curso-premium-2",
  "is_active": false,
  "expires_at": "2026-09-01T00:00:00Z"
}
```

Resposta `200` com o mesmo formato da criação.

<Warning>
  Em links de assinatura, `payment_method` continua aceitando `pix` na atualização, mas o checkout só processa cartão. Mantenha `card`.
</Warning>

### Remover

```http theme={null}
DELETE /api/v1/payment-links/pl_live_a1b2c3d4e5f6
```

Resposta `204` sem corpo. É uma remoção lógica: quem abrir a `checkout_url` depois disso recebe `404`.

***

## Rotas públicas (sem autenticação)

São as rotas que a página de checkout usa. Não exigem chave de API — o token do link já é o segredo.

### Dados públicos do link

```bash theme={null}
curl https://api.plugtopay.com/api/v1/p/pl_live_a1b2c3d4e5f6
```

Ou pelo par de slugs:

```bash theme={null}
curl https://api.plugtopay.com/api/v1/p/minha-empresa/curso-premium
```

**Resposta `200` — link avulso:**

```json theme={null}
{
  "token": "pl_live_a1b2c3d4e5f6...",
  "title": "Curso Premium",
  "description": "Acesso vitalício ao curso premium.",
  "type": "one_time",
  "amount": 29900,
  "currency": "BRL",
  "payment_method": "card",
  "items": [...],
  "plan": null
}
```

**Resposta `200` — link de assinatura:**

```json theme={null}
{
  "token": "pl_live_a1b2c3d4e5f6...",
  "title": "Assinatura mensal",
  "type": "subscription",
  "amount": null,
  "currency": "BRL",
  "payment_method": "card",
  "items": null,
  "plan": {
    "name": "Básico mensal",
    "amount": 4990,
    "interval": "monthly",
    "interval_count": 1,
    "trial_period_days": 0
  }
}
```

Esse recurso é um subconjunto seguro: não devolve `company_id`, as URLs de redirecionamento nem o `slug` do link.

### Criar a sessão de checkout

```http theme={null}
POST /api/v1/p/pl_live_a1b2c3d4e5f6/session
Content-Type: application/json
```

```json theme={null}
{
  "full_name": "Maria Silva",
  "email": "maria.silva@example.com",
  "cpf_cnpj": "12345678909",
  "coupon_code": "PROMO10"
}
```

| Campo         | Obrigatório | Descrição                                             |
| ------------- | ----------- | ----------------------------------------------------- |
| `full_name`   | sim         | Nome completo do comprador                            |
| `email`       | sim         | E-mail válido                                         |
| `cpf_cnpj`    | sim         | CPF (11 dígitos) ou CNPJ (14 dígitos)                 |
| `coupon_code` | não         | Cupom de desconto; só se aplica a links de assinatura |

Resposta `201` com o mesmo formato de [uma sessão de checkout comum](/checkout), incluindo `success_url`, `cancel_url` e `expires_at`. Guarde o `token`: ele é o `X-Checkout-Token` do passo seguinte.

<Warning>
  A sessão é **de uso único**. Uma tentativa recusada consome o token — crie outra sessão para a próxima tentativa.
</Warning>

***

## Submeter o pagamento

Com o token da sessão em mãos, o fluxo é exatamente o do [checkout](/checkout): `POST /api/v1/checkout/payments/card` ou `/pix` com o header `X-Checkout-Token`.

A sessão trava `amount`, `currency` e `merchant_order_id`, e o método também é respeitado: se o link foi criado apenas para cartão, enviar para a rota de PIX devolve `422`.

***

## Quando um link fica indisponível

As rotas públicas devolvem `404` em qualquer um destes casos:

| Condição               | Resultado      |
| ---------------------- | -------------- |
| `is_active` é `false`  | Indisponível   |
| `expires_at` já passou | Indisponível   |
| Link removido          | Não encontrado |

***

## Formato do token

O token tem o prefixo `pl_live_` seguido de 56 caracteres hexadecimais:

```
pl_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4
```

***

## Referência rápida de rotas

| Método   | Rota                                         | Auth                        |
| -------- | -------------------------------------------- | --------------------------- |
| `POST`   | `/api/v1/payment-links`                      | `X-Client-ID` + `X-API-Key` |
| `GET`    | `/api/v1/payment-links`                      | `X-Client-ID` + `X-API-Key` |
| `GET`    | `/api/v1/payment-links/{token}`              | `X-Client-ID` + `X-API-Key` |
| `PUT`    | `/api/v1/payment-links/{token}`              | `X-Client-ID` + `X-API-Key` |
| `DELETE` | `/api/v1/payment-links/{token}`              | `X-Client-ID` + `X-API-Key` |
| `GET`    | `/api/v1/p/{token}`                          | pública                     |
| `POST`   | `/api/v1/p/{token}/session`                  | pública                     |
| `GET`    | `/api/v1/p/{companySlug}/{linkSlug}`         | pública                     |
| `POST`   | `/api/v1/p/{companySlug}/{linkSlug}/session` | pública                     |

As mesmas rotas de gerenciamento existem sob `/api/v1/user/payment-links` para uso do painel, autenticadas com `Authorization: Bearer <jwt>` em vez da chave de API.
