# Rewards & Redemptions

Rewards let you turn part of a payment into credits that a customer can spend on a later one. The credits sit on a reward account tied to your marketplace and follow the customer who earned them only that customer can redeem them, and the credits expire after a set period (12 months by default).

The flow has two sides. You **generate** rewards by attaching a `reward` block to one or more splits inside a capture. You **redeem** them later by sending a `reward_redemption` block on the next capture. In between, you can read what each customer has on hand through the Customer endpoints. And if a payment is refunded, its rewards are unwound the *Reverting a payment that used rewards* section below covers that in detail.

## How rewards work

The reward account is **marketplace-scoped**. It's provisioned during setup there's no API to create one and you reference it by its UUID inside every reward generation and redemption.

Each entry on a reward account is tied to one customer. When a payment includes a split with a `reward` block, the customer named at the top of the request is credited for the amount in `reward.value`. That credit becomes part of the customer's available balance until it's redeemed or expires.

Redemption works the other way around: on a later capture for the same customer, send a `reward_redemption` block and the matching value is debited from the balance and applied against the payment.

### Supported payment methods

Rewards depend on a payment that confirms immediately, so both sides of the flow are only available on these methods:

| Payment Method | Code | Rewards |
|  --- | --- | --- |
| Credit/Debit Card | `CC` | ✅ |
| MB WAY | `MBW` | ✅ |
| Apple Pay | `AP` | ✅ |
| Google Pay | `GP` | ✅ |
| Samsung Pay | `SW` | ✅ |
| In-person Payments | `IPP` | ✅ |
| Multibanco | `MB` | ❌ |
| Virtual IBAN | `VI` | ❌ |
| Direct Debit | `DD` | ❌ |


## Generating rewards on a capture

Attach a `reward` block to any split inside the `capture.splits[]` array. The customer at the top of the request is the one credited.

```bash
curl -X POST 'https://api.test.easypay.pt/2.0/single' \
  -H 'AccountId: YOUR_ACCOUNT_ID' \
  -H 'ApiKey: YOUR_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"
          }
        }
      ]
    }
  }'
```

What each reward field does:

- `reward.account.id`: the marketplace's reward account that holds the credit. Use the same UUID across every reward you want pooled on the same balance.
- `reward.value`: how much of that split becomes a credit. It doesn't change what the customer pays at the till; it's an amount the marketplace contributes off the split's value.
- `reward.expiration_date`: optional. Defaults to 12 months from the transaction date, and cannot be set beyond the one-year maximum. Past this date the credit can no longer be redeemed.


## Reading a customer's balance

Two endpoints expose what a customer has.

`GET /customer/{id}` returns the customer plus a `reward_balances` array with one entry per reward account they hold credits on. This is the authoritative available balance it reflects redemptions, refunds and reverts. Use it when you want a quick total before showing a "Use your rewards" option at 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
        }
    ]
}
```

`available_balance` can be **negative**. This happens when cashback the customer already spent is taken back, for example because the payment that earned it was refunded (see *When spent cashback is taken back* below). A negative balance is the amount of cashback the customer still owes, and it means none of their credits has anything left to spend.

`GET /customer/{id}/rewards` lists every individual movement on the ledger, newest first. It supports filtering by account, type, expiry, and creation date. Each movement carries a `type`:

- `REWARD`: a credit was earned.
- `REDEMPTION`: a credit was spent on a payment.
- `REWARD_EXPIRATION`: a credit expired.
- `REWARD_REVERSAL`: an earned credit was clawed back (the earning payment was refunded).
- `REWARD_RESTORATION`: a spent credit was given back (a redemption was reverted).


```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: YOUR_ACCOUNT_ID' \
  -H 'ApiKey: YOUR_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 the ledger when you need a per-movement audit trail for example, when a customer asks where a particular credit came from, or when you want to warn about credits about to expire. For the current available total, prefer `GET /customer/{id}` it is the live figure. The movements always add up to that balance, including when it is negative.

## Redeeming rewards on a capture

To spend credits, add a `reward_redemption` block to the capture you're paying.

**Where the block goes depends on the endpoint:**

- **`POST /single`** (paying in a single step) the block goes **inside the `capture` object**: `capture.reward_redemption`.
- **`POST /capture/{id}`** (capturing an earlier authorisation) the block goes at the **capture body root**: `reward_redemption`.


Redemption is **per reward account**: one `reward_redemption` block targets one account UUID. The redeemed value must not exceed the available balance on that account, nor the value of the payment itself. A redemption above the available balance is refused with **HTTP 412** and `insufficient reward balance: available X.XX, requested Y.XX`. While the balance is zero or negative every redemption is refused, so only offer a "Use your rewards" option when `available_balance` is above zero.

**Partial redemption on `POST /single`** apply only some of the balance:

```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
        }
    }
}
```

**Full redemption on `POST /single`** apply the entire payment value:

```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
        }
    }
}
```

The `value` you send is the **gross** the amount before rewards. The customer is charged `value − reward_redemption.value` through the chosen payment method (the **rails** amount); the redeemed part is covered by the balance. The redeemed amount is debited from the balance immediately and shows up as a `REDEMPTION` entry on the ledger.

When the redemption equals the whole payment value (**full redemption**), the rails amount is `0`: **nothing is charged at the payment gateway**, and the payment completes on creation with `payment_status = paid`.

## Earning and spending in the same request

A single payment can do both. On `POST /single` the `reward_redemption` block sits inside `capture` and reduces the amount the customer pays; the `capture.splits[].reward` blocks credit the customer based on the splits. They don't interact you can redeem €10 from a previous balance and earn €5 of fresh credits in the same capture.

The `Sale with Splits and Reward Redemption` example on [POST /single](/openapi#tag/Single-Payment) shows the combined shape end-to-end.

## What happens when credits expire

Credits a customer never spends don't linger. A scheduled job finds credits whose expiry date has passed and that still have a positive balance, marks them expired (available balance → 0), and pays the leftover money out from the reward account to the marketplace's payout account. From that point the customer can no longer spend the credit, and the movement appears as `REWARD_EXPIRATION` on the customer ledger.

Only the part of a credit that is still unspent expires. Value that was spent, or that went to pay back cashback the customer owed, never expires and is never paid out.

## When spent cashback is taken back

Sometimes cashback is taken back after the customer has already spent it:

- **The payment that earned it is refunded.** The earned credit is clawed back (see *Reverting a payment that used rewards* below).
- **The payment that earned it is charged back.** The earned credit is clawed back the same way as on a refund.
- **A payment is confirmed late.** When a payment that redeemed cashback fails or is deleted, its redemption goes back to the customer's balance. If the payment method later confirms it anyway, the payment keeps its full redemption, even if the customer has spent that cashback on another payment in the meantime.


In all three cases the amount is taken first from the credit it belongs to, then from the customer's other credits on the same reward account, soonest expiry first. Only what the credits can't cover makes the balance negative. Each credit touched appears as its own movement on the ledger (`REWARD_REVERSAL` for a clawback).

**Example.** A customer earns €4.00 on order 1 (expiring in June) and €2.00 on order 2 (expiring in December). They spend €3.00 on order 3, which uses the June credit first, leaving €1.00 + €2.00 and a balance of €3.00. Order 1 is then refunded in full, so its €4.00 must be clawed back: €1.00 comes from the June credit, €2.00 from the December credit, and the remaining €1.00 can't be covered. The balance is now **−€1.00**, and both credits are spent.

### Paying back a negative balance

The debt stays on the balance until money comes back to the customer. The next movement that adds money pays the debt first:

- a new credit earned on a later payment (`REWARD`);
- cashback restored by a refund or a chargeback of a payment that redeemed it (`REWARD_RESTORATION`);
- a redemption given back because its payment failed or was deleted. No new movement is written: the `REDEMPTION` entry simply leaves the ledger.


The movement still shows its full amount, and the balance rises by that full amount. In the example, if the customer then earns €3.00, the ledger shows a `REWARD` of €3.00 and the balance goes from −€1.00 to €2.00. Only €2.00 of the new credit can be spent, and only those €2.00 can expire.

## Reverting a payment that used rewards

When you refund a capture that carried rewards, the rewards have to be unwound too the earned credits clawed back, and any redeemed cashback given back to the customer. You control this through a **`revert`** block on the refund.

A refund with a `revert` block has three moving parts:

- **`value`** the **gross** you are reverting (rails + any redeemed cashback). The rails money actually paid back and the cashback restored are derived from this and the reversal field below.
- **`revert.mode`** either `TOTAL` (revert the whole capture) or `PARTIAL` (revert specific splits).
- **`revert.reward_redemption_reversal`** *(optional)* how much redeemed **cashback** to restore to the customer's wallet in this refund. The rails money refunded to the payer is `value − reward_redemption_reversal`.


### Total revert

A `TOTAL` revert reverses every split on the capture. The `value` must equal the capture's gross. If you omit `reward_redemption_reversal`, it **defaults to the remaining redemption** (all cashback not yet restored by an earlier refund), and the rails refunded is the rest.

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

### Partial revert

A `PARTIAL` revert targets one or more splits by id. The `value` must equal the **sum of the targeted splits' gross**. The `reward_redemption_reversal` is a **free amount** you choose, drawn from the capture's redemption **pool** it is *not* tied to the targeted split's proportional share, so a single partial revert can restore the entire redemption if you want.

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

Here €12 of gross is reverted on the targeted split, €5 of cashback is restored to the customer's wallet, and `12 − 5 = €7` of rails money is refunded to the payer.

### How the cashback pool works

`reward_redemption_reversal` restores cashback from a shared **pool** the total redeemed on the capture, minus whatever earlier refunds already restored. Across a sequence of partial refunds you can never restore more than was originally redeemed. When you restore cashback, it lands back on the customer's available balance immediately (visible via `GET /customer/{id}`) as a `REWARD_RESTORATION` movement, and the earned credits on any reverted split are clawed back as `REWARD_REVERSAL`. If the customer's balance is negative, restored cashback pays that debt first.

The pool starts from the full value redeemed on the capture, even when the payment was confirmed late after its cashback had been spent elsewhere. A first `TOTAL` revert of such a payment can therefore restore its whole redemption.

### Chargebacks on rewarded payments

A chargeback unwinds rewards the same way a refund does, with no request on your side. On a capture that earned rewards, the earned credits are clawed back (`REWARD_REVERSAL`). On a capture that redeemed cashback, the redeemed cashback is restored to the customer (`REWARD_RESTORATION`). A full chargeback restores all of it, and a partial chargeback restores the same share of the redemption as the share of the payment charged back. A payment fully paid with cashback charges nothing to a card, so it can't be charged back.

### Balance check before a revert

If the merchant-balance check is enabled for your client (`SPLIT_REFUND_BALANCE_CHECK_ENABLED`), a revert is only accepted when the accounts being debited hold enough balance to cover it. If they don't, the revert is **refused** (HTTP 412) with `refund value can not exceed the balance of the debited accounts, try again later` try again once the balance is available.

### Validation errors

Reward data on an unsupported **method** is refused with **HTTP 412**. The field is named as the endpoint receives it:

- `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`
- Any of the three, when the reward is on a split: `capture.splits[0].reward: not supported for payment method DD`


Reward data on an unsupported **type** is refused the same way:

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


Reverts return **HTTP 412** with one of these messages when a rule is broken:

- `revert.reward_redemption_reversal cannot be negative`: the reversal is below 0.
- `revert.reward_redemption_reversal: the capture has no reward redemption to revert`: you sent a reversal but the capture never redeemed.
- `revert.reward_redemption_reversal cannot exceed the refund value`: the reversal is larger than `value`.
- `revert.reward_redemption_reversal exceeds the remaining redemption (X.XX)`: the reversal is larger than the cashback still available in the pool.
- `revert.reward_redemption_reversal must equal the remaining redemption (X.XX) on a TOTAL revert`: on a TOTAL revert the reversal isn't free; it must clear the whole remaining redemption.
- `revert.splits: the value of the splits to revert X.XX is different than the requested refund value V`: on a PARTIAL revert, `value` doesn't match the targeted splits' gross.
- `the rails refund (X.XX) exceeds the remaining refundable rails value (Y.XX)`: the rails money (`value − reversal`) exceeds what's still refundable.
- `revert_mode TOTAL is not supported for partial refund`: TOTAL sent with a `value` below the capture gross.
- `revert.mode PARTIAL requires at least one split id to be specified`: PARTIAL sent with no `revert.splits`.


To read back exactly what a revert restored, `GET /refund/{id}` exposes the `reward_redemption_reversal` object with `value` (restored in this refund) and `value_remaining` (cashback still restorable on the capture), alongside the embedded capture's `reward_redemption` and per-split `reward` objects. `GET /capture/{id}` likewise exposes the capture's `reward_redemption` and each split's `reward`.

## Next Steps

- [Customer endpoints](/openapi#tag/Customer): balance and ledger reference
- [Single Payment](/openapi#tag/Single-Payment): earn and redeem in one step
- [Captures](/openapi#tag/Captures): earn and redeem on a separate capture
- [Refunds](/openapi#tag/Refunds): revert a payment that used rewards
- [Authorizations & Captures](/docs/guides/authorizations-captures): when to split auth and capture