In-App Purchase Validation #

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:

IssueDescription
Fake PurchasesNakama 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 AttacksAll transactions are logged, preventing multiple submissions of the same purchase token or receipt.
Receipt SharingSuccessful 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 MismatchesEach 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 TruthWhile 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.

Interpreting validation results #

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.

Apple #

Nakama supports validating purchases made for products and subscriptions in iOS.

Apple purchase receipts are sent to Apple for validation. As suggested by Apple, both Production and Sandbox servers are used to validate receipts.

Setup #

To validate receipts against the App Store, Nakama requires your app’s shared secret.

  1. From App Store Connect navigate to the General > App Information page and the App-Specific Shared Secret section:

Apple App Store Connect
Apple App Store Connect

  1. Select Manage and in the dialog that appears, select Generate:

Generate Apple App Store Connect Shared Secret
Generate Apple App Store Connect Shared Secret

  1. Make a record of your shared secret for use in your Nakama configuration:

New Apple App Store Connect Shared Secret
New Apple App Store Connect Shared Secret

  1. Set the value of Nakama’s iap.apple.shared_password configuration flag to the value of the shared secret created above.

Validate purchase #

Nakama only supports validating iOS 7+ receipts.

Apple receipts can contain multiple purchases, Nakama will validate all of them and store them as individual purchase records.

Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/purchase/apple \
  --user 'defaultkey:' \
  --data '{"receipt":"base64_encoded_receipt_data"}'
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
local function validate_receipt(receipt)
  local result = client.validate_purchase_apple(receipt)
  if result.error then
    print(result.message)
    return
  end
  pprint(result)
end

-- Use https://defold.com/extension-iap/
iap.set_listener(function(self, transaction, error)
  if not error then
    validate_receipt(transaction.receipt)
  end
end)
iap.buy("com.defold.nakama.goldbars-10")
Client
1
2
3
4
5
6
string appleReceipt = "<receipt>";
var response = await client.ValidatePurchaseAppleAsync(session, appleReceipt);
foreach (var validatedPurchase in response.ValidatedPurchases)
{
    System.Console.WriteLine("Validated purchase: " + validatedPurchase);
}
Client
1
2
3
4
5
6
7
let appleReceipt = "<receipt>"

let response = try await client.validatePurchaseApple(session: session, receipt: appleReceipt)

for purchase in response.validatedPurchases {
    print("Validated purchase: \(purchase)")
}
Code snippet for this language Dart/Flutter has not been found. Please choose another language to show equivalent examples.
Client
1
2
3
4
5
const appleReceipt = "<receipt>";
const result = await client.validatePurchaseApple(session, appleReceipt);
result.validatedPurchases.forEach(validatedPurchase => {
  console.info("Validated purchase:", validatedPurchase)
});
Client
1
2
3
4
5
6
7
var apple_receipt = "<receipt>"
var result : NakamaAPI.ApiValidatePurchaseResponse = yield(client.validate_purchase_apple_async(session, apple_receipt), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for p in result.validated_purchase:
    print("Validated purchase: %s" % p.validated_purchase)
Client
1
2
3
4
5
6
7
var apple_receipt = "<receipt>"
var result : NakamaAPI.ApiValidatePurchaseResponse = await client.validate_purchase_apple_async(session, apple_receipt)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for p in result.validated_purchase:
    print("Validated purchase: %s" % p.validated_purchase)
Client
1
2
3
4
5
6
7
8
POST /v2/iap/purchase/apple
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "receipt": "base64_encoded_Apple_receipt_payload"
}

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.

Refer to the function reference page for the provided runtime purchase validation functions.

Validate subscription #

Client
1
2
3
4
string appleReceipt = "<receipt>";
var response = await client.ValidateSubscriptionAppleAsync(session, appleReceipt);

System.Console.WriteLine("Validated subscription: " + response.ValidatedSubscription);
Client
1
2
3
4
5
let appleReceipt = "<receipt>"

let response = try await client.validateSubscriptionApple(session: session, receipt: appleReceipt)

print("Validated subscription: \(response.validatedSubscription)")
Code snippet for this language Dart/Flutter has not been found. Please choose another language to show equivalent examples.
Client
1
2
3
4
5
6
7
8
var apple_receipt = "..."
var response : NakamaAPI.ApiValidateSubscriptionResponse = yield(client.validate_subscription_apple_async(session, apple_receipt), "completed")

if response.is_exception():
    print("An error occurred: %s" % response)
    return

print("Validated subscription: %s" % response.validated_subscription)
Client
1
2
3
4
5
6
7
8
var apple_receipt = "..."
var response : NakamaAPI.ApiValidateSubscriptionResponse = await client.validate_subscription_apple_async(session, apple_receipt)

if response.is_exception():
    print("An error occurred: %s" % response)
    return

print("Validated subscription: %s" % response.validated_subscription)
Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/subscription/apple \
  --user 'defaultkey:' \
  --data '{"receipt":"base64_encoded_receipt_data"}'
Client
1
2
3
4
5
6
7
8
POST /v2/iap/subscription/apple
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "receipt": "base64_encoded_Apple_receipt_payload"
}
Client
1
2
3
4
5
local apple_receipt = "<receipt>";
local response = client.validate_purchase_apple(receipt)
for i,purchase in ipairs(response.validated_purchase) do
  pprint("Validated purchase: ", purchase)
end
Client
1
2
3
const appleReceipt = "<receipt>";
const result = await client.validateSubscriptionApple(session, appleReceipt);
console.info("Validated subscription:", result.validatedSubscription);

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.

Refer to the function reference page for the provided runtime subscription validation functions.

IAP notifications #

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.

IAP refunds #

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
if err := initializer.RegisterPurchaseNotificationApple(func(ctx context.Context, logger runtime.Logger, db *sql.DB, purchase *api.ValidatedPurchase, providerPayload string) error {
    // Handle purchase refund.
    // providerPayload contains the raw notification body received from Apple.
}); err != nil {
  return err
}

// Subscriptions
if err := initializer.RegisterSubscriptionNotificationApple(func(ctx context.Context, logger runtime.Logger, db *sql.DB, subscription *api.ValidatedSubscription, providerPayload string) error {
    // Handle subscription refund.
    // providerPayload contains the raw notification body received from Apple.
}); err != nil {
  return err
}

Google #

Nakama supports validating purchases made for products and subscriptions on Android.

Setup #

To validate receipts against the Play Store, Nakama requires the client_email and private_key of the relevant Google Service Account for your project.

  1. Setup a Service Account in the Google API Console:

Create Service Account
Create Service Account

  1. Once a service account is created, you’ll need to create a key:

    Create Key
    Create Key

  2. Download the key as a JSON file:

Create JSON Key
Create JSON Key

Enable API
Enable API

  1. If you don’t already have a service account, create one as detailed in the official Google documentation.
  • Be sure to grant your service account the following permissions: View financial data, orders, and cancellation survey responses and Manage orders and subscriptions.
  1. 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:

Create API Access
Create API Access

  1. 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.

Grant Access
Grant Access

  1. Navigate to Users & Permissions to check that the service account is setup correctly:

List users with access
List users with access

Validate purchase #

The Google receipt expected is the string returned by Purchase.getOriginalJson(). See the official Google Developer docs for more details.
Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/purchase/google \
  --user 'defaultkey:' \
  --data '{"purchase":"json_encoded_purchase_data"}'
Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
local function validate_receipt(receipt)
  local result = client.validate_purchase_google(receipt)
  if result.error then
    print(result.message)
    return
  end
  pprint(result)
end

-- Use https://defold.com/extension-iap/
iap.set_listener(function(self, transaction, error)
  if not error then
    validate_receipt(transaction.receipt)
  end
end)
iap.buy("com.defold.nakama.goldbars-10")
Client
1
2
3
4
5
6
string googleReceipt = "<receipt>";
var response = await client.ValidatePurchaseGoogleAsync(session, googleReceipt);
foreach (var validatedPurchase in response.ValidatedPurchases)
{
    System.Console.WriteLine("Validated purchase: " + validatedPurchase);
}
Client
1
2
3
4
5
6
7
let googleReceipt = "<receipt>"

let response = try await client.validatePurchaseGoogle(session: session, receipt: googleReceipt)

for purchase in response.validatedPurchases {
    print("Validated purchase: \(purchase)")
}
Code snippet for this language Dart/Flutter has not been found. Please choose another language to show equivalent examples.
Client
1
2
3
4
5
const googleReceipt = "<receipt>";
const result = await client.validatePurchaseGoogle(session, googleReceipt);
result.validatedPurchases.forEach(validatedPurchase => {
  console.info("Validated purchase:", validatedPurchase)
});
Client
1
2
3
4
5
6
7
var google_receipt = "<receipt>"
var result : NakamaAPI.ApiValidatePurchaseResponse = yield(client.validate_purchase_google_async(session, google_receipt), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for p in result.validated_purchase:
    print("Validated purchase: %s" % p.validated_purchase)
Client
1
2
3
4
5
6
7
var google_receipt = "<receipt>"
var result : NakamaAPI.ApiValidatePurchaseResponse = await client.validate_purchase_google_async(session, google_receipt)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for p in result.validated_purchase:
    print("Validated purchase: %s" % p.validated_purchase)
Client
1
2
3
4
5
6
7
8
POST /v2/iap/purchase/google
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "purchase":"json_encoded_purchase_data"
}

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.

Refer to the function reference page for the provided runtime purchase validation functions.

Validate subscription #

Client
1
2
3
4
string googleReceipt = "<receipt>";
var response = await client.ValidateSubscriptionGoogleAsync(session, googleReceipt);

System.Console.WriteLine("Validated subscription: " + response.ValidatedSubscription);
Client
1
2
3
4
5
let googleReceipt = "<receipt>"

let response = try await client.validateSubscriptionGoogle(session: session, receipt: googleReceipt)

print("Validated subscription: \(response.validatedSubscription)")
Code snippet for this language Dart/Flutter has not been found. Please choose another language to show equivalent examples.
Client
1
2
3
4
5
6
7
8
var google_receipt = "..."
var response : NakamaAPI.ApiValidateSubscriptionResponse = yield(client.validate_subscription_google_async(session, google_receipt), "completed")

if response.is_exception():
    print("An error occurred: %s" % response)
    return

print("Validated subscription: %s" % response.validated_subscription)
Client
1
2
3
4
5
6
7
8
var google_receipt = "..."
var response : NakamaAPI.ApiValidateSubscriptionResponse = await client.validate_subscription_google_async(session, google_receipt)

if response.is_exception():
    print("An error occurred: %s" % response)
    return

print("Validated subscription: %s" % response.validated_subscription)
Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/subscription/google \
  --user 'defaultkey:' \
  --data '{"purchase":"json_encoded_purchase_data"}'
Client
1
2
3
4
5
6
7
8
POST /v2/iap/subscription/google
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "purchase":"json_encoded_purchase_data"
}
Client
1
2
3
4
5
local google_receipt = "<receipt>";
local response = client.validate_purchase_google(receipt)
for i,purchase in ipairs(response.validated_purchase) do
  pprint("Validated purchase: ", purchase)
end
Client
1
2
3
const googleReceipt = "<receipt>";
const result = await client.validateSubscriptionGoogle(session, googleReceipt);
console.info("Validated subscription:", result.validatedSubscription);

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.

Refer to the function reference page for the provided runtime subscription validation functions.

IAP notifications #

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.

IAP refunds #

Google provides an API to list refunded purchases. Nakama can be configured to poll this API periodically on configurable intervals.

To enable the periodic check of refunded purchases/subscriptions the following configurations have to be set:

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
if err := initializer.RegisterPurchaseNotificationGoogle(func(ctx context.Context, logger runtime.Logger, db *sql.DB, purchase *api.ValidatedPurchase, providerPayload string) error {
    // Handle purchase refund.
    // providerPayload contains a JSON representation of the Google voided purchase payload.
}); err != nil {
  return err
}

// Subscriptions
if err := initializer.RegisterSubscriptionNotificationGoogle(func(ctx context.Context, logger runtime.Logger, db *sql.DB, subscription *api.ValidatedSubscription, providerPayload string) error {
    // Handle subscription refund.
    // providerPayload contains a JSON representation of the Google voided purchase payload.
}); err != nil {
  return err
}

Facebook Instant Games #

Nakama supports validating purchases made for products on Facebook Instant Games.

Validate purchase #

Client
1
2
3
4
var signedRequest = "<signedRequest>";
var response = await client.ValidatePurchaseFacebookInstantAsync(session, signedRequest);

System.Console.WriteLine("Validated purchase: " + response.ValidatedPurchase);
Client
1
2
3
4
const signed = "<signedRequest>";
const response = await client.validatePurchaseFacebookInstant(session, signed);

console.info("Validated purchase:", response.validatedPurchase);

Code snippet for this language Swift has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Dart/Flutter has not been found. Please choose another language to show equivalent examples.
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Godot 3 has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Godot 4 has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
Code snippet for this language cURL has not been found. Please choose another language to show equivalent examples.
Code snippet for this language REST has not been found. Please choose another language to show equivalent examples.

Huawei #

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.

Validate purchase #

Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/purchase/huawei \
  --user 'defaultkey:' \
  --data '{"purchase":"json_encoded_purchase_data","signature":"purchase_data_signature"}'
Client
1
-- Huawei purchases are not yet supported by https://defold.com/extension-iap/
Client
1
2
3
4
5
6
7
string huaweiReceipt = "<receipt>";
string huaweiSignature = "<signature>";
var response = await client.ValidatePurchaseHuaweiAsync(session, huaweiReceipt, huaweiSignature);
foreach (var validatedPurchase in response.ValidatedPurchases)
{
    System.Console.WriteLine("Validated purchase: " + validatedPurchase);
}
Client
1
2
3
4
5
6
7
8
let huaweiReceipt = "<receipt>"
let huaweiSignature = "<signature>"

let response = try await client.validatePurchaseHuawei(session: session, receipt: huaweiReceipt, signature: huaweiSignature)

for purchase in response.validatedPurchases {
    print("Validated purchase: \(purchase)")
}
Code snippet for this language Dart/Flutter has not been found. Please choose another language to show equivalent examples.
Client
1
2
3
4
5
6
const huaweiReceipt = "<receipt>";
const huaweiSignature = "<signature>";
const result = await client.validatePurchaseHuawei(session, huaweiReceipt, huaweiSignature);
result.validatedPurchases.forEach(validatedPurchase => {
  console.info("Validated purchase:", validatedPurchase)
});
Client
1
2
3
4
5
6
7
8
var huawei_receipt = "<receipt>"
var huawei_signature = "<signature>"
var result : NakamaAPI.ApiValidatePurchaseResponse = yield(client.validate_purchase_huawei_async(session, huawei_receipt, huawei_signature), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for p in result.validated_purchase:
    print("Validated purchase: %s" % p.validated_purchase)
Client
1
2
3
4
5
6
7
8
var huawei_receipt = "<receipt>"
var huawei_signature = "<signature>"
var result : NakamaAPI.ApiValidatePurchaseResponse = await client.validate_purchase_huawei_async(session, huawei_receipt, huawei_signature)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for p in result.validated_purchase:
    print("Validated purchase: %s" % p.validated_purchase)
Client
1
2
3
4
5
6
7
8
9
POST /v2/iap/purchase/huawei
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "purchase":"json_encoded_purchase_data",
  "signature":"purchase_data_signature"
}

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.

Refer to the function reference page for the provided runtime purchase validation functions.

Unity IAP #

Unity IAP has support for integrating with most popular app stores, including Apple and Google.

Unity IAP purchase receipts contain the following information:

KeyValue
StoreThe name of the store in use, such as GooglePlay or AppleAppStore.
TransactionIDThis transaction’s unique identifier, provided by the store.
PayloadFor Apple, a Base64 encoded App Receipt. For Google, JSON encoded purchase data and a signature.
The Nakama IAP Validation APIs expect only the content of the “Payload” key to be provided for validation in the respective store endpoints.

Validate purchase #

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
16
17
18
19
20
21
22
23
24
type unityIAP struct {
	  Payload       string `json:"Payload"`
	  Store         string `json:"Store"`
	  TransactionID string `json:"TransactionID"`
}

switch unityIAP.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.")
        return ErrBadInput
}
if err != nil {
    logger.WithField("err", err).Error("Receipt validation error.")
    return err
}

if validatedReceipt.SeenBefore {
    // Handle already seen receipt.
    logger.Warn("Receipt replay attack.")
}

Subscriptions #

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.

Get subscription #

Client
1
2
3
4
string productId = "<productId>";
var response = await client.GetSubscriptionAsync(session, productId);

System.Console.WriteLine("Subscription: " + response);
Client
1
2
3
4
5
let productId = "<productId>"

let response = try await client.getSubscription(session: session, productId: productId)

print("Subscription: \(response)")
Code snippet for this language Dart/Flutter has not been found. Please choose another language to show equivalent examples.
Client
1
2
3
4
5
6
7
8
var product_id = "..."
var response : NakamaAPI.ApiValidatedSubscription = yield(client.get_subscription_async(session, product_id), "completed")

if response.is_exception():
    print("An error occurred: %s" % response)
    return

print("Validated subscription: %s" % response)
Client
1
2
3
4
5
6
7
8
var product_id = "..."
var response : NakamaAPI.ApiValidatedSubscription = yield(client.get_subscription_async(session, product_id), "completed")

if response.is_exception():
    print("An error occurred: %s" % response)
    return

print("Validated subscription: %s" % response)
Client
1
2
curl "http://127.0.0.1:7350/v2/iap/subscription/{productId} \
  -H 'Authorization: Bearer <session token>'
Client
1
2
3
4
5
GET /v2/iap/subscription/{productId}
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
local product_id = "<productId>";
local response = client.get_subscription(product_id)
pprint("Subscription:", response)
Client
1
2
3
const productId = "<productId>";
const response = await client.getSubscription(session, productId);
console.info("Subscription:", response);

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.

Refer to the function reference page for the provided runtime subscription validation functions.

List user subscriptions #

Client
1
2
3
4
5
6
7
string userId = "<userId>";
var response = await client.ListSubscriptionsAsync(userId);

foreach (var validatedSubscription in response.ValidatedSubscriptions)
{
    System.Console.WriteLine("Subscription: " + validatedSubscription);
}
Client
1
2
3
4
5
let response = try await client.listSubscriptions(session: session, limit: 20)

for subscription in response.validatedSubscriptions {
    print("Subscription: \(subscription)")
}
Code snippet for this language Dart/Flutter has not been found. Please choose another language to show equivalent examples.
Client
1
2
3
4
5
6
7
8
var response : NakamaAPI.ApiSubscriptionList = yield(client.get_subscription_list(session), "completed")

if response.is_exception():
    print("An error occurred: %s" % response)
    return

for p in result.validated_subscriptions:
    print("Validated subscription: %s" % p.validated_subscription)
Client
1
2
3
4
5
6
7
8
var response : NakamaAPI.ApiSubscriptionList = await client.get_subscription_list(session)

if response.is_exception():
    print("An error occurred: %s" % response)
    return

for p in result.validated_subscriptions:
    print("Validated subscription: %s" % p.validated_subscription)
Client
1
2
curl "http://127.0.0.1:7350/v2/iap/subscription/limit=<limit>&cursor=<cursor>" \
  -H 'Authorization: Bearer <session token>'
Client
1
2
3
4
5
GET /v2/iap/subscription/limit=<limit>&cursor=<cursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Client
1
2
3
4
local response = client.list_subscriptions()
for i,subscription in ipairs(response.validatedSubscriptions) do
  pprint("Subscription:", response)
end
Client
1
2
3
4
const response = await client.listSubscriptions(session, limit: 20)
response.validatedSubscriptions.forEach(validatedSubscription => {
  console.info("Subscription:", validatedSubscription)
});

Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.

Refer to the function reference page for the provided runtime subscription validation functions.