Restore purchases
Retrieve again the undelivered purchases for which the payment was completed but the product was not received because of a communication error or an abnormal termination, and deliver the missing products. This reduces payment-related complaints and protects the user's purchased assets.
Follow the steps below to restore undelivered purchases.
1. Retrieve undelivered receipts again
Call QueryPurchasesAsync() of the Google payment plugin to get the purchase receipts of undelivered products from Google Play again. Google Play has no separate restore flow. Because QueryPurchasesAsync() returns the user's purchase history, including unfinished purchases, this method serves as the restore flow.
BillingClient connection required
Before you call QueryPurchasesAsync(), you must connect the BillingClient with StartConnectionAsync(). For how to connect, see Step 1. Set up the integration.
QueryPurchasesAsync
To implement retrieving undelivered receipts again, call QueryPurchasesAsync() provided by the Google payment plugin. Call it for consumable products and subscription products separately to retrieve unfinished purchases.
Call parameters
| Field name | Type | Required | Description |
|---|---|---|---|
| productType | ProductType | Required | Product type to retrieve. Specify Inapp (consumable product) or Subs (subscription product). |
| ct | CancellationToken | Optional | Cancellation token. |
Call example
Check the success result of GooglePlayBillingServiceQueryPurchasesResult 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.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;
}
// Retrieve unfinished purchases of consumable products and subscription products
foreach (ProductType productType in new[]
{
ProductType.Inapp,
ProductType.Subs
})
{
GooglePlayBillingServiceQueryPurchasesResult result =
await googlePlugin.QueryPurchasesAsync(productType, CancellationToken.None);
switch (result)
{
case GooglePlayBillingServiceQueryPurchasesResult.Success success:
foreach (GooglePurchase purchase in success.Data.Purchases)
{
if (purchase.PurchaseState == PurchaseState.Purchased && !purchase.IsAcknowledged)
{
// Undelivered (unconsumed) purchase found. Keep the purchase token and proceed with follow-up processing
string orderId = purchase.OrderId;
Debug.Log($"Undelivered purchase ({productType}) — products: {string.Join(", ", purchase.Products)}, order: {orderId}");
}
}
break;
case GooglePlayBillingServiceQueryPurchasesResult.UnknownOutcome:
Debug.LogWarning("Unknown query result.");
break;
case GooglePlayBillingServiceQueryPurchasesResult.Failure failure:
Debug.LogError($"Failed to get undelivered purchases: {failure}");
break;
default:
Debug.LogWarning($"Unhandled result: {result.GetType().Name}");
break;
}
}
Response data
On success, the result is contained in Data (QueryPurchasesResponse) of GooglePlayBillingServiceQueryPurchasesResult.Success.
| Field name | Type | Description |
|---|---|---|
Data.Purchases | IReadOnlyList<GooglePurchase> | The user's purchase list. Includes unconsumed (undelivered) purchases. |
GooglePurchase
| Field name | Type | Description |
|---|---|---|
OrderId | string? | Market order number. (Example: GPA.3389-9543-8198-17604) |
PurchaseToken | string | Purchase receipt (purchaseToken). |
Products | IReadOnlyList<string> | List of purchased product IDs. |
PurchaseState | PurchaseState | Purchase state. Purchased (completed) or Pending (pending). |
PurchaseTime | DateTimeOffset | Purchase time. |
Quantity | int | Purchase quantity. |
IsAcknowledged | bool | Whether the purchase has been acknowledged. |
IsAutoRenewing | bool | Whether the purchase renews automatically. Used for subscription products. |
Signature | string | Purchase signature data. |
OriginalJson | string | Original purchase JSON data returned by Google Play. |
Response status
The returned object GooglePlayBillingServiceQueryPurchasesResult branches into one of the cases below. Handle it with a switch statement.
| Response case | Description | App client handling |
|---|---|---|
Success | The purchase list was retrieved successfully. Data.Purchases contains the unfinished purchases. | Send the PurchaseToken of each undelivered purchase to the app server, proceed with receipt verification, and then deliver the product |
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 |
Undelivered purchases retrieved again use the same receipt format as new purchases. Pass GooglePurchase.PurchaseToken as AxylReceipt in the payment result data save request of Step 3. Purchase a product, and set RequestType to 2 (purchase restoration).
2. Verify receipts and deliver products
Repeat the steps below with the receipts you got again.
- Prepare the receipt information and send it to the app server. Also set
requestTypeof the receipt verification request that the app server sends to2(purchase restoration). - The app server verifies the receipt with the Hive Axyl Server API.
- When verification is complete, deliver the product and finish the transaction.