# Gift codes and creator codes

> Hand out gift codes for free coins and items, and track the sales and the commission of your content creators with creator codes.

## What you will build

GameCoin has two kinds of codes. They look alike for a player, who types a short word, but they do very different jobs.

| | Gift code | Creator code |
|---|---|---|
| Who gets what | The player who types it gets **free coins and items**, once | The buyer gets a **bonus of free coins** on a purchase; the creator earns a **commission** |
| Where it is typed | In your game, when the player redeems it | At checkout, when the player buys a pack |
| Who makes it | You, in a **batch** of random codes (10 to 1,000 at a time) | You, one code per creator, with a name you choose (`LEAPLAY`) |
| Money | Never: free coins only, never paid coins | The commission is **tracked** in the dashboard; paying creators is not part of GameCoin yet |

Both live in the dashboard under **Codes** (in French for now), with one tab for each: **Cadeaux** (gifts) and **Créateurs** (creators). Like everything else, codes belong to an **environment**: a code created in `test` does not exist in `live`.

> [!NOTE]
> Codes are managed in the dashboard from the role **developer**. Members with the role **support** can see the numbers but not the codes themselves, which are worth coins.

## Gift codes

Use them for a contest, a convention, a partner or a streamer giveaway. Each code gives the same reward: some free coins, some items, or both.

### Create a batch

In the **Cadeaux** tab, choose **Nouveau lot** and fill in:

| Field | Meaning |
|---|---|
| Name | For you only (« Paris Games Week »). |
| Prefix | Optional, 2 to 8 letters or digits placed in front of every code (`PGW`), so a code tells where it came from. |
| Number of codes | From 1 to 1,000. Make another batch for more. |
| Uses per code | `1` for a single-use code; more for a code you share with a group. A player can still use a given code **only once**. |
| Valid until | Optional. The code works until the end of that day, Paris time. |
| What each code gives | A quantity of each free currency, and a quantity of each item. At least one. |

GameCoin generates the codes with ten random characters from an alphabet that has no look-alikes (no `0` and `O`, no `1`, `I` and `L`). With a prefix a code looks like `PGW-K7M2Q-XH4NP`. The dashes and the case do not matter when a player types it.

You can then open the batch to see every code, its state (active, used up, expired, disabled) and how often it was used, **export the codes as CSV** to print or send them, or **disable** a code or the whole batch. Disabling is final: the coins already given stay with the players, and you make a new batch to hand out codes again.

> [!WARNING]
> A code is worth its coins. Treat an exported file like a list of gift cards: send it only to the people who must have it, and disable a batch that leaked.

### Let a player redeem a code

The player types the code in your game, and your game calls one route.

From your **backend** (secret key), when you know who the player is:

```bash
curl -X POST "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/codes/redeem" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY" \
  -H "Idempotency-Key: redeem-user-42-pgw" \
  -H "Content-Type: application/json" \
  -d '{"code": "pgw k7m2q xh4np"}'
```

From a **game without a backend** (publishable key and player token), with `POST /client/me/codes/redeem`: it is the same body and the same answer. See [Codes API](/docs/api/codes) for the details, the errors and a browser example.

The answer lists what was given: the coins with the new balance, and the items.

```json
{
  "grants": [{ "currency": "gems", "amount": 100, "balance": { "currency": "gems", "paid": 0, "bonus": 100, "total": 100 } }],
  "items": [{ "sku": "potion", "quantity": 2 }],
  "skipped": []
}
```

### The rules that protect you

- **Free coins only.** A code credits the `bonus` bucket, which cannot be refunded in euros. The ledger shows the entry with the type `gift_code`.
- **Once per player, and a limited number of uses.** The count is exact even when a hundred players type the same code at the same moment: a single-use code goes to one player only.
- **Not transferable.** A code credits the player who types it. GameCoin has no way to pass coins from one player to another.
- **One answer for every refusal.** An unknown, expired, used-up, disabled or already used code all give `400 VALIDATION_FAILED` with `fieldErrors.code = ["CODE_INVALID"]`. This stops anyone from finding real codes by trying. Show one message: « This code is not valid ».
- **Limited attempts.** A player can try 10 codes per 15 minutes, right or wrong, and an IP address 30 (client API).
- **Safe to retry.** The call needs an `Idempotency-Key`. After a network error, send the same request with the same key: nothing is credited twice.

If a player already owns the most they can of a durable item, that item is skipped (the answer lists it in `skipped`) and the rest is still given. When the code has nothing left to give them, the call answers `409 LIMIT_REACHED` and the code is **not** used up.

## Creator codes

A creator code links a purchase to a content creator: a streamer, a YouTuber, a community. The creator shares the code; a buyer types it at checkout and gets extra free coins; you see the sales and the commission that the creator earned.

### Create a code

In the **Créateurs** tab, choose **Nouveau code créateur**:

| Field | Meaning |
|---|---|
| Code | 3 to 24 letters or digits, no accent (`LEAPLAY`). Players can type it in any case. It must be unique in the game. |
| Creator name | Shown on the buyer's receipt and in the order. |
| Buyer bonus | From 0 to 50 % of free coins on top of the pack. |
| Commission | From 0 to 30 % of the amount **excluding VAT**, tracked for the creator. |
| Creator's player | Optional. The player who is the creator in your game: they **cannot use their own code**. Enter `ext:<your id>`. |
| Packs | Optional. None ticked: the code works for every pack. |

You can change a code later (rates, name, packs) or disable it and enable it again. A change applies to the **next purchases**: an order keeps the bonus and the commission it had when the player started it.

### Use it at checkout

Pass the code the buyer typed as `creatorCode` when you create the order, with the server API or the client API:

```bash
curl -X POST "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/checkouts" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY" \
  -H "Idempotency-Key: checkout-gems-500-user-42" \
  -H "Content-Type: application/json" \
  -d '{"packSku": "gems-500", "creatorCode": "leaplay"}'
```

The order answers with the code, and **never** with the rates:

```json
{
  "order": {
    "id": "665f1c2e8a3b4d5e6f7081b4",
    "status": "pending",
    "pack": { "sku": "gems-500", "name": "Bag of gems" },
    "amountCents": 499,
    "creatorCode": { "code": "LEAPLAY", "creatorName": "Léa Play" }
  },
  "checkoutUrl": "https://gamecoin.apilow.com/pay/665f1c2e8a3b4d5e6f7081b4?t=…"
}
```

(Only the useful fields are shown.) A code that cannot be used — unknown, disabled, not valid for this pack, or the creator's own player — is refused with `400 VALIDATION_FAILED` and `fieldErrors.creatorCode = ["CODE_INVALID"]`, the same answer in every case. The order is not created: tell the player the code is not valid and let them try again or buy without it. Order objects without a code have `creatorCode: null`. The code also appears in the `order.*` [webhooks](/docs/guides/webhooks), because they carry the same order object.

### What the buyer gets, what the creator earns

Both are computed **when the order is delivered**, from the pack frozen in the order, and both are rounded **down**.

| | Rule | Example: pack of 500 coins + 50 free, 4,99 € incl. VAT (4,12 € excl. VAT at 21 %), bonus 10 %, commission 10 % |
|---|---|---|
| Buyer bonus | `floor(coins bought × bonus % / 100)`, per currency, free coins | `floor(500 × 10 / 100)` = **50** free coins. The 50 free coins of the pack do not count. |
| Commission | `floor(net amount in cents × commission in basis points / 10,000)` | `floor(412 × 1000 / 10000)` = **41** cents |

The buyer's receipt shows the bonus on its own line, with the code. The coins arrive in the same ledger entry as the pack: 500 paid and 100 free.

**Refund and chargeback cancel everything.** When the order is refunded, or the player disputes the payment, the coins are taken back as usual, the bonus included, and the commission is **cancelled**: the creator's sheet counts the order as canceled and the commission due drops. Nothing is counted for an order that was never delivered.

### Follow the sales

Open a creator in the dashboard to see, for a period you choose (delivery dates):

- the number of orders, the sales including and excluding VAT, the **commission due**, and the bonus coins given to buyers;
- the orders that were canceled since, with the commission that was cancelled;
- the list of sales, and an **export as CSV** of the sales of the period (for your accounting, or to show a creator what they earned).

> [!NOTE]
> **GameCoin tracks the commission; it does not pay it.** The amount shown is what you owe the creator under your agreement. Paying creators automatically is planned for a later version: until then you pay them yourself.

## Test it

In the `test` environment, create a batch, redeem a code with a test player, then create a creator code and buy a pack with it: the payment is simulated. Check the wallet (`bonus` goes up), the ledger entry (`gift_code`), and the creator's sheet. See [Testing](/docs/guides/testing).
