Skip to main content

Purchasing offers

E-commerce functionality is provided via the IOnlineStoreV2, IOnlineEntitlements and IOnlinePurchase interfaces. Purchasing items is done via the IOnlinePurchase interface.

Platform support

When purchasing offers on Steam, there is no notification for a successful purchase. You can initiate a purchase, and observe the change in entitlements by polling IOnlineEntitlements, but the purchase callback will be raised when the purchase is initiated, not completed.

DLC offers on Steam e-commerce

When using Steam e-commerce, DLC offers will be suffixed with dlc: to the offer IDs. You can only initiate the purchase for one DLC offer at a time, which will open the Steam overlay so the user can purchase the DLC.

Steam requires installing DLC outside of the game after purchasing, so there's no actions to take after DLC is purchased; the user must restart the game, install the DLC and launch the game again.

Refer to Initating a purchase for an example on how to purchase one or more offers.

Item offers on Steam e-commerce

When using Steam e-commerce, item offers will be suffixed with itemdef: to the offer IDs. You can purchase as many items as you want at the same time, and can specify quantities for each item. After a successful item purchase, the items will appear as entitlements after you query for them again with QueryEntitlements.

Refer to Initating a purchase for an example on how to purchase one or more offers.

Initating a purchase on any platform

When initiating a purchase, you should use offer IDs that you obtained when Retrieving offers. The example below shows offer IDs that you would see on Steam, but the format of offer IDs will differ per-platform.

To initiate a purchase, first get the online purchase interface:

#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "Interfaces/OnlinePurchaseInterface.h"

// ...

IOnlineSubsystem* Subsystem = Online::GetSubsystem(this->GetWorld());
IOnlinePurchasePtr Purchase = Subsystem->GetPurchaseInterface();

Then, use Checkout to initiate a purchase:

// The offer IDs should be obtained from QueryOffersByFilter/GetOffers.
FPurchaseCheckoutRequest Request = {};
Request.AddPurchaseOffer(TEXT(""), TEXT("itemdef:100"), 1);
Request.AddPurchaseOffer(TEXT(""), TEXT("itemdef:200"), 1);

Purchase->Checkout(
*Identity->GetUniquePlayerId(0).Get(), // The local player to get offers for.
Request,
FOnPurchaseCheckoutComplete::CreateLambda([](
const FOnlineError& Result,
const TSharedRef<FPurchaseReceipt>& Receipt)
{
if (Result.WasSuccessful())
{
// Refer to the "Platform Support" section to understand what the
// Checkout callback represents. On some platforms, it represents
// the checkout process completing, and on some platforms it only
// indicates that the process was started.
}
})
)

Querying and consuming entitlements on Epic Games Store

To consume a purchase that was made on Epic Games Store, you need to:

  • Query for the entitlements the user currently has using the entitlements interface.
  • Check the entitlement's ItemType attribute to see if it is set to Consumable.
  • Then call IOnlinePurchase::FinalizeReceiptValidationInfo with the entitlement ID passed in as InReceiptValidationInfo.

Consuming an entitlement on Epic Games Store

Once you have the entitlement ID from the entitlements interface, call FinalizeReceiptValidationInfo and pass the entitlement ID in the InReceiptValidationInfo parameter:

Purchase->FinalizeReceiptValidationInfo(
*Identity->GetUniquePlayerId(0).Get(), // The local player to get offers for.
EntitlementId, // The entitlement ID.
FOnFinalizeReceiptValidationInfoComplete::CreateLambda([
PurchaseWk = TWeakPtr<IOnlinePurchase, ESPMode::ThreadSafe>(Purchase),
UserId = this->Identity->GetUniquePlayerId(0)](
const FOnlineError& Result,
const FString& ValidationInfo))
{
if (auto Purchase = PurchaseWk.Pin())
{
if (Result.WasSuccessful())
{
// The entitlement was successfully consumed.
}
}
})
)

Querying entitlements on Steam

To query what has been purchased on Steam, use the entitlements interface to query entitlements.

Querying receipts and finalizing purchases on Google Play

To finalize purchases on Google Play, you need to check for outstanding receipts and call FinalizeReceipt.

Querying receipts on startup

When the game starts up, you should query for any outstanding receipts so that you can finalize the purchases and award items.

After a purchase is consumed with FinalizePurchase, the receipt will no longer appear when querying receipts or calling GetReceipts.

To query receipts, use QueryReceipts to start the query and GetReceipts to fetch the receipt data.

Purchase->QueryReceipts(
*Identity->GetUniquePlayerId(0).Get(), // The local player to get offers for.
true, // This value is ignored.
FOnQueryReceiptsComplete::CreateLambda([
PurchaseWk = TWeakPtr<IOnlinePurchase, ESPMode::ThreadSafe>(Purchase),
UserId = this->Identity->GetUniquePlayerId(0)](
const FOnlineError& Result)
{
if (auto Purchase = PurchaseWk.Pin())
{
if (Result.WasSuccessful())
{
// Receipts were successfully queried. Access via GetReceipts.
TArray<FPurchaseReceipt> Receipts;
Purchase->GetReceipts(*UserId, Receipts);
}
}
})
)

Watching for changes to receipts

When a purchase completes on Google Play, the receipt can end up with a TransactionState of either Purchased or Deferred. You must not award items to players until the receipt is in a Purchased state.

If a purchase ends up in the Deferred state, you may later see the receipt move to the Purchased state. When this happens, the OnUnexpectedPurchaseReceipt event will fire, and you can re-check the receipts via GetReceipts to see if any have moved from Deferred to Purchased.

You must handle deferred purchases on Google Play. Also, you can not know when a deferred purchase fails; this is a limitation of how Google Play Billing works. Instead, you simply don't award items until the receipt moves into a Purchased state, so a failed deferred receipt never gets handled by the game.

The OnUnexpectedPurchaseReceipt event fires:

  • When a deferred purchase moves to the Purchased state.
  • When a receipt is consumed via FinalizePurchase and removed from the GetReceipts array.
note

The TransactionId field of a receipt is the ReceiptId to use in other functions.

Consuming a purchase via FinalizePurchase

To acknowledge a purchase on Google Play and consume the purchase, you must call FinalizePurchase. If you don't call this function for a receipt, the user's purchase will automatically be refunded 3 days after it moved into the Purchased state.

To consume a purchase, call FinalizePurchase:

Purchase->FinalizePurchase(
*Identity->GetUniquePlayerId(0).Get(), // The local player to get offers for.
ReceiptId // The TransactionId field of the receipt.
);
caution

There is no callback to pass into FinalizePurchase. You can know when the purchase was consumed by listening for the OnUnexpectedPurchaseReceipt event.