// 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()); } } } 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 PurchaseReceipt = MakeShared(); 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 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 WeakMe = AsShared(); const FUniqueNetIdNull& NullUserId = static_cast(UserId); NullSubsystem.ExecuteNextTick([NullUserId, NullOffer, WeakMe] { FOnlinePurchaseNullPtr StrongThis = WeakMe.Pin(); if (StrongThis.IsValid()) { StrongThis->CheckoutSuccessfully(NullUserId, NullOffer); } }); } void FOnlinePurchaseNull::CheckoutSuccessfully(const FUniqueNetIdNull& UserId, TSharedPtr Offer) { // Cache this receipt TArray& 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(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 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 WeakMe = AsShared(); const FUniqueNetIdNull& NullUserId = static_cast(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(UserId); TArray* 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 WeakMe = AsShared(); const FUniqueNetIdNull& NullUserId = static_cast(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& UserReceipts = StrongThis->UserFakeReceipts.FindOrAdd(NullUserId); FPurchaseReceipt& PurchaseReceipt = UserReceipts.Emplace_GetRef(); PurchaseReceipt.AddReceiptOffer(MakeReceiptOfferEntry(NullUserId, RedeemCodeRequest.Code, RedeemCodeRequest.Code)); Delegate.ExecuteIfBound(FOnlineError(true), MakeShared(PurchaseReceipt)); } }); } void FOnlinePurchaseNull::QueryReceipts(const FUniqueNetId& UserId, bool bRestoreReceipts, const FOnQueryReceiptsComplete& Delegate) { const FUniqueNetIdNull& NullUserId = static_cast(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& OutReceipts) const { const FUniqueNetIdNull& NullUserId = static_cast(UserId); const TArray* FoundReceipts = UserFakeReceipts.Find(NullUserId); if (FoundReceipts == nullptr) { OutReceipts.Empty(); } else { OutReceipts = *FoundReceipts; } } void FOnlinePurchaseNull::FinalizeReceiptValidationInfo(const FUniqueNetId& UserId, FString& InReceiptValidationInfo, const FOnFinalizeReceiptValidationInfoComplete& Delegate) { }