# Recompensas e Resgates

As recompensas permitem-lhe transformar parte de um pagamento em créditos que o cliente pode gastar num pagamento posterior. Os créditos ficam numa conta de recompensas associada ao seu marketplace e seguem o cliente que os ganhou apenas esse cliente os pode resgatar, e os créditos expiram após um período definido (12 meses por defeito).

O fluxo tem dois lados. **Gera** recompensas anexando um bloco `reward` a um ou mais splits dentro de uma captura. **Resgata-as** mais tarde enviando um bloco `reward_redemption` na captura seguinte. Entre essas duas operações, pode consultar o que cada cliente tem disponível através dos endpoints de Customer. E se um pagamento for reembolsado, as suas recompensas são revertidas a secção *Reverter um pagamento que usou recompensas*, mais abaixo, explica isto em detalhe.

## Como funcionam as recompensas

A conta de recompensas é **por marketplace**. É provisionada na configuração inicial não existe API para a criar e é referenciada pelo seu UUID em cada geração e resgate de recompensa.

Cada entrada numa conta de recompensas está associada a um cliente. Quando um pagamento inclui um split com um bloco `reward`, o cliente identificado no topo do pedido é creditado pelo valor em `reward.value`. Esse crédito passa a fazer parte do saldo disponível do cliente até ser resgatado ou expirar.

O resgate funciona no sentido inverso: numa captura posterior para o mesmo cliente, envia um bloco `reward_redemption` e o valor correspondente é debitado do saldo e aplicado ao pagamento.

### Métodos de pagamento suportados

As recompensas dependem de um pagamento que confirma imediatamente, pelo que ambos os lados do fluxo só estão disponíveis nestes métodos:

| Método de Pagamento | Código | Recompensas |
|  --- | --- | --- |
| Cartão de Crédito/Débito | `CC` | ✅ |
| MB WAY | `MBW` | ✅ |
| Apple Pay | `AP` | ✅ |
| Google Pay | `GP` | ✅ |
| Samsung Pay | `SW` | ✅ |
| Pagamentos Presenciais | `IPP` | ✅ |
| Multibanco | `MB` | ❌ |
| IBAN Virtual | `VI` | ❌ |
| Débito Direto | `DD` | ❌ |


## Gerar recompensas numa captura

Anexe um bloco `reward` a qualquer split dentro do array `capture.splits[]`. O cliente identificado no topo do pedido é o que recebe o crédito.

```bash
curl -X POST 'https://api.test.easypay.pt/2.0/single' \
  -H 'AccountId: SEU_ACCOUNT_ID' \
  -H 'ApiKey: SUA_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "sale",
    "method": "cc",
    "value": 50.00,
    "currency": "EUR",
    "customer": {
      "id": "649e88cf-0b78-4c36-8f99-33f5ebb812a1",
      "name": "John Doe",
      "email": "john.doe@example.com"
    },
    "capture": {
      "descriptive": "Order #1029",
      "splits": [
        {
          "split_descriptive": "Marketplace seller",
          "account": {
            "id": "7e697e0c-c2bf-422a-9535-ab0b750bb832"
          },
          "value": 50.00,
          "reward": {
            "account": {
              "id": "458b2fc4-3092-4de3-abd4-fe1600c09420"
            },
            "value": 2.50,
            "expiration_date": "2027-04-02"
          }
        }
      ]
    }
  }'
```

O que cada campo de `reward` faz:

- `reward.account.id`: a conta de recompensas do marketplace que detém o crédito. Use o mesmo UUID em todas as recompensas que pretende agregar no mesmo saldo.
- `reward.value`: quanto desse split se transforma em crédito. Não altera o que o cliente paga no momento; é um valor que o marketplace contribui a partir do valor do split.
- `reward.expiration_date`: opcional. Por defeito são 12 meses a partir da data da transação, e não pode ser definida para além do máximo de um ano. Depois desta data o crédito já não pode ser resgatado.


## Ler o saldo de um cliente

Dois endpoints expõem o que um cliente tem.

`GET /customer/{id}` devolve o cliente juntamente com um array `reward_balances` com uma entrada por cada conta de recompensas em que tem créditos. Este é o saldo disponível autoritativo reflete resgates, reembolsos e reversões. Use-o quando quiser um total rápido antes de apresentar uma opção "Usar as suas recompensas" no checkout.

```json
{
    "id": "649e88cf-0b78-4c36-8f99-33f5ebb812a1",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "reward_balances": [
        {
            "account_id": "458b2fc4-3092-4de3-abd4-fe1600c09420",
            "available_balance": 12.5
        }
    ]
}
```

`GET /customer/{id}/rewards` lista todos os movimentos individuais no histórico, do mais recente para o mais antigo. Suporta filtragem por conta, tipo, expiração e data de criação. Cada movimento tem um `type`:

- `REWARD`: foi ganho um crédito.
- `REDEMPTION`: um crédito foi gasto num pagamento.
- `REWARD_EXPIRATION`: um crédito expirou.
- `REWARD_REVERSAL`: um crédito ganho foi recuperado (o pagamento que o gerou foi reembolsado).
- `REWARD_RESTORATION`: um crédito gasto foi devolvido (um resgate foi revertido).


```bash
curl 'https://api.test.easypay.pt/2.0/customer/649e88cf-0b78-4c36-8f99-33f5ebb812a1/rewards?type[]=REWARD&account_id[]=458b2fc4-3092-4de3-abd4-fe1600c09420' \
  -H 'AccountId: SEU_ACCOUNT_ID' \
  -H 'ApiKey: SUA_API_KEY'
```

```json
{
    "metadata": { "next_cursor": null, "count": 2 },
    "data": [
        {
            "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "account_id": "458b2fc4-3092-4de3-abd4-fe1600c09420",
            "type": "REWARD",
            "amount": 10.0,
            "expiration_date": "2027-01-15",
            "capture": { "id": "c6056234-a3f9-42de-b944-3ed793fcb6bb" },
            "created_at": "2026-01-15T10:30:00Z"
        },
        {
            "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
            "account_id": "458b2fc4-3092-4de3-abd4-fe1600c09420",
            "type": "REWARD",
            "amount": 2.5,
            "expiration_date": "2027-04-02",
            "capture": { "id": "b6f53027-0478-4728-9269-8bcc0f8088ea" },
            "created_at": "2026-04-02T09:12:00Z"
        }
    ]
}
```

Use o histórico quando precisar de um registo de auditoria por movimento por exemplo, quando um cliente pergunta de onde veio um determinado crédito, ou quando quer avisar sobre créditos prestes a expirar. Para o total disponível atual, prefira `GET /customer/{id}` é o valor real e atualizado.

## Resgatar recompensas numa captura

Para gastar créditos, adicione um bloco `reward_redemption` à captura que está a pagar.

**A localização do bloco depende do endpoint:**

- **`POST /single`** (pagamento num único passo) o bloco fica **dentro do objeto `capture`**: `capture.reward_redemption`.
- **`POST /capture/{id}`** (captura de uma autorização anterior) o bloco fica na **raiz do corpo da captura**: `reward_redemption`.


O resgate é **por conta de recompensas**: um bloco `reward_redemption` aponta para um UUID de conta. O valor resgatado não pode exceder o saldo disponível nessa conta, nem o valor do próprio pagamento.

**Resgate parcial em `POST /single`** aplicar apenas parte do saldo:

```json
{
    "type": "sale",
    "method": "CC",
    "value": 50.0,
    "customer": {
        "id": "649e88cf-0b78-4c36-8f99-33f5ebb812a1"
    },
    "capture": {
        "reward_redemption": {
            "account": {
                "id": "458b2fc4-3092-4de3-abd4-fe1600c09420"
            },
            "value": 5.0
        }
    }
}
```

**Resgate total em `POST /single`** aplicar todo o valor do pagamento:

```json
{
    "type": "sale",
    "method": "CC",
    "value": 50.0,
    "customer": {
        "id": "649e88cf-0b78-4c36-8f99-33f5ebb812a1"
    },
    "capture": {
        "reward_redemption": {
            "account": {
                "id": "458b2fc4-3092-4de3-abd4-fe1600c09420"
            },
            "value": 50.0
        }
    }
}
```

O `value` que envia é o **bruto** o valor antes das recompensas. O cliente é cobrado por `value − reward_redemption.value` através do método de pagamento escolhido (o valor de **rails**); a parte resgatada é coberta pelo saldo. O valor resgatado é debitado do saldo imediatamente e aparece como uma entrada `REDEMPTION` no histórico.

Quando o resgate iguala o valor total do pagamento (**resgate total**), o valor de rails é `0`: **nada é cobrado no gateway de pagamento** e o pagamento é concluído na criação com `payment_status = paid`.

## Ganhar e gastar no mesmo pedido

Um único pagamento pode fazer ambos. Em `POST /single`, o bloco `reward_redemption` fica dentro de `capture` e reduz o valor que o cliente paga; os blocos `capture.splits[].reward` creditam o cliente com base nos splits. Não interagem entre si pode resgatar €10 de um saldo anterior e ganhar €5 de novos créditos na mesma captura.

O exemplo `Sale with Splits and Reward Redemption` em [POST /single](/openapi#tag/Single-Payment) mostra a forma combinada de ponta a ponta.

## O que acontece quando os créditos expiram

Os créditos que um cliente nunca gasta não ficam indefinidamente. Um processo agendado encontra os créditos cuja data de expiração já passou e que ainda têm saldo positivo, marca-os como expirados (saldo disponível → 0) e paga o valor remanescente da conta de recompensas para a conta de payout do marketplace. A partir desse momento o cliente já não pode gastar o crédito, e o movimento aparece como `REWARD_EXPIRATION` no histórico do cliente.

## Reverter um pagamento que usou recompensas

Quando reembolsa uma captura que tinha recompensas, também é preciso revertê-las os créditos ganhos são recuperados, e qualquer cashback resgatado é devolvido ao cliente. Controla isto através de um bloco **`revert`** no reembolso.

Um reembolso com um bloco `revert` tem três partes:

- **`value`** o **bruto** que está a reverter (rails + qualquer cashback resgatado). O valor de rails efetivamente devolvido e o cashback restaurado são derivados a partir deste e do campo de reversão abaixo.
- **`revert.mode`** ou `TOTAL` (reverter a captura inteira) ou `PARTIAL` (reverter splits específicos).
- **`revert.reward_redemption_reversal`** *(opcional)* quanto do **cashback** resgatado devolver ao saldo do cliente neste reembolso. O valor de rails devolvido ao pagador é `value − reward_redemption_reversal`.


### Reversão total

Uma reversão `TOTAL` reverte todos os splits da captura. O `value` tem de ser igual ao bruto da captura. Se omitir `reward_redemption_reversal`, este assume por defeito o **resgate remanescente** (todo o cashback ainda não restaurado por um reembolso anterior), e o valor de rails devolvido é o restante.

```json
{
    "value": 50.0,
    "revert": {
        "mode": "TOTAL"
    }
}
```

### Reversão parcial

Uma reversão `PARTIAL` visa um ou mais splits por id. O `value` tem de ser igual à **soma do bruto dos splits visados**. O `reward_redemption_reversal` é um **valor livre** à sua escolha, retirado da **pool** de resgate da captura não está ligado à quota proporcional do split visado, pelo que uma única reversão parcial pode restaurar todo o resgate, se assim o pretender.

```json
{
    "value": 12.0,
    "revert": {
        "mode": "PARTIAL",
        "reward_redemption_reversal": 5.0,
        "splits": [{ "id": "3f2a9c10-7b4e-4d21-9a55-0c1e2f3a4b5c" }]
    }
}
```

Aqui, €12 de bruto são revertidos no split visado, €5 de cashback são restaurados ao saldo do cliente, e `12 − 5 = €7` de valor de rails são devolvidos ao pagador.

### Como funciona a pool de cashback

O `reward_redemption_reversal` restaura cashback a partir de uma **pool** partilhada o total resgatado na captura, menos o que reembolsos anteriores já restauraram. Ao longo de uma sequência de reembolsos parciais nunca pode restaurar mais do que o que foi originalmente resgatado. Quando restaura cashback, este volta ao saldo disponível do cliente imediatamente (visível em `GET /customer/{id}`) como um movimento `REWARD_RESTORATION`, e os créditos ganhos em qualquer split revertido são recuperados como `REWARD_REVERSAL`.

### Verificação de saldo antes de uma reversão

Se a verificação de saldo do comerciante estiver ativa para o seu cliente (`SPLIT_REFUND_BALANCE_CHECK_ENABLED`), uma reversão só é aceite quando as contas a debitar têm saldo suficiente para a cobrir. Se não tiverem, a reversão é **recusada** (HTTP 412) com `refund value can not exceed the balance of the debited accounts, try again later` tente novamente quando o saldo estiver disponível.

### Erros de validação

Dados de recompensa num **método** não suportado são recusados com **HTTP 412**. O campo é identificado tal como o endpoint o recebe:

- `POST /single`: `capture.reward_redemption: not supported for payment method MB`
- `POST /capture/{id}`: `reward_redemption: not supported for payment method DD`
- `POST /checkout`: `payment.capture.reward_redemption: not supported for payment method MB`
- Em qualquer dos três, quando a recompensa está num split: `capture.splits[0].reward: not supported for payment method DD`


Dados de recompensa num **tipo** não suportado são recusados da mesma forma:

- `POST /subscription`: `capture.reward_redemption: not supported for subscriptions`
- `POST /checkout`: `payment.capture.reward_redemption: only supported when type is single`


As reversões devolvem **HTTP 412** com uma destas mensagens quando uma regra é violada:

- `revert.reward_redemption_reversal cannot be negative`: a reversão é inferior a 0.
- `revert.reward_redemption_reversal: the capture has no reward redemption to revert`: enviou uma reversão mas a captura nunca resgatou.
- `revert.reward_redemption_reversal cannot exceed the refund value`: a reversão é superior ao `value`.
- `revert.reward_redemption_reversal exceeds the remaining redemption (X.XX)`: a reversão é superior ao cashback ainda disponível na pool.
- `revert.reward_redemption_reversal must equal the remaining redemption (X.XX) on a TOTAL revert`: numa reversão TOTAL a reversão não é livre; tem de esgotar todo o resgate remanescente.
- `revert.splits: the value of the splits to revert X.XX is different than the requested refund value V`: numa reversão PARTIAL, o `value` não corresponde ao bruto dos splits visados.
- `the rails refund (X.XX) exceeds the remaining refundable rails value (Y.XX)`: o valor de rails (`value − reversal`) excede o que ainda é reembolsável.
- `revert_mode TOTAL is not supported for partial refund`: TOTAL enviado com um `value` inferior ao bruto da captura.
- `revert.mode PARTIAL requires at least one split id to be specified`: PARTIAL enviado sem `revert.splits`.


Para consultar exatamente o que uma reversão restaurou, `GET /refund/{id}` expõe o objeto `reward_redemption_reversal` com `value` (restaurado neste reembolso) e `value_remaining` (cashback ainda restaurável na captura), juntamente com os objetos `reward_redemption` e `reward` (por split) da captura associada. `GET /capture/{id}` expõe igualmente o `reward_redemption` da captura e o `reward` de cada split.

## Próximos Passos

- [Endpoints de Customer](/openapi#tag/Customer): referência de saldo e histórico
- [Single Payment](/openapi#tag/Single-Payment): ganhar e resgatar num único passo
- [Captures](/openapi#tag/Captures): ganhar e resgatar numa captura separada
- [Refunds](/openapi#tag/Refunds): reverter um pagamento que usou recompensas
- [Autorizações e Capturas](/docs/guides/authorizations-captures): quando separar autorização e captura