Skip to content

Step 3. Purchase a product

Open the Apple App Store payment window for the product the user selected and proceed with the purchase approval process. As the result of the call, you receive the payment completion signal and the purchase receipt data.

Follow the process below to purchase an Apple 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.

Method

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 The 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 (two-letter ISO 3166-1 code)
Currency string Required Pass the payment currency (three-letter ISO 4217 code). Use the currency value of the product retrieved in Step 2. Get the product list
Language string Required Pass the language code (two-letter ISO 639-1 code)
Price decimal Required The 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 The 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 The market and payment method identifier (enum). Because this request is for Apple App Store payments only, specify only Apple.
ServerId string Optional The server ID you check on the App Server tab after you register the app server in App Info > App Server by following Hive Console App Server
AccountUuid string Optional A UUIDv5 value generated from the logged-in user's playerId. Pass it before the store payment window opens. See Generate an AccountUuid.
IapPayload string Optional The 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 The request time (UTC). If omitted, the server's current time is used.

IapPayload usage example

With IapPayload in PrePurchase, you can include app-specific data in the receipt that is issued after the purchase is completed. The following is one example of using IapPayload.

Note

For example, suppose that in a mobile app with three user profiles, A, B, and C, a user purchases 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 is completed but the product cannot be delivered because of a network error, you must retrieve 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 that 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.Apple,
    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 format
    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

// In the Success branch, proceed to open the store payment window without any additional data.

Response status

The following table 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-purchase information saved 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 Apple App Store payment

When the user selects the payment button, open the Apple App Store payment window through the Apple payment plugin. When the user approves the payment, you get the StoreKit 2 transaction JWS (JwsRepresentation) and the market order number. Because the Apple App Store does not support multi-item purchases due to platform policy, users can purchase only one product at a time.

Call PurchaseAsync() of the Apple payment plugin (IAppleStoreKitPlugin) to display the Apple App Store payment window and get the user's payment approval.

Method

PurchaseAsync

Call parameters

Field name Type Required Description
request PurchaseRequest Required The purchase request data object.
ct CancellationToken Optional The cancellation token.

PurchaseRequest

Field name Type Required Description
ProductId string Required The unique identifier (Product ID) of the product to purchase. Specify the product ID registered in the Apple App Store.
Options PurchaseOptions? Optional Purchase options.

PurchaseOptions

Field name Type Required Description
AppAccountToken string? Optional Pass the same UUID string as the value created in Generate an AccountUuid. Apple associates this value with the transaction.
Quantity int? Optional The purchase quantity. Because the Apple App Store does not support multi-item purchases, do not specify it. The default value is 1.

Pass a string in a valid UUID format to AppAccountToken. If you pass a value that is not in UUID format, the payment may proceed without the token.

For subscription products, also pass the same value to 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.

Call example

Check the success result of AppleStoreKitServicePurchaseResult 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.Core;
using Hive.Axyl.Payments.Addon.Apple;
using System.Threading;

if (!Guid.TryParse(accountUuid, out _))
{
    Debug.LogError("Check that AccountUuid is in UUID format.");
    return;
}

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

// Create the purchase request object
var request = new PurchaseRequest
{
    ProductId = "com.game.item.gold_100",
    Options = new PurchaseOptions
    {
        AppAccountToken = accountUuid
    }
};

AppleStoreKitServicePurchaseResult result = await applePlugin.PurchaseAsync(request, CancellationToken.None);

switch (result)
{
    case AppleStoreKitServicePurchaseResult.Success success:
        // Payment succeeded. Proceed to the next step with the transaction information
        AppleTransaction tx = success.Data.Transaction;
        string receipt = tx.JwsRepresentation; // JWS receipt for server verification
        ulong transactionId = tx.Id;           // Market transaction ID
        Debug.Log($"Purchase succeeded — product: {tx.ProductId}, transaction ID: {tx.Id}");
        break;

    case AppleStoreKitServicePurchaseResult.UserCanceled:
        // When the user canceled the payment
        Debug.Log("The user canceled the payment.");
        break;

    case AppleStoreKitServicePurchaseResult.Pending:
        // Awaiting parental approval, such as Ask-to-Buy. Receive the result later through the TransactionUpdated event
        Debug.Log("The payment is awaiting approval (Ask-to-Buy).");
        break;

    case AppleStoreKitServicePurchaseResult.UnknownOutcome:
        // Unknown result
        Debug.LogWarning("Unknown payment result.");
        break;

    case AppleStoreKitServicePurchaseResult.Failure failure:
        // Network or store error
        Debug.LogError($"Payment failed: {failure}");
        break;

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

Response data

On success, the result is contained in Data (PurchaseResponse) of AppleStoreKitServicePurchaseResult.Success.

Field name Type Description
Data.Transaction AppleTransaction The Apple App Store transaction information.

AppleTransaction

Field name Type Description
Id uint64 The unique ID of the transaction. Use this value when you finish the transaction (FinishTransactionAsync).
OriginalId uint64 The transaction ID of the original purchase. Used to track the original transaction when a subscription renews.
ProductId string The unique identifier of the purchased product.
ProductType AppleProductType The product type. The members are Consumable, NonConsumable, AutoRenewable, and NonRenewable.
PurchaseDate DateTimeOffset The purchase time.
JwsRepresentation string The receipt string in JWS (JSON Web Signature) format. Pass this value as AxylReceipt for server verification.
VerificationStatus AppleVerificationStatus The result of StoreKit verifying the receipt signature. Verified or Unverified.
Quantity int The purchase quantity.

Response status

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

Response case Description App client handling
Success Payment succeeded. Data.Transaction contains the transaction information. Save the receipt (JwsRepresentation) and the transaction ID (Id), and proceed to the next step
UserCanceled The user canceled the payment Display a payment cancellation notice UI
Pending Awaiting parental approval (Ask-to-Buy) Display an approval pending notice UI. Then receive the result through the TransactionUpdated 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
Ask-to-Buy (awaiting parental approval)

The Pending result is the state of waiting for parental approval when a minor in Family Sharing tries to make a purchase. When the parent approves or declines, you receive the result through the TransactionUpdated event. The app must inform the user of the Pending state and proceed with follow-up processing through the event.

Call the Apple payment plugin, and after the payment is approved, receive and save the following values.

  • StoreKit 2 transaction JWS (JwsRepresentation)
  • Market order number

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.

Method

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 The request data object for saving the payment result.
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 A UUIDv5 value generated from the logged-in user's playerId. Pass it when you record the purchase result after the store payment ends. If you passed it at the payment start step, use the same value. See Generate an AccountUuid.
AxylReceipt string Required The StoreKit 2 transaction JWS (JwsRepresentation) received from the Apple App Store.
Country string Required The country code (two-letter ISO 3166-1 code). Example: KR
Currency string Optional The payment currency (three-letter ISO 4217 code). Paired with Price, it is compared with the values that the server recorded. If you send only one of the two, the currency is not compared and only the amount is compared.
IapPayload string Optional The payload (JSON string) that the developer attached to the market payment. It is passed as is to app server verification and to the purchase completion callback.
Language string Required The language code (two-letter ISO 639-1 code). Example: ko
OrderId string Optional The Hive Axyl internal order number. Used for Steam and PG.
Price decimal Optional The payment amount. It may include decimals depending on the currency. If you send it, the server compares it with the amount it recorded. If you omit it, no comparison is made. For Apple payments, the request is rejected if the amounts differ. The amount recorded in the payment history is the value that the server confirmed with the market.
ProductId string Required The in-app product ID registered in the market.
ProjectInfo string Optional A free-form JSON string for each app. The value is only stored and is not passed to the app server. Set the values to pass to the app server in IapPayload.
ProviderId PurchaseRequestProviderId Required Because this request is only for saving Apple payment results, specify Apple.
Quantity int Optional The purchase quantity
RequestDate DateTimeOffset Optional The client request time (UTC). If omitted, the server's current time is used.
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.
StoreTransactionId string Optional The store transaction ID on the market side.

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 is completed, create the request object with the receipt issued by the market
var request = new PurchaseRequest
{
    AxylReceipt = receipt, // StoreKit 2 transaction JWS received from the Apple App Store
    ProviderId = PurchaseRequestProviderId.Apple,
    ProductId = "com.game.item.gold_100",
    Price = 1200.0m,
    Currency = "KRW",
    Country = "KR",
    Language = "ko",
    Quantity = 1,
    StoreTransactionId = "2000000123456789",
    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

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

Response status

The following table 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 Pass 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 the 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.

Method

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 The 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 The app version.
AxylReceipt string Required The StoreKit 2 transaction JWS (JwsRepresentation) of the subscription payment.
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
OriginalPrice decimal Optional The pre-discount price.
Price decimal Required The subscription price. Pass the price of the product retrieved in Step 2. Get the product list.
ProviderId SubscriptionPurchaseRequestProviderId Required Because this is an Apple subscription, specify Apple.
RequestTimeMs long Optional The request time (milliseconds).
ServerId string Optional The ID of the app server registered by following Hive Console App Server.
StoreTransactionId string Optional If you pass the transaction JWS, you can omit this because the lookup key originalTransactionId is in the receipt. Even if you send it together, the value in the receipt takes precedence.

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 ends, create the request object with the receipt issued by the store
var request = new SubscriptionPurchaseRequest
{
    AxylReceipt = transactionJws, // AppleTransaction.JwsRepresentation of the subscription payment
    ProviderId = SubscriptionPurchaseRequestProviderId.Apple,
    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 The transaction ID of the saved subscription record.
Data.HiveAxylProductId string? Optional The ID of the saved subscription product.
Data.HiveAxylStoreTransactionId string? Optional The 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 following table 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 StoreKit 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.