IAP with TS and unity

Hello, Im trying to implement google IAP here. i did

   client_email: "<GOOGLE_IAP_CLIENT_EMAIL>"
   private_key: "<GOOGLE_IAP_PRIVATE_KEY>"
   package_name: "<GOOGLE_IAP_PACKAGE_NAME>"
   refund_check_period_min: 15

and i make RPC, that handles the google notification
initializer.registerRpc("HANDLE_GOOGLE_NOTIFICATION", googleIAPNotification);

Please give me a little more description about the actual flow.

  • From the unity side to server-side validation (receipt)
  • That notification RPC when it calls
  • And how to handle when the player completes the Google Play purchase, and if the player’s network is lost, how to handle that condition, etc.,

This is my first implementation of Google IAP.

Hello @developerdinno,

For Google IAP Purchase validation notification, you should setup the configs mentioned here: In-app Purchase Validation - Heroic Labs Documentation .

Nakama already supports subscription related notifications, as well as refunds (although these currently rely on polling).

The flow should be as simple as calling the IAP validation endpoint, and if everything else is correctly configured, then you can set up some custom code for refund handling, but otherwise it should be good to go, and you shouldn’t need to implement your own RPC to handle the notifications.

Best.

Thank you for replying, sir.
Here I get Nakama already supports, but should I get the complete workflow and how it supports it? And what happens when player get connection lost in the middle of purchasing something from IAP than that transaction should be complete in the IAP but not on server side (don’t get the reward for now) how should i handle.

And i did something


let RpcValidateIAP: nkruntime.RpcFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string {
  logger.info("-------ctx.userId------------" + JSON.stringify(ctx.userId));
  const payloadObj = JSON.parse(payload);
  const receiptJson = JSON.parse(payloadObj.receipt).Payload;
  // logger.info("------receipt-----------" + JSON.stringify(receiptJson));

  let purchaseValidateGoogle = nk.purchaseValidateGoogle(ctx.userId, receiptJson);
  logger.info("------purchaseValidateGoogle-----------" + JSON.stringify(purchaseValidateGoogle));

  // let subscriptionValidateGoogle = nk.subscriptionValidateGoogle(ctx.userId, receiptJson);
  // validatePurchaseResponse.validatedPurchases && validatePurchaseResponse.validatedPurchases.forEach((p) => rewardPurchase(ctx, logger, nk, p));
  return JSON.stringify({ success: true });
};

that gives:
| {"level":"error","ts":"2025-05-26T10:28:35.692Z","caller":"server/runtime_javascript.go:573","msg":"JavaScript runtime function raised an uncaught exception","mode":"rpc","id":"validate_google_recept","error":"GoError: error validating Google receipt: non-200 response from Google service, status=401, payload={\n \"error\": {\n \"code\": 401,\n \"message\": \"The current user has insufficient permissions to perform the requested operation.\",\n \"errors\": [\n {\n \"message\": \"The current user has insufficient permissions to perform the requested operation.\",\n \"domain\": \"androidpublisher\",\n \"reason\": \"permissionDenied\"\n }\n ]\n }\n}\n at github.com/heroiclabs/nakama/v3/server.(*runtimeJavascriptNakamaModule).mappings.(*runtimeJavascriptNakamaModule).purchaseValidateGoogle.func105 (native)"}

The error seems to indicate an issue with the configured Google credentials.

After the IAP flow is completed on the client, you send the receipt for validation to Nakama in your custom RPC. The purchaseValidateGoogle in Nakama actually sends the receipt to Google to validate its authenticity, after which Nakama stores it and retuns a ValidatedReceipt with the SeenBefore flag set to false. At this stage you can then grant the player whatever resource he purchased. If SeenBefore is true, then Nakama has already seen the receipt, so you shouldn’t grant anything as the user would be double spending reusing an old receipt.

If you’re concerned about something happening between receipt validation and resource granting, causing Nakama to assimilate the receipt but failing to grant the resources, you likely want to keep some mapping of granted resource to a given receipt in a separate StorageObject, and use multiUpdate to ensure that the granting and mapping between receipt and grant are updated atomically. If you do this, you need to consolidate the state of a grant with your own record instead of relying solely on the SeenBefore flag.

If the receipt being sent to Nakama is in some transient state (e.g.: Google receipt in status Pending), then Nakama will reject the receipt altogether as invalid.

Hope this clarifies.

Thanks for replying. OK, i will try If there is any issue, than i will ask you :slight_smile: