Step 3. Purchase a product
Open the Google Play payment window for the product the user selected and proceed with the purchase approval process. As the result of the call, you receive a payment completion signal and the purchase receipt data.
Follow the steps below to purchase a Google Play product.
1. Display the in-app store UI
Implement the in-app store UI in the app and display it to users. Let users select a product and select the payment button. When the user selects the payment button, proceed to the next step.
2. Save pre-purchase information
After the user selects the payment button and before the store payment window opens, save the purchase attempt information. For consumable products, calling CreatePrePurchaseAsync() in this section is optional. For subscription products, you must call PrepareSubscriptionAsync() instead of CreatePrePurchaseAsync(). Because subscription auto-renewal notifications do not contain the Player ID, the Hive Axyl server finds the Player ID and IapPayload in the record saved with this method. For the request fields, see PrepareSubscriptionAsync, and for the full flow for consumable products, see Record purchase information.
CreatePrePurchaseAsync
CreatePrePurchaseAsync() saves the product, expected payment amount, currency, country and language, app server, and IapPayload as a pre-purchase tracking record.
Call parameters
| Field name | Type | Required | Description |
|---|---|---|---|
| request | PrePurchase | Required | Request data object for saving pre-purchase 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. |
PrePurchase
| Field name | Type | Required | Description |
|---|---|---|---|
Country | string | Required | Pass the user's country code (ISO 3166-1 two-letter) |
Currency | string | Required | Pass the payment currency (ISO 4217 three-letter). Use the currency value of the product retrieved in Step 2. Get the product list |
Language | string | Required | Pass the language code (ISO 639-1 two-letter) |
Price | decimal | Required | Expected payment amount. It may include decimals depending on the currency. Pass the price value of the product retrieved in Step 2. Get the product list |
ProductId | string | Required | Unique identifier of the in-app product the user selected. Pass the Product ID retrieved in Step 2. Get the product list |
ProviderId | PrePurchaseProviderId | Required | Market and payment method identifier (enum). Because this request is only for Google Play payments, specify only Google. |
ServerId | string | Optional | Server ID that you check on the App Server tab after registering the app server in App Info > App Server by following Hive Console App Server |
AccountUuid | string | Optional | UUIDv5 value generated from the logged-in user's playerId. Pass it before opening the store payment window. See Generate an AccountUuid. |
IapPayload | string | Optional | Payload (JSON string) that the developer attaches to the market payment. It is passed as is to the app server callback after the purchase is completed. |
RequestDate | DateTimeOffset | Optional | Request time (UTC). If omitted, the server's current time is used. |
IapPayload usage example
With IapPayload of PrePurchase, you can include app-specific data in the receipt issued after the purchase is completed. The following is one example of using IapPayload.
For example, suppose that in a mobile app with three user profiles, A, B, and C, a user purchased an in-app product with profile A. If you request the purchase with a JSON string such as {"character": "A"} in IapPayload, this value is included in the purchase receipt and passed to the app when the payment completes successfully.
If the payment was completed but the product could not be delivered because of a network error, you must look up the failed delivery, verify its receipt again, and then deliver the product. At this point, you need to know which of the user's profiles A, B, and C should receive the product. With the {"character": "A"} information in the IapPayload included in the purchase receipt, you can confirm that profile A is the delivery target and deliver the product accurately.
This is only one example. You can put information your app needs, such as the product purchase time and purchasing user information, in IapPayload and attach it to the purchase receipt. Use it to fit your app's situation.
Call example
Check the success result of PaymentsCreatePrePurchaseResult 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 System;
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 the request object with the product information at the time the user selected the payment button
var request = new PrePurchase
{
Country = "KR",
Currency = "KRW",
Language = "ko",
Price = 1200.0m,
ProductId = "com.game.item.gold_100",
ProviderId = PrePurchaseProviderId.Google,
ServerId = "server01",
RequestDate = DateTimeOffset.UtcNow,
AccountUuid = accountUuid
};
PaymentsCreatePrePurchaseResult result = await payments.CreatePrePurchaseAsync(request);
switch (result)
{
case PaymentsCreatePrePurchaseResult.Success success:
// Pre-save succeeded. No data is returned. Proceed to open the store payment window.
break;
// Problem with request values — check the required values and formats
case PaymentsCreatePrePurchaseResult.PaymentBadRequest:
case PaymentsCreatePrePurchaseResult.PaymentInvalidParameter:
Debug.LogError("Check the request parameters.");
break;
case PaymentsCreatePrePurchaseResult.UnknownOutcome unknownOutcome:
Debug.LogWarning($"Unknown result: {unknownOutcome.Code}");
break;
// Handle common Failure
case PaymentsCreatePrePurchaseResult.Failure failure:
HiveError err = failure.Problem;
Debug.LogError($"[{err.Code}] {err.Message} (trace: {err.TraceId})");
break;
// Safety net: unhandled results
default:
Debug.LogWarning($"Unhandled result: {result.GetType().Name}");
break;
}
Response data
No data is returned on success.
Response example
Response status
The table below summarizes the success result of PaymentsCreatePrePurchaseResult 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. No data is returned on success.
| Response case | Description | App client handling |
|---|---|---|
Success | Pre-save succeeded | Proceed to open the store payment window |
PaymentBadRequest | The payment request cannot be processed. | Check the request values and call conditions |
PaymentInvalidParameter | The payment request parameters are invalid. | Check the request field values |
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 |
3. Request a Google Play payment
When the user selects the payment button, open the Google Play payment window through the Google payment plugin. When the user approves the payment, you get the purchase receipt (purchaseToken) and the market order number. This request has no purchase quantity field, so set only the ProductId of each product and, if needed, the OfferToken.
Call LaunchBillingFlowAsync() of the Google payment plugin to launch the Google Play payment window. This method returns only whether the payment window launched successfully, and you receive the actual purchase result asynchronously through the PurchasesUpdated event.
BillingClient connection required
Before you call LaunchBillingFlowAsync(), you must connect the BillingClient with StartConnectionAsync(). For how to connect, see Step 1. Set up the integration.
3.1. Subscribe to the purchase result event
Subscribe to the PurchasesUpdated event before you open the payment window. This event delivers not only the payment results from LaunchBillingFlowAsync() but also purchase results that occur externally, such as promo code redemptions.
using Hive.Axyl.Core;
using Hive.Axyl.Payments.Addon.Google;
// 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;
}
// Subscribe to the purchase result event (register only once, early in the app lifecycle)
googlePlugin.PurchasesUpdated += (PurchasesUpdatedEventArgs args) =>
{
foreach (GooglePurchase purchase in args.Purchases)
{
if (purchase.PurchaseState == PurchaseState.Purchased)
{
// Payment completed. Save purchaseToken and orderId and proceed to the next step
string purchaseToken = purchase.PurchaseToken;
string orderId = purchase.OrderId;
Debug.Log($"Purchase completed — products: {string.Join(", ", purchase.Products)}, order: {orderId}");
}
else if (purchase.PurchaseState == PurchaseState.Pending)
{
// Payment pending (for example, convenience store payment). The event is received again when the payment is confirmed
Debug.Log("The payment is pending. You receive the event again after the payment is confirmed.");
}
}
};
// Subscribe to the BillingClient disconnection event (the plugin does not reconnect automatically)
googlePlugin.BillingServiceDisconnected += () =>
{
Debug.LogWarning("BillingClient is disconnected. Reconnect it.");
};
3.2. Open the payment window
LaunchBillingFlowAsync
To open the Google Play payment window, call LaunchBillingFlowAsync() provided by the Google payment plugin. Pass the ProductId and OfferToken of the product obtained from QueryProductDetailsAsync() in Step 2. Get the product list.
Call parameters
| Field name | Type | Required | Description |
|---|---|---|---|
| request | LaunchBillingFlowRequest | Required | Request data for opening the payment window. |
| ct | CancellationToken | Optional | Cancellation token. |
LaunchBillingFlowRequest
| Field name | Type | Required | Description |
|---|---|---|---|
Products | IReadOnlyList<LaunchBillingFlowProductParams> | Required | List of products to purchase. Must contain at least one item. |
ObfuscatedAccountId | string? | Optional | Pass the same value you created in Generate an AccountUuid. Google Play links it to the purchase information, and it is used to match the account during receipt verification. |
ObfuscatedProfileId | string? | Optional | Obfuscated user profile ID. |
Pass ObfuscatedAccountId in the same way for consumable products and subscription products. For subscription products, also pass OfferToken in the same request.
For subscription products, also pass the same value in SubscriptionPrePurchaseRequest.AccountUuid of PrepareSubscriptionAsync(), which you call in 2. Save pre-purchase information. This value is the key for finding the pre-purchase record during subscription auto-renewal and restoration. For the request fields, see PrepareSubscriptionAsync.
LaunchBillingFlowProductParams
| Field name | Type | Required | Description |
|---|---|---|---|
ProductId | string | Required | Unique identifier (Product ID) of the product to purchase. |
OfferToken | string | Required | Token of a subscription product or a multiple one-time offer. Pass the current value retrieved from QueryProductDetailsAsync(). For a single one-time offer, leave it as an empty string. |
Call example
GooglePlayBillingServiceLaunchBillingFlowResult, the object that LaunchBillingFlowAsync() returns, indicates only whether the payment window launched successfully. You receive the actual purchase result through the PurchasesUpdated event you subscribed to above.
using Hive.Axyl.Core;
using Hive.Axyl.Payments.Addon.Google;
using System.Threading;
// Retrieve the Google payment plugin
if (!HiveCore.TryResolve<IGooglePlayBillingPlugin>(out var googlePlugin))
{
Debug.LogError("The Google Play Billing plugin is not registered.");
return;
}
// Create the request object for opening the payment window
var request = new LaunchBillingFlowRequest
{
Products = new[]
{
new LaunchBillingFlowProductParams
{
ProductId = "com.game.item.gold_100",
OfferToken = offerToken // Offer token obtained from QueryProductDetailsAsync()
}
},
ObfuscatedAccountId = accountUuid
};
GooglePlayBillingServiceLaunchBillingFlowResult result =
await googlePlugin.LaunchBillingFlowAsync(request, CancellationToken.None);
switch (result)
{
case GooglePlayBillingServiceLaunchBillingFlowResult.Success:
// Payment window launched. The actual purchase result is received through the PurchasesUpdated event
Debug.Log("The Google Play payment window was launched.");
break;
case GooglePlayBillingServiceLaunchBillingFlowResult.UnknownOutcome:
Debug.LogWarning("Unknown payment window launch result.");
break;
case GooglePlayBillingServiceLaunchBillingFlowResult.Failure failure:
Debug.LogError($"Failed to launch the payment window: {failure}");
break;
default:
Debug.LogWarning($"Unhandled result: {result.GetType().Name}");
break;
}
Response status
The returned object GooglePlayBillingServiceLaunchBillingFlowResult branches into one of the cases below. Handle it with a switch statement.
| Response case | Description | App client handling |
|---|---|---|
Success | The payment window launched successfully. The actual purchase result is received through the PurchasesUpdated event. | Wait for the PurchasesUpdated event |
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 |
Purchase information received through the PurchasesUpdated event
Get the following information from the GooglePurchase object of the PurchasesUpdated event.
| Field name | Type | Description |
|---|---|---|
OrderId | string? | Market order number. (Example: GPA.3389-9543-8198-17604) |
PurchaseToken | string | Purchase receipt (purchaseToken). Used later for receipt verification and finishing the transaction. |
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. |
Signature | string | Purchase signature data. |
OriginalJson | string | Original purchase JSON data returned by Google Play. |
Call the Google payment plugin, and after the payment is approved, receive and save the following values.
- Purchase receipt (purchaseToken)
- Market order number (example:
GPA.3389-9543-8198-17604)
4. Save payment result data
For consumable products, save the receipt and transaction information right after the payment is completed. Because this keeps a record of the payment before receipt verification, you can use it for verification, product delivery, customer support, and settlement even if later processing is interrupted. Complete this step before receipt verification. For subscription products, proceed with 5. Save subscription purchase information instead of this step. For the full flow, see Record purchase information.
RecordStorePurchaseAsync
RecordStorePurchaseAsync() saves payment results, such as the receipt and transaction ID, to the Hive Axyl server.
Call parameters
| Field name | Type | Required | Description |
|---|---|---|---|
| request | PurchaseRequest | Required | Request data object for saving payment results. |
| context | ApiCallContext | Optional | Per-call settings object that specifies the idempotency key, cancellation token, and request policy. If omitted, the default values are used. |
PurchaseRequest
| Field name | Type | Required | Description |
|---|---|---|---|
AccountUuid | string | Optional | UUIDv5 value generated from the logged-in user's playerId. Pass it when you record the purchase result after the store payment finishes. If you passed it at the payment start step, use the same value. See Generate an AccountUuid. |
AxylReceipt | string | Required | Google Play purchase token (purchaseToken). |
Country | string | Required | Country code (ISO 3166-1 two-letter). Example: KR |
Currency | string | Optional | Payment currency (ISO 4217 three-letter). Paired with Price, it is used to compare against the values the server recorded. If you send only one of the two, the currency is not compared, and only the amount is compared. Example: KRW |
IapPayload | string | Optional | Payload (JSON string) that the developer attached to the market payment. It is passed as is to app server verification and the purchase completion callback. |
Language | string | Required | Language code (ISO 639-1 two-letter). Example: ko |
OrderId | string | Optional | Hive Axyl internal order number. Used for Steam/PG. |
Price | decimal | Optional | Payment amount. It may include decimals depending on the currency. If you send it, the server compares it with the recorded amount; if you omit it, no comparison is made. For Google payments, a mismatched amount is only recorded, and the request is not rejected. The amount kept in the payment history is the value the server confirmed with the market. |
ProductId | string | Required | In-app product ID registered in the market. |
ProjectInfo | string | Optional | App-specific free-form JSON string. The value is only stored and is not passed to the app server. Set values to pass to the app server in IapPayload. |
ProviderId | PurchaseRequestProviderId | Required | Because this request is only for saving Google Play payment results, specify Google. |
Quantity | int | Optional | Purchase quantity |
RequestDate | DateTimeOffset | Optional | Client request time (UTC). If omitted, the server's current time is used. |
RequestType | int | Optional | Request type. 1: new purchase, 2: purchase restoration |
ServerId | string | Optional | App server ID registered by following Hive Console App Server. |
StoreTransactionId | string | Optional | Google Play Store transaction ID. |
Call example
Check the success result of PaymentsRecordStorePurchaseResult 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 System;
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>();
// Right after the store payment completes, create the request object with the receipt issued by the market
var request = new PurchaseRequest
{
AxylReceipt = purchaseToken, // Google Play purchase token
ProviderId = PurchaseRequestProviderId.Google,
ProductId = "com.game.item.gold_100",
Price = 1200.0m,
Currency = "KRW",
Country = "KR",
Language = "ko",
Quantity = 1,
StoreTransactionId = "GPA.3389-9543-8198-17604",
RequestType = 1, // 1: new purchase
RequestDate = DateTimeOffset.UtcNow,
ServerId = "server01",
AccountUuid = accountUuid
};
PaymentsRecordStorePurchaseResult result = await payments.RecordStorePurchaseAsync(request);
switch (result)
{
case PaymentsRecordStorePurchaseResult.Success success:
// Save succeeded. You can check success.Data.Meta.
break;
// Problem with request values or payment status
case PaymentsRecordStorePurchaseResult.PaymentBadRequest:
case PaymentsRecordStorePurchaseResult.PaymentInvalidParameter:
case PaymentsRecordStorePurchaseResult.PaymentResourceNotFound:
case PaymentsRecordStorePurchaseResult.PaymentUnauthorized:
case PaymentsRecordStorePurchaseResult.VerifyError:
Debug.LogError("Check the request values and the payment status.");
break;
case PaymentsRecordStorePurchaseResult.UnknownOutcome unknownOutcome:
Debug.LogWarning($"Unknown result: {unknownOutcome.Code}");
break;
// Handle common Failure
case PaymentsRecordStorePurchaseResult.Failure failure:
HiveError err = failure.Problem;
Debug.LogError($"[{err.Code}] {err.Message} (trace: {err.TraceId})");
break;
// Safety net: unhandled results
default:
Debug.LogWarning($"Unhandled result: {result.GetType().Name}");
break;
}
Response data
On success, the result is contained in Data (PurchaseResponseData) of PaymentsRecordStorePurchaseResult.Success.
| Field name | Type | Required | Description |
|---|---|---|---|
Data.Meta | string | Optional | Response meta information |
Response example
Response status
The table below summarizes the success result of PaymentsRecordStorePurchaseResult 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 | Payment result saved successfully | Send the receipt to the app server and proceed to the receipt verification step |
PaymentBadRequest | The payment request cannot be processed. | Check the request values and call conditions |
PaymentInvalidParameter | The payment request parameters are invalid. | Check request fields such as AxylReceipt |
PaymentResourceNotFound | The payment information to save cannot be found. | Check the receipt and transaction information |
PaymentUnauthorized | No permission for the payment request. | Check the app and authentication status |
VerifyError | An error occurred during payment verification. | Check the receipt and the market 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 |
5. Save subscription purchase information
For subscription products, save the subscription purchase information to the Hive Axyl server with PurchaseSubscriptionAsync() before you request receipt verification from the app server. Because the Hive Axyl server uses the same receipt to find the subscription record from this step in Complete the subscription, keep the AxylReceipt you passed in this step until subscription completion.
PurchaseSubscriptionAsync
PurchaseSubscriptionAsync() saves the subscription purchase information with the subscription receipt and returns the transaction ID and lookup key of the saved subscription record.
Call parameters
| Field name | Type | Required | Description |
|---|---|---|---|
| request | SubscriptionPurchaseRequest | Required | Request data object for saving subscription purchase 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. |
SubscriptionPurchaseRequest
| Field name | Type | Required | Description |
|---|---|---|---|
AppVersion | string | Optional | App version. |
AxylReceipt | string | Required | Google Play purchase token (purchaseToken) of the subscription payment. |
Country | string | Required | Country code (ISO 3166-1 two-letter). Example: KR |
Currency | string | Required | Currency (ISO 4217 three-letter). Example: KRW |
Language | string | Required | Language code (ISO 639-1 two-letter). Example: ko |
OriginalPrice | decimal | Optional | Price before discount. |
Price | decimal | Required | Subscription price. Pass the price of the product retrieved in Step 2. Get the product list. |
ProviderId | SubscriptionPurchaseRequestProviderId | Required | Because this is a Google subscription, specify Google. |
RequestTimeMs | long | Optional | Request time (milliseconds). |
ServerId | string | Optional | App server ID registered by following Hive Console App Server. |
StoreTransactionId | string | Optional | You do not need to pass this for Google subscriptions. The server creates the lookup key from the receipt and does not use this value even if you pass it. |
Call example
Check the success result of PaymentsPurchaseSubscriptionResult 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>();
// Right after the subscription payment finishes, create the request object with the receipt issued by the store
var request = new SubscriptionPurchaseRequest
{
AxylReceipt = purchaseToken, // GooglePurchase.PurchaseToken of the subscription payment
ProviderId = SubscriptionPurchaseRequestProviderId.Google,
Price = 5500.0m,
Currency = "KRW",
Country = "KR",
Language = "ko",
ServerId = "server01"
};
PaymentsPurchaseSubscriptionResult result = await payments.PurchaseSubscriptionAsync(request);
switch (result)
{
case PaymentsPurchaseSubscriptionResult.Success success:
// Save succeeded. Request subscription receipt verification from the app server.
Debug.Log($"Subscription record transaction ID: {success.Data.HiveAxylTransactionId}");
break;
case PaymentsPurchaseSubscriptionResult.PaymentResourceConflict:
// The subscription is already saved. Request subscription receipt verification from the app server.
break;
// Problem with request values or payment status
case PaymentsPurchaseSubscriptionResult.PaymentBadRequest:
case PaymentsPurchaseSubscriptionResult.PaymentInvalidParameter:
case PaymentsPurchaseSubscriptionResult.PaymentResourceNotFound:
case PaymentsPurchaseSubscriptionResult.PaymentUnauthorized:
case PaymentsPurchaseSubscriptionResult.VerifyError:
Debug.LogError("Check the request values and the payment status.");
break;
case PaymentsPurchaseSubscriptionResult.UnknownOutcome unknownOutcome:
Debug.LogWarning($"Unknown result: {unknownOutcome.Code}");
break;
// Handle common Failure
case PaymentsPurchaseSubscriptionResult.Failure failure:
HiveError err = failure.Problem;
Debug.LogError($"[{err.Code}] {err.Message} (trace: {err.TraceId})");
break;
// Safety net: unhandled results
default:
Debug.LogWarning($"Unhandled result: {result.GetType().Name}");
break;
}
Response data
On success, the save result is contained in Data (SubscriptionPurchaseResponseData) of PaymentsPurchaseSubscriptionResult.Success.
| Field name | Type | Required | Description |
|---|---|---|---|
Data.HiveAxylTransactionId | string | Required | Transaction ID of the saved subscription record. |
Data.HiveAxylProductId | string? | Optional | Product ID of the saved subscription. |
Data.HiveAxylStoreTransactionId | string? | Optional | Lookup key of the subscription record. The server uses this key to find the same subscription record during subscription completion. |
Data.Meta | string? | Optional | Response meta information. |
Response status
The table below summarizes the success result of PaymentsPurchaseSubscriptionResult 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 subscription purchase information was saved. | Request subscription receipt verification from the app server |
PaymentBadRequest | The payment request cannot be processed. | Check the request values and call conditions |
PaymentInvalidParameter | The payment request parameters are invalid. | Check the request fields |
PaymentResourceConflict | The subscription purchase information is already saved. | Request subscription receipt verification from the app server |
PaymentResourceNotFound | The payment information cannot be found. | Check the receipt and transaction ID |
PaymentUnauthorized | No permission for the payment request. | Check the credentials and request permissions |
VerifyError | An error occurred during payment verification. | Check the Google Play receipt 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 |
Next steps
Proceed to Step 4. Verify receipts.