Manual Purchase Handling
This section covers functions designed for developers who prefer to manually handle purchase verification and acknowledgment on their own server. Instead of relying on automatic consumption or acknowledgment, these functions provide direct control over the purchase flow — allowing you to launch a purchase, receive its token, and confirm or consume it later after your backend validation.
Manual purchase handling is useful when:
- You need to verify purchases securely through your backend before granting items or subscriptions.
- You want to implement custom logic for fraud detection or entitlement management.
- You require more flexibility in managing purchase states and tokens across multiple devices or accounts.
Launch Billing Flow Manual
This asynchronous function works similarly to the standard LaunchBillingFlow, but it does not automatically acknowledge or consume the purchase after completion. It is intended for developers who want full control over purchase validation and confirmation on their own backend.
When using this function, you’ll receive a purchase details struct that includes the purchase token. This token can then be verified on your server before calling Acknowledge Purchase or Consume Purchase. This approach provides maximum flexibility and is recommended for apps that rely on secure server-side validation of transactions.
Acknowledge Purchase
This asynchronous function must be called for every completed purchase in order to finalize it. Google Play requires that all purchases be acknowledged within three days, otherwise the transaction will be automatically refunded.
The function takes the purchase token as a parameter, confirms the purchase with Google Play, and then returns the same token back — allowing you to use it later for Consume Purchase, if the product is consumable.
This manual acknowledgment flow ensures that only verified purchases are finalized, giving your backend full control over entitlement logic and item delivery.
Consume Purchase
This function is used to consume a previously acknowledged purchase. Consuming a purchase means marking it as used, so the same item can be bought again in the future. This applies only to one-time consumable products (for example, in-game currency or resources).
The function takes the purchase token obtained during the purchase or acknowledgment process, performs the consume request, and confirms the operation with Google Play. After a successful consume, the token becomes invalid and a new one will be issued on the next purchase.
Example
In this example, we handle a purchase manually using Launch Billing Flow Manual. After the player successfully completes a purchase, the returned FPurchase struct is broken down to extract the Purchase Token. That token is then used to explicitly call Acknowledge Purchase and, after acknowledgment succeeds, Consume Purchase to reset the product for repurchase.
This flow ensures full control over acknowledgment and consumption, allowing you to integrate custom validation logic or backend verification.
C++ Usage
You can handle manual purchases directly from C++ to maintain full control over acknowledgment and consumption.
#include "BillingCPPLibrary.h"
void UMyGameInstance::HandleManualPurchase()
{
FString ProductId = TEXT("testproduct1");
FString OfferToken = TEXT(""); // Leave empty for in-app products
// Step 1: Launch manual billing flow
FOnPurchaseSuccessfulNative OnSuccess;
OnSuccess.BindLambda([](const FPurchase& Purchase)
{
UE_LOG(LogTemp, Log, TEXT("Purchase successful"));
// Extract the purchase token
FString Token = Purchase.PurchaseToken;
// Step 2: Acknowledge the purchase
FOnAcknowledgeSuccessfulNative OnAckSuccess;
OnAckSuccess.BindLambda([Token]()
{
UE_LOG(LogTemp, Log, TEXT("Purchase acknowledged: %s"), *Token);
// Step 3: Consume the purchase (for consumables)
FOnConsumeSuccessfulNative OnConsumeSuccess;
OnConsumeSuccess.BindLambda([]()
{
UE_LOG(LogTemp, Log, TEXT("Purchase consumed successfully"));
});
FOnConsumeFailedNative OnConsumeFail;
OnConsumeFail.BindLambda([](EBillingResponseCode Code, const FString& Error)
{
UE_LOG(LogTemp, Error, TEXT("Consume failed: %s (Code %d)"), *Error, static_cast<int32>(Code));
});
UBillingCPPLibrary::ConsumePurchase(Token, OnConsumeSuccess, OnConsumeFail);
});
FOnAcknowledgeFailedNative OnAckFail;
OnAckFail.BindLambda([](EBillingResponseCode Code, const FString& Error)
{
UE_LOG(LogTemp, Error, TEXT("Acknowledge failed: %s (Code %d)"), *Error, static_cast<int32>(Code));
});
UBillingCPPLibrary::AcknowledgePurchase(Token, OnAckSuccess, OnAckFail);
});
FOnPurchasePendingNative OnPending;
OnPending.BindLambda([](const FPurchase& Purchase)
{
UE_LOG(LogTemp, Warning, TEXT("Purchase pending..."));
});
FOnPurchaseFailedNative OnFailure;
OnFailure.BindLambda([](EBillingResponseCode Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Purchase failed: %s (Code %d)"), *Message, static_cast<int32>(Code));
});
UBillingCPPLibrary::LaunchBillingFlowManual(ProductId, EProductType::INAPP, OfferToken, OnSuccess, OnPending, OnFailure);
}