Skip to content
Skip the menu

Guides

A game without a backend

Build a browser game that sells a currency pack and an item with only the browser SDK and a publishable key, from first line to a working page.

View as Markdown

On this page

What you will build

A page where a player buys gems with euros (simulated in test), buys a potion with gems, and drinks it. There is no server of your own: the game talks to GameCoin straight from the browser, with the SDK.

This is the path for a game on itch.io, in a game jam, in a no-code engine, or written with an AI assistant. If your game has its own server, read A game with a backend instead.

Before you start

You need a game and a catalog. The dashboard is in French for now.

  1. Create an account and a game (« Nouveau jeu »). The two test keys are created with it.
  2. Create the paid currency gems in « Monnaies » (type « Payante »).
  3. Create the item potion in « Objets »: type « Consommable », « Prix en monnaie » 20 in gems, and tick « Achetable depuis le jeu (API cliente) ». Without that box the browser cannot buy it.
  4. Create the pack gems-500 in « Packs »: gems with « Quantité payée » 500 and « Quantité offerte » 50, « Prix (en euros) » 4,99. A new pack is a draft: use « Publier » so players can see it.
  5. In « Clés d'API », copy your publishable key gc_pk_test_….

The examples use these codes. Use your own codes where yours differ.

WhatCodeContent
Paid currencygems
ItempotionConsumable, 20 gems, buyable from the game
Packgems-500500 gems + 50 free gems, 4.99 €

Leave « Autoriser les joueurs anonymes » on in the game's settings (« Réglages »). It is on by default, and this path needs it.

Step 1: Load the SDK and start it

The SDK is one file with no dependency and no build step. It adds a global GameCoin and talks to the host it was loaded from.

index.html
<script src="https://gamecoin.apilow.com/sdk/gamecoin.js"></script>
<script type="module">
  const gc = await GameCoin.init({ publishableKey: "gc_pk_test_…" });

  console.log(gc.player.id);        // the anonymous player, remembered in this browser
  console.log(gc.balance("gems"));  // 0: the player has bought nothing yet
</script>

init does the work of a login for you. On the first visit it creates an anonymous player and keeps its secret in localStorage. On the next visits it asks for a fresh token with that secret. The same browser is always the same player, with the same balance. Clearing the browser's data makes a new player.

Note

Open the page from a web server (a local one is fine), not from a file:// address. Itch.io and most engines already serve your game over https://.

Step 2: Show the shop from the catalog

Do not copy prices into your code. Ask the catalog: it holds only the active currencies, items and packs.

JavaScript
const catalog = await gc.getCatalog();
console.log(catalog.packs[0]);
One pack of the catalog
{
  "sku": "gems-500",
  "name": "Bag of gems",
  "description": null,
  "priceCents": 499,
  "currency": "EUR",
  "grants": [{ "currency": "gems", "amount": 500, "bonus": 50 }],
  "items": [],
  "badge": null,
  "maxPerPlayer": null
}

A pack's price is in euro cents, VAT included: 499 is 4.99 €. Divide by 100 to display it.

Step 3: Keep the screen in sync

The SDK keeps a copy of the player's wallet and inventory. gc.balance("gems") and gc.owned("potion") read it with no request. The change event fires whenever the copy changes, after a spend, a purchase, a consume, a payment or a refresh. Draw your numbers in one function and call it from the event:

JavaScript
const render = () => {
  document.getElementById("gems").textContent = gc.balance("gems");
  document.getElementById("potions").textContent = gc.owned("potion");
};
gc.on("change", render);
render();

Step 4: Sell a pack

checkout sells a pack for euros. It opens the payment page in a window and finishes when the order is over. Call it straight from a click, or the browser blocks the window.

JavaScript
document.getElementById("buy-pack").onclick = async () => {
  const order = await gc.checkout("gems-500");
  console.log(order.status); // "fulfilled", "failed" or "pending"
};
order.statusWhat happenedWhat to do
fulfilledThe player paid. The gems are in, and gc.balance("gems") is already up to date.Thank the player
failedThe payment was declinedOffer to try again
pendingThe player closed the window without payingNothing: the order expires by itself after an hour

In test, the payment page is simulated: it shows the order, the VAT and two buttons, « Payer » and « Refuser ». No money moves. The page is in French for now.

If the browser blocks the window, the SDK sends the current tab to the payment page instead, and brings the player back to the same URL. You can force that with gc.checkout("gems-500", { mode: "redirect" }). When the page loads again, init reads the order, removes ?order=…&status=… from the address, and exposes the order as gc.returnedOrder. See Browser SDK.

Step 5: Spend, buy an item, consume it

Three calls change a balance downwards:

CallUse it for
gc.spend("gems", 5, "continue")A cost with no item: "continue?", a skip, an entry fee
gc.buyItem("potion", 1)Buy an item with its own price, in its own currency
gc.consume("potion", 1)Use up a consumable the player owns

Each one can fail with INSUFFICIENT_FUNDS, and that is a normal event, not a bug. Its details say how short the player is:

JavaScript
async function run(action) {
  try {
    await action();
  } catch (error) {
    if (error.code === "INSUFFICIENT_FUNDS") {
      const { required, available } = error.details;
      say(`You need ${required - available} more.`); // send the player to the shop
    } else {
      say(`Something went wrong (${error.code}).`);
    }
  }
}

The whole page

All the steps together. Put your own publishable key in place of gc_pk_test_….

index.html
<h1>Potion shop</h1>
<p>Gems: <strong id="gems">0</strong> · Potions: <strong id="potions">0</strong></p>

<button id="buy-pack">Buy 500 gems</button>
<button id="buy-potion">Buy a potion (20 gems)</button>
<button id="drink">Drink a potion</button>
<p id="message" role="status"></p>

<script src="https://gamecoin.apilow.com/sdk/gamecoin.js"></script>
<script type="module">
  const gc = await GameCoin.init({ publishableKey: "gc_pk_test_…" });
  const $ = (id) => document.getElementById(id);
  const say = (text) => ($("message").textContent = text);

  // The screen follows the SDK's copy of the wallet and the inventory.
  const render = () => {
    $("gems").textContent = gc.balance("gems");
    $("potions").textContent = gc.owned("potion");
  };
  gc.on("change", render);
  render();

  // Run a call and turn its errors into messages.
  async function run(action) {
    try {
      await action();
      say("Done.");
    } catch (error) {
      if (error.code === "INSUFFICIENT_FUNDS") {
        say(`You need ${error.details.required - error.details.available} more gems: buy a pack.`);
      } else {
        say(`Something went wrong (${error.code}).`);
      }
    }
  }

  // Pack paid in euros. Called straight from the click: the payment window needs it.
  $("buy-pack").onclick = () =>
    run(async () => {
      const order = await gc.checkout("gems-500");
      say(order.status === "fulfilled" ? "Thank you!" : `Payment ${order.status}.`);
    });

  // Item paid with the game's currency, then used up: no window, no euros.
  $("buy-potion").onclick = () => run(() => gc.buyItem("potion"));
  $("drink").onclick = () => run(() => gc.consume("potion"));
</script>

Try it: the buttons for the potion fail at first, because the player has no gems. Buy the pack, pay in the simulated page, and the gems appear. Then buy a potion and drink it. The balance and the potion count update on their own.

What a player cannot do

The SDK has no grant, and neither does the API it calls with a publishable key. A player can open the developer tools and call anything the SDK can, but that is only spending their own gems or paying for a pack. They can never add coins to their balance, except the free coins of a gift code that you issued (one use per player). That is why this path is safe for real money. Security model explains it.

It also means a game without a backend cannot give gems as a reward. Points that your game computes locally, such as a score, stay in your game. If you want rewards in currency, add a server: A game with a backend.

If something goes wrong

You seeWhyFix
publishableKey must be a publishable keyThe key does not start with gc_pk_Copy the publishable key, not the secret one. The SDK refuses a gc_sk_ key on purpose.
baseUrl is required when the SDK is not loaded from a <script src>The SDK was bundled or imported, so it cannot tell where GameCoin isPass baseUrl: "https://gamecoin.apilow.com" to init
FORBIDDEN on initAnonymous players are turned off for this gameTurn them back on in the settings, or use a backend
FORBIDDEN on buyItemThe item is not « Achetable depuis le jeu »Tick the box on the item
NOT_FOUND on checkoutThe pack code is wrong, or the pack is still a draftPublish the pack and check its sku
The payment window does not openThe click was not the direct cause of checkoutCall checkout from the click handler, with no await before it

Where next

  • Browser SDK: every option and call.
  • Selling packs: VAT, return URLs and the life of an order.
  • Testing: the test environment, test players and the demo game.