Skip to content

Implement Google Play subscription payments

To sell auto-renewing subscriptions on Google Play 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 Google Play 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 credentials and register subscription products in the Google Play Console
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 Google 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

Google Play automatically refunds payments that are not confirmed within a set period after purchase. Call step 9 as soon as benefit delivery is complete.

1. Configure the Google Play Console

Get a license key and a service account key JSON file from the Google Play Console, and register the 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 license key and the service account key Required Google Play 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

Install the required SDK modules and the Google 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 Google 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.Google;

var config = CoreConfig.CreateBuilder("{appId}").Build();

HiveBootstrap.Initialize(config, builder =>
{
    builder.AddAuth();
    builder.AddToken();
    builder.AddPayments(sandbox: true);
    builder.AddPlayBilling();
});

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.Google/: Code for Google Play

To call recipes from your app code, add Hive.Axyl.Samples.Recipes, Hive.Axyl.Samples.Recipes.Payments, and Hive.Axyl.Samples.Recipes.Payments.Google to references in your app's assembly definition.

5. Create a PurchaseOrder

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. Specify ProductType.Subs for QueryProductDetailsAsync() and subscription for ProductType in Get product details from the Hive Axyl server. The recipe opens the payment window with the first offer that has an offer token among the subscription offers that Google Play returned. Therefore, find the offer in Offers of the retrieval result whose OfferToken is the same as that offer's, and use its Price and Currency.

using System.Linq;
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);

// The first offer that has an offer token among the subscription offers returned by QueryProductDetailsAsync()
string offerToken = playDetails.SubscriptionOfferDetails
    .First(o => !string.IsNullOrEmpty(o.OfferToken)).OfferToken;

// Find the same offer in the FetchGoogleProductsAsync() result.
ProductOffer offer = product.Offers?.FirstOrDefault(o => o.OfferToken == offerToken);

if (offer?.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:         offer.Price.Value,     // Price of the offer shown in the payment window
    currency:      offer.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 Play 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 Google Play payments before checking the result status, and delete it only after the subscription is confirmed successfully.

var recipe = new SubscriptionRecipe(new GooglePurchaseSource());

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

The recipe performs the following tasks internally.

Category Call Where to check
Hive Axyl SDK Save pre-subscription information with PrepareSubscriptionAsync() PrepareSubscriptionAsync()
Store plugin Connect BillingClient with StartConnectionAsync() BillingClient connection
Store plugin Get the subscription product and offer token with QueryProductDetailsAsync() Get Google Play product details
Store plugin Subscribe to the PurchasesUpdated event Subscribe to the purchase result event
Store plugin Open the payment window and receive the result with LaunchBillingFlowAsync() Open the payment window

Because the purchase result can arrive the moment the payment window opens, the recipe subscribes to the event first and then opens the payment window.

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.

The recipe retrieves the product before opening the payment window for two reasons. The Play Billing Library displays in the payment window only the products retrieved in the current connection, and the offer token needed for a subscription purchase is contained only in the retrieval result.

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: GOOGLE
  • axylReceipt: The Google Play purchase receipt in AxylReceipt. The same value is also used to save the subscription purchase information in step 7 and to confirm the subscription in step 9
  • country, language, currency, serverId, iapPayload, accountUuid, appVersion: The values of the same names in Order
  • requestType: 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.

  1. Check the expiration time hiveAxylExpiresDate and the subscription product hiveAxylProductId in the verification response. Even if there is a previous delivery record, check every time whether the subscription is currently valid.
  2. Check the app server's delivery records to see whether benefits for this cycle have already been delivered. If hiveAxylDuplicated is true, you must check this. If the cycle has already been delivered, skip item 3.
  3. Deliver benefits valid until the expiration time and record delivery completion.
  4. 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 hiveAxylProductId from 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.

  • hiveAxylRefundDate: The refund time. Google Play does not provide the refund time, so this is always null; determine whether a refund occurred from the auto-renewal notifications in step 10
  • hiveAxylAccountUuidCompare: The result of comparing the paying account with the requesting account. 1 is a match, 2 is a mismatch, and 9 means the comparison is not possible
  • hiveAxylPayload: The iapPayload passed 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.
}

When the Hive Axyl server receives the confirmation request, it also sends a purchase acknowledgment to Google Play. In case one of the two paths fails, the recipe sends the acknowledgment once more from the device. Both calls are safe to send multiple times, and a response from Play that the purchase is already acknowledged is also treated as a success. 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 Acknowledge the purchase to Play with AcknowledgePurchaseAsync() Acknowledge non-consumable and subscription product purchases

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. Google Play 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 Google Play 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 Google Play payments.