Selling on the Web with Stripe
A supporter pack and crystal packs for the browser version, with Stripe checkout, a few small cloud functions and server-side entitlements. Also why a browser save can never be trusted, and a splash screen that waits until everything is ready.

Google Play handles payments in the app. The browser has no store, so the web version needed its own way to sell the two things the game offers: crystal packs and a supporter pack.
The supporter pack
The supporter pack is the purchase I am most comfortable with. It is a one-time purchase: all ad rewards are granted instantly without watching a video, plus 100 crystals once. No countdown, no "limited offer", no pop-up. It sits calmly at the top of the crystal shop. On Android it is restored automatically on a new device; on the web it is tied to the Google account.
Why purchases need a server
A browser game cannot be trusted with anything valuable. The save lives in local storage, and anyone with the developer tools can edit it. For a single-player game that is fine: if you give yourself a million crystals in the browser, you only cheat yourself.
It stops being fine with cloud saves. Someone could edit a browser save and pull it onto Android. So paid entitlements never come from the save alone. They live on the server, written only by the payment provider's confirmation.
The flow:
- The player taps "buy" and signs in with Google if they have not yet.
- A small cloud function creates a Stripe Checkout session with the player's account id.
- Stripe's own page handles the payment; the game never sees card details.
- Stripe calls a webhook: a signature-checked function that writes the entitlement to the player's record, in a transaction, exactly once.
- The game watches that record and unlocks the supporter pack or credits the crystals. Crystal packs are claimed by exactly one device, credited, saved, and only then marked as delivered.
Returning from Stripe brings the player straight back to the crystal shop with a "your purchase is on its way" note, until it arrives a second later.
Merchant of record
Selling digital goods to consumers across Europe means handling VAT in many countries. As a private developer that is not realistic. Stripe offers a mode where it acts as the merchant of record: it sells the product, handles taxes, receipts, withdrawal and refunds under its own terms, and pays out the rest. Prices are set including VAT in euro and US dollar (later also a few more currencies). I also wrote terms of use and updated the privacy policy and the legal notice on my homepage, which needed them anyway.
Testing real payments
Stripe has a test mode with fake cards, which covered most cases. A developer menu, visible only to admin accounts confirmed by the server, can reset test purchases so the whole flow can be run again. A purchase with a real card in live mode is the final check.
A splash that waits
The same day fixed something that had bothered me for a while. On the very first start, the main screen flashed for a moment, then the intro picture appeared, then Sepp a moment later. It felt unfinished.
Now the splash screen stays until everything the next screen needs is loaded: fonts, the main screen's images, the intro scene and Sepp, and the ad consent form. Until then it shows three bouncing snow dots. Only then does "tap to start" appear. It costs a second at most, and the first impression is clean.