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.
- StartConnectionAsync(): Create and connect
BillingClient - EndConnectionAsync(): End the
BillingClientconnection - QueryProductDetailsAsync(): Get product details and the list of products that could not be retrieved
- LaunchBillingFlowAsync(): Launch the Google Play purchase screen
- QueryPurchasesAsync(): Get active purchases and unfinished purchases
- AcknowledgePurchaseAsync(): Acknowledge a purchase of a non-consumable or subscription product
- ConsumeAsync(): Consume a purchase of a consumable product
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.
- Response: StartConnectionResponse
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().
- Response: EndConnectionResponse
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.
| 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. |
- Response: QueryProductDetailsResponse
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: WhenproductIdsisnullor 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.
- Request: LaunchBillingFlowRequest
- Response: LaunchBillingFlowResponse
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: WhenrequestisnullArgumentException: Whenrequest.Productsisnullor 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
productType | ProductType | Required | The product type to retrieve, Inapp or Subs. |
- Response: QueryPurchasesResponse
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.
| Parameter | Type | Required | Description |
|---|---|---|---|
purchaseToken | string | Required | The GooglePurchase.PurchaseToken value of the purchase to acknowledge. |
- Response: AcknowledgePurchaseResponse
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: WhenpurchaseTokenisnullor 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
purchaseToken | string | Required | The GooglePurchase.PurchaseToken value of the purchase to consume. |
- Response: ConsumeResponse
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: WhenpurchaseTokenisnullor 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.
| 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.
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. |