In-app purchases are one of the highest-value actions in a game. A client-side purchase callback is useful, but it should not be the final reason premium currency or items are granted.
The client starts the purchase, the backend trusts the store
The normal safe flow is simple. The client starts the purchase through Google Play, Apple, or Steam. The client sends the receipt, purchase token, or transaction information to your backend. The backend asks the store whether the purchase is real. Only then does the backend grant the reward.
This keeps store credentials and private keys on the server. It also gives you one trusted place to prevent duplicate claims and handle refunds.
A simple backend flow can look like this:
Grant each transaction once
Every store purchase has some kind of transaction identity. Store that identity in a purchase ledger. If the same transaction is sent again, return the existing result instead of granting the reward twice.
This protects against replay attempts and honest retry problems. A phone may lose connection after the backend grants the purchase. The next retry should not duplicate the reward.
Store a purchase ledger entry with enough detail to answer future questions:
- Platform, such as Google Play, Apple, Steam, or another store.
- Transaction ID or purchase token.
- Game account ID that received the reward.
- Product ID and granted item or currency amount.
- Validation status, grant status, and timestamps.
- Refund, chargeback, or subscription status when available.
Bind purchases to accounts carefully
Think through account switching. What happens if a player buys on one account and then sends the receipt from another account? What happens if the same platform account is linked to multiple game accounts? Your backend should have a clear rule before release.
Handle refunds and chargebacks
Secure validation is not only about granting purchases. It is also about later changes. Stores can report refunds, chargebacks, subscription expiration, or pending payments. Your backend should update entitlement state when those changes matter.
Keep store secrets out of the client
Store API credentials, service account keys, shared secrets, and private tokens must not be shipped inside Unity builds. Anything in the client can be extracted. Put those secrets on the backend and let the backend talk to the store.
The Unity client should only send purchase proof it received from the platform. It should never contain the service account JSON, App Store shared secret, Steam publisher key, or any credential that can validate purchases by itself.
Log failed validation attempts
Log invalid receipts, repeated transaction IDs, account mismatch cases, and unusual purchase timing. One failed validation may be harmless. A pattern across many accounts can reveal abuse.
Do
- Validate receipts or purchase tokens from your backend, not only from the Unity client.
- Store transaction IDs so the same receipt cannot be claimed twice.
- Handle refunds, chargebacks, pending purchases, and subscription changes.
Don't
- Do not put store API credentials or private keys inside the game client.
- Do not grant premium currency only because a local purchase callback fired.
- Do not ignore duplicate receipt attempts or account switching edge cases.