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

# Buy a Voucher

> The Voucher purchase flow: run it in the WebApp or call it yourself, create an order, pay it, wait for the Vouchers to be issued, then manage them over their life.

A [Voucher](/glossary#voucher) Offer lets a [Consumer](/glossary#consumer) buy a gift card from a partner [Brand](/glossary#brand) for less than its face value. The Consumer places an order from the bank app, pays it, and receives one or more Vouchers with their codes and a PDF. Paylead sources the Vouchers from its supplier and handles the money.

For the [Offer](/glossary#offer) catalog these Vouchers come from, see [The Offer catalog](/program/user-journey/guides/work-with-offers).

<Warning>
  The Voucher API is in **beta**. The flow below is stable, the exact field names are still subject to change. Only `GET /perks/consumers/{consumer_id}/voucher-orders` is published in the [API reference](/program/api/overview) today.
</Warning>

## Two ways to run the purchase

Which route you take depends on what the Program enabled. The rest of this page describes the **full API** integration: your app renders every screen and calls the endpoints itself.

The other route is the **WebApp**. [Easy Vouchers (EVO)](/glossary#easy-vouchers-evo) is a WebApp option that renders the purchase and the Voucher wallet for you, so your app opens a page URL and supplies the native capabilities the flow needs. The model below still applies in that mode: same order statuses, same discount modes, same refunds. What the WebApp renders across the whole journey is listed in [What the WebApp handles](/program/user-journey/concepts/web-app#what-the-webapp-handles).

| What you provide in WebApp mode                                                                 | Where it is documented                                              |
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| The entry points: buy from an Offer, the Voucher wallet, one Voucher, the archive               | [Page URLs](/program/webapp/page-urls#vouchers), paths under `/acb` |
| `file.download` to hand over the Voucher PDF, `clipboard.write` to let the Consumer copy a code | [Mobile Bridge](/program/webapp/bridge/overview)                    |
| The charge itself, when the Program is set to delegated payment                                 | [Seamless Payment](/program/user-journey/guides/seamless-payment)   |

Both routes share the prerequisites below, since enrollment and the debit account are yours either way.

## Before you start

| Prerequisite                          | Endpoint                                             | When                                                                             |
| ------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------- |
| The Consumer is enrolled in Perks     | `POST /perks/consumers`                              | Always. Every Voucher route answers `PL-404-02 Consumer Not Enrolled` otherwise. |
| A debit account is declared           | `POST /perks/consumers/{consumer_id}/accounts/payin` | Delegated payment only.                                                          |
| A payout account and KYC are in place | `POST .../accounts/payment`, `PUT .../kyc`           | Only to pay out a [Cashback](/glossary#cashback) discount, never to buy.         |

The `consumer_id` is your own bank-side identifier. Paylead resolves it internally.

## What a Voucher Offer carries

Voucher Offers are the entries typed `VOUCHER` in the catalog, and their `perks.voucher` block defines what the Consumer may buy. The fields are listed in [What an Offer carries](/program/user-journey/guides/work-with-offers#what-an-offer-carries).

An Offer is either **fixed amounts** (pick from `amounts`) or a **free amount** (`min_amount` to `max_amount`, respecting `amount_step`). An order that breaks those constraints is rejected with `PL-400-09` or `PL-400-12`.

## The purchase flow

<Steps>
  <Step title="Create the order">
    Call `POST /perks/consumers/{consumer_id}/offers/{offer_id}/vouchers/orders`. The body carries `amount_selections`, one entry per face value and quantity, plus `payin_account_id` in delegated payment. One order buys one or more Vouchers from a single Offer.

    ```json theme={null}
    {
      "amount_selections": [
        { "amount": 50, "quantity": 1 },
        { "amount": 25, "quantity": 2 }
      ]
    }
    ```

    The response returns the `order_id`. The order starts in `CREATED` and no Voucher exists yet.
  </Step>

  <Step title="Pay the order">
    The payment model is a [Program](/glossary#program) setting, not a per-call choice. Both models use `POST /perks/consumers/{consumer_id}/vouchers/orders/{order_id}/payments`, with a different body and a different follow-up.

    <Tabs>
      <Tab title="Paylead-hosted payment">
        Send `return_url`, `language` (ISO 639-1) and `card_type` (`CB_VISA_MASTERCARD`, `AMEX`, `MAESTRO` or `BCMC`). The response carries a `redirect_url`: send the Consumer to that payment webview.
      </Tab>

      <Tab title="Delegated payment">
        The bank charges the Consumer on its own platform, so the body is empty. The response carries a `failure_reason`, `null` when the payment is accepted.

        When the outcome is not immediate, return the result through the callback described in [Seamless Payment](/program/user-journey/guides/seamless-payment#step-2-payment-result-callback-asynchronous-only). Without that call, the order moves to `PAYMENT_FAILED` once the configured delay expires.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Confirm the payment (Paylead-hosted only)">
    When the Consumer comes back from the webview, call `POST /perks/consumers/{consumer_id}/vouchers/orders/{order_id}/payments/confirm`. Paylead checks the pay-in with the payment provider.

    A `payin_psp_status` of `SUCCEEDED` moves the order to `CONFIRMED` and returns the `vouchers` list. Any other status returns the provider status and an empty list. Calling `confirm` again on an order already confirmed is safe, which covers a Consumer refreshing the confirmation screen.
  </Step>

  <Step title="Wait for the Vouchers to be issued">
    `CONFIRMED` does not mean usable. The supplier issues the Vouchers asynchronously, and the PDF and the codes stay empty until the order reaches `READY`. Show a waiting state and poll `GET /perks/consumers/{consumer_id}/vouchers?order_id={order_id}` until the status changes. Paylead chases the supplier in the background, so no action is needed on your side.
  </Step>

  <Step title="Deliver the Vouchers">
    Once the order is `READY`, display the face value, the expiry date, the Brand and the codes, and offer the PDF through `GET /perks/consumers/{consumer_id}/vouchers/{voucher_id}/pdf`.
  </Step>
</Steps>

## Order statuses

A Voucher reports the status of the order it came from, in its own `status` field.

| Status           | What it means                                                               | What to display |
| ---------------- | --------------------------------------------------------------------------- | --------------- |
| `CREATED`        | The order is valid, the payment has not gone through.                       | In progress     |
| `CONFIRMED`      | The payment succeeded, the supplier is issuing the Vouchers.                | Being delivered |
| `READY`          | The Vouchers are issued and usable.                                         | Available       |
| `PAYMENT_FAILED` | The payment was refused or failed.                                          | Payment failed  |
| `CANCELLED`      | No payment was ever attempted, or the pay-in stayed pending past the delay. | Cancelled       |

Two attributes live outside that flow: `archived_at`, set when the Consumer archives the Voucher or when it expires, and `refunded_at`, set when the Voucher is refunded. A daily Paylead job archives expired Vouchers and settles orders left in `CREATED`: a successful pay-in confirms the order, a failed one moves it to `PAYMENT_FAILED`, and a pay-in that never lands moves it to `CANCELLED`.

## How the discount is served

The Offer decides how the Consumer gets the discount, which changes what they pay.

| Mode        | Price paid                       | What the Consumer gets                                           |
| ----------- | -------------------------------- | ---------------------------------------------------------------- |
| `IMMEDIATE` | Face value minus `discount_rate` | The discount is taken off at payment.                            |
| `CASHBACK`  | Full face value                  | A [Reward](/glossary#reward) of type `EVO_CASHBACK` is credited. |

In `CASHBACK` mode, the Reward is created at `CONFIRMED` with the Consumer status `VALIDATION`, for the order amount times `discount_rate`. When the order reaches `READY` it is validated, moves to `POOLED`, and its amount is recomputed on the Vouchers actually delivered. A fully refunded order cancels it.

From there the Reward follows the standard pool route: it appears in `GET /perks/consumers/{consumer_id}/rewards` and is paid out through `POST /perks/consumers/{consumer_id}/pools/me/payouts`. Plan for the full Reward journey, including KYC and the payout account, or the discount stays in the pool. See [Reward lifecycle](/program/user-journey/guides/reward-lifecycle).

## After the purchase

| Action                        | Endpoint                                                  | Rules                                                                                                                                                                              |
| ----------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| List the Vouchers             | `GET /perks/consumers/{consumer_id}/vouchers`             | Filters: `status[]`, `is_archived` (defaults to `false`), `is_refunded` (defaults to `false`), `order_id`. Paginated through `page` and `per_page`, answering `items` plus `meta`. |
| Read one Voucher              | `GET .../vouchers/{voucher_id}`                           | Returns the face value, expiry, Brand and codes.                                                                                                                                   |
| Download the PDF              | `GET .../vouchers/{voucher_id}/pdf`                       | Binary stream. Answers `404` until the PDF exists.                                                                                                                                 |
| Declare the remaining balance | `PUT .../vouchers/{voucher_id}/amount`                    | `remaining_amount`, between `0` and the face value. The Consumer declares it: Paylead does not know what was actually spent, so present it as their own figure.                    |
| Archive, unarchive            | `POST .../vouchers/{voucher_id}/archive`, `.../unarchive` | Hides the Voucher from the default lists. Answers `PL-409-13` on a Voucher that cannot be modified.                                                                                |

`GET /perks/consumers/{consumer_id}/voucher-orders` gives the order history instead: total amount, Voucher count, Brand and date, filtered by Brand name and sorted by date. It lists `READY` orders only.

<Warning>
  `pin_code_1`, `pin_code_2`, `card_number` and `card_url` are the monetary value itself, and they are only filled once the order is `READY`. Never log them, never cache them, and never serve them outside the Consumer's authenticated session.
</Warning>

## Partial deliveries and refunds

Refunds are driven by Paylead, not by your integration. When the supplier delivers an incomplete order, the missing Vouchers are refunded to the Consumer automatically: pro rata of the price paid, or in full when nothing was delivered. `refunded_at` is set on the Vouchers concerned.

Two consequences to handle on screen. A `READY` order can hold fewer Vouchers than were ordered, so build the list from the response rather than from the order. And a refunded Voucher stays readable through its ID while being excluded from the default lists by `is_refunded=false`.

## Errors

Voucher endpoints return the standard [error envelope](/program/user-journey/errors#error-envelope). Treat `PL-409-12`, `PL-409-15` and `PL-400-10` as part of the normal flow rather than as technical failures: each one has a Consumer-facing way out. The full list is on [Errors](/program/user-journey/errors#paylead-error-codes).

## What's next

<CardGroup cols={2}>
  <Card title="Reward lifecycle" icon="refresh-cw" href="/program/user-journey/guides/reward-lifecycle">
    Track the Reward a discount in Cashback mode generates: the status tracks, and the list and detail endpoints.
  </Card>

  <Card title="Seamless Payment" icon="credit-card" href="/program/user-journey/guides/seamless-payment">
    The delegated payment contract: the endpoint you expose and the result callback.
  </Card>

  <Card title="Pool & payout" icon="hand-coins" href="/program/user-journey/concepts/ventilation">
    How a validated Reward reaches the Consumer's pool and gets paid out.
  </Card>

  <Card title="Errors" icon="triangle-alert" href="/program/user-journey/errors">
    The error envelope, the Paylead codes, and the retry strategy.
  </Card>
</CardGroup>
