> ## 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.

# Upsell com um clique

> Como cobrar uma oferta adicional logo após um pagamento aprovado, sem pedir o cartão de novo

O token de upsell permite cobrar uma segunda transação no cartão que o portador acabou de usar, sem pedir os dados do cartão de novo. Ele atende checkouts que oferecem um produto adicional na tela de confirmação (order bump, upsell, downsell). A UvviPay devolve o token na resposta do pagamento aprovado. Ele vale por 15 minutos e para uma única cobrança.

## Como funciona

```mermaid theme={null}
sequenceDiagram
    participant C as Checkout do lojista
    participant U as UvviPay
    C->>U: POST /v1/payments (cartão aberto)
    U-->>C: 200 status paid + upsell.token
    Note over C: Portador aceita a oferta adicional
    C->>U: POST /v1/payments (card.upsellToken)
    U-->>C: 200 nova transação (paid ou refused)
    Note over U: Token consumido
```

1. O seu backend envia `POST /v1/payments` com `paymentMethod: credit_card` e os dados abertos do cartão (`card.number`, `card.cvv` etc.).
2. Se a adquirente aprovar (`status: paid`), a resposta inclui o objeto `upsell` com `token` e `expiresAt`.
3. O portador aceita a oferta adicional na sua tela de confirmação.
4. O seu backend envia um novo `POST /v1/payments` com `card.upsellToken` no lugar dos dados do cartão.
5. A UvviPay cobra o mesmo cartão e retorna uma nova transação, com o próprio `id`.

O token fica vinculado à transação de origem, e não ao cartão. Se a origem for estornada, o token deixa de valer.

<Info>
  Não existe flag de ativação. Toda transação aprovada com cartão aberto retorna o objeto `upsell`. Se o seu checkout não oferece upsell, basta ignorar o objeto.
</Info>

## Pré-requisitos

<Steps>
  <Step title="Cartão aberto na transação de origem">
    O token só é emitido quando a origem usa `card.number` + `card.cvv`. Cobranças com `card.cardTokenId` ou com `card.upsellToken` não emitem token.
  </Step>

  <Step title="Transação aprovada">
    O token só é emitido quando a origem retorna `status: paid`. Transações `refused` ou `in_analysis` não retornam `upsell`.
  </Step>

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

## Passo 1: criar a transação de origem

Envie o pagamento como de costume; não há campo novo para preencher.

```bash theme={null}
curl -X POST https://api.uvvipay.com.br/v1/payments \
  -H "Content-Type: application/json" \
  -H "client-id: SUA_CLIENT_ID" \
  -H "client-secret: SUA_CLIENT_SECRET" \
  -d '{
    "externalId": "pedido-123",
    "amount": 9900,
    "paymentMethod": "credit_card",
    "card": {
      "number": "************1111",
      "holderName": "JOAO DA SILVA",
      "cvv": "***",
      "expirationMonth": 12,
      "expirationYear": 2030,
      "installments": 1
    },
    "customer": {
      "name": "João da Silva",
      "email": "joao@exemplo.com",
      "document": { "number": "12345678901", "type": "cpf" }
    },
    "items": [
      { "title": "Curso completo", "quantity": 1, "unitPrice": 9900 }
    ]
  }'
```

### Resposta (200)

```json theme={null}
{
  "id": "a1b2c3d4-0000-4000-8000-000000000001",
  "status": "paid",
  "amount": 9900,
  "paymentMethod": "credit_card",
  "upsell": {
    "token": "AbCdEfGhIjKlMnOpQrStUv",
    "expiresAt": "2026-09-11T20:15:00.000Z"
  },
  "createdAt": "2026-09-11T20:00:00.000Z"
}
```

Guarde `upsell.token` e `upsell.expiresAt` na sessão do checkout. Não grave o token em logs.

## Passo 2: cobrar o upsell

Envie um novo `POST /v1/payments` com `card.upsellToken`. Não envie `card.number`, `card.cvv`, `customer` nem `threeDSData`.

```bash theme={null}
curl -X POST https://api.uvvipay.com.br/v1/payments \
  -H "Content-Type: application/json" \
  -H "client-id: SUA_CLIENT_ID" \
  -H "client-secret: SUA_CLIENT_SECRET" \
  -H "x-idempotency-key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
  -d '{
    "externalId": "pedido-123-upsell",
    "amount": 2990,
    "paymentMethod": "credit_card",
    "card": {
      "upsellToken": "AbCdEfGhIjKlMnOpQrStUv",
      "installments": 3
    },
    "items": [
      { "title": "Mentoria", "quantity": 1, "unitPrice": 2990 }
    ]
  }'
```

### Resposta (200)

```json theme={null}
{
  "id": "a1b2c3d4-0000-4000-8000-000000000002",
  "status": "paid",
  "amount": 2990,
  "paymentMethod": "credit_card",
  "createdAt": "2026-09-11T20:01:30.000Z"
}
```

A resposta é uma transação comum, com o próprio `id`. Consulte, estorne e receba webhooks dessa transação como faz com qualquer outra. Ela não traz um novo objeto `upsell`, então não é possível encadear uma terceira cobrança a partir dela.

## Regras do payload de upsell

| Campo                                                      | Regra                                                       |
| ---------------------------------------------------------- | ----------------------------------------------------------- |
| `card.upsellToken`                                         | Obrigatório. 22 caracteres, formato `^[A-Za-z0-9_-]{22}$`   |
| `card.installments`                                        | Livre. Não precisa igualar as parcelas da origem            |
| `amount`, `items`, `externalId`, `description`, `metaData` | Livres. Não há limite de valor em relação à origem          |
| `subMerchant`, `split`                                     | Livres                                                      |
| `card.number`, `card.cvv`, `card.holderName`, expiração    | Proibidos. A API responde `400`                             |
| `card.cardTokenId`                                         | Proibido. A API responde `400`                              |
| `customer`                                                 | Proibido. A transação usa o customer da origem              |
| `threeDSData`                                              | Proibido. A cobrança é iniciada pelo portador (CIT) sem 3DS |

## Ciclo de vida do token

| Situação                                      | Efeito                                                       |
| --------------------------------------------- | ------------------------------------------------------------ |
| Emissão                                       | Válido por 15 minutos. O limite está em `upsell.expiresAt`   |
| Cobrança aprovada                             | Token consumido                                              |
| Cobrança recusada pela adquirente             | Token consumido. Peça o cartão de novo para tentar outra vez |
| Falha técnica antes da resposta da adquirente | Token liberado. Repita o request                             |
| Estorno ou chargeback da origem               | Token revogado. A API responde `404`                         |
| Prazo de 15 minutos encerrado                 | Token expirado. A API responde `410`                         |

<Warning>
  O token é de uso único, e uma recusa da adquirente também o consome. Reenviar o mesmo token resulta em `409`.
</Warning>

## Erros

| Status | `code`                   | Quando acontece                                           | O que fazer                                          |
| ------ | ------------------------ | --------------------------------------------------------- | ---------------------------------------------------- |
| `400`  | sem `code`               | Payload viola as regras da tabela acima                   | Corrija o payload                                    |
| `404`  | `UPSELL_TOKEN_NOT_FOUND` | Token inexistente, revogado por estorno ou de outra conta | Peça o cartão de novo                                |
| `409`  | `UPSELL_TOKEN_USED`      | Token já consumido ou em uso em request concorrente       | Não repita. Consulte a transação de upsell já criada |
| `409`  | `UPSELL_ORIGIN_NOT_PAID` | Origem deixou de estar `paid` antes da cobrança           | Peça o cartão de novo                                |
| `410`  | `UPSELL_TOKEN_EXPIRED`   | Prazo de 15 minutos encerrado                             | Peça o cartão de novo                                |

Formato do erro:

```json theme={null}
{
  "code": "UPSELL_TOKEN_USED",
  "message": "Este token de upsell já foi utilizado."
}
```

Veja [Erros](/errors) para os demais códigos HTTP.

## Idempotência

Envie [`x-idempotency-key`](/authentication#idempotencia) nos dois requests.

| Request | Efeito do replay com a mesma chave e o mesmo payload                                |
| ------- | ----------------------------------------------------------------------------------- |
| Origem  | Retorna a mesma transação e o mesmo `upsell.token`, se o token ainda estiver válido |
| Upsell  | Retorna a mesma transação de upsell. O token já consumido não bloqueia o replay     |

Sem `x-idempotency-key`, um retry do upsell após timeout pode responder `409 UPSELL_TOKEN_USED`. Nesse caso, localize a transação de upsell pelo `externalId` antes de pedir o cartão de novo.

## Segurança

* O token é opaco e não contém dados do cartão.
* Trate o token como credencial: não grave em logs nem exponha em URLs.
* O token só vale para a conta que criou a origem. Um token de outra conta responde `404`.
* A cobrança de upsell usa o cartão guardado no Cofre da UvviPay. O `cardTokenId` interno não é exposto ao integrador.

## Próximos passos

<CardGroup cols={2}>
  <Card title="Criar pagamento" href="/api-reference/payments/create">
    Referência completa de POST /v1/payments
  </Card>

  <Card title="Cofre" href="/card-tokenization">
    Guarde o cartão para cobranças futuras fora da janela de 15 minutos
  </Card>

  <Card title="Idempotência" href="/authentication#idempotencia">
    Retry seguro com x-idempotency-key
  </Card>

  <Card title="Webhooks" href="/webhooks">
    Receba o status da transação de upsell
  </Card>
</CardGroup>
