Purchase Flow

To start a purchase request from your app, call the AN_Billing.Purchase method Pass a reference to a AN_Product object containing the relevant data to complete the purchase, such as the product ID (skuId) of the item and product type (SkuType.INAPP for a one-time product or SkuType.SUBS for a subscription).

using SA.Android.Vending.Billing;;
...

AN_Product product = new AN_Product("android.test.purchased", AN_ProductType.inapp);
AN_Billing.Purchase(product, (result) => {
    AN_Logger.Log("Purchase result.IsSucceeded: " + result.IsSucceeded);
    if(result.IsSucceeded) {
        var purchase = result.Purchase;
        AN_Logger.Log("purchase.OrderId" + purchase.OrderId);
        AN_Logger.Log("purchase.ProductId" + purchase.ProductId);
        AN_Logger.Log("purchase.PackageName" + purchase.PackageName);
        AN_Logger.Log("purchase.PurchaseState" + purchase.PurchaseState);
        AN_Logger.Log("purchase.PurchaseTime" + purchase.PurchaseTime);
        AN_Logger.Log("purchase.Signature" + purchase.Signature);
        AN_Logger.Log("purchase.Token" + purchase.Token);
        AN_Logger.Log("purchase.Type" + purchase.Type);
        AN_Logger.Log("purchase.DeveloperPayload" + purchase.DeveloperPayload);
        AN_Logger.Log("purchase.AutoRenewing" + purchase.AutoRenewing);
        AN_Logger.Log("purchase.OriginalJson" + purchase.OriginalJson);
        AN_Logger.Log("----------------------------------------------------");
    } else {
        AN_Logger.Log("Purchase failed: " + result.Error.FullMessage);
    }
});

Successful purchases also generate a purchase token, which is a unique identifier representing the user and the product ID for the in-app product they purchased. Your apps can store the purchase token locally or, ideally, pass it to your secure backend server where it can be used to verify the purchase and protect against fraud. The purchase token is unique for every one-time product purchase. However, because subscriptions are purchased once and automatically renewed on a regular billing period, the purchase token for subscriptions stays the same for each billing period.

The user is also emailed a receipt of the transaction containing an order ID or a unique ID of the transaction. Users receive an email with a unique order ID for each one-time product purchase, and also for the initial subscription purchase and subsequent recurring automatic renewals. You can use the order ID to manage refunds in the Google Play Console. For further details, refer to toView and refund your app’s orders and subscriptions.

Note: Order numbers representing recurrences of a subscription have an additional integer representing a specific recurrance of that order. For example, an initial subscription order ID might be GPA.1234-5678-9012-34567 with subsequent order IDs being GPA.1234-5678-9012-34567..0(first recurrence orderID), GPA.1234-5678-9012-34567..1 (second recurrence orderID), and so on.

Note: If the user doesn't owe money when they purchase an in-app product, such as during a free trial of a subscription, the order ID is issued for $0. For example, when a user cancels a subscription, the subscription remains valid until the end of the billing period. If the user decides to re-signup, some credit remains in their account. In this case, a new purchase token is created, an order ID is created for $0, and the subscription renews after the credit runs out.

Consume

After use has successfully purchased the product, it will stay inside the user inventory,  until you consume it. Also if the user will not able to purchase the same product before you consume it from the inventory. The code snippet below demonstrates how to consume the first item from user purchases. 

using SA.Android.Vending.Billing;;
...

AN_Billing.Consume(AN_Billing.Inventory.Purchases[0], (SA_Result result) =>  {
    AN_Logger.Log("Consume result.IsSucceeded: " + result.IsSucceeded);
});

Keep purchases up-to-date

It is possible to lose track of what purchases a user has made. Following are two scenarios where your app could lose track of purchases and where querying for purchases is important.

Handling server outages

  1. A user buys a one-time product, such as extra gas for a driving game.
  2. The app sends the purchase token to the secure backend server for verification.
  3. The server is temporarily down.
  4. The app recognizes that the server is down and notifies the user that there’s a problem with the purchase.
  5. The Android app retries sending the purchase token to the secure backend server and finish the purchase as soon as the server is restored.
  6. The app releases the content.

Handling multiple devices

  1. A user buys a subscription on their Android phone.
  2. The app sends the purchase token to the secure backend server for verification.
  3. The server verifies the purchase token.
  4. The app releases the content.
  5. The user switches to an Android tablet to use the subscription.
  6. The app on the new device queries for an updated list of purchases.
  7. The app recognizes the subscription and grants access to it on the tablet.

User Inventory

Once you connected to the billing service, the information about user purchases already been delivered to your application and can be easily retrieved from the connection result. You can find the code example here. Also, keep in mind that the AN_Inventory object is always available after the successful connection to the service. So you do not have to cache the connection result in order to keep track of user purchases and available products. We also dynamically update the AN_Inventory object when a purchase or consume operation are made. The example below will print all the inventory information.

Note: You can only use Inventory after you successfully connected to the billing service. Before it connected to the billing service it always will have zero purchases registered. But it will have a list of the products that you specified inside the console. Once you connected products info will be updated accordingly to your application settings inside the Google Play Console

using SA.Android.Vending.Billing;;
...

//Printing user inventory info:
foreach (AN_Purchase purchase in AN_Billing.Inventory.Purchases) {
    AN_Logger.Log("purchase.OrderId" + purchase.OrderId);
    AN_Logger.Log("purchase.ProductId" + purchase.ProductId);
    AN_Logger.Log("purchase.PackageName" + purchase.PackageName);
    AN_Logger.Log("purchase.PurchaseState" + purchase.PurchaseState);
    AN_Logger.Log("purchase.PurchaseTime" + purchase.PurchaseTime);
    AN_Logger.Log("purchase.Signature" + purchase.Signature);
    AN_Logger.Log("purchase.Token" + purchase.Token);
    AN_Logger.Log("purchase.Type" + purchase.Type);
    AN_Logger.Log("purchase.DeveloperPayload" + purchase.DeveloperPayload);
    AN_Logger.Log("purchase.AutoRenewing" + purchase.AutoRenewing);
    AN_Logger.Log("purchase.OriginalJson" + purchase.OriginalJson);
    AN_Logger.Log("----------------------------------------------------");
}

foreach (AN_Product product in AN_Billing.Inventory.Products) {
    AN_Logger.Log("product.ProductId" + product.ProductId);
    AN_Logger.Log("product.Type" + product.Type);
    AN_Logger.Log("product.Price" + product.Price);
    AN_Logger.Log("product.Title" + product.Title);
    AN_Logger.Log("product.Description" + product.Description);
    AN_Logger.Log("product.PriceAmountMicros" + product.PriceAmountMicros);
    AN_Logger.Log("product.PriceCurrencyCode" + product.PriceCurrencyCode);
    AN_Logger.Log("product.SubscriptionPeriod" + product.SubscriptionPeriod);
    AN_Logger.Log("product.FreeTrialPeriod" + product.FreeTrialPeriod);
    AN_Logger.Log("product.SubscriptionPeriod" + product.SubscriptionPeriod);
    AN_Logger.Log("product.FreeTrialPeriod" + product.FreeTrialPeriod);
    AN_Logger.Log("product.IntroductoryPrice" + product.IntroductoryPrice);
    AN_Logger.Log("product.IntroductoryPriceAmountMicros" + product.IntroductoryPriceAmountMicros);
    AN_Logger.Log("product.IntroductoryPricePeriod" + product.IntroductoryPricePeriod);
    AN_Logger.Log("product.IntroductoryPriceCycles" + product.IntroductoryPriceCycles);
    AN_Logger.Log("product.OriginalJson" + product.OriginalJson);
    AN_Logger.Log("----------------------------------------------------");
}

There is also a couple of useful methods we made for your convenience:

You can check if the product was already purchased by a user. The purchase information is shared across all user devices. So this is a good practice to iterate through user purchases, to make sure all the content in your game is unlocked accordingly.

using SA.Android.Vending.Billing;
...
string poductId = "android.test.purchased";
if(AN_Billing.Inventory.IsProdcutPurchased(poductId)) {
    Debug.Log("User has purchased the product");
    //Let's get the purchase details:
    AN_Purchase purchase = AN_Billing.Inventory.GetPurchaseByProductId(poductId);
} else {
    Debug.Log("User does not own this product yet");
}

You may also get the product details by its id as well:

using SA.Android.Vending.Billing;
...
string poductId = "android.test.purchased";
AN_Product myProduct = AN_Billing.Inventory.GetProductById(poductId);

Test with static responses

Google Play Billing provides a combination of reserved product IDs and associated static responses that you can use to test your Google Play Billing implementation. These responses enable you to verify that your application is handling the primary Google Play responses correctly. You can test your Google Play Billing implementation using these static responses before involving testers, and even if the app hasn't been published yet.

To test your implementation with static responses, you make a Google Play Billing request using a special item that has a reserved product ID. Each reserved product ID returns a specific static response from Google Play. No money is transferred when you make Google Play Billing requests with the reserved product IDs. Also, you cannot specify the form of payment when you make a billing request with a reserved product ID.

Note: Static responses cannot be used to test subscriptions.

You do not need to list the reserved products in your application's product list. Google Play already knows about the reserved product IDs. Also, you do not need to upload your application to the Play Console to perform static response tests with the reserved product IDs. You can simply install your application on a device, log into the device, and make billing requests using the reserved product IDs.

Note: Previously you could test an app by uploading an unpublished "draft" version. This functionality is no longer supported. However, you can test your app with static responses even before you upload it to the Google Play Store. For more information, see Test with static responses.

There are three reserved product IDs for testing static Google Play Billing responses:

  • android.test.purchased

    When you make an Google Play Billing request with this product ID, Google Play responds as though you successfully purchased an item. The response includes a JSON string, which contains fake purchase information (for example, a fake order ID).

  • android.test.canceled

    When you make an Google Play Billing request with this product ID Google Play responds as though the purchase was canceled. This can occur when an error is encountered in the order process, such as an invalid credit card, or when you cancel a user's order before it is charged.

  • android.test.item_unavailable

    When you make an Google Play Billing request with this product ID, Google Play responds as though the item being purchased was not listed in your application's product list.