Skip to content
Skip the menu

Guides

Selling packs

Sell a pack for euros with a checkout and the hosted payment page, from the VAT to the return URL and the life of an order.

View as Markdown

On this page

What you will do

A pack is what a player buys with euros: currency, items, or both. You sell it in four steps:

  1. Create a checkout. GameCoin makes an order and a payment URL.
  2. Send the player to the payment page, which is hosted for you.
  3. The player pays. GameCoin delivers the currency and items.
  4. The player comes back to your game, and you read the order.

The browser SDK does all four with one call, gc.checkout("gems-500"). This page is for the steps behind it, and for selling from your server. The examples use the pack gems-500: 500 gems and 50 free gems for 4.99 €.

Note

In the test environment, payments are simulated: the payment page offers a button to pay and a button to decline, and no money moves. Real payments are not available yet.

POST/players/{player}/checkouts
FieldRequiredMeaning
packSkuyesThe code of the pack
successUrlnoWhere to send the player after a successful payment
cancelUrlnoWhere to send the player after a declined payment
countrynoThe buyer's country, which decides the VAT. Server API only.

The call needs an idempotency key. It answers 201 with the new order and the URL of the payment page.

LangageLanguage
curl -X POST "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/checkouts" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY" \
  -H "Idempotency-Key: order-1001" \
  -H "Content-Type: application/json" \
  -d '{"packSku":"gems-500","country":"FR","successUrl":"https://example.com/shop/thanks","cancelUrl":"https://example.com/shop"}'
Answer to the server call
{
  "order": {
    "id": "6ac419fbdbe02af6c99c3d47",
    "playerId": "6ac419fa60e877541898f38d",
    "status": "pending",
    "pack": { "sku": "gems-500", "name": "Bag of gems" },
    "amountCents": 499,
    "currency": "EUR",
    "country": "FR",
    "vatRateBp": 2000,
    "vatCents": 83,
    "netCents": 416,
    "createdAt": "2026-10-05T21:43:23.065Z",
    "paidAt": null,
    "fulfilledAt": null,
    "refundedAt": null,
    "creatorCode": null
  },
  "checkoutUrl": "https://gamecoin.apilow.com/pay/6ac419fbdbe02af6c99c3d47?t=…"
}

Open checkoutUrl in the player's browser: redirect the tab, or open a window. Do not store it or parse it. It works for one order, for one hour.

The payment page

The page is hosted. It shows the pack, the contents, the price with VAT, a country selector, and the buttons. In test the buttons are « Payer » (pay) and « Refuser » (decline). The page is in French for now.

The buyer can change the country on the page. The VAT is recalculated before they pay, and the order is updated with the final country and VAT.

VAT

Pack prices are in euros with VAT included. GameCoin works out the VAT from the buyer's country and writes it on the order, together with the amount without VAT:

Order fieldMeaningExample (4.99 € in France)
amountCentsWhat the buyer pays, VAT included499
vatRateBpThe rate in basis points: 2000 is 20 %2000
vatCentsThe VAT inside the price83
netCentsThe price without VAT416

The country is chosen in this order: the country of the checkout, then the country of the player's profile, then BE. A browser checkout cannot send a country, so set the player's country on your server, or let the buyer pick it on the payment page.

Packs are sold in the countries of the European Union: AT, BE, BG, HR, CY, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IT, LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, ES and SE. Any other country answers 400 VALIDATION_FAILED with the field error COUNTRY_NOT_SELLABLE.

Return URLs

successUrl and cancelUrl send the player back to your game.

  • Use https:// URLs. In the test environment, http:// is accepted for the local machine only (localhost, 127.0.0.1, [::1]). Any other http:// answers URL_SCHEME_NOT_ALLOWED.
  • A URL with a user name or password is refused (URL_INVALID).
  • From a browser, they must have the same origin as the page that asks, so a script on another site cannot redirect your players. Otherwise: ORIGIN_MISMATCH.

After a successful payment, GameCoin redirects to successUrl and adds the order and its status to the query. After a declined payment it does the same with cancelUrl:

https://example.com/shop/thanks?order=6ac419fbdbe02af6c99c3d47&status=fulfilled
https://example.com/shop?order=6ac41c5a62015d21b714ae0a&status=failed

If you give no URL, the player lands on a confirmation page of GameCoin, which also tells the window that opened it with a postMessage: { type: "gamecoin:order", orderId, status }. The browser SDK listens for that message, so you need no URL at all with gc.checkout.

What to do when the player comes back

Never trust the address. Anyone can type ?status=fulfilled. The truth is the order, read from GameCoin:

LangageLanguage
curl "https://gamecoin.apilow.com/api/v1/orders/6ac419fbdbe02af6c99c3d47" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY"
Answer
{
  "id": "6ac419fbdbe02af6c99c3d47",
  "playerId": "6ac419fa60e877541898f38d",
  "status": "fulfilled",
  "pack": { "sku": "gems-500", "name": "Bag of gems" },
  "amountCents": 499,
  "currency": "EUR",
  "country": "FR",
  "vatRateBp": 2000,
  "vatCents": 83,
  "netCents": 416,
  "createdAt": "2026-10-05T21:43:23.065Z",
  "paidAt": "2026-10-05T21:43:42.641Z",
  "fulfilledAt": "2026-10-05T21:43:42.641Z",
  "refundedAt": null,
  "creatorCode": null
}

Deliver nothing yourself. When the status is fulfilled, the coins and items are already in the player's account: the payment and the delivery happen together. A server only needs to read the order to update its screen. Read the wallet to see the result, and the ledger to see the entries (purchase_credit, with ref.orderId).

The life of an order

pending ─ paid ─▶ fulfilled ─▶ refunded
   │
   ├────────────▶ failed       the payment was declined
   └────────────▶ expired      nobody paid within one hour
StatusMeaningFinal
pendingCreated. Waiting for the payment.no
fulfilledPaid and delivered.no: it can be refunded
refundedRefunded: coins and items were taken back.yes
failedThe payment was declined. Nothing was delivered.yes
expiredNot paid within the hour.yes

(paid is a passing state that you will not see: payment and delivery are one step. canceled and chargeback exist in the API but nothing produces them yet.)

A failed or expired order is final. To let the player try again, create a new checkout with a new idempotency key. The same key would return the old order.

Retry safely

A checkout needs an idempotency key. If your request times out, send it again with the same key: you get the same order and the same payment URL, with status 200 instead of 201. The order is returned in its current state, so a replay after the player paid shows fulfilled.

Build the key from your own order or basket number (order-1001), not from the clock.

Limits per player

A pack can have a maxPerPlayer, such as a welcome pack that each player may buy once. When the limit is reached, a new checkout answers 409 LIMIT_REACHED with maxPerPlayer and bought in details. Only orders that were paid count: a pending, declined, expired or refunded order does not use up the limit.

Limited and timed packs

A pack can be sold only during a window, in limited stock, or as a founder pack. A checkout then answers 409 PACK_NOT_AVAILABLE outside the window and 409 PACK_SOLD_OUT when the stock is used up; the catalog tells you in advance with availability. See Founder packs.

Items inside a pack

A pack can include items. They are delivered with the currency. If one cannot be delivered, because the player already owns the most they can of a durable item, the pack is still delivered: the player gets the currency and the other items, and the item is marked as not delivered on the order. You see it in the order's page in the dashboard (« Objets non livrés »); the API's Order object does not list it. If a refund follows, only what was delivered is taken back.

Where next