Skip to content

Step 5. Deliver products and finish transactions

When receipt verification succeeds, the app server delivers the product to the user, and the app client records the delivery result and then finishes the transaction. The hiveAxylTransactionId and subscription product ID that the app client uses in this step are the values that the app server received in the receipt verification response and passed on. For the roles of the payment confirmation request and delivery result recording, see Confirm payments and record delivery results.

Follow the process below to deliver products and finish transactions.

1. Deliver products

After checking the receipt verification result, the app server delivers the product to the user who paid. Your app implements product delivery, and the delivery method can differ from app to app.

RequestPurchaseAsync(), which sends the payment confirmation request, is used only for consumable products. For subscription products, confirm the subscription with 3. Complete the subscription. In Google payment, the app decides the order of product delivery and the RequestPurchaseAsync() call, but deliver the product first and then send the payment confirmation request. 2. Save product delivery results is not a prerequisite for 4. Finish the transaction, so do not delay finishing the transaction even if saving the delivery result fails.

2. Save product delivery results

Method

ItemResultAsync

To implement saving delivery results for consumable products, call ItemResultAsync() provided by the Hive Axyl SDK. It records the result in the Hive Axyl server after product delivery is complete. This method does not confirm the payment or decide whether to deliver the product.

Call parameters

Field name Type Required Description
request ItemResultBody Required The request data object for saving consumable product delivery 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.

ItemResultBody

Field name Type Required Description
AxylTransactionId string Required The Hive Axyl payment transaction ID (the prefix identifies the market). Use the hiveAxylTransactionId value that the app server received in the receipt verification response and passed on.
Status int Required The delivery result status. 1: delivery succeeded, 2: canceled without reclaiming the product, 3: canceled and the product reclaimed. Any other value is invalid.
Assets IReadOnlyList<ItemResultAsset> Optional The list of products actually delivered. If there are no items to record, omit this field.
ProjectPayloadInfo string Optional App-specific free-form JSON data (JSON string)

ItemResultAsset

Field name Type Required Description
AssetId string Optional Unique ID of the product in the app
AssetName string Optional Name of the product in the app
Quantity int Optional The delivered quantity. If specified, it must be 1 or more.

Call example

Check the success result of PaymentsItemResultResult 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>();

// After product delivery is complete, create the request object with the delivery result
var request = new ItemResultBody
{
    AxylTransactionId = "GO_1234567890", // hiveAxylTransactionId from the receipt verification response passed by the app server (GO_ prefix = Google)
    Status = 1, // 1: Delivery succeeded
    Assets = new[]
    {
        new ItemResultAsset { AssetId = "item_gold_100", AssetName = "골드 100개", Quantity = 1 }
    },
    ProjectPayloadInfo = "{\"serverId\":\"server01\"}" // (Optional) App-specific free-form JSON data
};

PaymentsItemResultResult result = await payments.ItemResultAsync(request);

switch (result)
{
    case PaymentsItemResultResult.Success success:
        // Delivery result recorded. You can check success.Data.Meta.
        break;

    // Request value problem — check the required values, quantity, and status value
    case PaymentsItemResultResult.PaymentInvalidParameter:
    case PaymentsItemResultResult.PaymentBadRequest:
        Debug.LogError("Check the request parameters.");
        break;

    // Handle common Failure
    case PaymentsItemResultResult.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 response meta information is contained in Data (SuccessResponseData) of PaymentsItemResultResult.Success.

Field name Type Required Description
Data.Meta string? Optional Response meta information.

Response example

// You can check success.Data.Meta in the Success branch.

Response status

The following table summarizes the success result of PaymentsItemResultResult 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 Delivery result recorded Proceed with the next payment processing step
InvalidQuantity The delivered quantity is invalid. Check the delivered quantity
InvalidStatus The delivery result status value is invalid. Check the Status value
PaymentBadRequest The request cannot be processed. Check the request values and call conditions
PaymentInvalidParameter Invalid parameter (for example, AxylTransactionId is missing) Check the required parameter settings
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

3. Complete the subscription

For subscription products, after the app server finishes subscription receipt verification and delivering subscription benefits, call PostSubscriptionAsync() to confirm the subscription on the Hive Axyl server. When the subscription is confirmed, proceed to 4.2. Acknowledge non-consumable and subscription product purchases.

Method

PostSubscriptionAsync

PostSubscriptionAsync() finds the subscription record saved in Save subscription purchase information, confirms the subscription, and finishes the transaction.

Call parameters

Field name Type Required Description
request SubscriptionPurchasePostRequest Required The request data object for confirming the subscription.
context ApiCallContext Optional Per-call settings object that specifies the idempotency key, cancellation token, and request policy. If omitted, the default values are used.

SubscriptionPurchasePostRequest

Field name Type Required Description
AppVersion string Optional The app version.
AxylReceipt string Required Pass the purchase token (purchaseToken) that you passed in the PurchaseSubscriptionAsync() request in Save subscription purchase information exactly as is. The server uses this value to find the saved subscription record, so if the value differs, the subscription is not confirmed.
Country string Required The country code (two-letter ISO 3166-1 code). Example: KR
Currency string Required The currency (three-letter ISO 4217 code). Example: KRW
Language string Required The language code (two-letter ISO 639-1 code). Example: ko
ProductId string Required The subscription product ID. Use the hiveAxylProductId that the app server received in the subscription product receipt verification response and passed on.
ProviderId SubscriptionPurchasePostRequestProviderId Required Because this is a Google subscription, specify Google.
RequestTimeMs long Optional The request time (milliseconds).
RequestType int Optional The request type. 1: new purchase, 2: purchase restoration
ServerId string Optional The ID of the app server registered by following Hive Console App Server.

Call example

Check the success result of PaymentsPostSubscriptionResult 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>();

var request = new SubscriptionPurchasePostRequest
{
    AxylReceipt = purchaseToken,       // The same purchase token as the value sent to PurchaseSubscriptionAsync()
    ProductId = verifiedProductId,     // hiveAxylProductId from the subscription receipt verification response passed by the app server
    Country = "KR",
    Currency = "KRW",
    Language = "ko",
    ProviderId = SubscriptionPurchasePostRequestProviderId.Google
};

PaymentsPostSubscriptionResult result = await payments.PostSubscriptionAsync(request);

switch (result)
{
    case PaymentsPostSubscriptionResult.Success success:
        if (string.IsNullOrEmpty(success.Data.HiveAxylTransactionId))
        {
            // No subscription record to confirm was found. Check that the value is the same as the AxylReceipt sent when saving the subscription purchase information.
            Debug.LogError("There is no confirmed subscription.");
            break;
        }
        // Subscription confirmed. Proceed with purchase acknowledgment (AcknowledgePurchaseAsync).
        break;

    case PaymentsPostSubscriptionResult.VerifyDuplicated:
        // The subscription is already confirmed. Proceed with purchase acknowledgment (AcknowledgePurchaseAsync).
        break;

    // Problem with request values or payment status
    case PaymentsPostSubscriptionResult.PaymentBadRequest:
    case PaymentsPostSubscriptionResult.PaymentInvalidParameter:
    case PaymentsPostSubscriptionResult.PaymentResourceNotFound:
    case PaymentsPostSubscriptionResult.PaymentUnauthorized:
    case PaymentsPostSubscriptionResult.VerifyError:
        Debug.LogError("Check the request values and the payment status.");
        break;

    case PaymentsPostSubscriptionResult.UnknownOutcome unknownOutcome:
        Debug.LogWarning($"Unknown result: {unknownOutcome.Code}");
        break;

    // Handle common Failure
    case PaymentsPostSubscriptionResult.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 confirmation result is contained in Data (SubscriptionPurchasePostResponseData) of PaymentsPostSubscriptionResult.Success. Even if Success is returned, Data.HiveAxylTransactionId is empty when no subscription record to confirm is found, so use this value to check whether the subscription was actually confirmed.

Field name Type Required Description
Data.HiveAxylProductId string Required The subscription product ID of the confirmation request.
Data.HiveAxylStoreTransactionId string? Optional The lookup key of the subscription record.
Data.HiveAxylTransactionId string? Optional The transaction ID of the confirmed subscription record. Empty if there is no subscription record to confirm.
Data.Meta string? Optional Response meta information.

Response status

The following table summarizes the success result of PaymentsPostSubscriptionResult 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 request was processed. Check whether the subscription was actually confirmed with Data.HiveAxylTransactionId. If the value exists, proceed to finish the transaction. If it is empty, check AxylReceipt and whether the subscription purchase information was saved
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
PaymentResourceNotFound The payment information cannot be found. Check the receipt and transaction information
PaymentUnauthorized No permission for the payment request. Check the credentials and request permissions
VerifyDuplicated The subscription is already confirmed. Proceed to finish the transaction without confirming it again
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

4. Finish the transaction

Notify the store through the Google payment plugin that the purchase is complete. The method to call depends on the product type.

  • Consumable products: Call ConsumeAsync() after product delivery and the payment confirmation request are complete. Purchase acknowledgment is also processed, and after consumption, the same product can be purchased again
  • Non-consumable products and subscription products: Call AcknowledgePurchaseAsync(), which only acknowledges the purchase without consuming it. For subscription products, call it after subscription completion

Before you call ConsumeAsync() or AcknowledgePurchaseAsync(), you must connect the BillingClient with StartConnectionAsync(). For how to connect, see Step 1. Set up the integration.

Purchase acknowledgment required within 3 days

Under Google Play policy, if you do not call ConsumeAsync() or AcknowledgePurchaseAsync() within 3 days after the purchase, Google automatically refunds the payment.

4.1. Finish consumable product transactions

Method

ConsumeAsync

To implement finishing transactions for consumable products, call ConsumeAsync() provided by the Google payment plugin. The transaction to consume is identified by the purchase receipt (purchaseToken) received at payment. After consumption, the user can purchase the same product again.

Call parameters

Field name Type Required Description
purchaseToken string Required The receipt (purchaseToken) of the purchase to consume. Pass the GooglePurchase.PurchaseToken value obtained from the PurchasesUpdated event or QueryPurchasesAsync().
ct CancellationToken Optional The cancellation token.

Call example

Check the success result of GooglePlayBillingServiceConsumeResult 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;
}

// Finish the consumable product transaction (purchaseToken is the value received at payment)
GooglePlayBillingServiceConsumeResult consumeResult =
    await googlePlugin.ConsumeAsync(purchaseToken, CancellationToken.None);

switch (consumeResult)
{
    case GooglePlayBillingServiceConsumeResult.Success:
        // Consumption succeeded. The user can purchase the same product again.
        Debug.Log("Consumable product transaction finished successfully");
        break;

    case GooglePlayBillingServiceConsumeResult.PurchaseNotFound:
        // No purchase corresponding to the purchaseToken was found
        Debug.LogError("Purchase not found. Check the purchaseToken.");
        break;

    case GooglePlayBillingServiceConsumeResult.UnknownOutcome:
        Debug.LogWarning("Unknown consume result.");
        break;

    case GooglePlayBillingServiceConsumeResult.Failure failure:
        Debug.LogError($"Consume failed: {failure}");
        break;

    default:
        Debug.LogWarning($"Unhandled result: {consumeResult.GetType().Name}");
        break;
}

Response status

The returned object GooglePlayBillingServiceConsumeResult branches into one of the following cases. Handle them with a switch statement.

Response case Description App client handling
Success Consumption succeeded. Purchase acknowledgment is also processed. End the payment flow
PurchaseNotFound No purchase corresponding to the purchaseToken was found Check the purchaseToken value
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

4.2. Acknowledge non-consumable and subscription product purchases

Method

AcknowledgePurchaseAsync

To implement purchase acknowledgment for non-consumable or subscription products, call AcknowledgePurchaseAsync() provided by the Google payment plugin. It only acknowledges the purchase without consuming it.

Call parameters

Field name Type Required Description
purchaseToken string Required The receipt (purchaseToken) of the purchase to acknowledge.
ct CancellationToken Optional The cancellation token.

Call example

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;
}

// Acknowledge the non-consumable or subscription product purchase (purchaseToken is the value received at payment)
GooglePlayBillingServiceAcknowledgePurchaseResult ackResult =
    await googlePlugin.AcknowledgePurchaseAsync(purchaseToken, CancellationToken.None);

switch (ackResult)
{
    case GooglePlayBillingServiceAcknowledgePurchaseResult.Success:
        Debug.Log("Purchase acknowledgment succeeded");
        break;

    case GooglePlayBillingServiceAcknowledgePurchaseResult.PurchaseNotFound:
        Debug.LogError("Purchase not found. Check the purchaseToken.");
        break;

    case GooglePlayBillingServiceAcknowledgePurchaseResult.AlreadyAcknowledged:
        // The purchase has already been acknowledged. No further processing needed
        Debug.Log("The purchase has already been acknowledged.");
        break;

    case GooglePlayBillingServiceAcknowledgePurchaseResult.UnknownOutcome:
        Debug.LogWarning("Unknown acknowledgment result.");
        break;

    case GooglePlayBillingServiceAcknowledgePurchaseResult.Failure failure:
        Debug.LogError($"Acknowledgment failed: {failure}");
        break;

    default:
        Debug.LogWarning($"Unhandled result: {ackResult.GetType().Name}");
        break;
}

Response status

The returned object GooglePlayBillingServiceAcknowledgePurchaseResult branches into one of the following cases. Handle them with a switch statement.

Response case Description App client handling
Success Purchase acknowledged End the payment flow
PurchaseNotFound No purchase corresponding to the purchaseToken was found Check the purchaseToken value
AlreadyAcknowledged The purchase has already been acknowledged No further processing needed
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

Learn more

If a product could not be delivered during the payment process because of a network error or a similar problem, implement Restore purchases.