# Points and rewards

Players earn PlayerPal points when they play with linked operators and game
providers. PlayerPal calculates the points itself from the activity partners
report, and records every credit in an append-only ledger. A rewards shop,
where players spend points on rewards, is in development.

| Part | Status |
|---|---|
| Earning points from qualifying activity | [Available] |
| Points ledger and balance | [Available] |
| Rewards shop and redemption | [In development] |

## Earning points

[Available]

Partners report facts about play; they never calculate points. PlayerPal
applies a versioned campaign rule to each qualifying event and credits the
result to the player's ledger.

```mermaid
flowchart LR
  P[Partner sends<br/>settled rounds] --> Q{Qualifies?}
  Q -- yes --> C[Campaign version<br/>calculates points]
  C --> L[(Points ledger<br/>credit)]
  Q -- no --> R[Event rejected<br/>with a reason]
```

An activity event qualifies only when all of these hold:

- It is a **settled round** for a player alias PlayerPal issued to that
  partner.
- The player's link to that partner was **in force when the round occurred**:
  on or after the link became effective, and before any unlink.
- The stake is in **EUR**.
- The round's market, if given, is one the integration is licensed for.

How many Points a qualifying round earns is set by the active campaign's
rules. Campaign rules are versioned: every credit records the
exact campaign version that produced it, so past awards stay explainable after
a rule changes.

Earning starts only after a player registers with PlayerPal and links an
account. There is no backdated earning for play before the link. A player who
unlinks stops earning from that partner from then on; points already earned
stay on the ledger.

## The points ledger

[Available]

- **Append-only.** Entries are never edited or deleted. By design, any
  correction is a new, compensating entry.
- **The balance is derived.** A player's balance is the sum of their ledger
  entries.
- **No double credits.** Each credit is unique per campaign version, activity
  event and action. A partner can safely resend a batch: a round already
  credited is not credited again.
- **Players can see it.** In PlayerPal, players see their balance and recent
  ledger entries, with the game each credit came from.
- **Partners see aggregates only.** Operators and game providers can read the
  points issued for their integrations as a KPI. See
  [`own.points_issued`](../analytics/metrics.mdx#points-kpi).

Today the ledger records credits. Spending points arrives with the rewards
shop below.

## The rewards shop

[In development]

:::info[In development]
The rewards shop is being built. The design below describes how it is intended
to work; behaviour, states and limits may change before it is available.
:::

### From points to a reward

  
### Browse the catalogue

    The player sees rewards for their market, each with its price in points
    and whether they can redeem it now. If they are short, the shop shows how
    many more points they need.
  
  
### Confirm

    The player confirms one reward. The shop checks eligibility on the server
    and debits the points.
  
  
### Fulfilment

    PlayerPal hands the order to the reward's source and tracks the outcome
    asynchronously.
  
  
### Access the reward

    Once fulfilled, the player reveals their code or claim link through a
    separate, protected step.
  

Prices are set in points, optionally per market. The player is always taken
from their signed-in session; no request names a player.

### Eligibility

Each reward shows one state, decided on the server. The checks run in this
order, so a reward that is not on sale says so before the player's balance is
mentioned.

| Order | State | Meaning | Redeemable |
|---|---|---|---|
| 1 | Expired | The reward's sale window has ended | No |
| 2 | Coming soon | Not yet on sale | No |
| 3 | Not in your market | Not offered in the player's market | No |
| 4 | Out of stock | Limited stock, none left | No |
| 5 | Not enough points | Balance below the price | No |
| 6 | Limited stock | Only a few left | Yes |
| 7 | Available | Everything else | Yes |

A refused redemption never touches the player's points.

### Redemption lifecycle

```mermaid
stateDiagram-v2
    [*] --> RESERVED
    RESERVED --> POINTS_DEBITED
    RESERVED --> CANCELLED
    POINTS_DEBITED --> FULFILLMENT_PENDING
    POINTS_DEBITED --> FULFILLMENT_FAILED
    FULFILLMENT_PENDING --> FULFILLED
    FULFILLMENT_PENDING --> FULFILLMENT_FAILED
    FULFILLED --> USED
    FULFILLED --> EXPIRED
    FULFILLMENT_FAILED --> POINTS_REVERSED
    POINTS_REVERSED --> [*]
    USED --> [*]
    EXPIRED --> [*]
    CANCELLED --> [*]
```

| State | Meaning |
|---|---|
| `RESERVED` | The redemption is recorded and limited stock is held |
| `POINTS_DEBITED` | The price has been taken from the balance |
| `FULFILLMENT_PENDING` | The reward source is processing the order |
| `FULFILLED` | The reward is ready for the player |
| `USED` | The player marked the reward as used |
| `EXPIRED` | The reward passed its expiry date unused |
| `FULFILLMENT_FAILED` | The reward source definitively refused the order |
| `POINTS_REVERSED` | The points were returned to the player |
| `CANCELLED` | The redemption was cancelled before any points moved |

Any transition not shown is refused.

### Idempotency

Each confirmation carries a unique idempotency key, and a retry of the same
confirmation reuses it.

- **Same key, same reward:** returns the original redemption and its current
  state. Points are debited once, even for simultaneous retries.
- **Same key, different reward:** refused as a conflict.
- **Toward the reward source:** the redemption's own ID is passed on as the
  idempotency key, so a resend cannot create a second order.

### Fulfilment and failures

Each reward source sits behind one common interface, whether it is a gift
card, an operator reward, a game provider entitlement, a claim link or a
reward completed by hand.

| Outcome | What happens |
|---|---|
| Fulfilled | The reward is stored securely and the redemption becomes `FULFILLED` |
| Definitive failure | The points are returned to the player, held stock is released, and the redemption ends `POINTS_REVERSED` |
| Pending or unknown | The redemption stays pending and PlayerPal checks the status again. Points are never returned while the outcome is uncertain. |
| Order never received | PlayerPal resends it with the same idempotency key |

If an uncertain outcome is still unresolved after repeated checks, the
redemption is flagged for review by PlayerPal staff rather than guessed. A late
or duplicate answer from a reward source is ignored once the redemption has
moved on.

Fulfilment requests carry no player email, name or phone number. A partner's
reward receives only that partner's own alias for the player, never a global
PlayerPal identifier.

### Secure reward access

- Codes and claim links are encrypted before storage, bound to their
  redemption so they cannot be moved to another.
- Lists and status views show only a masked hint, such as the last group of
  characters.
- The full code is revealed only through a separate access step, only to the
  player who redeemed it, and only once it is fulfilled.
- Reveals are rate-limited per player and each one is audited without
  recording the secret.

### Availability

Redemption depends on PlayerPal. If PlayerPal is unavailable, redemption is
unavailable too. This never blocks deposits, withdrawals, wagering or game
settlement at the operator or game provider.

## Related

  - [Player experience](../product/player-experience.mdx): 
    What players see when they link, play and earn.
  
  - [Sending activity](../developers/sending-activity.mdx): 
    Report settled rounds so linked players earn points.
  
  - [For reward partners](../product/for-reward-partners.mdx): 
    How reward partners will plug into the shop.
