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 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 made the payment. You implement product delivery in the app, and the delivery method may differ by app.

Use RequestPurchaseAsync(), which sends the payment confirmation request, only for consumable products. For subscription products, confirm the subscription with 3. Complete the subscription. In Apple payments, the app decides the call order of product delivery and RequestPurchaseAsync(), 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 consumable product delivery results, call ItemResultAsync() provided by the Hive Axyl SDK. It records the result on 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 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 with 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 Free-form JSON data for each app (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 following example 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 = axylTransactionId, // hiveAxylTransactionId from the receipt verification response passed by the app server
    Status = 1, // 1: Delivery succeeded
    Assets = new[]
    {
        new ItemResultAsset { AssetId = "item_gold_100", AssetName = "골드 100개", Quantity = 1 }
    },
    ProjectPayloadInfo = "{\"serverId\":\"server01\"}" // (Optional) Free-form JSON data for each app
};

PaymentsItemResultResult result = await payments.ItemResultAsync(request);

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

    // Problem with request values — check 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, Data (SuccessResponseData) of PaymentsItemResultResult.Success contains the response meta information.

Field name Type Required Description
Data.Meta string? Optional The 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 successfully Proceed with the next payment processing
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 When a parameter is invalid (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 know. Treat it as a failure and record the result code
Failure A common Failure. See Common error handling. Handle it according to the common error handling criteria

3. Complete the subscription

For subscription products, after the app server finishes subscription receipt verification and delivers the subscription benefits, call PostSubscriptionAsync() to confirm the subscription on the Hive Axyl server. When the subscription is confirmed, proceed with 4. Finish the transaction.

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, as is, the StoreKit 2 transaction JWS that you passed in the PurchaseSubscriptionAsync() request in Save subscription purchase information. 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 (ISO 3166-1, two letters). Example: KR
Currency string Required The currency (ISO 4217, three letters). Example: KRW
Language string Required The language code (ISO 639-1, two letters). 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 Specify Apple because this is an Apple subscription.
RequestTimeMs long Optional The request time (milliseconds).
RequestType int Optional The request type. 1: new purchase, 2: purchase restoration
ServerId string Optional The app server ID 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 following example 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 = transactionJws,      // The same JWS 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.Apple
};

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 it is the same value as the AxylReceipt sent when saving the subscription purchase information.
            Debug.LogError("There is no confirmed subscription.");
            break;
        }
        // Subscription confirmed. Proceed with finishing the transaction (FinishTransactionAsync).
        break;

    case PaymentsPostSubscriptionResult.VerifyDuplicated:
        // The subscription is already confirmed. Proceed with finishing the transaction (FinishTransactionAsync).
        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, Data (SubscriptionPurchasePostResponseData) of PaymentsPostSubscriptionResult.Success contains the confirmation result. 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 The 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 with finishing 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 There is no permission for the payment request. Check the credentials and request permission
VerifyDuplicated The subscription is already confirmed. Proceed with finishing 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 know. Treat it as a failure and record the result code
Failure A common Failure. See Common error handling. Handle it according to the common error handling criteria

4. Finish the transaction

Call FinishTransactionAsync() of the Apple payment plugin (IAppleStoreKitPlugin) to notify the Apple App Store that the transaction is complete. For consumable products, call it after you complete product delivery and the payment confirmation request; for subscription products, call it after you complete the subscription. For consumable products, the user can purchase the same product again after the transaction is finished.

Method

FinishTransactionAsync

Call parameters

Field name Type Required Description
request FinishTransactionRequest Required The request data object for finishing the transaction.
ct CancellationToken Optional The cancellation token.

FinishTransactionRequest

Field name Type Required Description
TransactionId uint64 Required The Apple transaction ID to finish. Use the AppleTransaction.Id value from the PurchaseAsync() success response.

Call example

Check the success result of AppleStoreKitServiceFinishTransactionResult and the domain-specific results (Outcome) defined by this method in the following example 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.Apple;
using System.Threading;

// Retrieve the Apple StoreKit plugin
if (!HiveCore.TryResolve<IAppleStoreKitPlugin>(out var applePlugin))
{
    Debug.LogError("The Apple StoreKit plugin is not registered.");
    return;
}

// Finish the transaction with the transaction ID received in the PurchaseAsync() success response
var request = new FinishTransactionRequest
{
    TransactionId = transactionId // AppleTransaction.Id value
};

AppleStoreKitServiceFinishTransactionResult result =
    await applePlugin.FinishTransactionAsync(request, CancellationToken.None);

switch (result)
{
    case AppleStoreKitServiceFinishTransactionResult.Success:
        // Transaction finished successfully. The user can purchase the same product again.
        Debug.Log("Transaction finished successfully");
        break;

    case AppleStoreKitServiceFinishTransactionResult.TransactionNotFound:
        // When the transaction ID cannot be found
        Debug.LogError("Transaction not found. Check the transaction ID.");
        break;

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

    case AppleStoreKitServiceFinishTransactionResult.Failure failure:
        Debug.LogError($"Failed to finish the transaction: {failure}");
        break;

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

Response data

No data is returned on success.

Response status

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

Response case Description App client handling
Success Transaction finished successfully End the payment flow
TransactionNotFound When the transaction ID cannot be found Check the TransactionId value
UnknownOutcome Unknown result Retry or show an error message
Failure A common Failure. See Common error handling. Handle it according to the common error handling criteria

Learn more

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