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)
You must pass the user’s unique userID (IN_GAME_ID
) to now.gg Payments SDK 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) { // To be implemented in a later section. } }; 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 = PurchasesUpdatedListener { billingResult, purchases -> // To be implemented in a later section. } private var 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.
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.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. } } @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. } } 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.INAPP); billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() { @Override public void onSkuDetailsResponse(int billingResult, List<SkuDetails> skuDetailsList) { // Process the result. } });
fun querySkuDetails() { val skuList = ArrayList<String>() skuList.add("premium_upgrade") skuList.add("gas") val params = SkuDetailsParams.newBuilder() params.setSkusList(skuList).setType(SkuType.INAPP) withContext(Dispatchers.IO) { billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList -> // Result Processing } } }
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) .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) .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>) { if (billingResult == 0 && purchases != null) { for (Purchase purchase : purchases) { 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) { 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 verifyPayment API. You can use this API to verify the purchases with our backend server.
To verify the purchase, call the verifyPayment API from your app backend server with purchaseToken
and orderId
, as illustrated in the following sample request code:
curl --location 'https://payments.now.gg/v1/console/order/verifyPayment'\ --header 'publisherToken: <your_publisherToken_here>'\ --header 'Content-Type: application/json'\ --data '{ "purchaseToken": "<your_purchaseToken_here>", "orderId": "<your_orderId_here>" }'
import requests import json url = "https://payments.now.gg/v1/console/order/verifyPayment" payload = json.dumps( { "purchaseToken": "<your_purchaseToken_here>", "orderId": "<your_orderId_here>", } ) headers = { "publisherToken": "<your_publisherToken_here>", "Content-Type": "application/json", } response = requests.request("POST", url, headers=headers, data=payload) print(response.text)
publisherToken
can be found under the Account Information section of nowStudio. (More Information).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 now grant the entitlement of the purchased product to the user and mark the product as consumed.
To grant the entitlement, call the consumeAsync()
method. This method will grant the entitlement of a product to the user and make the product available for re-purchase once it has been consumed/delivered to the user.
Using consumeAsync():
consumed
, allowing the now.gg billing service to make it available for re-purchase.ConsumeResponseListener
interface.onConsumeResponse()
, which you can override.Important Information:
ConsumeResponseListener
interface is not updated.The following code snippet demonstrates purchase verification and consumption operation:
void handlePurchase(Purchase purchase) { // Purchase retrieved from BillingClient#queryPurchases or your PurchasesUpdatedListener. Purchase purchase = ...; // Verify the purchase. // Ensure entitlement was not already granted for this purchaseToken. // Grant the entitlement to the user. 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#queryPurchases or your PurchasesUpdatedListener. val purchase: Purchase = ...; // Verify the purchase. // Ensure entitlement was not already granted for this purchaseToken. // Grant the entitlement to the user. billingClient.consumeAsync(purchase.getPurchaseToken(), { billingResult, outToken - > if (billingResult == 0) { // Handle the success of the consume operation. } }) }
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.queryPurchases(SkuType.INAPP)
.You can use the queryPurchases method to return the list of unconsumed purchases for a user, as illustrated below:
Purchase.PurchasesResult result = BillingClient.queryPurchases(BillingClient.SkuType.INAPP); if (result.getResponseCode() == BillingClient.BillingResponse.OK) { List < Purchase > purchases = result.getPurchasesList(); }
val result = BillingClient.queryPurchases(BillingClient.SkuType.INAPP) if (result.responseCode == BillingClient.BillingResponse.OK) { val purchases = result.purchasesList }
Document Rev. 1.0