Skip to content

Restore undelivered Steam purchases

Retrieve Steam purchases whose payment is complete but whose product delivery is not finished, and complete the remaining product delivery and transaction finishing.

First complete the setup and installation in Implement Steam payments.

When to run

A purchase remains unfinished in the following situations.

  • The app terminated or the network disconnected right after receipt verification
  • Verification succeeded, but product delivery or purchase finalization failed

For Steam purchases, the Hive Axyl server keeps the list of undelivered purchases. Run the restoration when the app launches and when the in-app store screen opens. Even if the two times overlap, do not let the restoration run twice at the same time, and do not process an order while an in-progress purchase is processing the same order.

Overall flow

Step Category What you do
1 Recipe code Get undelivered purchases
2 Recipe code Prepare purchase information
3 App code, App server, Hive Axyl Server API Verify receipts and deliver products
4 Recipe code Finish the transaction
5 Hive Axyl SDK Record delivery results

1. Get undelivered purchases

  • Recipe code  Call the recipe code from the app.

Get the list of orders that are not finished yet, and then save all the retrieved items before you process them. If you save them all, you do not lose the remaining items even if processing one item fails.

1.1. Get the list of undelivered purchases

Call FindUndeliveredAsync() to get the list of orders that are not finished yet. If you specify serverId, only the orders for that app server are retrieved; if you omit it, all orders are retrieved.

using Hive.Axyl.Samples.Recipes;

var recipe = new UndeliveredPurchaseRecipe(new SteamPurchaseSource());

var query = new UndeliveredQuery(
    country:     "KR",
    language:    "ko",
    serverId:    "server01",
    appVersion:  Application.version,
    accountUuid: accountUuid);

FindUndeliveredOutcome found = await recipe.FindUndeliveredAsync(query, token);

if (found.Status != FindUndeliveredStatus.Success)
{
    Debug.LogError($"{found.FailedStep}: {found.BusinessOutcome} {found.Error?.Message}");
    return;
}

The recipe performs the following tasks internally.

Category Call Where to check
Hive Axyl SDK Get unfinished transactions with RestorePurchasesAsync() Restore unfinished transactions

1.2. Save the retrieved items

Before you process them, save all the retrieved items to pendingStore, the purchase information store described in Implement Steam payments, and then process them one at a time, in order, starting from step 2. An empty list means that there are no purchases to finish; it is not an error.

The returned items already contain the amount and currency from the time of payment, because the Hive Axyl server keeps the order information. Do not overwrite them with the current price shown on the screen; save them as they are.

foreach (PendingPurchase entry in found.Undelivered)
{
    // Save all the retrieved items before processing them.
    await pendingStore.SaveAsync(entry);
}

foreach (PendingPurchase entry in found.Undelivered)
{
    // Process the items one at a time, in order, starting from step 2.
}

2. Prepare purchase information

  • Recipe code  Call the recipe code from the app.

Call PreparePurchaseAsync() with the retrieved item to prepare the purchase information to send to the app server. When you restore a Steam purchase, this step does not call the Hive Axyl server; it only checks whether the retrieved item has a receipt.

PreparePurchaseOutcome prepared = await recipe.PreparePurchaseAsync(entry, token);

if (prepared.Pending != null)
{
    // Replace the saved purchase information with the value the prepare step returned.
    await pendingStore.SaveAsync(prepared.Pending);
}

if (prepared.Status != PreparePurchaseStatus.Success)
{
    // Keep the saved purchase information and move on to the next item.
    continue;
}

3. Verify receipts and deliver products

  • 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

For restored purchases, as for new purchases, the app server verifies the receipt and delivers the product. Send prepared.Pending from step 2 to the app server to request verification and delivery as a purchase restoration, and proceed to step 4 only when the app server responds with delivery completed.

3.1. Request verification and delivery from the app server

VerifyAndGrantOnceAsync() in the example code is the app's method implemented in Implement Steam payments. For restoration, pass 2, which indicates a purchase restoration, as the second argument requestType. The usage example passes PurchaseVerificationKind.Recovery as this argument, and the app server integration code converts it to 2.

// Request receipt verification and product delivery from the app server as a purchase restoration.
PurchaseDeliveryConfirmation delivery = await VerifyAndGrantOnceAsync(
    prepared.Pending,
    requestType: 2,   // 2: Purchase restoration. PurchaseVerificationKind.Recovery in the usage example
    token);

if (delivery == null)
{
    // The app server did not confirm delivery.
    // Keep the saved purchase information and move on to the next item.
    continue;
}

3.2. Verify receipts and deliver products on the app server

Have the app server verify the receipt and deliver the product in the same way as in Implement Steam payments. The differences from a new purchase are as follows.

  1. Put 2, which indicates a purchase restoration, in requestType of the receipt verification request.
  2. Check the same delivery records as for new purchases to see whether the purchase has already been delivered. requestType is not the basis for preventing duplicate delivery.
  3. If the purchase has already been delivered, do not deliver it again; respond with delivery completed. The app finishes the transaction in step 4 only if you respond with delivery completed.

Order.IapPayload of the retrieved item contains the iapPayload passed when the purchase started. If you put this value in the verification request, it comes back as is in hiveAxylPayload of the response, so use this value to decide which delivery target or server to deliver to.

4. Finish the transaction

  • Recipe code  Call the recipe code from the app.

When the app server responds with delivery completed, call ClosePurchaseAsync() to finish the purchase. If you do not finish it, it is retrieved again in the next restoration.

ClosePurchaseOutcome closed = await recipe.ClosePurchaseAsync(
    prepared.Pending, delivery, token);

if (closed.Status == ClosePurchaseStatus.Success)
{
    // The purchase is complete, so delete the saved purchase information.
    await pendingStore.DeleteAsync(prepared.Pending);
}

The recipe performs the following tasks internally.

Category Call Where to check
Hive Axyl SDK Request purchase finalization from the server with FinalizePurchaseAsync() Finalize the purchase

5. Record delivery results

Record the delivery result on the Hive Axyl server. This is the same as when you process a new purchase.

In AxylTransactionId, put the hiveAxylTransactionId that VerifyAndGrantOnceAsync() in 3.1 kept separately from the app server response. This value is not contained in PurchaseDeliveryConfirmation.

using Hive.Axyl.Payments;

var payments = HiveCore.Resolve<IPaymentsService>();

await payments.ItemResultAsync(new ItemResultBody
{
    AxylTransactionId = axylTransactionId,   // hiveAxylTransactionId received in the app server response in 3.1 and kept separately
    Status = 1,   // 1: Delivery succeeded
});

Failure handling

Restoration iterates over multiple items, so even if one item fails, keep processing the rest. What the app does in each situation is as follows.

  • Empty query result: A normal state with no purchases to finish
  • Query failure: A state you must distinguish from an empty list. Record the error and query again at the next run
  • Preparation failure: Keep the saved purchase information and proceed to the next item
  • Delivery not confirmed by the app server: Keep the saved purchase information and proceed to the next item
  • Finishing failure: Verification and delivery are already done. It is retrieved again in the next restoration, so you can leave it as is

For how to handle each BusinessOutcome value, see Rejection reasons in payment recipes.

Next steps

For the procedure to implement new purchases, see Implement Steam payments.