# Flexible Service

<Callout type="caution" title="Important">
    Once an order moves to the pending status, it can no longer be modified.
</Callout>

This means that if there was a mistake — for example:
* in the vehicle’s license plate number,
* in the country of registration,
* or in the vignette start date

it will not be possible to make any changes.

<Callout type="tip" title="Flex Service">
   To address this limitation, we introduced an additional feature called Flex Service.
</Callout>

Flex Service comes in two plans, selected per product via `flex.type`:

| Plan | Price | What it allows |
| --- | --- | --- |
| `default` | **€3.29** | Changes to an upcoming vignette up to 6 hours before the start time, i.e. until 18:00 on the previous day. |
| `expanded` | **€5.99** | Changes to an already active vignette, at any point during its validity period. |

* Orders with Flex Service are processed separately, as they are sorted into a dedicated group.

This gives users extra flexibility in case they need to correct details such as license plate number, country of registration, or vignette start date.

Example payload for an order with Flex Service:
```json
{
"order_has_been_paid": false,
...
"products": [
    {
        "name": "vignette-si-2a",
        ...
        "flex": {
            "type": "default",
            "enabled": true
        }
    }
  ]
}
```

`type` selects the plan (`default` or `expanded`), `enabled` confirms that the partner accepted and processed Flex Service for this product.

## Refunds

Flex Service also makes a paid order **refundable**. Orders without Flex can never be refunded.

The refund is calculated on the **vignette price alone** — it excludes the Flex fee (that is what paid for the refund right) and any insurance. So a €19.44 order made of a €15.45 vignette plus €3.99 Flex refunds €15.45, never €19.44.

### Refund tiers

How much is refunded depends on how much time remains before the vignette start (`start_from`). The tiers are **mutually exclusive** — at most one applies at a time:

| Tier | When | Refund |
| --- | --- | --- |
| **Full refund** | More than 6 hours before start | 100% of the vignette price |
| **Partial refund** | 5–6 hours before start | 50% of the vignette price |
| **Same-day** | Vignette bought today to start today, within 1 hour of purchase | 50% of the vignette price |

Less than 5 hours before start, no refund is available.

### How the windows decay

Both the modify and refund windows close as the start time approaches. For a `default` Flex order:

| Capability | Open while | Closes at |
| --- | --- | --- |
| Modify · `default` | More than ~6 hours before start | `modify.expires_at` |
| Modify · `expanded` | Until the vignette expires | end of the vignette's last valid day |
| Full refund · 100% | 6h+ before start | `full_refund.expires_at` |
| Partial refund · 50% | 5–6h before start | `partial_refund.expires_at` |
| Partial refund · 50% (same-day) | Bought today to start today, within 1h of purchase | purchase time + 1h |

`expanded` Flex keeps the **modify** window open until the vignette expires; it does **not** change the refund terms.

### Read the capability blocks — don't compute

Every order response (Get Order and the list endpoints) carries three blocks — `modify`, `full_refund`, `partial_refund` — that tell you exactly what is possible right now, until when, and for how much:

```json
"full_refund": {
    "eligible": true,
    "available_at": null,
    "expires_at": 1785907061,
    "amount_eur": 15.45,
    "percent": 100,
    "reason_code": "more_than_6h_before_start"
}
```

* Render `amount_eur` **verbatim — never recompute it**. `percent` is display-only; do not multiply anything by it.
* Trust `expires_at` over your own clock arithmetic — it is the authoritative value the server enforces (the modify window in particular closes on a truncated whole-hour comparison, so it can be up to an hour earlier than the "6 hours" rule of thumb suggests).
* A **pending** tier still reports its terms: when `partial_refund` is ineligible only because the 100% tier is currently live (`reason_code: full_refund_applies`), it still tells you what it will pay (`amount_eur`) and when it opens (`available_at` — exactly `full_refund.expires_at`). That is how you show *"€15.45 now, or €7.73 after 05 Aug 05:14"*.
* Re-read the order **immediately before** executing a modify or refund — these values are time-volatile and a list fetched minutes ago may have crossed a tier boundary.

<Callout type="tip" title="Drive your UI from reason_code">
    `reason_code` is always populated, whether the action is eligible or not. Use it for your button states and copy instead of your own clock arithmetic.
</Callout>

<Callout type="caution" title="Deprecated: flexible">
    The boolean `flexible` field is deprecated. It is still accepted, but if a product contains both `flexible` and `flex`, `flex` wins and `flexible` is ignored.
</Callout>