- explicitly version every change to the store catalogue and require the version to be passed with a purchase, disallowing purchases from old catalogues
- make an expiring signed piece of data (like JWT) for each offer in the store catalogue and require it to be passed back with a purchase, accepting old prices briefly
- make the purchase API two-phase, locking in the price on the first phase and letting the client notice that it changed and potentially neglect to go through with the second phase
And tbh, I think they are okay with 10 hackers are ruining the game, as long as 1 thousand mothers are putting their credit card to pay for gems.
The same concerns apply for a typical shopping cart design. The price can change in between the time when you sent the price to the client, and the time when the user actually decides to buy.
There is a TTL on the shopping cart itself. However, that meant for a period of time you had a frozen cost per-line-item in a shopping cart.
This is exceedingly sensible. By including the expected price, the client is saying "the user has agreed to purchase a, b, and c for tootal cost X." That price was the one being displayed to the user, and it was being calculated live on the client as the user fiddled with quantities and whatnot. The actual cost might be totally different if a card's price changes or a sale ends or who knows what, and charging a user a different value than what was displayed is totally unacceptable. It's a sanity check, or alternately a form of optimistic locking. It prevents a whole class of nasty billing bugs.