The spectrum of monetization models and tools is extremely varied, from ad-supported, microtransactions, freemium, one-off purchases, and everything in between. A key tool in many of these solutions is the In-App Purchase(IAP). IAPs enable single purchases for unlocks, in-game consumables, subscriptions for premium access, and more.
There are a number of readily available attacks against the most common in-app purchase implementations. These are usually focused around:
Feeding the client fake purchase responses which indicate success
Replaying a valid purchase response multiple times
Sharing a purchase response with another client, so multiple players can receive the reward from a single purchase
For in-app purchases a trusted source of truth is required. Nakama checks and tracks purchases and purchase history, solving a significant set of possible vulnerabilities and pain points:
Issue
Description
Fake Purchases
Nakama directly connects to Apple, Google and Huawei services to check the validity of all incoming purchase receipts. This verification is completely outside the client’s code, and cannot be intercepted and tampered with. Every purchase receipt is verified, every time, and invalid ones are rejected.
Replay Attacks
All transactions are logged, preventing multiple submissions of the same purchase token or receipt.
Receipt Sharing
Successful transactions are bound to the account that submits them. Different users cannot submit the same transaction, even a valid one, in an attempt to receive the associated reward.
Product Mismatches
Each validated purchase receipt exposes data (e.g. product ID) that can be used to tie a purchase to a product, preventing attacks that attempt to use a valid (cheap) purchase to unlock a different (expensive) reward.
Single Source of Truth
While Nakama maintains an internal record of all transactions, the remote payment provider is always used for validation.
In-App Purchase Validation is available for Apple, Google, and Huawei purchases, regardless of platform. Both single product and subscription purchases are supported.
Apple and Google purchases made via Unity IAP are also supported.
A validation result contains a list of validated purchases.
Because Apple may contain multiple purchases in a single receipt, each validated purchase in the resulting list will contain only the purchases which have been validated and a boolean value to indicate if it has been seen before or not. If a purchase has already been validated by Nakama previously then the “seen before” value will be true, allowing the developer to discriminate new purchases and protect against replay attacks.
For Google and Huawei, each validation corresponds to a single purchase, which is included in the validation response list and also has a “seen before” value as above.
Each validated purchase also includes the payload of the provider validation response, should the developer need it for any reason.
Should the purchase/receipt be invalid, the validation fail for any reason, or the provider be unreachable, an error will be returned.
localfunctionvalidate_receipt(receipt)localresult=client.validate_purchase_apple(receipt)ifresult.errorthenprint(result.message)returnendpprint(result)end-- Use https://defold.com/extension-iap/iap.set_listener(function(self,transaction,error)ifnoterrorthenvalidate_receipt(transaction.receipt)endend)iap.buy("com.defold.nakama.goldbars-10")
The Apple App Store supports Server Notifications to monitor IAP state updates, such as purchase refunds or subscription auto-renewals. Nakama supports receiving these notifications via a callback URL that can be activated and set up in App Store Connect to act upon a subset of these state updates.
To activate the callback URL the notifications_endpoint_id configuration needs to be set, and takes on the following path: /v2/console/apple/subscriptions/<notifications_endpoint_id>.
Once the URL is set up in both Nakama and App Store Connect, any Apple subscription that was previously validated using the Nakama API will automatically have its state updated via the real-time notifications so that, for example, auto-renewing subscriptions expiry time are automatically kept in sync with the App Store.
The Apple App Store can send Server Notifications relating to refunded purchases and subscriptions.
After setting up the callback URL described in the IAP Apple Notifications section, it is possible to register hooks with custom code to handle a refund for either a purchase or subscription.
The hook will only be fired for purchases/subscriptions that were previously validated via the respective Nakama Apple IAP validation APIs.
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Code snippet for this language TypeScript has not been found. Please choose another language to show equivalent examples.
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Purchases
iferr:=initializer.RegisterPurchaseNotificationApple(func(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,purchase*api.ValidatedPurchase,providerPayloadstring)error{// Handle purchase refund.
// providerPayload contains the raw notification body received from Apple.
});err!=nil{returnerr}// Subscriptions
iferr:=initializer.RegisterSubscriptionNotificationApple(func(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,subscription*api.ValidatedSubscription,providerPayloadstring)error{// Handle subscription refund.
// providerPayload contains the raw notification body received from Apple.
});err!=nil{returnerr}
Be sure to grant your service account the following permissions: View financial data, orders, and cancellation survey responses and Manage orders and subscriptions.
Finally you will need to ensure you grant Nakama access to the purchase validation APIs. From the Google Play Developer Console and navigate to Settings > API Access:
The service account you created in the previous steps should be listed above. Grant access to the service account to access the API, making sure you give the service account access to Visibility, View Financial Data, and Manage Orders. These permissions are required for Nakama to validate receipts against Google Play.
Navigate to Users & Permissions to check that the service account is setup correctly:
localfunctionvalidate_receipt(receipt)localresult=client.validate_purchase_google(receipt)ifresult.errorthenprint(result.message)returnendpprint(result)end-- Use https://defold.com/extension-iap/iap.set_listener(function(self,transaction,error)ifnoterrorthenvalidate_receipt(transaction.receipt)endend)iap.buy("com.defold.nakama.goldbars-10")
Google supports setting up real-time developer notifications to monitor subscription state changes (e.g. when a subscription auto-renews).
The Nakama callback URL can be activated by setting the notifications_endpoint_id configuration, and takes on the following path: /v2/console/google/subscriptions/<notifications_endpoint_id>.
Once the URL is set up both in Nakama and the Google Developer Console, any Google subscription that was previously validated using the Nakama API will automatically have its state updated via the real-time notifications so that, for example, auto-renewing subscriptions expiry time are automatically kept in sync with Google.
To allow acting on a refunded purchase/subscription, hooks are provided to register custom functions that will be invoked for each, but only for those that have been previously validated using the respective Nakama Google IAP validation APIs.
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Code snippet for this language TypeScript has not been found. Please choose another language to show equivalent examples.
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Purchases
iferr:=initializer.RegisterPurchaseNotificationGoogle(func(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,purchase*api.ValidatedPurchase,providerPayloadstring)error{// Handle purchase refund.
// providerPayload contains a JSON representation of the Google voided purchase payload.
});err!=nil{returnerr}// Subscriptions
iferr:=initializer.RegisterSubscriptionNotificationGoogle(func(ctxcontext.Context,loggerruntime.Logger,db*sql.DB,subscription*api.ValidatedSubscription,providerPayloadstring)error{// Handle subscription refund.
// providerPayload contains a JSON representation of the Google voided purchase payload.
});err!=nil{returnerr}
Nakama validates Huawei purchases against their IAP validation service. As suggested by Huawei, the validity of the purchase data is also checked against the provided signature before contacting the Huawei service. If the data is invalid for any reason, the purchase is rejected before validation with Huawei’s validation service.
typeunityIAPstruct{Payloadstring`json:"Payload"`Storestring`json:"Store"`TransactionIDstring`json:"TransactionID"`}switchunityIAP.Store{case"GooglePlay":validatedReceipt,err=nk.PurchaseValidateGoogle(ctx,userID,wrapper.Payload)case"AppleAppStore":validatedReceipt,err=nk.PurchaseValidateApple(ctx,userID,wrapper.Payload)default:logger.Warn("Unrecognised store type in Unity IAP.")returnErrBadInput}iferr!=nil{logger.WithField("err",err).Error("Receipt validation error.")returnerr}ifvalidatedReceipt.SeenBefore{// Handle already seen receipt.
logger.Warn("Receipt replay attack.")}
In addition to validation of purchases and subscriptions, you can also get subscriptions by specific product ID or list all subscriptions for a given user.