Files
UnrealEngine/Engine/Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlinePurchaseInterface.h
2025-05-18 13:04:45 +08:00

367 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Interfaces/OnlineEntitlementsInterface.h"
#include "Interfaces/OnlineStoreInterfaceV2.h"
#include "OnlineJsonSerializer.h"
struct FOnlineError;
#define PURCHASE_REDEMPTION_CODE_USED "errors.com.epicgames.coderedemption.code_used"
#define PURCHASE_REDEMPTION_CODE_NOT_FOUND "errors.com.epicgames.coderedemption.code_not_found"
#define PURCHASE_REDEMPTION_NOT_ELIGIBLE "errors.com.epicgames.ecommerce.fulfillment.code.criteria.reject"
ONLINESUBSYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogOnlinePurchase, Log, All);
#define UE_LOG_ONLINE_PURCHASE(Verbosity, Format, ...) \
{ \
UE_LOG(LogOnlinePurchase, Verbosity, TEXT("%s%s"), ONLINE_LOG_PREFIX, *FString::Printf(Format, ##__VA_ARGS__)); \
}
#define UE_CLOG_ONLINE_PURCHASE(Conditional, Verbosity, Format, ...) \
{ \
UE_CLOG(Conditional, LogOnlinePurchase, Verbosity, TEXT("%s%s"), ONLINE_LOG_PREFIX, *FString::Printf(Format, ##__VA_ARGS__)); \
}
/**
* Info needed for checkout
*/
class FPurchaseCheckoutRequest
{
public:
/**
* Add a offer entry for purchase
*
* @param InNamespace namespace of offer to be purchased
* @param InOfferId id of offer to be purchased
* @param InQuantity number to purchase
* @param bInIsConsumable is the offer consumable or one time purchase
*/
void AddPurchaseOffer(const FOfferNamespace& InNamespace, const FUniqueOfferId& InOfferId, int32 InQuantity, bool bInIsConsumable = true)
{
PurchaseOffers.Add(FPurchaseOfferEntry(InNamespace, InOfferId, InQuantity, bInIsConsumable));
}
/**
* Single offer entry for purchase
*/
struct FPurchaseOfferEntry
{
FPurchaseOfferEntry(const FOfferNamespace& InOfferNamespace, const FUniqueOfferId& InOfferId, int32 InQuantity, bool bInIsConsumable)
: OfferNamespace(InOfferNamespace)
, OfferId(InOfferId)
, Quantity(InQuantity)
{ }
/** Namespace in which the offer resides */
FOfferNamespace OfferNamespace;
/** Platform specific offer id (defined on backend) */
FUniqueOfferId OfferId;
/** Number of offers of this type to purchase */
int32 Quantity;
};
/** List of offers being purchased */
TArray<FPurchaseOfferEntry> PurchaseOffers;
/** Optional AccountId to include with the purchase request (if supported) */
TOptional<FString> AccountId;
/** Key - value pair properties for passing to the checkout. */
TMap<FString, FString> AdditionalCheckoutProperties;
};
/**
* State of a purchase transaction
*/
enum class EPurchaseTransactionState : uint8
{
/** processing has not started on the purchase */
NotStarted,
/** currently processing the purchase */
Processing,
/** purchase completed successfully */
Purchased,
/** purchase completed but failed */
Failed,
/** purchase has been deferred (neither failed nor completed) */
Deferred,
/** purchase canceled by user */
Canceled,
/** prior purchase that has been restored */
Restored,
/** purchase failed as not allowed */
NotAllowed,
/** purchase failed as invalid */
Invalid
};
/**
* Receipt result from checkout
*/
class FPurchaseReceipt
: public FOnlineJsonSerializable
{
public:
FPurchaseReceipt()
: TransactionState(EPurchaseTransactionState::NotStarted)
{
}
struct FLineItemInfo
: public FOnlineJsonSerializable
{
/** The platform identifier of this purchase type */
FString ItemName;
/** unique identifier representing this purchased item (the specific instance owned by this account) */
FUniqueEntitlementId UniqueId;
/** platform-specific opaque validation info (required to verify UniqueId belongs to this account) */
FString ValidationInfo;
inline bool IsRedeemable() const { return !ValidationInfo.IsEmpty(); }
BEGIN_ONLINE_JSON_SERIALIZER
ONLINE_JSON_SERIALIZE("itemName", ItemName);
ONLINE_JSON_SERIALIZE("uniqueItemId", UniqueId);
ONLINE_JSON_SERIALIZE("validationInfo", ValidationInfo);
END_ONLINE_JSON_SERIALIZER
};
/**
* Single purchased offer offer
*/
struct FReceiptOfferEntry
: public FOnlineJsonSerializable
{
FReceiptOfferEntry()
: Quantity(0)
{
}
FReceiptOfferEntry(const FOfferNamespace& InNamespace, const FUniqueOfferId& InOfferId, int32 InQuantity)
: Namespace(InNamespace)
, OfferId(InOfferId)
, Quantity(InQuantity)
{
}
FOfferNamespace Namespace;
FUniqueOfferId OfferId;
int32 Quantity;
/** Information about the individual items purchased */
TArray<FLineItemInfo> LineItems;
BEGIN_ONLINE_JSON_SERIALIZER
ONLINE_JSON_SERIALIZE("namespace", Namespace);
ONLINE_JSON_SERIALIZE("offerId", OfferId);
ONLINE_JSON_SERIALIZE("quantity", Quantity);
ONLINE_JSON_SERIALIZE_ARRAY_SERIALIZABLE("items", LineItems, FLineItemInfo);
END_ONLINE_JSON_SERIALIZER
};
/**
* Add a offer entry that has been purchased
*
* @param InNamespace of the offer that has been purchased
* @param InOfferId id of offer that has been purchased
* @param InQuantity number purchased
*/
void AddReceiptOffer(const FOfferNamespace& InNamespace, const FUniqueOfferId& InOfferId, int32 InQuantity)
{
ReceiptOffers.Add(FReceiptOfferEntry(InNamespace, InOfferId, InQuantity));
}
void AddReceiptOffer(const FReceiptOfferEntry& ReceiptOffer)
{
ReceiptOffers.Add(ReceiptOffer);
}
void AddReceiptOffer(FReceiptOfferEntry&& ReceiptOffer)
{
ReceiptOffers.Add(MoveTemp(ReceiptOffer));
}
BEGIN_ONLINE_JSON_SERIALIZER
ONLINE_JSON_SERIALIZE("transactionId", TransactionId);
ONLINE_JSON_SERIALIZE_ARRAY_SERIALIZABLE("receiptList", ReceiptOffers, FReceiptOfferEntry);
END_ONLINE_JSON_SERIALIZER
public:
/** Unique Id for this transaction/order */
FString TransactionId;
/** Current state of the purchase */
EPurchaseTransactionState TransactionState;
/** List of offers that were purchased */
TArray<FReceiptOfferEntry> ReceiptOffers;
};
/**
* Info needed for code redemption
*/
class FRedeemCodeRequest
{
public:
/** Code to redeem */
FString Code;
/** Optional CodeUseId that was given if code was previously locked before redeeming - See IOnlineCodeRedemption::LockCode */
FString CodeUseId;
/** Where this code is being fulfilled from - e.g. Launcher, GameName*/
FString FulfillmentSource;
};
/**
* Delegate called when checkout process completes
*/
DECLARE_DELEGATE_TwoParams(FOnPurchaseCheckoutComplete, const FOnlineError& /*Result*/, const TSharedRef<FPurchaseReceipt>& /*Receipt*/);
/**
* Delegate called when checkout process completes, this delegate is used when the entitlement or receipt information of the purchase is not needed by the caller.
*/
DECLARE_DELEGATE_OneParam(FOnPurchaseReceiptlessCheckoutComplete, const FOnlineError& /*Result*/);
/**
* Delegate called when code redemption process completes
*/
DECLARE_DELEGATE_TwoParams(FOnPurchaseRedeemCodeComplete, const FOnlineError& /*Result*/, const TSharedRef<FPurchaseReceipt>& /*Receipt*/);
/**
* Delegate called when query receipt process completes
*/
DECLARE_DELEGATE_OneParam(FOnQueryReceiptsComplete, const FOnlineError& /*Result*/);
/**
* Delegate called when receipt validation completes
*/
DECLARE_DELEGATE_TwoParams(FOnFinalizeReceiptValidationInfoComplete, const FOnlineError& /*Result*/, const FString& /*ValidationInfo*/);
/**
* Delegate called when we are informed of a new receipt we did not initiate in-game
*/
DECLARE_MULTICAST_DELEGATE_OneParam(FOnUnexpectedPurchaseReceipt, const FUniqueNetId& /*UserId*/);
typedef FOnUnexpectedPurchaseReceipt::FDelegate FOnUnexpectedPurchaseReceiptDelegate;
/**
* IOnlinePurchase - Interface for IAP (In App Purchases) services
*/
class IOnlinePurchase
{
public:
virtual ~IOnlinePurchase() {}
/**
* Determine if user is allowed to purchase from store
*
* @param UserId user initiating the request
*
* @return true if user can make a purchase
*/
virtual bool IsAllowedToPurchase(const FUniqueNetId& UserId) = 0;
/**
* Initiate the checkout process for purchasing offers via payment
*
* @param UserId user initiating the request
* @param CheckoutRequest info needed for the checkout request
* @param Delegate completion callback (guaranteed to be called)
*/
virtual void Checkout(const FUniqueNetId& UserId, const FPurchaseCheckoutRequest& CheckoutRequest, const FOnPurchaseCheckoutComplete& Delegate) = 0;
/**
* Initiate the checkout process for purchasing offers via payment without receipts returned
* Recommended for use when the entitlement or receipt information of the purchase is not needed by the caller
*
* @param UserId user initiating the request
* @param CheckoutRequest info needed for the checkout request
* @param Delegate completion callback (guaranteed to be called)
*/
virtual void Checkout(const FUniqueNetId& UserId, const FPurchaseCheckoutRequest& CheckoutRequest, const FOnPurchaseReceiptlessCheckoutComplete& Delegate) = 0;
/**
* Finalizes a purchase with the supporting platform
* Acknowledges that the purchase has been properly redeemed by the application
*
* @param UserId user where the purchase was made
* @param ReceiptId purchase id for this platform
*/
virtual void FinalizePurchase(const FUniqueNetId& UserId, const FString& ReceiptId) = 0;
/**
* Finalizes a purchase with the supporting platform
* Acknowledges that the purchase has been properly redeemed by the application. Prefer this overload whenever the validation info is available.
*
* @param UserId user where the purchase was made
* @param ReceiptId purchase id for this platform
* @param ReceiptInfo the receipt validation info for this platform
*/
virtual void FinalizePurchase(const FUniqueNetId& UserId, const FString& ReceiptId, const FString& ReceiptInfo)
{
// Redirect to the classic method for platforms that don't support this overload yet.
FinalizePurchase(UserId, ReceiptId);
}
/**
* Initiate the checkout process for obtaining offers via code redemption
*
* @param UserId user initiating the request
* @param RedeemCodeRequest info needed for the redeem request
* @param Delegate completion callback (guaranteed to be called)
*/
virtual void RedeemCode(const FUniqueNetId& UserId, const FRedeemCodeRequest& RedeemCodeRequest, const FOnPurchaseRedeemCodeComplete& Delegate) = 0;
/**
* Query for all of the user's receipts from prior purchases
*
* @param UserId user initiating the request
* @param bRestoreReceipts initiate recovery of any receipts on the specific platform
* @param Delegate completion callback (guaranteed to be called)
*/
virtual void QueryReceipts(const FUniqueNetId& UserId, bool bRestoreReceipts, const FOnQueryReceiptsComplete& Delegate) = 0;
/**
* Get list of cached receipts for user (includes transactions currently being processed)
*
* @param UserId user initiating the request
* @param OutReceipts [out] list of receipts for the user
*/
virtual void GetReceipts(const FUniqueNetId& UserId, TArray<FPurchaseReceipt>& OutReceipts) const = 0;
virtual void FinalizeReceiptValidationInfo(const FUniqueNetId& UserId, FString& InReceiptValidationInfo, const FOnFinalizeReceiptValidationInfoComplete& Delegate) = 0;
/**
* Delegate fired when the local system tells us of a new completed purchase we may not have initiated in-game.
* Use this to know about new pending receipts in instances the local client did not start a purchase,
* such as when the application is in the background.
*
* @param UserId The beneficiary of this new receipt
*
*/
DEFINE_ONLINE_DELEGATE_ONE_PARAM(OnUnexpectedPurchaseReceipt, const FUniqueNetId& /*UserId*/);
private:
void OnRedirectToCheckoutComplete(const FOnlineError& ErrorResult, const TSharedRef<FPurchaseReceipt>& Receipt, const FOnPurchaseReceiptlessCheckoutComplete Delegate);
};
inline const TCHAR* LexToString(EPurchaseTransactionState State)
{
switch (State)
{
case EPurchaseTransactionState::NotStarted: return TEXT("NotStarted");
case EPurchaseTransactionState::Processing: return TEXT("Processing");
case EPurchaseTransactionState::Purchased: return TEXT("Purchased");
case EPurchaseTransactionState::Failed: return TEXT("Failed");
case EPurchaseTransactionState::Deferred: return TEXT("Deferred");
case EPurchaseTransactionState::Canceled: return TEXT("Canceled");
case EPurchaseTransactionState::Restored: return TEXT("Restored");
case EPurchaseTransactionState::NotAllowed: return TEXT("NotAllowed");
case EPurchaseTransactionState::Invalid: return TEXT("Invalid");
default: return TEXT("Unknown");
}
}