This document features various code samples
based on the official now.gg sample apps and games. You can check the in-line references throughout the document to better understand and implement now.gg Payments module.
1. Extract the compressed now.gg Payments module and locate the .aar file in the package:
Payments.aar
2. Add the following dependency to the build.gradle
file of your game:
dependencies { implementation fileTree(dir: 'libs', include: ['*.aar']) }
The Purchase Lifecycle flow for now.gg Payments is as follows:
now.gg Payments uses Purchase Tokens and Order IDs to track the products and transactions within your game or App.
The term entitlement or granting of entitlement is used to signify ‘delivery of in-app product’ to the user after a product purchase.
SERVICE_DISCONNECTED
error code suggests the connection between the now.gg Payments service, and your game is disconnected. The solution to this is to reinitialize your app/game to re-establish the connection with now.gg payments service.The BillingClient interface acts as the primary communication mechanism between the now.gg Payments module and your app/game. It is responsible for providing both synchronous and asynchronous methods for general billing operations.
BillingClient Creation --> Use newBuilder() Setup Updates of Purchase --> Call setListener() Receive updates throughout app/game --> PurchasesUpdatedListener Set PAYMENT_ID for billing --> use setAppId(PAYMENT_ID) Set IN_GAME_ID to use existing in-game user ID --> use setInGameId(IN_GAME_ID) Query unconsumed purchases after initialization --> use queryPurchasesAsync()
You must pass the user’s unique userID (IN_GAME_ID
) to now.gg Payments module while creating and initializing the BillingClient
, as illustrated in the following sample code:
private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() { @Override public void onPurchasesUpdated(int billingResult, List<Purchase> purchases) { if (billingResult == BillingResponse.OK) { if (purchases == null) { return; } for (Purchase purchase: purchases) { String payload = purchase.getDeveloperPayload(); // get developer payload // handle successful purchase here } } else if (billingResult == BillingResponse.USER_CANCELED) { Log.i(TAG, "onPurchasesUpdated() - user cancelled the purchase flow - skipping"); } else { Log.w(TAG, "onPurchasesUpdated() got unknown billingResult: " + billingResult); } } }; private BillingClient billingClient = BillingClient.newBuilder(activity) .setListener(purchasesUpdatedListener) .setAppId(PAYMENT_ID) .setInGameId(IN_GAME_ID) // unique ID to identify the user. .build();
private val purchasesUpdatedListener = object : PurchasesUpdatedListener { override fun onPurchasesUpdated(billingResult: Int, purchases: List<Purchase>?) { if (billingResult == BillingResponse.OK) { if (purchases == null) { return } for (purchase in purchases) { val payload = purchase.developerPayload // get developer payload // handle successful purchase here } } else if (billingResult == BillingResponse.USER_CANCELED) { Log.i(TAG, "onPurchasesUpdated() - user cancelled the purchase flow - skipping") } else { Log.w(TAG, "onPurchasesUpdated() got unknown billingResult: $billingResult") } } } private val billingClient = BillingClient.newBuilder(activity) .setListener(purchasesUpdatedListener) .setAppId(PAYMENT_ID) .setInGameId(IN_GAME_ID) // unique ID to identify the user. .build()
IN_GAME_ID
is a unique identifier for your user.
IN_GAME_ID
is unique for every user.
IN_GAME_ID.
IN_GAME_ID
please add it as an internal tester within nowStudio.PAYMENT_ID
is a unique identifier for your app.
PAYMENT_ID
can be found within the App Details section of nowStudio – more information.PAYMENT_ID
will be available only after you have added your app/game to nowStudio.now.gg Payments service has an asynchronous connection process and requires you to implement a BillingClientStateListener
to capture callback on completion of client setup.
startConnection()
.onBillingServiceDisconnected()
.onBillingServiceDisconnected()
, ensure that your BillingClient is calling the startConnection()
method to re-establish connection with now.gg Payments Service.queryPurchasesAsync()
method after initialization to ensure that any unconsumed purchases from the user’s previous game session are properly allotted to the user.
Note: You may choose to write your own retry logic. However, we have also provided a retry logic that you may choose to implement.
Example
billingClient.startConnection(new BillingClientStateListener() { @Override public void onBillingSetupFinished(int billingResult) { if (billingResult == 0) { // The BillingClient is ready. You can query purchases here. QueryPurchasesParams params = QueryPurchasesParams.newBuilder().setProductType(BillingClient.SkuType.ALL).build(); billingClient.queryPurchasesAsync(params, new PurchasesResponseListener() { @Override public void onQueryPurchasesResponse(BillingResult billingResult, List < Purchase > list) { // Allot the purchased item to the user and call consume/acknowledge. } }); } } @Override public void onBillingServiceDisconnected() { // Try to restart the connection on the next request to // Payments module by calling the startConnection() method. } });
billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: Int) { if (billingResult == 0) { // The BillingClient is ready. You can query purchases here. val params = QueryPurchasesParams.newBuilder().setProductType(BillingClient.SkuType.ALL).build() billingClient.queryPurchasesAsync(params, object : PurchasesResponseListener { override fun onQueryPurchasesResponse(billingResult: BillingResult, list: List< Purchase >) { // Allot the purchased item to the user and call consume/acknowledge. } }) } } override fun onBillingServiceDisconnected() { // Try to restart the connection on the next request to // Payments module by calling the startConnection() method. } })
This section walks you through the process of querying and listing the available in-app products in your app/game and handling the purchases.
The steps for this flow are:
Before you begin, check that you have:
The first step here is to query for available products and list them to your users. To perform a query for in-app product details, you need to call querySkuDetailsAsync()
. When you query for SKU details, the now.gg Payments module will return localized product information to be displayed to your users.
Note: Before displaying the in-app products to the user, you should query the SKU details, as it returns updated and relevant product information.
SKU Types and Order Strings
Below are the SKUType
associated with now.gg Payments:
While you call querySkuDetailsAsync()
, remember to pass an instance of SkuDetailsParams
, which specifies the SKUType
along with the product ID that you configured in the nowStudio.
Results
After querying for available products, you must assign a listener to capture the results of the Asynchronous operation you passed.
The following example will showcase the implementation of a SkuDetailsResponseListener
interface and will allow you to override the onSkuDetailsResponse()
, to notify the listener when the query is finished.
List < String > skuList = new ArrayList < > (); skuList.add("premium_upgrade"); skuList.add("gas"); SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder(); params.setSkusList(skuList).setType(SkuType.ALL); billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() { @Override public void onSkuDetailsResponse(int billingResult, List < SkuDetails > skuDetailsList) { // Process the result. } });
val skuList = mutableListOf<String>() skuList.add("premium_upgrade") skuList.add("gas") val params = SkuDetailsParams.newBuilder() .setSkusList(skuList) .setType(SkuType.ALL) billingClient.querySkuDetailsAsync(params.build(), object : SkuDetailsResponseListener { override fun onSkuDetailsResponse(billingResult: Int, skuDetailsList: List<SkuDetails>) { // Process the result. } })
SkuDetails
object can be called using various methods to list information about the in-app products.SkuDetails
object.After you have listed the available products for the user to buy, you must initiate the purchase flow from the app/game.
To start the purchase flow, you will need to call the launchBillingFlow()
method from the App’s main thread.
Here, the BillingFlowParams object is called for a reference, as it contains relevant details stored within the SkuDetails
object. The SkuDetails object is obtained by calling querySkuDetailsAsync()
.
To start the purchase flow, you need to create the BillingFlowParams
object using the BillingFlowParams.Builder class. Use the following code snippets as reference:
// An activity reference for billing flow initiation Activity activity = ...; // Call querySkuDetailsAsync() to retrieve a value for 'skuDetails'. BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setSkuDetails(skuDetails) .setDeveloperPayload("optional developer payload") .build(); billingClient.launchBillingFlow(activity, billingFlowParams); //Result
// An activity reference for billing flow initiation val activity : Activity = ...; // Call querySkuDetailsAsync() to retrive a value for 'skuDetails'. val flowParams = BillingFlowParams.newBuilder() .setSkuDetails(skuDetails) .setDeveloperPayload("optional developer payload") .build() billingClient.launchBillingFlow(activity, flowParams) //Result
You can use BillingClient.BillingResponse as a reference for the return response codes.
0
) indicates a successful launch.launchBillingFlow()
.onPurchasesUpdated()
to communicate the purchase result to a listener that implements the PurchasesUpdatedListener
interface.setListener()
method.The following example outlines the process to override the onPurchasesUpdated()
method:
@Override void onPurchasesUpdated(int billingResult, List<Purchase> purchases) { if (billingResult == 0 && purchases != null) { for (Purchase purchase : purchases) { String payload = purchase.getDeveloperPayload(); // Get Developer Payload handlePurchase(purchase); } } else if (billingResult == 1) { // Error Handling - Caused due to user terminating the purchase flow } else { // Error Handling - Any other causes (based on error code) } }
override fun onPurchasesUpdated(billingResult: int, purchases: List<Purchase>?) { if (billingResult == 0 && purchases != null) { for (purchase in purchases) { val payload: String = purchase.developerPayload // Get Developer Payload handlePurchase(purchase) } } else if (billingResult == 1) { // Error Handling - Caused due to the user terminating the purchase flow. } else { // Error Handling - Any other causes (based on error code) } }
Note: The onPurchasesUpdated()
method should be implemented to handle possible response codes.
This section focuses on processing the purchases. After you have started the purchase flow, you are required to process the purchases in the following manner:
You can verify the purchases using either of the following two methods:
The first method to verify the purchase is using verifyPurchase API. You can use this API to verify the purchases with our backend server.
To verify the purchase, call the verifyPurchase API from your app backend server with purchaseToken
, as illustrated in the following sample request code:
curl --location 'https://cloud-api.bluestacks.cn/v2/seller/order/verifyPurchase' \ --header 'Authorization: <payment_api_key_here>' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'purchaseToken=<nowgg_purchase_token>'
import requests url = "https://cloud-api.bluestacks.cn/v2/seller/order/verifyPurchase" payload = 'purchaseToken=<nowgg_purchase_token>' headers = { 'Authorization': '<payment_api_key_here>', 'Content-Type': 'application/x-www-form-urlencoded' } response = requests.request("POST", url, headers=headers, data=payload) print(response.text)
The second method to verify the purchase is using a public key. An implementation for verifying purchases using the public key has been illustrated in the demo’s billingManager.java
.
verifyValidSignature
function and then locate verifyPurchase
.By referring to the implementation using the above-mentioned functions in our demo’s billingManager.java
, you can implement the purchase verification in your App.
Note:
public key
by following the steps listed here.After you have verified the purchase, your App can grant the entitlement of the purchased product to the user and mark the product as consumed (consumable products) or Acknowledged (Subscriptions)
The following methods illustrate how to assign purchase entitlement to the user:
We have provided two methods that you can use to consume a purchase:
We have provided two methods that you can use to acknowledge a purchase:
We have provided two methods to consume a purchase:
Note: If your app is client-only without a backend server, you should use this method.
To grant the entitlement:
consumeAsync()
method.
Consumed
, allowing the now.gg billing service to make it available for re-purchase.onConsumeResponse()
, which you can override.ConsumeResponseListener
interface is not updated.The following code snippet demonstrates purchase verification and consume operation:
void handlePurchase(Purchase purchase) { // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener. Purchase purchase = ...; // Verify the purchase. // Ensure entitlement was not already granted for this purchaseToken. // Grant the entitlement to the user. // Consume Purchase ConsumeResponseListener listener = new ConsumeResponseListener() { @Override public void onConsumeResponse(int billingResult, String purchaseToken) { if (billingResult == 0) { // Handle the success of the consume operation. } } }; billingClient.consumeAsync(purchase.getPurchaseToken(), listener); }
fun handlePurchase(purchase: Purchase) { // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener. val purchase: Purchase = ...; // Verify the purchase. // Ensure entitlement was not already granted for this purchaseToken. // Grant the entitlement to the user. // Consume Purchase billingClient.consumeAsync(purchase.getPurchaseToken(), { billingResult, outToken - > if (billingResult == 0) { // Handle the success of the consume operation. } }) }
The consumePurchase
API should be used for server-side purchase consumption for apps with a backend server.
After you have verified the purchase, you should mark it as consumed.
Purchase consumption involves:
The following sample code illustrates the associated request using the consumePurchase
API.
import requests url = "https://cloud-api.bluestacks.cn/v2/order/consumePurchase" payload = 'purchaseToken=nowgg-_purchase_token' headers = { 'Authorization': 'payment_api_key_here', 'Content-Type': 'application/x-www-form-urlencoded' } response = requests.request("POST", url, headers=headers, data=payload) print(response.text)
Reference
consumePurchase
API.Payment API Key
can be found within the credentials section of nowStudio. More information.We have provided two methods for subscription acknowledgement:
Note: If your app is client-only without a backend server, you should use this method.
To grant the entitlement:
acknowledgePurchase()
method.
acknowledged
, allowing the now.gg billing service to make it available for re-purchase.onAcknowledgePurchaseResponse()
, which you can override.Important Information:
AcknowledgePurchaseResponseListener
interface is not updated.The following code snippet demonstrates purchase verification and acknowledge operation:
void handlePurchase(Purchase purchase) { // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener. Purchase purchase = ...; // Verify the purchase. // Ensure entitlement was not already granted for this purchaseToken. // Grant the entitlement to the user. // Acknowledge Subscription final AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = new AcknowledgePurchaseResponseListener() { @Override public void onAcknowledgePurchaseResponse(@BillingResponse int responseCode) { if (responseCode == 0) { // Handle the success of the acknowledgement operation. } } }; AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchaseToken) .build(); BillingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener); }
fun handlePurchase(purchase: Purchase) { // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener. val purchase: Purchase = ...; // Verify the purchase. // Ensure entitlement was not already granted for this purchaseToken. // Grant the entitlement to the user. // Acknowledge Subscription val acknowledgePurchaseResponseListener = object : AcknowledgePurchaseResponseListener { override fun onAcknowledgePurchaseResponse(@BillingResponse responseCode: Int) { if (responseCode == 0) { // Handle the success of the acknowledgement operation. } } val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchaseToken) .build() BillingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener) }
The acknowledgePurchase
API should be used for server-side purchase acknowledgement for apps with a backend server.
After you have verified the purchase, you should mark it as acknowledged.
The following sample code illustrates the associated request using the acknowledgePurchase
API.
import requests url = "https://cloud-api.bluestacks.cn/v2/seller/order/acknowledgepurchase" payload = 'purchaseToken=nowgg-_purchase_token' headers = { 'Authorization': 'payment_api_key_here', 'Content-Type': 'application/x-www-form-urlencoded' } response = requests.request("POST", url, headers=headers, data=payload) print(response.text)
Reference
Payment API Key
can be found within the credentials section of nowStudio. More information.The PurchasesUpdatedListener
provides you with the purchase results. However, in some scenarios, your app/game might not be updated with all the successful purchases a user has made.
Some of these scenarios include:
PurchasesUpdatedListener
, due to connectivity/network loss.To successfully process purchases related to the scenarios listed above:
onResume()
and onCreate()
methods, make sure that your app calls BillingClient.queryPurchasesAsync(SkuType.ALL)
.The following section illustrates the process of checking and alloting unconsumed in-app purchases and subscriptions.
You can use the queryPurchasesAsync method to return the list of unconsumed purchases for a user, as illustrated below:
QueryPurchasesParams params = QueryPurchasesParams.newBuilder().setProductType(BillingClient.SkuType.ALL).build(); billingClient.queryPurchasesAsync(params, new PurchasesResponseListener() { @Override public void onQueryPurchasesResponse(BillingResult billingResult, List < Purchase > list) { // Allot the purchased item to the user and call consume/acknowledge } });
val params = QueryPurchasesParams.newBuilder() .setProductType(BillingClient.SkuType.ALL) .build() billingClient.queryPurchasesAsync(params, object : PurchasesResponseListener { override fun onQueryPurchasesResponse(billingResult: BillingResult, list: List) { // Allot the purchased item to the user and call consume/acknowledge } })
queryPurchasesAsync()
method after initialization to ensure that any unconsumed purchases from the user’s previous game session are properly allotted to the user.
This section illustrates the API specs for you to provide us with a SubscriptionStatusCallback
API to send subscription status updates.
The following is the associated workflow:
Refer: Subscription Status Callback API section.
Payments Module
Payments Module
Document Rev. 1.0