How a Spark pack is bought
Which part comes from our server, which from Apple / Google, and how a pack's Spark amount can be changed. For Leong, 2026-09-29. Describes the fixed branch tora/iap-fixes (not live yet).

① The purchase workflow

HOOP app (on the phone) HOOP server Apple App Store / Google Play
HOOP app HOOP server Apple / Google 1 Which packs exist? 6 pack IDs + Sparks + bonus % (no prices) 2 Price for these 6 IDs? price in the buyer's currency — only packs that exist in the store 3 Screen shows the packs 4 Buyer taps "RM 4.90" → buy this ID (tagged with the HOOP account) 5 Store's own payment sheet Face ID / Google Pay — store charges 6 Signed receipt (proof of payment) 7 Here is the receipt 8 Server checks it: · signature really from Apple / Google · Sparks looked up by pack ID in OUR table · added to the wallet once, history written (Google: server also confirms to Google) 9 "500 Sparks added, balance 1,500" 10 "Finished" — only after step 9 (else the store re-sends it) Later, if the buyer gets a refund from Apple / Google: refund notice → server takes the Sparks back
Money never passes through HOOP. The buyer pays Apple / Google on their own payment sheet (step 5). HOOP only checks the receipt afterwards and adds the Sparks. If the phone loses connection between steps 6 and 9, the store keeps the purchase "unfinished" and hands it back later (after login, when the app returns to the front, or via Restore), so nothing is lost.

② Top-up screen: where each part comes from

Hoop Spark Restore F Your balance ✦ 1,250 History › A 500 RM 4.90 Most popular 6500 +18% bonus RM 54.90 B C D E … up to 6 packs … Payment received. Adding your Sparks… G Prices are shown in your local currency by the store… H
#PartComes from
ABalance and Spark historyHOOP server
BSpark amount on each pack (500, 6500 …)HOOP server the pack table
CBonus badge (+18%)HOOP server
D"Most popular"HOOP server
EPrice button (RM 4.90)Apple / Google set by Jeff in the store console. A pack with no store price is not shown at all.
FRestoreApple / Google hands back unfinished purchases
GStatus banners (adding / awaiting payment / couldn't add …)App from the server's answer
HSmall print, Terms, PrivacyApp

③ Who owns what

FactLives inMust match
Pack ID (e.g. …green.500)server table App Store Google PlayExactly the same string in all three. An ID can never be renamed or reused.
Pricestores onlyOur server never sends or stores a price to show. Both stores should charge the same (Leong 09-28).
Sparks givenserver table onlyThe store listing's name / description ("500 Sparks") must say the same number. The phone can't claim a different amount.
Bonus %, "Most popular"server tableThe bonus badge is checked by a test: it can only under-promise, never over-promise.
Paymentstores onlyHOOP never sees card details.

④ Changing how many Sparks a pack gives

A · New pack ID  recommended for permanent changes

  1. Jeff creates a new ID in both stores, e.g. …spark.600, with its price and a listing that says "600 Sparks".
  2. I add it to the server's pack table (server update; no app build needed).
  3. The old pack is deactivated in both stores, so it disappears from the screen by itself. It stays in the server table, so purchases of it that are still arriving (unfinished, redelivered) are still honoured.

Why: the store listing and what we deliver always match; Apple / Google review the listing.

B · Admin bonus on top  not built — proposal

  1. An admin sets e.g. "+20% on all packs, Sat 00:00 – Sun 23:59".
  2. The app shows it on every card before the buyer taps (e.g. "500 + 100 bonus").
  3. The server adds the base Sparks plus the bonus at step 8; history shows the bonus as its own line.

Base amounts stay equal to the store listing, so it is safe with store rules. Needs a design OK before I build it.

Not recommended: letting an admin change the base amount per ID. The store listing would then say one number while we give another.