Skip to content

Google Play Billing payment Add-on

This Add-on wraps BillingClient of Google Play Billing Library 9, the Google Play in-app billing library, on Android. It provides connection management, product retrieval, launching the purchase screen, purchase retrieval, and purchase acknowledgment and consumption, each as a separate method. The Add-on does not save purchases, and it does not automatically choose between acknowledgment and consumption based on the product type, so the app decides this flow itself.

The receipt received from a purchase is recorded on the Hive Axyl server through the Payments module, and then the app server verifies it with the Hive Axyl Server API. Both steps use GooglePurchase.PurchaseToken as the receipt value. For how to set the value, see AxylReceipt rules, and for the verification flow, see Receipt verification.

Module information

  • Package: com.com2usplatform.hiveaxyl.payments.addon.google
  • Interface: IGooglePlayBillingPlugin
  • Namespace: Hive.Axyl.Payments.Addon.Google
  • Registration method: AddPlayBilling()
  • Supported platforms: Android
  • Minimum requirements: Android API 29+, Unity 6000.0+

Registration and retrieval

Register the Add-on together with the Payments module in the registration step of HiveBootstrap.Initialize, and then retrieve it with HiveCore.TryResolve<T>().

using Hive.Axyl.Core;
using Hive.Axyl.Core.Unity;   // HiveBootstrap
using Hive.Axyl.Payments;
using Hive.Axyl.Payments.Addon.Google;

HiveBootstrap.Initialize(config, builder =>
{
    builder.AddPayments()
           .AddPlayBilling();
});

if (HiveCore.TryResolve<IGooglePlayBillingPlugin>(out var billing))
{
    // Code that runs only in Android builds
}
Not registered in the Unity Editor

This Add-on is registered only in players built for Android. It is not registered in the Unity Editor even if you switch the platform to Android, so using HiveCore.Resolve<T>() throws RegistrationNotFoundException. Always check with TryResolve<T>() before using it.

For the package installation and registration procedure, see Install the Hive Axyl Google payment plugin.

Method summary

Every method takes CancellationToken ct = default as its last parameter. For the calling conventions, see Call context.

This Add-on provides the following methods.

Methods

StartConnectionAsync

Creates a single BillingClient, sets up the purchase update listener and pending purchase support, and then connects with startConnection. The Add-on does not connect automatically, so call this method directly from the app.

Task<GooglePlayBillingServiceStartConnectionResult> StartConnectionAsync(CancellationToken ct = default)

Result cases — GooglePlayBillingServiceStartConnectionResult

Result case Wire code Description
Success — Connected successfully.
AlreadyConnected already_connected BillingClient is already connected, so no action was taken.
UnknownOutcome UNKNOWN A new result that this SDK version does not recognize.
Failure FAILURE The call could not be completed. Check the cause with the HiveError in Problem.

For the implementation procedure, see BillingClient connection.

EndConnectionAsync

Ends the connection with endConnection. Calling it when the connection is already closed is not an error. Ending the connection also clears the product retrieval results that the SDK kept, so to launch the purchase screen after reconnecting, retrieve the products again with QueryProductDetailsAsync().

Task<GooglePlayBillingServiceEndConnectionResult> EndConnectionAsync(CancellationToken ct = default)

Result cases — GooglePlayBillingServiceEndConnectionResult

Result case Wire code Description
Success — Ended the connection.
UnknownOutcome UNKNOWN A new result that this SDK version does not recognize.
Failure FAILURE The call could not be completed. Check the cause with the HiveError in Problem.

For the implementation procedure, see BillingClient connection.

QueryProductDetailsAsync

Calls queryProductDetailsAsync with the specified product IDs and product type. Following the partial failure model of Billing Library 9, retrieved products are placed in ProductDetailsList, and products that could not be retrieved are placed in UnfetchedProductList. The call as a whole does not fail even if some products cannot be retrieved.

Task<GooglePlayBillingServiceQueryProductDetailsResult> QueryProductDetailsAsync(IReadOnlyList<string> productIds, ProductType productType, CancellationToken ct = default)
Parameter Type Required Description
productIds IReadOnlyList<string> Required The list of product IDs registered in the Google Play Console.
productType ProductType Required The product type to retrieve. Specify only one of Inapp and Subs per retrieval.

Result cases — GooglePlayBillingServiceQueryProductDetailsResult

Result case Wire code Description
Success — The retrieval request was processed. Check the products that could not be retrieved in Data.UnfetchedProductList.
UnknownOutcome UNKNOWN A new result that this SDK version does not recognize.
Failure FAILURE The call could not be completed. Check the cause with the HiveError in Problem.

Exceptions

  • ArgumentException: When productIds is null or empty

For the implementation procedure, see Get Google Play product details.

LaunchBillingFlowAsync

Calls launchBillingFlow with the requested products to launch the Google Play purchase screen. The Add-on gets the current foreground Activity automatically. Success only means that the purchase screen was launched; the actual purchase result is delivered through the PurchasesUpdated event.

Task<GooglePlayBillingServiceLaunchBillingFlowResult> LaunchBillingFlowAsync(LaunchBillingFlowRequest request, CancellationToken ct = default)

Result cases — GooglePlayBillingServiceLaunchBillingFlowResult

Result case Wire code Description
Success — Launched the purchase screen. Check the purchase result in the PurchasesUpdated event.
UnknownOutcome UNKNOWN A new result that this SDK version does not recognize.
Failure FAILURE The call could not be completed. Check the cause with the HiveError in Problem.

Exceptions

  • ArgumentNullException: When request is null
  • ArgumentException: When request.Products is null or empty

Call example

Because the purchase result is delivered through an event, subscribe to PurchasesUpdated before launching the purchase screen. Before calling LaunchBillingFlowAsync(), connect with StartConnectionAsync(), and retrieve the products to purchase with QueryProductDetailsAsync().

using Hive.Axyl.Core;
using Hive.Axyl.Payments.Addon.Google;

// Subscribe only once when the app starts.
billing.PurchasesUpdated += args =>
{
    foreach (GooglePurchase purchase in args.Purchases)
    {
        if (purchase.PurchaseState == PurchaseState.Purchased)
        {
            string purchaseToken = purchase.PurchaseToken;   // AxylReceipt of the Payments module
        }
        else if (purchase.PurchaseState == PurchaseState.Pending)
        {
            // The payment is pending. Do not grant entitlement until it becomes Purchased.
        }
    }
};

var result = await billing.LaunchBillingFlowAsync(new LaunchBillingFlowRequest
{
    Products = new[]
    {
        new LaunchBillingFlowProductParams
        {
            ProductId = "{productId}",
            OfferToken = offerToken,   // Offer token retrieved with QueryProductDetailsAsync
        },
    },
});

if (result is GooglePlayBillingServiceLaunchBillingFlowResult.Failure failure)
{
    HiveError error = failure.Problem;
}

For the purchased receipt, use PurchaseToken as AxylReceipt and save the payment result with RecordStorePurchaseAsync(). For the implementation procedure, see Request a Google Play payment.

QueryPurchasesAsync

Uses queryPurchasesAsync to retrieve the active purchases of the specified product type and the unfinished purchases that have not yet been acknowledged or consumed. Google Play has no separate purchase restoration flow, so use this method for purchase restoration as well.

Task<GooglePlayBillingServiceQueryPurchasesResult> QueryPurchasesAsync(ProductType productType, CancellationToken ct = default)
Parameter Type Required Description
productType ProductType Required The product type to retrieve, Inapp or Subs.

Result cases — GooglePlayBillingServiceQueryPurchasesResult

Result case Wire code Description
Success — Retrieved successfully.
UnknownOutcome UNKNOWN A new result that this SDK version does not recognize.
Failure FAILURE The call could not be completed. Check the cause with the HiveError in Problem.

For the implementation procedure, see Retrieve undelivered Google Play receipts again.

AcknowledgePurchaseAsync

Acknowledges a purchase of a non-consumable or subscription product with acknowledgePurchase. The Add-on does not automatically choose between acknowledgment and ConsumeAsync() based on the product type, so the app makes the choice.

Task<GooglePlayBillingServiceAcknowledgePurchaseResult> AcknowledgePurchaseAsync(string purchaseToken, CancellationToken ct = default)
Parameter Type Required Description
purchaseToken string Required The GooglePurchase.PurchaseToken value of the purchase to acknowledge.

Result cases — GooglePlayBillingServiceAcknowledgePurchaseResult

Result case Wire code Description
Success — Acknowledged the purchase.
PurchaseNotFound purchase_not_found No purchase matches the purchase token.
AlreadyAcknowledged already_acknowledged The purchase has already been acknowledged.
UnknownOutcome UNKNOWN A new result that this SDK version does not recognize.
Failure FAILURE The call could not be completed. Check the cause with the HiveError in Problem.

Exceptions

  • ArgumentException: When purchaseToken is null or an empty string

For the implementation procedure, see Acknowledge non-consumable and subscription product purchases.

ConsumeAsync

Consumes a purchase of a consumable product with consumeAsync. Consuming a purchase also acknowledges it.

Task<GooglePlayBillingServiceConsumeResult> ConsumeAsync(string purchaseToken, CancellationToken ct = default)
Parameter Type Required Description
purchaseToken string Required The GooglePurchase.PurchaseToken value of the purchase to consume.

Result cases — GooglePlayBillingServiceConsumeResult

Result case Wire code Description
Success — Consumed the purchase.
PurchaseNotFound purchase_not_found No purchase matches the purchase token.
UnknownOutcome UNKNOWN A new result that this SDK version does not recognize.
Failure FAILURE The call could not be completed. Check the cause with the HiveError in Problem.

Exceptions

  • ArgumentException: When purchaseToken is null or an empty string

For the implementation procedure, see Finish consumable product transactions.

Events

Both events are invoked on the engine main thread, so you can use engine APIs inside the handlers.

PurchasesUpdated

Raised every time the PurchasesUpdatedListener.onPurchasesUpdated callback is called. It delivers not only the results of purchases launched with LaunchBillingFlowAsync() but also purchases that happen independently of launching the purchase screen, such as purchases on another device, promo code redemptions, and pending purchases that changed to Purchased.

event Action<PurchasesUpdatedEventArgs> PurchasesUpdated
Parameter Type Description
— PurchasesUpdatedEventArgs The raw data of a single callback.

The SDK does not branch on the response code. Interpret BillingResult.ResponseCode and process Purchases in the app.

BillingServiceDisconnected

Raised when the connection to the Billing service is lost and onBillingServiceDisconnected is called. The Add-on does not reconnect automatically, so the app decides whether and when to call StartConnectionAsync() again.

event Action BillingServiceDisconnected

For the implementation procedure, see BillingClient connection.

Data types

AccountIdentifiers

The raw information of Purchase.AccountIdentifiers.

Field Type Required Description
ObfuscatedAccountId string? Optional The getObfuscatedAccountId() value. It is not recorded in SDK logs.
ObfuscatedProfileId string? Optional The getObfuscatedProfileId() value. It is not recorded in SDK logs.

AcknowledgePurchaseResponse

No fields.

BillingResult

The raw information of com.android.billingclient.api.BillingResult. It is delivered through the PurchasesUpdated event; failures of method calls are delivered through the HiveError of Failure, not through this type. ResponseCode is an unprocessed integer, so the app interprets response codes such as USER_CANCELED.

Property Type Required Description
ResponseCode int Required The getResponseCode() value. The raw integer that corresponds to BillingClient.BillingResponseCode.
DebugMessage string Required The getDebugMessage() value.
SubResponseCode int Required The getSubResponseCode() value of Billing Library 9. It is a detailed response code of launchBillingFlow, such as PAYMENT_DECLINED_DUE_TO_INSUFFICIENT_FUNDS, USER_INELIGIBLE, or NO_APPLICABLE_SUB_RESPONSE_CODE, and the SDK does not branch on this value.

ConsumeResponse

No fields.

EndConnectionResponse

No fields.

GoogleProductDetails

The raw information of com.android.billingclient.api.ProductDetails. The app is responsible for interpreting the fields.

Field Type Required Description
ProductId string Required The getProductId() value.
ProductType ProductType Required The getProductType() value.
Title string Required The getTitle() value. It includes the app name suffix.
Description string Required The getDescription() value.
Name string Required The getName() value. Unlike Title, it is the product name without the app name suffix.
OneTimePurchaseOfferDetails OneTimePurchaseOfferDetails? Optional The getOneTimePurchaseOfferDetails() value. It is set only for one-time products with a single offer, and it is not set for subscription products or one-time products with multiple offers.
OneTimePurchaseOfferDetailsList IReadOnlyList<OneTimePurchaseOfferDetails> Required The getOneTimePurchaseOfferDetailsList() value. It is used for one-time products with multiple offers, and it is empty for one-time products with a single offer.
SubscriptionOfferDetails IReadOnlyList<SubscriptionOfferDetails> Required The getSubscriptionOfferDetails() value, which is the list of base plans and offers of the subscription. It is empty for one-time products.

GooglePurchase

The raw information of com.android.billingclient.api.Purchase. All values, including Signature and OriginalJson used for server verification, are passed without processing. PurchaseToken, Signature, OriginalJson, and OrderId are sensitive information, so they are not recorded in plain text in SDK logs.

Field Type Required Description
OrderId string? Optional The getOrderId() value. It is not set for pending purchases.
PackageName string Required The getPackageName() value.
Products IReadOnlyList<string> Required The getProducts() value, which is the list of product IDs.
PurchaseTime DateTimeOffset Required The getPurchaseTime() value, which is the purchase time.
PurchaseState PurchaseState Required The getPurchaseState() value.
PurchaseToken string Required The getPurchaseToken() value. It is used for AxylReceipt in Payments module requests, axylReceipt in the receipt verification request that the app server sends, purchase acknowledgment, and purchase consumption.
Quantity int Required The getQuantity() value.
Signature string Required The getSignature() value, which is the Base64 RSA signature used for server verification.
OriginalJson string Required The getOriginalJson() value, which is the original purchase JSON used for server verification. When you put a JSON string that contains purchase_data and signature in AxylReceipt, put this value in purchase_data as is, without serializing it again.
IsAcknowledged bool Required The isAcknowledged() value.
IsAutoRenewing bool Required The isAutoRenewing() value.
IsSuspended bool Required The isSuspended() value. If true, the subscription is paused or has failed to renew, so you must not grant entitlement.
AccountIdentifiers AccountIdentifiers? Optional The getAccountIdentifiers() value.
PendingPurchaseUpdate PendingPurchaseUpdate? Optional The getPendingPurchaseUpdate() value, which contains the pending top-up, upgrade, or downgrade information of a prepaid plan.

LaunchBillingFlowProductParams

Corresponds to BillingFlowParams.ProductDetailsParams. Specifies the product to purchase and the offer token selected for that product.

Field Type Required Description
ProductId string Required The ProductDetails.getProductId() value of a product returned by QueryProductDetailsAsync().
OfferToken string Required The SubscriptionOfferDetails.getOfferToken() value for subscription products, and the OneTimePurchaseOfferDetails.getOfferToken() value for one-time products with multiple offers. For one-time products with a single offer, pass an empty string.

LaunchBillingFlowRequest

Field Type Required Description
Products IReadOnlyList<LaunchBillingFlowProductParams> Required The list of BillingFlowParams.ProductDetailsParams. It must contain at least one item.
ObfuscatedAccountId string? Optional The BillingFlowParams.Builder.setObfuscatedAccountId value. A user identifier for fraud prevention that the app specifies. You must set the same value as the AccountUuid that you send in Payments module requests and receipt verification requests so that the account identifier comparison in the receipt verification response results in a match. It is not recorded in SDK logs.
ObfuscatedProfileId string? Optional The BillingFlowParams.Builder.setObfuscatedProfileId value. It is not recorded in SDK logs.

LaunchBillingFlowResponse

No fields. It means that the purchase screen was launched; the actual purchase result is delivered through the PurchasesUpdated event.

OneTimePurchaseOfferDetails

The raw information of ProductDetails.OneTimePurchaseOfferDetails. Prices are passed in the currency that Google Play returned; they are not converted to another currency or adjusted to the decimal places of each currency.

Field Type Required Description
FormattedPrice string Required The getFormattedPrice() value, which is the localized display string. Example: ₩1,200
PriceAmount decimal Required The price that holds the getPriceAmountMicros() value without loss of precision. It keeps the six decimal places of the micro unit, so 1200000 in micros becomes 1.200000.
PriceCurrencyCode string Required The getPriceCurrencyCode() value, which is the ISO 4217 currency code. Example: KRW
OfferId string? Optional The getOfferId() value. It is set only for one-time products with multiple offers.
OfferToken string? Optional The getOfferToken() value. It is set only for one-time products with multiple offers, and you pass it when launching the purchase screen.
PreorderDetails PreorderDetails? Optional The getPreorderDetails() value, which is set only for preorder offers.
RentalDetails RentalDetails? Optional The getRentalDetails() value, which is set only for rental offers.

PendingPurchaseUpdate

The raw information of Purchase.PendingPurchaseUpdate.

Field Type Required Description
Products IReadOnlyList<string> Required The getProducts() value.
PurchaseToken string Required The getPurchaseToken() value. Because it is sensitive information, it is not recorded in plain text in SDK logs.

PreorderDetails

The raw information of ProductDetails.OneTimePurchaseOfferDetails.PreorderDetails, which is a preorder offer for a one-time product.

Field Type Required Description
PreorderPresaleEndTimeUnixMillis long Required The getPreorderPresaleEndTimeMillis() value, in Unix epoch milliseconds.
PreorderReleaseTimeUnixMillis long Required The getPreorderReleaseTimeMillis() value, in Unix epoch milliseconds.

PricingPhase

The raw information of ProductDetails.PricingPhase.

Field Type Required Description
FormattedPrice string Required The getFormattedPrice() value.
PriceAmount decimal Required The price that holds the getPriceAmountMicros() value without loss of precision. It keeps the six decimal places of the micro unit.
PriceCurrencyCode string Required The getPriceCurrencyCode() value.
BillingPeriod string Required The getBillingPeriod() value, in ISO 8601 duration format. Example: P1M
BillingCycleCount int Required The getBillingCycleCount() value.
RecurrenceMode RecurrenceMode Required The getRecurrenceMode() value.

PurchasesUpdatedEventArgs

The raw data of a single onPurchasesUpdated callback delivered through the PurchasesUpdated event. The SDK does not branch on the response code, so the app interprets BillingResult.ResponseCode and processes Purchases.

Property Type Required Description
BillingResult BillingResult Required The raw BillingResult of this update.
Purchases IReadOnlyList<GooglePurchase> Required The list of updated purchases. It is empty when BillingResult is not a successful response, such as when the user cancels.

QueryProductDetailsResponse

Field Type Required Description
ProductDetailsList IReadOnlyList<GoogleProductDetails> Required The list of successfully retrieved products.
UnfetchedProductList IReadOnlyList<UnfetchedProduct> Required The list of products that could not be retrieved, with their raw status codes. It is empty if all products were retrieved.

QueryPurchasesResponse

Field Type Required Description
Purchases IReadOnlyList<GooglePurchase> Required The list of active purchases and unfinished purchases of the retrieved product type.

RentalDetails

The raw information of ProductDetails.OneTimePurchaseOfferDetails.RentalDetails, which is a rental offer for a one-time product.

Field Type Required Description
RentalPeriod string Required The getRentalPeriod() value, in ISO 8601 duration format.
RentalExpirationPeriod string Required The getRentalExpirationPeriod() value, in ISO 8601 duration format. It is an empty string if not set.

StartConnectionResponse

No fields.

SubscriptionOfferDetails

The raw information of ProductDetails.SubscriptionOfferDetails.

Field Type Required Description
BasePlanId string Required The getBasePlanId() value.
OfferId string? Optional The getOfferId() value. It is not set for the base plan itself.
OfferToken string Required The getOfferToken() value. You pass it when launching the purchase screen.
PricingPhases IReadOnlyList<PricingPhase> Required The getPricingPhases().getPricingPhaseList() value.
OfferTags IReadOnlyList<string> Required The getOfferTags() value.

UnfetchedProduct

A single product that queryProductDetailsAsync could not retrieve.

Field Type Required Description
ProductId string Required The requested product ID.
StatusCode int Required The raw per-product status code of ProductDetailsResult.

Enums

Specify Add-on enums by their C# member names. The 'Value' column in the tables is the integer used for serialization.

ProductType

Corresponds to BillingClient.ProductType.

C# member Value Description
Unspecified 0 The default value, which is not a valid product type.
Inapp 1 A one-time product. Corresponds to inapp in Google Play.
Subs 2 A subscription product. Corresponds to subs in Google Play.

PurchaseState

The Purchase.getPurchaseState() value.

C# member Value Description
Unspecified 0 The state cannot be determined. Corresponds to UNSPECIFIED_STATE, which Google documents.
Purchased 1 The purchase is completed.
Pending 2 The purchase is pending, such as while awaiting a cash payment. Do not grant entitlement until it becomes Purchased.

RecurrenceMode

The PricingPhase.getRecurrenceMode() value.

C# member Value Description
Unspecified 0 The default value, with no recurrence mode specified.
InfiniteRecurring 1 Recurs until the subscription is canceled.
FiniteRecurring 2 Recurs for a fixed number of billing cycles. Check BillingCycleCount as well.
NonRecurring 3 Applies only once and does not recur.