Files
o3de/Gems/InAppPurchases/Code/Source/Platform/Android/InAppPurchasesAndroid.cpp
T
lumberyard-employee-dm f648cb1fd8 Update the FileIO Aliases (#4186)
* Update the FileIOAlias naming to make the cache, project root and engine
root paths more clear

The alias of `@root@`, `@assets@`, and `@projectplatformcache@` has been
collapsed to `@projectproductassets@`

The alias of `@devroot@` and `@engroot@` has been collapsed to
`@engroot@`

The alias of `@devassets@` and `@projectroot@` has been collapsed to
`@projectroot@`

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated use of devassets and devroot properties in python

Those properties now use projectroot and engroot

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updating the alias @engroot@ alias path comment in each platform specific LocalFileIO_*.cpp file

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed hardcoded size of 9 for the product asset alias.

The ResolvePath function now just appends the @projectproductassets@
alias with the input path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Remove duplicate @projectproductassets@ check in ProcessFileTreeRequest

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fix for typos in Hydra python test

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated LocalFileIO::Copy call on Windows to use the Unicode aware CopyFileW API

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the AWSMetreicsGemAllocatorFixture to properly suppress asset
cache write errors for Test file creation.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed unneeded call to set the @projectproductasstes@ alias at the bottom of the AssetSeedManagerTest SetUp

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Added a deprecated alias map to the FileIO System

When a deprecated alias is accessed, the FileIO System logs an AZ_Error and indicates the alias that should be used

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated python test scripts to use the projectroot binding

Retrieving the AutomatedTesting project path based on "<devroot>/AutomatedTesting" has been removed.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated references to devroot and devgame within the codebase

The GetAbsoluteDevGameFolderPath functions has been replaced with direct call to AZ::Utils::GetProjectPath
The GetAbsoluteDevRootFolderPath functions has been replaced with direct calls to AZ::Utils::GetEnginePath

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated <engroot>/AutomatedTesting references to projectroot


Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Replaced references that assumes the project path is <engroot>/AutomatedTesting with <projectroot> in the AutomatedTesting python test

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Correct casing in emfxworkspace file


Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed newly added AppendPathParts function
Removed the Path constructors which accepts a PathIterable instance

The PathIterable isn't safe to return to a user of the Path class as it might be referencing temporary paths supplied via PathView arguments

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed unused parameter warning

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Undid change to the LexicallyProximate function to set the path result to the base path.

It needs to return the *this path if the pathResult is empty

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Moved the LocalFileIO ConvertToAbsolutePath implementations to AZ::Utils

Fixed the ConvertToAbsolutePath implementation for Unix platforms to use a buffer that is size PATH_MAX(4096 on all our supported Unix platforms).
Because the buffer before was AZ::IO::MaxPathLength which as a size of 1024, this was resulting in the Unix `realpath` function smashing the stack when it converts a path to an absolute path that is longer than 1024 characters

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the EditorCore.Tests to attach the AZ Environment to the EditorCore shared library that is statically loaded on launch.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed for DeprecatedAlaisesKeyVisitor Visit function causing the non string_view overloads being hidden causing a hidden base class function warning

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Changed the AWSMetricsGemMock to use a temporary for writing test assets

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the LocalFileIO::ResolvePath function to use HasRootPath to determine if a path starts with a root path such as "D:", "D:/" or "/"

IsAbsolute was not the corect check as the path of "D:" is a relative
path.
To be absolute according to the Windows the path must have a root
directory. i.e "D:/" or "D:\"

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removed absolute path comment from LocalFile_UnixLike.cpp and LocalFile_Android.cpp FindFiles implementations
Updated the ConvertToAlias to supply an AZ::IO::FixedMaxPath

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Replaced usage of the @projectproductassets@ alias with @engroot@ when referring to the LmbrCentral source folder in the CopyDependencyBuilderTest and the SeedBuilderTests

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the ScriptCanvas Upgrade Tool to output backed up files to the
Project User Path instead of the engine root

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed whitespacing issues in Application.cpp

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Remove unnecessary creation of a FixedMaxPath in the UpgradeTool.cpp

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Modified testSeedFolder variable in the SeedBuilderTests to use the
@engroot@ alias instead of @projectproductassets@/.. alias when
referring to the LmbrCentral Gem source path

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated references to the Project Asset Cache in the PythonTests.

Those tests no longer use the logic `azlmbr.paths.projectroot / "Cache" / "pc"` to retrieve a path to the cache root but instead the `azlmbr.paths.projectproductassets` constant

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Fixed the FileIO Deprecated Alias test on Windows

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Removing @projectsourceassets@ alias, as it is only used once.

Updated the PhysX EditorSystemComponent.cpp to query the ProjectPath
form the SettingsRegistry.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Replaced @projectproductassets@ alias with @products@

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Rollback changes to the PhysX EditorSystemComponent.cpp

The changes to use the ProjectPath from the SettingsRegistry has been implemented in PR #4497

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-10-05 15:12:53 -05:00

351 lines
15 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "InAppPurchasesAndroid.h"
#include "InAppPurchasesModule.h"
#include <InAppPurchases/InAppPurchasesResponseBus.h>
#include <AzCore/NativeUI/NativeUIRequests.h>
#include <AzCore/Android/JNI/JNI.h>
#include <AzCore/Android/Utils.h>
#include <AzCore/EBus/BusImpl.h>
#include <AzCore/JSON/document.h>
#include <AzCore/JSON/error/en.h>
#include <AzCore/IO/FileIO.h>
namespace InAppPurchases
{
static bool IsFieldIdValid(jfieldID fid)
{
if (fid == NULL)
{
return false;
}
return true;
}
static PurchasedProductDetailsAndroid* ParseReceiptDetails(JNIEnv* env, jobjectArray jpurchasedProductDetails, int index)
{
jobject jpurchasedProduct = env->GetObjectArrayElement(jpurchasedProductDetails, index);
const int NUM_FIELDS_PURCHASED_PRODUCTS = 7;
jfieldID fid[NUM_FIELDS_PURCHASED_PRODUCTS];
jclass cls = env->GetObjectClass(jpurchasedProduct);
fid[0] = env->GetFieldID(cls, "m_productId", "Ljava/lang/String;");
fid[1] = env->GetFieldID(cls, "m_orderId", "Ljava/lang/String;");
fid[2] = env->GetFieldID(cls, "m_packageName", "Ljava/lang/String;");
fid[3] = env->GetFieldID(cls, "m_purchaseToken", "Ljava/lang/String;");
fid[4] = env->GetFieldID(cls, "m_signature", "Ljava/lang/String;");
fid[5] = env->GetFieldID(cls, "m_purchaseTime", "J");
fid[6] = env->GetFieldID(cls, "m_isAutoRenewing", "Z");
for (int i = 0; i < NUM_FIELDS_PURCHASED_PRODUCTS; i++)
{
if (!IsFieldIdValid(fid[i]))
{
AZ_TracePrintf("LumberyardInAppBilling", "Invaild FieldId in PurchasedProductDetails\n");
return nullptr;
}
}
PurchasedProductDetailsAndroid* purchasedProductDetails = new PurchasedProductDetailsAndroid();
purchasedProductDetails->SetProductId(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jpurchasedProduct, fid[0]))));
purchasedProductDetails->SetOrderId(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jpurchasedProduct, fid[1]))));
purchasedProductDetails->SetPackageName(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jpurchasedProduct, fid[2]))));
purchasedProductDetails->SetPurchaseToken(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jpurchasedProduct, fid[3]))));
purchasedProductDetails->SetPurchaseSignature(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jpurchasedProduct, fid[4]))));
purchasedProductDetails->SetPurchaseTime(env->GetLongField(jpurchasedProduct, fid[5]));
purchasedProductDetails->SetIsAutoRenewing(env->GetBooleanField(jpurchasedProduct, fid[6]));
return purchasedProductDetails;
}
void ProductInfoRetrieved(JNIEnv* env, jobject obj, jobjectArray jproductDetails)
{
int numProducts = env->GetArrayLength(jproductDetails);
InAppPurchasesInterface::GetInstance()->GetCache()->ClearCachedProductDetails();
const int NUM_FIELDS_PRODUCTS = 7;
jfieldID fid[NUM_FIELDS_PRODUCTS];
jclass cls;
if (numProducts > 0)
{
cls = env->GetObjectClass(env->GetObjectArrayElement(jproductDetails, 0));
fid[0] = env->GetFieldID(cls, "m_productId", "Ljava/lang/String;");
fid[1] = env->GetFieldID(cls, "m_type", "Ljava/lang/String;");
fid[2] = env->GetFieldID(cls, "m_price", "Ljava/lang/String;");
fid[3] = env->GetFieldID(cls, "m_currencyCode", "Ljava/lang/String;");
fid[4] = env->GetFieldID(cls, "m_title", "Ljava/lang/String;");
fid[5] = env->GetFieldID(cls, "m_description", "Ljava/lang/String;");
fid[6] = env->GetFieldID(cls, "m_priceMicro", "J");
}
for (int i = 0; i < NUM_FIELDS_PRODUCTS; i++)
{
if (!IsFieldIdValid(fid[i]))
{
AZ_TracePrintf("LumberyardInAppBilling", "Invaild FieldId in ProductDetails\n");
return;
}
}
for (int i = 0; i < numProducts; i++)
{
jobject jproduct = env->GetObjectArrayElement(jproductDetails, i);
ProductDetailsAndroid* productDetails = new ProductDetailsAndroid();
productDetails->SetProductId(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[0]))));
productDetails->SetProductType(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[1]))));
productDetails->SetProductPrice(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[2]))));
productDetails->SetProductCurrencyCode(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[3]))));
productDetails->SetProductTitle(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[4]))));
productDetails->SetProductDescription(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[5]))));
productDetails->SetProductPriceMicro(env->GetLongField(jproduct, fid[6]));
InAppPurchasesInterface::GetInstance()->GetCache()->AddProductDetailsToCache(productDetails);
}
EBUS_EVENT(InAppPurchasesResponseBus, ProductInfoRetrieved, InAppPurchasesInterface::GetInstance()->GetCache()->GetCachedProductDetails());
}
void PurchasedProductsRetrieved(JNIEnv* env, jobject object, jobjectArray jpurchasedProductDetails)
{
InAppPurchasesInterface::GetInstance()->GetCache()->ClearCachedPurchasedProductDetails();
int numPurchasedProducts = env->GetArrayLength(jpurchasedProductDetails);
for (int i = 0; i < numPurchasedProducts; i++)
{
PurchasedProductDetailsAndroid* purchasedProduct = ParseReceiptDetails(env, jpurchasedProductDetails, i);
if (purchasedProduct != nullptr)
{
InAppPurchasesInterface::GetInstance()->GetCache()->AddPurchasedProductDetailsToCache(purchasedProduct);
}
}
EBUS_EVENT(InAppPurchasesResponseBus, PurchasedProductsRetrieved, InAppPurchasesInterface::GetInstance()->GetCache()->GetCachedPurchasedProductDetails());
}
void NewProductPurchased(JNIEnv* env, jobject object, jobjectArray jpurchaseReceipt)
{
PurchasedProductDetailsAndroid* purchasedProduct = ParseReceiptDetails(env, jpurchaseReceipt, 0);
if (purchasedProduct != nullptr)
{
InAppPurchasesInterface::GetInstance()->GetCache()->AddPurchasedProductDetailsToCache(purchasedProduct);
EBUS_EVENT(InAppPurchasesResponseBus, NewProductPurchased, purchasedProduct);
}
}
void PurchaseConsumed(JNIEnv* env, jobject object, jstring jpurchaseToken)
{
const char* purchaseToken = env->GetStringUTFChars(jpurchaseToken, nullptr);
EBUS_EVENT(InAppPurchasesResponseBus, PurchaseConsumed, AZStd::string(purchaseToken, env->GetStringUTFLength(jpurchaseToken)));
env->ReleaseStringUTFChars(jpurchaseToken, purchaseToken);
}
static JNINativeMethod methods[] = {
{ "nativeProductInfoRetrieved", "([Ljava/lang/Object;)V", (void*)ProductInfoRetrieved },
{ "nativePurchasedProductsRetrieved", "([Ljava/lang/Object;)V", (void*)PurchasedProductsRetrieved },
{ "nativeNewProductPurchased", "([Ljava/lang/Object;)V", (void*)NewProductPurchased },
{ "nativePurchaseConsumed", "(Ljava/lang/String;)V", (void*)PurchaseConsumed }
};
InAppPurchasesInterface* InAppPurchasesInterface::CreateInstance()
{
return new InAppPurchasesAndroid();
}
void InAppPurchasesAndroid::Initialize()
{
JNIEnv* env = AZ::Android::JNI::GetEnv();
jobject activityObject = AZ::Android::Utils::GetActivityRef();
jclass billingClass = AZ::Android::JNI::LoadClass("com/amazon/lumberyard/iap/LumberyardInAppBilling");
jmethodID mid = env->GetMethodID(billingClass, "<init>", "(Landroid/app/Activity;)V");
jobject billingInstance = env->NewObject(billingClass, mid, activityObject);
m_billingInstance = env->NewGlobalRef(billingInstance);
env->RegisterNatives(billingClass, methods, sizeof(methods) / sizeof(methods[0]));
mid = env->GetMethodID(billingClass, "IsKindleDevice", "()Z");
jboolean result = env->CallBooleanMethod(m_billingInstance, mid);
if (result)
{
EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, "Kindle Device Detected", "IAP currently unsupported on Kindle devices", false);
}
env->DeleteGlobalRef(billingClass);
env->DeleteLocalRef(billingInstance);
}
InAppPurchasesAndroid::~InAppPurchasesAndroid()
{
JNIEnv* env = AZ::Android::JNI::GetEnv();
jclass billingClass = env->GetObjectClass(m_billingInstance);
jmethodID mid = env->GetMethodID(billingClass, "UnbindService", "()V");
env->CallVoidMethod(m_billingInstance, mid);
env->DeleteLocalRef(billingClass);
env->DeleteGlobalRef(m_billingInstance);
}
void InAppPurchasesAndroid::QueryProductInfo(AZStd::vector<AZStd::string>& productIds) const
{
JNIEnv* env = AZ::Android::JNI::GetEnv();
jobjectArray jproductIds = static_cast<jobjectArray>(env->NewObjectArray(productIds.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")));
for (int i = 0; i < productIds.size(); i++)
{
env->SetObjectArrayElement(jproductIds, i, env->NewStringUTF(productIds[i].c_str()));
}
jclass billingClass = env->GetObjectClass(m_billingInstance);
jmethodID mid = env->GetMethodID(billingClass, "QueryProductInfo", "([Ljava/lang/String;)V");
env->CallVoidMethod(m_billingInstance, mid, jproductIds);
env->DeleteLocalRef(jproductIds);
env->DeleteLocalRef(billingClass);
}
void InAppPurchasesAndroid::QueryProductInfo() const
{
AZ::IO::FileIOBase* fileReader = AZ::IO::FileIOBase::GetInstance();
AZStd::string fileBuffer;
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
AZ::u64 fileSize = 0;
if (!fileReader->Open("@products@/product_ids.json", AZ::IO::OpenMode::ModeRead, fileHandle))
{
AZ_TracePrintf("LumberyardInAppBilling", "Unable to open file product_ids.json\n");
return;
}
if ((!fileReader->Size(fileHandle, fileSize)) || (fileSize == 0))
{
AZ_TracePrintf("LumberyardInAppBilling", "Unable to read file product_ids.json - file truncated\n");
fileReader->Close(fileHandle);
return;
}
fileBuffer.resize(fileSize);
if (!fileReader->Read(fileHandle, fileBuffer.data(), fileSize, true))
{
fileBuffer.resize(0);
fileReader->Close(fileHandle);
AZ_TracePrintf("LumberyardInAppBilling", "Failed to read file product_ids.json\n");
return;
}
fileReader->Close(fileHandle);
rapidjson::Document document;
document.Parse(fileBuffer.data());
if (document.HasParseError())
{
[[maybe_unused]] const char* errorStr = rapidjson::GetParseError_En(document.GetParseError());
AZ_TracePrintf("LumberyardInAppBilling", "Failed to parse product_ids.json: %s\n", errorStr);
return;
}
const rapidjson::Value& productList = document["product_ids"];
const auto& end = productList.End();
AZStd::vector<AZStd::string> productIds;
for (auto it = productList.Begin(); it != end; it++)
{
const auto& elem = *it;
productIds.push_back(elem["id"].GetString());
}
QueryProductInfo(productIds);
}
void InAppPurchasesAndroid::PurchaseProduct(const AZStd::string& productId, const AZStd::string& developerPayload) const
{
JNIEnv* env = AZ::Android::JNI::GetEnv();
const AZStd::vector <AZStd::unique_ptr<ProductDetails const> >& cachedProductDetails = InAppPurchasesInterface::GetInstance()->GetCache()->GetCachedProductDetails();
AZStd::string productType = "";
for (int i = 0; i < cachedProductDetails.size(); i++)
{
const ProductDetailsAndroid* productDetails = azrtti_cast<const ProductDetailsAndroid*>(cachedProductDetails[i].get());
const AZStd::string& cachedProductId = productDetails->GetProductId();
if (cachedProductId.compare(productId) == 0)
{
productType = productDetails->GetProductType();
break;
}
}
if (productType.empty())
{
AZ_TracePrintf("LumberyardInAppBilling", "Failed to find product with id: %s", productId.c_str());
return;
}
jstring jproductId = env->NewStringUTF(productId.c_str());
jstring jdeveloperPayload = env->NewStringUTF(developerPayload.c_str());
jstring jproductType = env->NewStringUTF(productType.c_str());
jclass billingClass = env->GetObjectClass(m_billingInstance);
jmethodID mid = env->GetMethodID(billingClass, "PurchaseProduct", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
env->CallVoidMethod(m_billingInstance, mid, jproductId, jdeveloperPayload, jproductType);
env->DeleteLocalRef(jproductId);
env->DeleteLocalRef(jdeveloperPayload);
env->DeleteLocalRef(jproductType);
env->DeleteLocalRef(billingClass);
}
void InAppPurchasesAndroid::PurchaseProduct(const AZStd::string& productId) const
{
PurchaseProduct(productId, "");
}
void InAppPurchasesAndroid::QueryPurchasedProducts() const
{
JNIEnv* env = AZ::Android::JNI::GetEnv();
jclass billingClass = env->GetObjectClass(m_billingInstance);
jmethodID mid = env->GetMethodID(billingClass, "QueryPurchasedProducts", "()V");
env->CallVoidMethod(m_billingInstance, mid);
env->DeleteLocalRef(billingClass);
}
void InAppPurchasesAndroid::RestorePurchasedProducts() const
{
}
void InAppPurchasesAndroid::ConsumePurchase(const AZStd::string& purchaseToken) const
{
JNIEnv* env = AZ::Android::JNI::GetEnv();
jclass billingClass = env->GetObjectClass(m_billingInstance);
jmethodID mid = env->GetMethodID(billingClass, "ConsumePurchase", "(Ljava/lang/String;)V");
jstring jpurchaseToken = env->NewStringUTF(purchaseToken.c_str());
env->CallVoidMethod(m_billingInstance, mid, jpurchaseToken);
env->DeleteLocalRef(jpurchaseToken);
env->DeleteLocalRef(billingClass);
}
void InAppPurchasesAndroid::FinishTransaction(const AZStd::string& transactionId, bool downloadHostedContent) const
{
}
InAppPurchasesCache* InAppPurchasesAndroid::GetCache()
{
return &m_cache;
}
}