Step 2. Get the product list
Retrieve in real time the in-app product information registered in Google Play, such as product names, prices, and currencies. Use this to show users the latest list of products currently available for sale and accurate local prices.
Follow the steps below to retrieve product information.
1. Get the product ID list
Retrieve the list of product IDs registered in the Hive Axyl server. This list includes the unique identifiers (Product IDs) of the products registered in the Hive Console.
ListStoreProductIdsAsync
To implement Product ID list retrieval, call ListStoreProductIdsAsync() provided by the Hive Axyl SDK. It returns, by market, the list of unique identifiers (Product IDs) of the in-app products that the app is selling.
Call parameters
| Field name | Type | Required | Description |
|---|---|---|---|
| request | StoreRequest | Required | Request data that contains the market to query and the app information. |
| context | ApiCallContext | Optional | Per-call settings object that specifies the idempotency key, cancellation token, and request policy. If omitted, the default values are used. |
StoreRequest
| Field name | Type | Required | Description |
|---|---|---|---|
AppVersion | string | Optional | Pass the app's current build version as a string. Example: 1.0.0 |
Country | string | Required | Pass the user's country code (ISO 3166-1 two-letter). Used to filter products. Example: KR |
Language | string | Required | Pass the language code (ISO 639-1 two-letter) to use for localizing product names and other text. Example: ko |
ProviderId | StoreRequestProviderId (enum) | Required | Market identifier. Because this request is only for retrieving Google Play products, specify only Google. |
Call example
Check the success result of PaymentsListStoreProductIdsResult and the domain-specific results (Outcome) defined by this method in the example below and in the response status. For the result model and handling principles of common failures (Failure) that prevent the request from being performed, see Common error handling.
using Hive.Axyl.Payments;
using Hive.Axyl.Core;
// payments: IPaymentsService registered during initialization (for how to retrieve it, see [Install and initialize the module](../init.md))
IPaymentsService payments = HiveCore.Resolve<IPaymentsService>();
// Create a request object that contains the market to query and the app information.
var request = new StoreRequest
{
AppVersion = "1.0.0",
Country = "KR",
Language = "ko",
ProviderId = StoreRequestProviderId.Google
};
PaymentsListStoreProductIdsResult result = await payments.ListStoreProductIdsAsync(request);
switch (result)
{
case PaymentsListStoreProductIdsResult.Success success:
// Check the Product ID list for each market.
foreach (var store in success.Data.Stores)
{
Debug.Log($"{store.ProviderId}: {store.Products.Count} consumable(s), {store.ProductSubscriptions.Count} subscription(s)");
}
break;
case PaymentsListStoreProductIdsResult.PaymentBadRequest:
case PaymentsListStoreProductIdsResult.PaymentInvalidParameter:
Debug.LogError("Check the request fields and required values.");
break;
// Handle common Failure
case PaymentsListStoreProductIdsResult.Failure failure:
HiveError err = failure.Problem;
Debug.LogError($"[{err.Code}] {err.Message} (trace: {err.TraceId})");
break;
// Safety net: unhandled results and unknown new results (UnknownOutcome)
default:
Debug.LogWarning($"Unhandled result: {result.GetType().Name}");
break;
}
Response data
On success, the result is contained in Data (StoreResponseData) of PaymentsListStoreProductIdsResult.Success.
| Field name | Type | Required | Description |
|---|---|---|---|
Data.Stores | IReadOnlyList<StoreProduct> | Required | List of product ID information for each market. Empty if there are no results. |
Data.Meta | string | Optional | Response meta information. null if there is none. |
StoreProduct
| Field name | Type | Required | Description |
|---|---|---|---|
AppId | string | Optional | App ID. Example: com.game |
Products | IReadOnlyList<string> | Optional | List of Product IDs of consumable products. |
ProductSubscriptions | IReadOnlyList<string> | Optional | List of Product IDs of subscription products. |
ProviderId | StoreProductProviderId (enum) | Required | Market identifier. This response returns Google. |
Response example
// Example of success.Data in the Success branch
// success.Data.Stores[0].ProviderId = StoreProductProviderId.Google
// success.Data.Stores[0].AppId = "com.game"
// success.Data.Stores[0].Products = ["com.game.product1", "com.game.product2"]
// success.Data.Stores[0].ProductSubscriptions = ["com.game.subscription1", "com.game.subscription2"]
Response status
The table below summarizes the success result of PaymentsListStoreProductIdsResult and the domain-specific results (Outcome) defined by this method. For the result model and handling principles of common failures (Failure) that prevent the request from being performed, see Common error handling.
| Response case | Description | App client handling |
|---|---|---|
Success | The Product ID list was retrieved successfully. Data contains the list of product IDs for each market. | Proceed to retrieve store product information with the received Product IDs |
PaymentBadRequest | The request cannot be processed. | Check the request values and call conditions |
PaymentInvalidParameter | Invalid parameter. | Check the request fields and retry |
UnknownOutcome | A domain-specific result that the SDK does not recognize. | Treat it as a failure and log the result code |
Failure | Common Failure. See Common error handling. | Handle it according to the common error handling guidelines |
2. Get Google Play product details (plugin)
Based on the product ID list, call QueryProductDetailsAsync() of the Google payment plugin to retrieve product details directly from the Google Play BillingClient. Use the results to build the in-app store screen or as input data for the subsequent Hive Axyl server product retrieval (FetchGoogleProductsAsync()).
BillingClient connection required
Before you call QueryProductDetailsAsync(), you must connect the BillingClient with StartConnectionAsync(). For how to connect, see Step 1. Set up the integration.
QueryProductDetailsAsync
To retrieve the details of products registered in Google Play, call QueryProductDetailsAsync() provided by the Google payment plugin. Specify ProductType.Inapp for consumable products and ProductType.Subs for subscription products.
Call parameters
| Field name | Type | Required | Description |
|---|---|---|---|
| productIds | IReadOnlyList<string> | Required | List of product IDs to retrieve. Pass the Product IDs retrieved in step 1. |
| productType | ProductType | Required | Product type. Specify Inapp (consumable product) or Subs (subscription product). |
| ct | CancellationToken | Optional | Cancellation token. |
Call example
Check the success result of GooglePlayBillingServiceQueryProductDetailsResult and the domain-specific results (Outcome) defined by this method in the example below and in the response status. For the result model and handling principles of common failures (Failure) that prevent the request from being performed, see Common error handling. Because the plugin supports the partial-failure model of Google Play Billing Library 9, products that were retrieved successfully (ProductDetailsList) and products that could not be retrieved (UnfetchedProductList) can be returned together.
using System.Collections.Generic;
using System.Linq;
using Hive.Axyl.Core;
using Hive.Axyl.Payments.Addon.Google;
using System.Threading;
// Retrieve the Google payment plugin (must be registered with builder.AddPlayBilling() during initialization)
if (!HiveCore.TryResolve<IGooglePlayBillingPlugin>(out var googlePlugin))
{
Debug.LogError("The Google Play Billing plugin is not registered.");
return;
}
// List of consumable product IDs retrieved in step 1
var productIds = new List<string> { "com.game.product1", "com.game.product2" };
GooglePlayBillingServiceQueryProductDetailsResult result =
await googlePlugin.QueryProductDetailsAsync(productIds, ProductType.Inapp, CancellationToken.None);
switch (result)
{
case GooglePlayBillingServiceQueryProductDetailsResult.Success success:
// Products retrieved successfully
foreach (GoogleProductDetails detail in success.Data.ProductDetailsList)
{
Debug.Log($"Product: {detail.ProductId}, name: {detail.Name}, title: {detail.Title}");
// Price information of consumable products
if (detail.OneTimePurchaseOfferDetails != null)
{
Debug.Log($" Price: {detail.OneTimePurchaseOfferDetails.FormattedPrice}");
}
// Offer information of subscription products
if (detail.SubscriptionOfferDetails != null)
{
foreach (var offer in detail.SubscriptionOfferDetails)
{
Debug.Log($" Subscription offer token: {offer.OfferToken}");
}
}
}
// Products that could not be retrieved (partial failure)
if (success.Data.UnfetchedProductList.Count > 0)
{
var unfetchedIds = success.Data.UnfetchedProductList.Select(p => p.ProductId);
Debug.LogWarning($"Products not retrieved: {string.Join(", ", unfetchedIds)}");
}
break;
case GooglePlayBillingServiceQueryProductDetailsResult.UnknownOutcome:
Debug.LogWarning("Unknown query result.");
break;
case GooglePlayBillingServiceQueryProductDetailsResult.Failure failure:
Debug.LogError($"Failed to get product details: {failure}");
break;
default:
Debug.LogWarning($"Unhandled result: {result.GetType().Name}");
break;
}
Response data
On success, the result is contained in Data (QueryProductDetailsResponse) of GooglePlayBillingServiceQueryProductDetailsResult.Success.
| Field name | Type | Description |
|---|---|---|
Data.ProductDetailsList | IReadOnlyList<GoogleProductDetails> | List of details of the products retrieved successfully. Empty if no products were retrieved. |
Data.UnfetchedProductList | IReadOnlyList<UnfetchedProduct> | List of products that could not be retrieved. Empty if there is no partial failure. |
GoogleProductDetails
| Field name | Type | Description |
|---|---|---|
ProductId | string | Unique identifier of the product. |
ProductType | ProductType | Product type. Inapp (consumable product) or Subs (subscription product). |
Title | string | Product title. |
Name | string | Product name. |
Description | string | Product description. |
OneTimePurchaseOfferDetails | OneTimePurchaseOfferDetails? | Offer information for one-time purchases (consumable/non-consumable). null for subscription products. |
OneTimePurchaseOfferDetailsList | IReadOnlyList<OneTimePurchaseOfferDetails> | List of multiple one-time offers. Empty if there is only a single one-time offer or if the product is a subscription product. |
SubscriptionOfferDetails | IReadOnlyList<SubscriptionOfferDetails> | List of subscription offer information. Empty for consumable products. |
Response status
The returned object GooglePlayBillingServiceQueryProductDetailsResult branches into one of the cases below. Handle it with a switch statement.
| Response case | Description | App client handling |
|---|---|---|
Success | Product details were retrieved successfully. ProductDetailsList contains the product information, and UnfetchedProductList contains the products that failed. | Build the in-app store screen with the retrieved products or use them as input data for FetchGoogleProductsAsync() |
UnknownOutcome | Unknown result | Retry or show an error message |
Failure | Common Failure. See Common error handling. | Handle it according to the common error handling guidelines |
3. Optional: Get product details from the Hive Axyl server
Based on the product ID list, retrieve through the Hive Axyl server the details of the products registered in Google Play, such as product names, localized prices, currencies, and product descriptions, in the service standard format (Single Standard). Use the product information retrieved with QueryProductDetailsAsync() of the Google payment plugin in step 2 above as input.
FetchGoogleProductsAsync
To implement Google Play product list retrieval, call FetchGoogleProductsAsync() provided by the Hive Axyl SDK. When you pass the product information retrieved with the Google payment plugin (QueryProductDetailsAsync()) in step 2 above, it returns a product list refined into the service standard format (Single Standard).
Call parameters
| Field name | Type | Required | Description |
|---|---|---|---|
| request | ProductGoogle | Required | Request data that contains the product information retrieved from Google Play. |
| context | ApiCallContext | Optional | Per-call settings object that specifies the idempotency key, cancellation token, and request policy. If omitted, the default values are used. |
ProductGoogle
| Field name | Type | Required | Description |
|---|---|---|---|
AppVersion | string | Optional | Pass the app's current build version as a string. Example: 1.0.0 |
Country | string | Required | Pass the user's country code (ISO 3166-1 two-letter). Example: KR |
Currency | string | Required | Pass the payment currency (ISO 4217 three-letter). Example: KRW |
Language | string | Required | Pass the language code (ISO 639-1 two-letter) to use for localization. Example: ko |
ProductType | string | Required | Product type. subscription (subscription), consumable (consumable) |
Products | IReadOnlyList<ProductDetails> | Required | List of product information retrieved from the store. Must contain at least one item. |
ProviderId | ProductGoogleProviderId (enum) | Required | Market identifier. Because this request is only for Google Play, specify only Google. |
ProductDetails
| Field name | Type | Required | Description |
|---|---|---|---|
Description | string | Optional | Product description. |
OneTimePurchaseOfferDetails | ProductGoogleOfferDetails | Optional | One-time purchase offer. If a value is present, the price information of this offer is used. |
OneTimePurchaseOfferDetailsList | IReadOnlyList<ProductGoogleOfferDetails> | Optional | List of one-time purchase offers. Used when you pass discount offers and the base offer together. |
ProductId | string | Required | Unique identifier (PID) of the product. If it is empty, the request is rejected. Example: com.game.product1 |
ProductType | int | Optional | Product type. 1 is a consumable product, and 2 is a subscription product. |
SubscriptionOfferDetails | IReadOnlyList<ProductGoogleSubscriptionOffer> | Optional | List of subscription offers. The price information is contained in the PricingPhases of each offer. |
Title | string | Optional | Product title. Example: 1000 Gold |
ProductGoogleOfferDetails
| Field name | Type | Required | Description |
|---|---|---|---|
FormattedPrice | string | Optional | Price formatted by Google Play for display. |
OfferId | string | Optional | Offer ID. null for the base offer. |
OfferToken | string | Optional | Offer token. Use the new value each time you retrieve the product again. |
PreorderDetails | string | Optional | Raw JSON string of the preorder offer information. If the plugin object exists, use the result of ToJson(). |
PriceAmount | decimal | Optional | Price in currency units. If you also pass PriceAmountMicros, the micro-unit value is used. |
PriceAmountMicros | long | Optional | Price in micro-units. If you pass this value, it is used as the price basis. |
PriceCurrencyCode | string | Optional | Currency code. Uses the ISO 4217 three-letter format. |
RentalDetails | string | Optional | Raw JSON string of the rental offer information. If the plugin object exists, use the result of ToJson(). |
When you build the input JSON yourself, use keys that match the C# property names, such as productId and oneTimePurchaseOfferDetails. Pass the product and offer information retrieved from Google Play without changing it.
ProductGoogleSubscriptionOffer
| Field name | Type | Required | Description |
|---|---|---|---|
BasePlanId | string | Optional | Base plan ID. |
OfferId | string | Optional | Subscription offer ID. null for the base offer. |
OfferTags | IReadOnlyList<string> | Optional | Subscription offer tags. |
OfferToken | string | Optional | Subscription offer token. Use the new value each time you retrieve the product again. |
PricingPhases | IReadOnlyList<ProductGooglePricingPhase> | Optional | List of subscription pricing phases. |
ProductGooglePricingPhase
| Field name | Type | Required | Description |
|---|---|---|---|
BillingCycleCount | int | Optional | Number of billing cycles. 0 for an infinitely recurring phase. |
BillingPeriod | string | Optional | Billing period in the ISO 8601 duration format. |
FormattedPrice | string | Optional | Price formatted by Google Play for display. |
PriceAmount | decimal | Optional | Price in currency units. If you also pass PriceAmountMicros, the micro-unit value is used. |
PriceAmountMicros | long | Optional | Price in micro-units. If you pass this value, it is used as the price basis. |
PriceCurrencyCode | string | Optional | Currency code. Uses the ISO 4217 three-letter format. |
RecurrenceMode | int | Optional | Recurrence mode. 1 recurs infinitely, 2 recurs a finite number of times, and 3 is charged only once. |
Plugin result mapping
GoogleProductDetails and ProductDetails have similar names but are different C# types, so you cannot assign one directly to the other. Convert the ProductDetailsList from step 2 with the mapper below and set the result in Products of the step 3 request. The mapper maps all offers and pricing phases, and converts only PreorderDetails and RentalDetails of one-time offers into raw JSON strings.
using System.Collections.Generic;
using System.Linq;
using Hive.Axyl.Payments;
using Google = Hive.Axyl.Payments.Addon.Google;
internal static class GoogleProductMapper
{
public static IReadOnlyList<ProductDetails> Map(IReadOnlyList<Google.GoogleProductDetails> sources)
{
return sources.Select(Map).ToArray();
}
private static ProductDetails Map(Google.GoogleProductDetails source)
{
return new ProductDetails
{
Description = source.Description,
OneTimePurchaseOfferDetails = source.OneTimePurchaseOfferDetails is { } offer
? Map(offer)
: null,
OneTimePurchaseOfferDetailsList = source.OneTimePurchaseOfferDetailsList.Select(Map).ToArray(),
ProductId = source.ProductId,
ProductType = (int)source.ProductType,
SubscriptionOfferDetails = source.SubscriptionOfferDetails.Select(Map).ToArray(),
Title = source.Title
};
}
private static ProductGoogleOfferDetails Map(Google.OneTimePurchaseOfferDetails source)
{
return new ProductGoogleOfferDetails
{
FormattedPrice = source.FormattedPrice,
OfferId = source.OfferId,
OfferToken = source.OfferToken,
PreorderDetails = source.PreorderDetails?.ToJson(),
PriceAmount = source.PriceAmount,
PriceCurrencyCode = source.PriceCurrencyCode,
RentalDetails = source.RentalDetails?.ToJson()
};
}
private static ProductGoogleSubscriptionOffer Map(Google.SubscriptionOfferDetails source)
{
return new ProductGoogleSubscriptionOffer
{
BasePlanId = source.BasePlanId,
OfferId = source.OfferId,
OfferTags = source.OfferTags.ToArray(),
OfferToken = source.OfferToken,
PricingPhases = source.PricingPhases.Select(phase => new ProductGooglePricingPhase
{
BillingCycleCount = phase.BillingCycleCount,
BillingPeriod = phase.BillingPeriod,
FormattedPrice = phase.FormattedPrice,
PriceAmount = phase.PriceAmount,
PriceCurrencyCode = phase.PriceCurrencyCode,
RecurrenceMode = (int)phase.RecurrenceMode
}).ToArray()
};
}
}
Call example
Check the success result of PaymentsFetchGoogleProductsResult and the domain-specific results (Outcome) defined by this method in the example below and in the response status. For the result model and handling principles of common failures (Failure) that prevent the request from being performed, see Common error handling.
using Hive.Axyl.Payments;
using Hive.Axyl.Core;
// payments: IPaymentsService registered during initialization (for how to retrieve it, see [Install and initialize the module](../init.md))
IPaymentsService payments = HiveCore.Resolve<IPaymentsService>();
// Convert ProductDetailsList from the QueryProductDetailsAsync() success response and put it in the request.
var request = new ProductGoogle
{
AppVersion = "1.0.0",
Country = "KR",
Currency = "KRW",
Language = "ko",
ProductType = "consumable",
ProviderId = ProductGoogleProviderId.Google,
Products = GoogleProductMapper.Map(productDetailsList)
};
PaymentsFetchGoogleProductsResult result = await payments.FetchGoogleProductsAsync(request);
switch (result)
{
case PaymentsFetchGoogleProductsResult.Success success:
// Build the in-app store screen with the product list refined into the standard format.
foreach (var product in success.Data.Products)
{
Debug.Log($"{product.ProductId}: {product.Title} ({product.DisplayPrice})");
}
break;
// Handle request validation failure results (Outcome)
case PaymentsFetchGoogleProductsResult.PaymentBadRequest:
case PaymentsFetchGoogleProductsResult.PaymentInvalidParameter:
// Check the request fields and required values.
break;
// Handle common Failure
case PaymentsFetchGoogleProductsResult.Failure failure:
HiveError err = failure.Problem;
Debug.LogError($"[{err.Code}] {err.Message} (trace: {err.TraceId})");
break;
// Safety net: unhandled results and unknown new results (UnknownOutcome)
default:
Debug.LogWarning($"Unhandled result: {result.GetType().Name}");
break;
}
Response data
On success, the result is contained in Data (ProductResponseData) of PaymentsFetchGoogleProductsResult.Success.
| Field name | Type | Required | Description |
|---|---|---|---|
Data.AppVersion | string | Optional | App version. |
Data.Country | string | Optional | Country information. |
Data.Currency | string | Optional | Currency. |
Data.Language | string | Required | Language. |
Data.Products | IReadOnlyList<ProductProducts> | Optional | List of product information refined into the standard format. |
Data.ProviderId | ProductProviderId (enum) | Required | Market identifier. This response returns Google. |
Data.Meta | string | Optional | Response meta information. null if there is none. |
ProductProducts
| Field name | Type | Required | Description |
|---|---|---|---|
Currency | string | Optional | Currency. Example: KRW |
Description | string | Optional | Product description. |
DisplayOriginalPrice | string | Optional | Price before discount, for display. Example: ₩1,100 |
DisplayPrice | string | Optional | Price for display. Example: 1,200 KRW |
Offers | IReadOnlyList<ProductOffer> | Optional | List of one-time or subscription offers. null if there are no offers. |
OriginalPrice | decimal | Optional | Price before discount. Example: 1100 or 11.00 |
Price | decimal | Optional | Price. Example: 1200 or 9.99 |
ProductId | string | Optional | Unique identifier (PID) of the product. |
ProductType | string | Optional | Product type. subscription (subscription), consumable (consumable) |
Title | string | Optional | Product title. Example: 1000 Gold |
ProductOffer
| Field name | Type | Required | Description |
|---|---|---|---|
BasePlanId | string | Optional | Base plan ID of the subscription product. null for one-time purchase offers. |
Currency | string | Optional | Currency code. Uses the ISO 4217 three-letter format. |
DisplayPrice | string | Optional | Price to display on the screen. |
OfferToken | string | Optional | Offer token. The value sent in the request is returned as is. Google Play issues a new one each time the product is retrieved, so do not cache it; use the value from a new retrieval. |
Price | decimal | Optional | Price in currency units. |
Response example
// Example of success.Data in the Success branch
// success.Data.ProviderId = ProductProviderId.Google
// success.Data.Currency = "KRW"
// success.Data.Products[0].ProductId = "com.game.product1"
// success.Data.Products[0].Title = "1000 Gold"
// success.Data.Products[0].Price = 1200
// success.Data.Products[0].DisplayPrice = "1,200 KRW"
// success.Data.Products[0].ProductType = "consumable"
Response status
The table below summarizes the success result of PaymentsFetchGoogleProductsResult and the domain-specific results (Outcome) defined by this method. For the result model and handling principles of common failures (Failure) that prevent the request from being performed, see Common error handling.
| Response case | Description | App client handling |
|---|---|---|
Success | The product list was retrieved successfully. Data contains the product list in the standard format. | Build the in-app store screen with the product list |
PaymentBadRequest | The request cannot be processed. | Check the request values and call conditions |
PaymentInvalidParameter | Invalid parameter. | Check the request field values and retry |
PaymentResourceNotFound | The payment information cannot be found. | Check the receipt, order number, and payment status |
UnknownOutcome | A domain-specific result that the SDK does not recognize. | Treat it as a failure and log the result code |
Failure | Common Failure. See Common error handling. | Handle it according to the common error handling guidelines |