Files
UnrealEngine/Engine/Plugins/Online/OnlineSubsystemNull/Source/Private/OnlinePurchaseInterfaceNull.cpp
2025-05-18 13:04:45 +08:00

304 lines
9.8 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "OnlinePurchaseInterfaceNull.h"
#include "OnlineSubsystemNull.h"
#include "OnlineError.h"
namespace
{
static FPurchaseReceipt::FReceiptOfferEntry MakeReceiptOfferEntry(const FUniqueNetIdNull& NullUserId, const FString& Id, const FString& Name)
{
FPurchaseReceipt::FReceiptOfferEntry OfferEntry(FString(), Id, 1);
{
FPurchaseReceipt::FLineItemInfo LineItem;
LineItem.ItemName = Name;
LineItem.UniqueId = Id;
OfferEntry.LineItems.Emplace(MoveTemp(LineItem));
}
return OfferEntry;
}
}
FOnlinePurchaseNull::FOnlinePurchaseNull(FOnlineSubsystemNull& InNullSubsystem)
: NullSubsystem(InNullSubsystem)
{
}
FOnlinePurchaseNull::~FOnlinePurchaseNull()
{
}
void FOnlinePurchaseNull::Tick()
{
if (PendingPurchaseFailTime.IsSet() && PendingPurchaseDelegate.IsSet())
{
if (FPlatformTime::Seconds() > PendingPurchaseFailTime.GetValue())
{
FOnPurchaseCheckoutComplete Delegate = MoveTemp(PendingPurchaseDelegate.GetValue());
PendingPurchaseDelegate.Reset();
PendingPurchaseFailTime.Reset();
Delegate.ExecuteIfBound(FOnlineError(TEXT("Checkout was cancelled or timed out")), MakeShared<FPurchaseReceipt>());
}
}
}
bool FOnlinePurchaseNull::IsAllowedToPurchase(const FUniqueNetId& UserId)
{
return true;
}
void FOnlinePurchaseNull::Checkout(const FUniqueNetId& UserId, const FPurchaseCheckoutRequest& CheckoutRequest, const FOnPurchaseCheckoutComplete& Delegate)
{
// Lambda to wrap calling our delegate with an error and logging the message
auto CallDelegateError = [this, &Delegate](FString&& ErrorMessage)
{
NullSubsystem.ExecuteNextTick([Delegate, MovedErrorMessage = MoveTemp(ErrorMessage)]() mutable
{
UE_LOG_ONLINE(Error, TEXT("%s"), *MovedErrorMessage);
const TSharedRef<FPurchaseReceipt> PurchaseReceipt = MakeShared<FPurchaseReceipt>();
PurchaseReceipt->TransactionState = EPurchaseTransactionState::Failed;
Delegate.ExecuteIfBound(FOnlineError(MoveTemp(MovedErrorMessage)), PurchaseReceipt);
});
};
if (CheckoutRequest.PurchaseOffers.Num() == 0)
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, there were no entries passed to purchase"));
return;
}
else if (CheckoutRequest.PurchaseOffers.Num() != 1)
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, there were more than one entry passed to purchase. We currently only support one."));
return;
}
check(CheckoutRequest.PurchaseOffers.IsValidIndex(0));
const FPurchaseCheckoutRequest::FPurchaseOfferEntry& Entry = CheckoutRequest.PurchaseOffers[0];
if (Entry.Quantity != 1)
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, purchase quantity not set to one. We currently only support one."));
return;
}
if (Entry.OfferId.IsEmpty())
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, OfferId is blank."));
return;
}
const IOnlineStoreV2Ptr NullStoreInt = NullSubsystem.GetStoreV2Interface();
TSharedPtr<FOnlineStoreOffer> NullOffer = NullStoreInt->GetOffer(Entry.OfferId);
if (!NullOffer.IsValid())
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, Could not find corresponding offer."));
return;
}
if (PendingPurchaseDelegate.IsSet())
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, there was another purchase in progress."));
return;
}
PendingPurchaseDelegate = Delegate;
TWeakPtr<FOnlinePurchaseNull, ESPMode::ThreadSafe> WeakMe = AsShared();
const FUniqueNetIdNull& NullUserId = static_cast<const FUniqueNetIdNull&>(UserId);
NullSubsystem.ExecuteNextTick([NullUserId, NullOffer, WeakMe]
{
FOnlinePurchaseNullPtr StrongThis = WeakMe.Pin();
if (StrongThis.IsValid())
{
StrongThis->CheckoutSuccessfully(NullUserId, NullOffer);
}
});
}
void FOnlinePurchaseNull::CheckoutSuccessfully(const FUniqueNetIdNull& UserId, TSharedPtr<FOnlineStoreOffer> Offer)
{
// Cache this receipt
TArray<FPurchaseReceipt>& UserReceipts = UserFakeReceipts.FindOrAdd(UserId);
FPurchaseReceipt& PurchaseReceipt = UserReceipts.Emplace_GetRef();
PurchaseReceipt.AddReceiptOffer(MakeReceiptOfferEntry(UserId, Offer->OfferId, Offer->Title.ToString()));
check(PendingPurchaseDelegate.IsSet());
// Have a pending purchase
FOnPurchaseCheckoutComplete Delegate = MoveTemp(PendingPurchaseDelegate.GetValue());
PendingPurchaseDelegate.Reset();
PendingPurchaseFailTime.Reset();
// Finish pending purchase
Delegate.ExecuteIfBound(FOnlineError(true), MakeShared<FPurchaseReceipt>(PurchaseReceipt));
}
void FOnlinePurchaseNull::Checkout(const FUniqueNetId& UserId, const FPurchaseCheckoutRequest& CheckoutRequest, const FOnPurchaseReceiptlessCheckoutComplete& Delegate)
{
// Lambda to wrap calling our delegate with an error and logging the message
auto CallDelegateError = [this, &Delegate](FString&& ErrorMessage)
{
NullSubsystem.ExecuteNextTick([Delegate, MovedErrorMessage = MoveTemp(ErrorMessage)]() mutable
{
UE_LOG_ONLINE(Error, TEXT("%s"), *MovedErrorMessage);
Delegate.ExecuteIfBound(FOnlineError(MoveTemp(MovedErrorMessage)));
});
};
if (CheckoutRequest.PurchaseOffers.Num() == 0)
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, there were no entries passed to purchase"));
return;
}
else if (CheckoutRequest.PurchaseOffers.Num() != 1)
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, there were more than one entry passed to purchase. We currently only support one."));
return;
}
check(CheckoutRequest.PurchaseOffers.IsValidIndex(0));
const FPurchaseCheckoutRequest::FPurchaseOfferEntry& Entry = CheckoutRequest.PurchaseOffers[0];
if (Entry.Quantity != 1)
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, purchase quantity not set to one. We currently only support one."));
return;
}
if (Entry.OfferId.IsEmpty())
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, OfferId is blank."));
return;
}
const IOnlineStoreV2Ptr NullStoreInt = NullSubsystem.GetStoreV2Interface();
TSharedPtr<FOnlineStoreOffer> NullOffer = NullStoreInt->GetOffer(Entry.OfferId);
if (!NullOffer.IsValid())
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, Could not find corresponding offer."));
return;
}
if (PendingPurchaseHandle.IsValid())
{
CallDelegateError(TEXT("FOnlinePurchaseNull::Checkout failed, there was another purchase in progress."));
return;
}
PendingPurchaseHandle = Delegate.GetHandle();
TWeakPtr<FOnlinePurchaseNull, ESPMode::ThreadSafe> WeakMe = AsShared();
const FUniqueNetIdNull& NullUserId = static_cast<const FUniqueNetIdNull&>(UserId);
NullSubsystem.ExecuteNextTick([NullUserId, WeakMe, Delegate]
{
FOnlinePurchaseNullPtr StrongThis = WeakMe.Pin();
if (StrongThis.IsValid())
{
StrongThis->CheckoutSuccessfully(NullUserId, Delegate);
}
});
}
void FOnlinePurchaseNull::CheckoutSuccessfully(const FUniqueNetIdNull& UserId, FOnPurchaseReceiptlessCheckoutComplete Delegate)
{
// Reset pending purchase handle
check(PendingPurchaseHandle.IsValid());
PendingPurchaseHandle.Reset();
// Finish pending purchase
Delegate.ExecuteIfBound(FOnlineError(EOnlineErrorResult::Success));
}
void FOnlinePurchaseNull::FinalizePurchase(const FUniqueNetId& UserId, const FString& ReceiptId)
{
const FUniqueNetIdNull& NullUserId = static_cast<const FUniqueNetIdNull&>(UserId);
TArray<FPurchaseReceipt>* UserReceipts = UserFakeReceipts.Find(NullUserId);
if (UserReceipts)
{
for (const FPurchaseReceipt& UserReceipt : *UserReceipts)
{
for (const FPurchaseReceipt::FReceiptOfferEntry& ReceiptOffer : UserReceipt.ReceiptOffers)
{
if (ReceiptOffer.OfferId == ReceiptId)
{
UE_LOG_ONLINE(Log, TEXT("Consumption of Entitlement %s completed was successful"), *ReceiptId);
return;
}
}
}
}
UE_LOG_ONLINE(Verbose, TEXT("Didn't find receipt with id %s"), *ReceiptId);
}
void FOnlinePurchaseNull::RedeemCode(const FUniqueNetId& UserId, const FRedeemCodeRequest& RedeemCodeRequest, const FOnPurchaseRedeemCodeComplete& Delegate)
{
TWeakPtr<FOnlinePurchaseNull, ESPMode::ThreadSafe> WeakMe = AsShared();
const FUniqueNetIdNull& NullUserId = static_cast<const FUniqueNetIdNull&>(UserId);
NullSubsystem.ExecuteNextTick([NullUserId, WeakMe, RedeemCodeRequest, Delegate]
{
FOnlinePurchaseNullPtr StrongThis = WeakMe.Pin();
if (StrongThis.IsValid())
{
UE_LOG_ONLINE(Log, TEXT("FOnlinePurchaseNull::RedeemCode redeemed successfully"));
// Cache this receipt
TArray<FPurchaseReceipt>& UserReceipts = StrongThis->UserFakeReceipts.FindOrAdd(NullUserId);
FPurchaseReceipt& PurchaseReceipt = UserReceipts.Emplace_GetRef();
PurchaseReceipt.AddReceiptOffer(MakeReceiptOfferEntry(NullUserId, RedeemCodeRequest.Code, RedeemCodeRequest.Code));
Delegate.ExecuteIfBound(FOnlineError(true), MakeShared<FPurchaseReceipt>(PurchaseReceipt));
}
});
}
void FOnlinePurchaseNull::QueryReceipts(const FUniqueNetId& UserId, bool bRestoreReceipts, const FOnQueryReceiptsComplete& Delegate)
{
const FUniqueNetIdNull& NullUserId = static_cast<const FUniqueNetIdNull&>(UserId);
if (!NullUserId.IsValid())
{
NullSubsystem.ExecuteNextTick([Delegate]
{
UE_LOG_ONLINE(Error, TEXT("FOnlinePurchaseNull::QueryReceipts user is invalid"));
Delegate.ExecuteIfBound(FOnlineError(TEXT("User is invalid")));
});
return;
}
NullSubsystem.ExecuteNextTick([Delegate]
{
Delegate.ExecuteIfBound(FOnlineError(true));
});
}
void FOnlinePurchaseNull::GetReceipts(const FUniqueNetId& UserId, TArray<FPurchaseReceipt>& OutReceipts) const
{
const FUniqueNetIdNull& NullUserId = static_cast<const FUniqueNetIdNull&>(UserId);
const TArray<FPurchaseReceipt>* FoundReceipts = UserFakeReceipts.Find(NullUserId);
if (FoundReceipts == nullptr)
{
OutReceipts.Empty();
}
else
{
OutReceipts = *FoundReceipts;
}
}
void FOnlinePurchaseNull::FinalizeReceiptValidationInfo(const FUniqueNetId& UserId, FString& InReceiptValidationInfo, const FOnFinalizeReceiptValidationInfoComplete& Delegate)
{
}