Implement Apple App Store subscription payments
To sell auto-renewable subscriptions on the Apple App Store and deliver subscription benefits to users, complete the following procedure in order.
Before you begin, complete the common prerequisites.
Overall flow
Unlike consumable products, subscriptions renew every cycle even after the first payment. Renewal payments are notified from the App Store to the Hive Axyl server without going through the app, so the app server must be ready to receive those notifications. In step 8, the app server handles receipt verification of the first payment and delivery of subscription benefits with the Hive Axyl Server API.
| Step | Category | What you do |
|---|---|---|
| 1 | Store console | Issue authentication keys and register subscription products in App Store Connect |
| 2 | Hive Console | Register credentials, subscription products, and the subscription server notification URL |
| 3 | Hive Axyl SDK, Store plugin | Install and register the SDK modules and the Apple payment plugin |
| 4 | Recipe code | Copy the recipe folders |
| 5 | Recipe code | Create a PurchaseOrder |
| 6 | Recipe code | Start the subscription |
| 7 | Recipe code | Save subscription purchase information |
| 8 | App code, App server, Hive Axyl Server API | Verify receipts and deliver subscription benefits |
| 9 | Recipe code | Confirm the subscription |
| 10 | App server | Handle auto-renewal notifications |
| 11 | Hive Console, Hive Axyl Server API | Check the purchase history |
Do not delay step 9
Until the subscription is confirmed, StoreKit treats the transaction as unfinished and delivers it again every time the app launches. Call step 9 as soon as benefit delivery is complete.
1. Configure App Store Connect
-
Store console Configure this in App Store Connect.
Detailed procedure: Apple console payment integration
Get the Private Key, Private Key ID, and Issuer ID from App Store Connect, and register the auto-renewable subscription products to sell.
2. Configure the Hive Console
- Hive Console Configure or check this in the Hive Console.
Register the values you got in step 1 in the Hive Console, and register the subscription products and the URL that receives renewal notifications.
| Setting | Required | Where to check |
|---|---|---|
| Register the authentication key | Required | App Store credentials |
| Register products with the product type set to subscription | Required | Register product information |
| Register the subscription server notification URL | Required | Register notification URLs for each server |
The subscription server notification URL is the channel through which the app server receives renewal, cancellation, and refund notifications. If you do not register it, the app server cannot know about status changes after the first payment.
3. Prepare SDK modules and plugins
-
Hive Axyl SDK Call the Hive Axyl SDK from the app.
Store plugin Call the store payment plugin from the app.
Detailed procedure: Install the module, initialize, and log in, Register the plugin
Install the required SDK modules and the Apple payment plugin, and then register them when you initialize the SDK. The setup is the same as for consumable product payment. Payment works on the basis of the logged-in user, so along with the payment module, you must also register the authentication module AddAuth() and the token module AddToken(). If you do not register the payment module, the recipe fails with a FailedPrecondition error.
The following example code registers the authentication, token, and payment modules and the Apple payment plugin when initializing the SDK. The example connects the payment module to the sandbox server for development and testing, so in production builds, register it with AddPayments() without sandbox: true.
using Hive.Axyl.Core;
using Hive.Axyl.Core.Unity;
using Hive.Axyl.Auth;
using Hive.Axyl.Payments;
using Hive.Axyl.Payments.Addon.Apple;
var config = CoreConfig.CreateBuilder("{appId}").Build();
HiveBootstrap.Initialize(config, builder =>
{
builder.AddAuth();
builder.AddToken();
builder.AddPayments(sandbox: true);
builder.AddStoreKit();
});
4. Install the recipe code
- Recipe code Copy the recipe code into your project.
A recipe is source code that you copy into your project instead of installing as a package. It uses the same folders as consumable product payment.
- Items to copy
- Recipes.asmdef: Common assembly definition
- AssemblyInfo.cs: Setting that exposes internal helpers to other recipe assemblies
- Helper/: Common code shared by multiple recipes
- Payments/: Recipe body and common types
- Payments.Apple/: Code for the Apple App Store
To call recipes from your app code, add Hive.Axyl.Samples.Recipes, Hive.Axyl.Samples.Recipes.Payments, and Hive.Axyl.Samples.Recipes.Payments.Apple to references in your app's assembly definition.
5. Create a PurchaseOrder
-
Recipe code Call the recipe code from the app.
Detailed procedure: Get the product list
Subscriptions also start with the same PurchaseOrder as consumable products. Do not write the product ID, price, and currency directly in code; fill them in from the retrieval result of the subscription product the user selected.
Retrieve subscription products in the same order as in Get the product list, but take the product IDs from ProductSubscriptions in the ListStoreProductIdsAsync() response, and specify subscription for ProductType in Get product details. Use the ProductId, Price, and Currency of the retrieval result as is, and do not sell products whose Price is empty, because their price could not be confirmed.
using Hive.Axyl.Samples.Recipes;
// Namespace UUID that the app sets once and never changes
const string k_Namespace = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
AccountUuid.TryCompute(k_Namespace, playerId, out var accountUuid);
if (product.Price == null)
{
// Do not sell products whose price could not be confirmed.
return;
}
var order = new PurchaseOrder(
productId: product.ProductId, // Subscription product the user selected
price: product.Price.Value, // Price shown on the screen
currency: product.Currency,
country: "KR",
language: "ko",
storePlayerId: 0L,
serverId: "server01",
iapPayload: "{\"character\":\"A\"}",
appVersion: Application.version,
accountUuid: accountUuid);
Do not omit accountUuid for subscriptions
Auto-renewal notifications do not contain user information. The Hive Axyl server uses accountUuid to find the first payment record and decide whom to deliver benefits to. Without this value, the delivery target cannot be restored at renewal. For the generation rules, see Generate an AccountUuid.
6. Start the subscription
- Recipe code Call the recipe code from the app.
When the user selects the subscribe button, call the recipe's StartSubscriptionAsync(). It records the subscription intent on the server, opens the StoreKit payment window, and receives the result.
started.Pending contains the receipt and order information. As in the following example code, save it first to the purchase information store described in Implement Apple App Store payments before checking the result status, and delete it only after the subscription is confirmed successfully.
var recipe = new SubscriptionRecipe(new ApplePurchaseSource());
StartSubscriptionOutcome started = await recipe.StartSubscriptionAsync(order, token);
if (started.Pending != null)
{
// Save it first, regardless of the result status.
await pendingStore.SaveAsync(started.Pending);
}
switch (started.Status)
{
case StartSubscriptionStatus.Success:
// Proceed to step 7.
break;
case StartSubscriptionStatus.UserCanceled:
// Only the payment window was closed.
break;
default:
Debug.LogError($"{started.FailedStep}: {started.BusinessOutcome} {started.Error?.Message}");
break;
}
Do not specify a quantity
Create ApplePurchaseSource without arguments, as in the example above. The Apple App Store does not support multi-item purchases, and the quantity of a subscription is always 1.
The recipe performs the following tasks internally.
| Category | Call | Where to check |
|---|---|---|
| Hive Axyl SDK | Save pre-subscription information with PrepareSubscriptionAsync() | PrepareSubscriptionAsync() |
| Store plugin | Open the payment window and receive the result with PurchaseAsync() | Request Apple App Store payment |
Unlike for consumable products, saving pre-subscription information is not skipped. This record becomes the reference point for finding the delivery target in every later renewal cycle.
7. Save subscription purchase information
- Recipe code Call the recipe code from the app.
Call SaveSubscriptionAsync() to save the subscription purchase information on the Hive Axyl server and prepare the purchase information to send to the app server. The recipe does not request receipt verification in this step, so even if you receive a success result, you must not deliver subscription benefits yet.
SaveSubscriptionOutcome prepared = await recipe.SaveSubscriptionAsync(started.Pending, token);
if (prepared.Pending != null)
{
// Replace the saved purchase information with the value the save step returned.
await pendingStore.SaveAsync(prepared.Pending);
}
if (prepared.Status != SaveSubscriptionStatus.Success)
{
// Do not delete the saved purchase information. When the app is relaunched, proceed again from step 7 with this value.
return;
}
// In step 8, send prepared.Pending to the app server.
Even if the save request is rejected because the subscription is already saved, the recipe treats it as a success. This is because such a rejection can occur when the app stops between saving and confirming and then retries. The recipe performs the following tasks internally.
| Category | Call | Where to check |
|---|---|---|
| Hive Axyl SDK | Save the subscription purchase information with PurchaseSubscriptionAsync() | Save subscription purchase information |
8. Verify receipts and deliver subscription benefits
-
App code Implement this in the app.
App server Implement this on the app server.
Hive Axyl Server API Call the Hive Axyl Server API from the app server.
Detailed procedure: Verify receipts with the Hive Axyl Server API
The app server handles receipt verification and subscription benefit delivery, and the recipe is not involved in this step. When the app sends prepared.Pending from step 7 to the app server, the app server verifies the subscription receipt with the Hive Axyl Server API, delivers the benefits only once, and then responds to the app with the result.
8.1. Request verification and delivery from the app server
Call the app server from the app to request subscription receipt verification and benefit delivery. Send Market, AxylReceipt, VerifyToken, OrderId, and StoreTransactionId of prepared.Pending and the order information in Order to the app server. The app decides how to call the app server and the response format.
VerifyAndGrantSubscriptionOnceAsync() in the example code is the app's method that calls the app server. In the usage example, IPurchaseApplication.VerifyAndGrantSubscriptionOnceAsync() plays the same role. Implement this method so that it creates and returns new SubscriptionDeliveryConfirmation(productId) with the subscription product ID in the response when the app server responds with delivery completed, and returns null when delivery could not be confirmed because of a rejection or failure.
// Request subscription receipt verification and benefit delivery from the app server.
SubscriptionDeliveryConfirmation delivery = await VerifyAndGrantSubscriptionOnceAsync(
prepared.Pending,
token);
if (delivery == null)
{
// The app server did not confirm delivery.
// Keep the saved purchase information and do not confirm the subscription.
return;
}
8.2. Verify subscription receipts with the Hive Axyl Server API
Have the app server call POST /payment/v1/subscription/verify of the Hive Axyl Server API to verify the subscription receipt. Put the App ID registered in the Hive Console in the X-App-Id request header, and for the address to send the request to, see Base URL. Get the access token for the app server that the call requires with Issue a token. For all request and response fields, see Verify subscription product receipts.
Put the PendingPurchase values that the app sent into the request fields as follows.
providerId:APPLEaxylReceipt: The StoreKit transaction JWS inAxylReceipt. The same value is also used to save the subscription purchase information in step 7 and to confirm the subscription in step 9country,language,currency,serverId,iapPayload,accountUuid,appVersion: The values of the same names inOrderrequestType:1, which indicates a new purchase
8.3. Deliver subscription benefits and respond with delivery completed
After checking the verification response, have the app server deliver the subscription benefits only once and respond to the app with the result. The app server must do the following.
- Check the expiration time
hiveAxylExpiresDateand the subscription producthiveAxylProductIdin the verification response. Even if there is a previous delivery record, check every time whether the subscription is currently valid. IfhiveAxylRefundDatehas a value, the subscription has been refunded, so do not deliver the benefits, and reclaim any benefits already delivered. - Check the app server's delivery records to see whether benefits for this cycle have already been delivered. If
hiveAxylDuplicatedistrue, you must check this. If the cycle has already been delivered, skip item 3. - Deliver benefits valid until the expiration time and record delivery completion.
- Respond to the app with delivery completed. Respond with delivery completed even for cycles already delivered so that the app confirms the subscription in step 9. Also return
hiveAxylProductIdfrom the verification response so that it can be used to confirm the subscription in step 9.
The response values you also check when deciding on delivery are as follows.
hiveAxylAccountUuidCompare: The result of comparing the paying account with the requesting account.1is a match,2is a mismatch, and9means the comparison is not possiblehiveAxylPayload: TheiapPayloadpassed in step 5, returned as is
Do not deliver products based only on values the app sent
Have the app server authenticate the requesting user's account directly and check the subscription product and expiration time with the verification response. The SubscriptionDeliveryConfirmation that the app creates is not a value that proves payment. The app server always decides whether to deliver.
9. Confirm the subscription
- Recipe code Call the recipe code from the app.
Call ConfirmSubscriptionAsync() after the app server responds with delivery completed. Without the delivery created in step 8, the recipe does not confirm the subscription and returns Failure. The recipe requests confirmation with the subscription product ID in delivery, and if this value is empty, it uses the product ID of the order from step 5.
ConfirmSubscriptionOutcome confirmed = await recipe.ConfirmSubscriptionAsync(
prepared.Pending, // Value returned by step 7
delivery, // Value created in step 8 from the app server's delivery completed response
token);
if (confirmed.Status == ConfirmSubscriptionStatus.Success)
{
// The subscription is confirmed, so delete the saved purchase information.
await pendingStore.DeleteAsync(prepared.Pending);
}
else
{
// Delivery is already done and only confirmation remains. Keep the saved purchase information and call again without delay.
}
The recipe first requests subscription confirmation from the server and finishes the StoreKit transaction afterward. This is because finishing the transaction first removes the only path back to that subscription. The recipe performs the following tasks internally.
| Category | Call | Where to check |
|---|---|---|
| Hive Axyl SDK | Request subscription confirmation from the server with PostSubscriptionAsync() | Complete the subscription |
| Store plugin | Finish the StoreKit transaction with FinishTransactionAsync() | Finish the transaction |
10. Handle auto-renewal notifications
- App server Implement this on the app server.
Renewals, cancellations, and refunds after the first payment do not go through the app. The App Store notifies the Hive Axyl server, and the Hive Axyl server forwards the notification to the app server at the URL registered in step 2.
Have the app server receive the notifications, update the subscription status, and reclaim the benefits of users whose expiration time has passed. Notifications do not contain user information, so find the delivery target with the accountUuid passed in step 5.
11. Check the purchase history
-
Hive Console Configure or check this in the Hive Console.
Hive Axyl Server API Call the Hive Axyl Server API from the app server.
During operation, get the purchase history to check the subscription status.
| Category | Method | Where to check |
|---|---|---|
| Hive Console | Specify search conditions in the console and check | Specify search conditions |
| Hive Axyl Server API | Call the purchase history API from the app server | Get purchase history |
Failure handling
Recipe methods do not throw exceptions; they return result objects. Always check Status first.
Status | Meaning | What the app does |
|---|---|---|
Success | The step finished normally. | Proceed to the next step. |
BusinessOutcome | The Hive Axyl server or the market rejected the request. | Branch on the BusinessOutcome value. For how to handle each value, see Rejection reasons in payment recipes. |
Failure | A technical failure such as a network error or timeout. | Record Error and decide whether to retry. |
UserCanceled | The user closed the payment window. | This is not an error, so return to the subscription screen. |
UserCanceled exists only in the step that starts the subscription. The result types of the save and confirm steps do not have it, so branching on that value does not compile.
Do not discard PendingPurchase even if it fails
SaveSubscriptionOutcome and ConfirmSubscriptionOutcome return the Pending they received as is, even when they fail. Replace the purchase information saved in step 6 with the returned Pending, and keep the purchase information even when the app server could not confirm delivery in step 8. If the purchase information of an unconfirmed subscription remains when the app is relaunched, proceed again from step 7 with that value. The recipe continues processing even if the subscription is already saved or already confirmed.
Subscriptions and undelivered purchase restoration
Do not handle subscriptions with Restore undelivered Apple App Store purchases. That restoration handles only consumable products. Renewals, cancellations, and refunds after the first payment are handled by the auto-renewal notifications in step 10.
Next steps
If you also sell consumable products, see Implement Apple App Store payments.