Skip to content

Coin packages

A coin package is one SKU in the coin store: “Get 500 coins + 100 bonus for $4.99”. Each package can be enabled on a subset of the 5 payment rails. Store rails (Apple/Google) need matching SKUs configured in App Store Connect / Play Console.

Monetization → Coin packages (/monetization/coin-packages).

FieldTypeNotes
SKUstring, uniqueImmutable. Match this on the store side. Convention: tc_coins_500_bonus100.
CoinsintBase coins.
Bonus coinsint, ≥0Marketing lever; shown as “+bonus” in the store UI.
Price (cents)intUSD cents. Provider conversions handle FX at checkout.
Currencystring, ISO-3Usually USD; store providers do local conversion.
Railsstring[]Any subset of apple, google, card_web, dlocal_*, pawapay_*, vas_*.
Statusactive / pausedPaused = hidden from mobile but still resolvable for existing pending payments.

Store rails (Apple/Google) require an out-of-band SKU to exist on the store side before you enable the rail here. If the SKU doesn’t exist:

  • Apple: RevenueCat will refuse to complete the purchase.
  • Google: Play returns SKU_NOT_FOUND.

The admin does not validate the store side: it’s your responsibility to make sure both sides line up.

CoinsBonusPriceNotes
1000$0.99Entry-level impulse buy.
50050$4.99The workhorse.
1200200$9.99Best value on the shelf.
3000700$24.99High-spender tier.
65002000$49.99Whale bait.

Bonus coins are minted into wallet_bonus; base coins into wallet_paid.

Store IAP → RevenueCat webhook → API:

event_id = <UUID>
- source_iap_apple : -($4.99 in coins) (metadata: purchase_id, transaction_id)
- wallet_paid : +500 coins
- wallet_bonus : +50 coins (offset by source_bonus)
- source_bonus : -50 coins

Zero-sum across the event_id (except FX/fees, which we track separately).

Packages are displayed sorted by coins ASC on the mobile app. There’s no manual sort field in v2: if you want a specific order, adjust the coin/price to fit.

Set status='paused'. New store fetches will not include it. In-flight purchases (webhook already fired) still complete.

  • Deleting a package: the API refuses if any Purchase row points at that package. Pause instead.
  • Editing coins or price on a live SKU: allowed but audited. The mobile app caches store info for a few minutes; changes take that long to propagate.
  • Rail conflicts: a package enabled on apple but not google will only appear in the iOS store screen. Nothing enforces symmetry; that’s an editorial choice.
ActionEndpoint
ListGET /v1/admin/coin-packages
DetailGET /v1/admin/coin-packages/:id
CreatePOST /v1/admin/coin-packages
UpdatePUT /v1/admin/coin-packages/:id
DeleteDELETE /v1/admin/coin-packages/:id

Full details in API: Monetization.