InAppPurchasesAndroid.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "InAppPurchasesAndroid.h"
  9. #include "InAppPurchasesModule.h"
  10. #include <InAppPurchases/InAppPurchasesResponseBus.h>
  11. #include <AzCore/NativeUI/NativeUIRequests.h>
  12. #include <AzCore/Android/JNI/JNI.h>
  13. #include <AzCore/Android/Utils.h>
  14. #include <AzCore/EBus/BusImpl.h>
  15. #include <AzCore/JSON/document.h>
  16. #include <AzCore/JSON/error/en.h>
  17. #include <AzCore/IO/FileIO.h>
  18. namespace InAppPurchases
  19. {
  20. static bool IsFieldIdValid(jfieldID fid)
  21. {
  22. if (fid == NULL)
  23. {
  24. return false;
  25. }
  26. return true;
  27. }
  28. static PurchasedProductDetailsAndroid* ParseReceiptDetails(JNIEnv* env, jobjectArray jpurchasedProductDetails, int index)
  29. {
  30. jobject jpurchasedProduct = env->GetObjectArrayElement(jpurchasedProductDetails, index);
  31. const int NUM_FIELDS_PURCHASED_PRODUCTS = 7;
  32. jfieldID fid[NUM_FIELDS_PURCHASED_PRODUCTS];
  33. jclass cls = env->GetObjectClass(jpurchasedProduct);
  34. fid[0] = env->GetFieldID(cls, "m_productId", "Ljava/lang/String;");
  35. fid[1] = env->GetFieldID(cls, "m_orderId", "Ljava/lang/String;");
  36. fid[2] = env->GetFieldID(cls, "m_packageName", "Ljava/lang/String;");
  37. fid[3] = env->GetFieldID(cls, "m_purchaseToken", "Ljava/lang/String;");
  38. fid[4] = env->GetFieldID(cls, "m_signature", "Ljava/lang/String;");
  39. fid[5] = env->GetFieldID(cls, "m_purchaseTime", "J");
  40. fid[6] = env->GetFieldID(cls, "m_isAutoRenewing", "Z");
  41. for (int i = 0; i < NUM_FIELDS_PURCHASED_PRODUCTS; i++)
  42. {
  43. if (!IsFieldIdValid(fid[i]))
  44. {
  45. AZ_TracePrintf("LumberyardInAppBilling", "Invaild FieldId in PurchasedProductDetails\n");
  46. return nullptr;
  47. }
  48. }
  49. PurchasedProductDetailsAndroid* purchasedProductDetails = new PurchasedProductDetailsAndroid();
  50. purchasedProductDetails->SetProductId(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jpurchasedProduct, fid[0]))));
  51. purchasedProductDetails->SetOrderId(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jpurchasedProduct, fid[1]))));
  52. purchasedProductDetails->SetPackageName(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jpurchasedProduct, fid[2]))));
  53. purchasedProductDetails->SetPurchaseToken(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jpurchasedProduct, fid[3]))));
  54. purchasedProductDetails->SetPurchaseSignature(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jpurchasedProduct, fid[4]))));
  55. purchasedProductDetails->SetPurchaseTime(env->GetLongField(jpurchasedProduct, fid[5]));
  56. purchasedProductDetails->SetIsAutoRenewing(env->GetBooleanField(jpurchasedProduct, fid[6]));
  57. return purchasedProductDetails;
  58. }
  59. void ProductInfoRetrieved(JNIEnv* env, jobject obj, jobjectArray jproductDetails)
  60. {
  61. int numProducts = env->GetArrayLength(jproductDetails);
  62. InAppPurchasesInterface::GetInstance()->GetCache()->ClearCachedProductDetails();
  63. const int NUM_FIELDS_PRODUCTS = 7;
  64. jfieldID fid[NUM_FIELDS_PRODUCTS];
  65. jclass cls;
  66. if (numProducts > 0)
  67. {
  68. cls = env->GetObjectClass(env->GetObjectArrayElement(jproductDetails, 0));
  69. fid[0] = env->GetFieldID(cls, "m_productId", "Ljava/lang/String;");
  70. fid[1] = env->GetFieldID(cls, "m_type", "Ljava/lang/String;");
  71. fid[2] = env->GetFieldID(cls, "m_price", "Ljava/lang/String;");
  72. fid[3] = env->GetFieldID(cls, "m_currencyCode", "Ljava/lang/String;");
  73. fid[4] = env->GetFieldID(cls, "m_title", "Ljava/lang/String;");
  74. fid[5] = env->GetFieldID(cls, "m_description", "Ljava/lang/String;");
  75. fid[6] = env->GetFieldID(cls, "m_priceMicro", "J");
  76. }
  77. for (int i = 0; i < NUM_FIELDS_PRODUCTS; i++)
  78. {
  79. if (!IsFieldIdValid(fid[i]))
  80. {
  81. AZ_TracePrintf("LumberyardInAppBilling", "Invaild FieldId in ProductDetails\n");
  82. return;
  83. }
  84. }
  85. for (int i = 0; i < numProducts; i++)
  86. {
  87. jobject jproduct = env->GetObjectArrayElement(jproductDetails, i);
  88. ProductDetailsAndroid* productDetails = new ProductDetailsAndroid();
  89. productDetails->SetProductId(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[0]))));
  90. productDetails->SetProductType(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[1]))));
  91. productDetails->SetProductPrice(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[2]))));
  92. productDetails->SetProductCurrencyCode(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[3]))));
  93. productDetails->SetProductTitle(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[4]))));
  94. productDetails->SetProductDescription(AZ::Android::JNI::ConvertJstringToString(static_cast<jstring>(env->GetObjectField(jproduct, fid[5]))));
  95. productDetails->SetProductPriceMicro(env->GetLongField(jproduct, fid[6]));
  96. InAppPurchasesInterface::GetInstance()->GetCache()->AddProductDetailsToCache(productDetails);
  97. }
  98. EBUS_EVENT(InAppPurchasesResponseBus, ProductInfoRetrieved, InAppPurchasesInterface::GetInstance()->GetCache()->GetCachedProductDetails());
  99. }
  100. void PurchasedProductsRetrieved(JNIEnv* env, jobject object, jobjectArray jpurchasedProductDetails)
  101. {
  102. InAppPurchasesInterface::GetInstance()->GetCache()->ClearCachedPurchasedProductDetails();
  103. int numPurchasedProducts = env->GetArrayLength(jpurchasedProductDetails);
  104. for (int i = 0; i < numPurchasedProducts; i++)
  105. {
  106. PurchasedProductDetailsAndroid* purchasedProduct = ParseReceiptDetails(env, jpurchasedProductDetails, i);
  107. if (purchasedProduct != nullptr)
  108. {
  109. InAppPurchasesInterface::GetInstance()->GetCache()->AddPurchasedProductDetailsToCache(purchasedProduct);
  110. }
  111. }
  112. EBUS_EVENT(InAppPurchasesResponseBus, PurchasedProductsRetrieved, InAppPurchasesInterface::GetInstance()->GetCache()->GetCachedPurchasedProductDetails());
  113. }
  114. void NewProductPurchased(JNIEnv* env, jobject object, jobjectArray jpurchaseReceipt)
  115. {
  116. PurchasedProductDetailsAndroid* purchasedProduct = ParseReceiptDetails(env, jpurchaseReceipt, 0);
  117. if (purchasedProduct != nullptr)
  118. {
  119. InAppPurchasesInterface::GetInstance()->GetCache()->AddPurchasedProductDetailsToCache(purchasedProduct);
  120. EBUS_EVENT(InAppPurchasesResponseBus, NewProductPurchased, purchasedProduct);
  121. }
  122. }
  123. void PurchaseConsumed(JNIEnv* env, jobject object, jstring jpurchaseToken)
  124. {
  125. const char* purchaseToken = env->GetStringUTFChars(jpurchaseToken, nullptr);
  126. EBUS_EVENT(InAppPurchasesResponseBus, PurchaseConsumed, AZStd::string(purchaseToken, env->GetStringUTFLength(jpurchaseToken)));
  127. env->ReleaseStringUTFChars(jpurchaseToken, purchaseToken);
  128. }
  129. static JNINativeMethod methods[] = {
  130. { "nativeProductInfoRetrieved", "([Ljava/lang/Object;)V", (void*)ProductInfoRetrieved },
  131. { "nativePurchasedProductsRetrieved", "([Ljava/lang/Object;)V", (void*)PurchasedProductsRetrieved },
  132. { "nativeNewProductPurchased", "([Ljava/lang/Object;)V", (void*)NewProductPurchased },
  133. { "nativePurchaseConsumed", "(Ljava/lang/String;)V", (void*)PurchaseConsumed }
  134. };
  135. InAppPurchasesInterface* InAppPurchasesInterface::CreateInstance()
  136. {
  137. return new InAppPurchasesAndroid();
  138. }
  139. void InAppPurchasesAndroid::Initialize()
  140. {
  141. JNIEnv* env = AZ::Android::JNI::GetEnv();
  142. jobject activityObject = AZ::Android::Utils::GetActivityRef();
  143. jclass billingClass = AZ::Android::JNI::LoadClass("com/amazon/lumberyard/iap/LumberyardInAppBilling");
  144. jmethodID mid = env->GetMethodID(billingClass, "<init>", "(Landroid/app/Activity;)V");
  145. jobject billingInstance = env->NewObject(billingClass, mid, activityObject);
  146. m_billingInstance = env->NewGlobalRef(billingInstance);
  147. env->RegisterNatives(billingClass, methods, sizeof(methods) / sizeof(methods[0]));
  148. mid = env->GetMethodID(billingClass, "IsKindleDevice", "()Z");
  149. jboolean result = env->CallBooleanMethod(m_billingInstance, mid);
  150. if (result)
  151. {
  152. EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, "Kindle Device Detected", "IAP currently unsupported on Kindle devices", false);
  153. }
  154. env->DeleteGlobalRef(billingClass);
  155. env->DeleteLocalRef(billingInstance);
  156. }
  157. InAppPurchasesAndroid::~InAppPurchasesAndroid()
  158. {
  159. JNIEnv* env = AZ::Android::JNI::GetEnv();
  160. jclass billingClass = env->GetObjectClass(m_billingInstance);
  161. jmethodID mid = env->GetMethodID(billingClass, "UnbindService", "()V");
  162. env->CallVoidMethod(m_billingInstance, mid);
  163. env->DeleteLocalRef(billingClass);
  164. env->DeleteGlobalRef(m_billingInstance);
  165. }
  166. void InAppPurchasesAndroid::QueryProductInfo(AZStd::vector<AZStd::string>& productIds) const
  167. {
  168. JNIEnv* env = AZ::Android::JNI::GetEnv();
  169. jobjectArray jproductIds = static_cast<jobjectArray>(env->NewObjectArray(productIds.size(), env->FindClass("java/lang/String"), env->NewStringUTF("")));
  170. for (int i = 0; i < productIds.size(); i++)
  171. {
  172. env->SetObjectArrayElement(jproductIds, i, env->NewStringUTF(productIds[i].c_str()));
  173. }
  174. jclass billingClass = env->GetObjectClass(m_billingInstance);
  175. jmethodID mid = env->GetMethodID(billingClass, "QueryProductInfo", "([Ljava/lang/String;)V");
  176. env->CallVoidMethod(m_billingInstance, mid, jproductIds);
  177. env->DeleteLocalRef(jproductIds);
  178. env->DeleteLocalRef(billingClass);
  179. }
  180. void InAppPurchasesAndroid::QueryProductInfo() const
  181. {
  182. AZ::IO::FileIOBase* fileReader = AZ::IO::FileIOBase::GetInstance();
  183. AZStd::string fileBuffer;
  184. AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
  185. AZ::u64 fileSize = 0;
  186. if (!fileReader->Open("@products@/product_ids.json", AZ::IO::OpenMode::ModeRead, fileHandle))
  187. {
  188. AZ_TracePrintf("LumberyardInAppBilling", "Unable to open file product_ids.json\n");
  189. return;
  190. }
  191. if ((!fileReader->Size(fileHandle, fileSize)) || (fileSize == 0))
  192. {
  193. AZ_TracePrintf("LumberyardInAppBilling", "Unable to read file product_ids.json - file truncated\n");
  194. fileReader->Close(fileHandle);
  195. return;
  196. }
  197. fileBuffer.resize(fileSize);
  198. if (!fileReader->Read(fileHandle, fileBuffer.data(), fileSize, true))
  199. {
  200. fileBuffer.resize(0);
  201. fileReader->Close(fileHandle);
  202. AZ_TracePrintf("LumberyardInAppBilling", "Failed to read file product_ids.json\n");
  203. return;
  204. }
  205. fileReader->Close(fileHandle);
  206. rapidjson::Document document;
  207. document.Parse(fileBuffer.data());
  208. if (document.HasParseError())
  209. {
  210. [[maybe_unused]] const char* errorStr = rapidjson::GetParseError_En(document.GetParseError());
  211. AZ_TracePrintf("LumberyardInAppBilling", "Failed to parse product_ids.json: %s\n", errorStr);
  212. return;
  213. }
  214. const rapidjson::Value& productList = document["product_ids"];
  215. const auto& end = productList.End();
  216. AZStd::vector<AZStd::string> productIds;
  217. for (auto it = productList.Begin(); it != end; it++)
  218. {
  219. const auto& elem = *it;
  220. productIds.push_back(elem["id"].GetString());
  221. }
  222. QueryProductInfo(productIds);
  223. }
  224. void InAppPurchasesAndroid::PurchaseProduct(const AZStd::string& productId, const AZStd::string& developerPayload) const
  225. {
  226. JNIEnv* env = AZ::Android::JNI::GetEnv();
  227. const AZStd::vector <AZStd::unique_ptr<ProductDetails const> >& cachedProductDetails = InAppPurchasesInterface::GetInstance()->GetCache()->GetCachedProductDetails();
  228. AZStd::string productType = "";
  229. for (int i = 0; i < cachedProductDetails.size(); i++)
  230. {
  231. const ProductDetailsAndroid* productDetails = azrtti_cast<const ProductDetailsAndroid*>(cachedProductDetails[i].get());
  232. const AZStd::string& cachedProductId = productDetails->GetProductId();
  233. if (cachedProductId.compare(productId) == 0)
  234. {
  235. productType = productDetails->GetProductType();
  236. break;
  237. }
  238. }
  239. if (productType.empty())
  240. {
  241. AZ_TracePrintf("LumberyardInAppBilling", "Failed to find product with id: %s", productId.c_str());
  242. return;
  243. }
  244. jstring jproductId = env->NewStringUTF(productId.c_str());
  245. jstring jdeveloperPayload = env->NewStringUTF(developerPayload.c_str());
  246. jstring jproductType = env->NewStringUTF(productType.c_str());
  247. jclass billingClass = env->GetObjectClass(m_billingInstance);
  248. jmethodID mid = env->GetMethodID(billingClass, "PurchaseProduct", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
  249. env->CallVoidMethod(m_billingInstance, mid, jproductId, jdeveloperPayload, jproductType);
  250. env->DeleteLocalRef(jproductId);
  251. env->DeleteLocalRef(jdeveloperPayload);
  252. env->DeleteLocalRef(jproductType);
  253. env->DeleteLocalRef(billingClass);
  254. }
  255. void InAppPurchasesAndroid::PurchaseProduct(const AZStd::string& productId) const
  256. {
  257. PurchaseProduct(productId, "");
  258. }
  259. void InAppPurchasesAndroid::QueryPurchasedProducts() const
  260. {
  261. JNIEnv* env = AZ::Android::JNI::GetEnv();
  262. jclass billingClass = env->GetObjectClass(m_billingInstance);
  263. jmethodID mid = env->GetMethodID(billingClass, "QueryPurchasedProducts", "()V");
  264. env->CallVoidMethod(m_billingInstance, mid);
  265. env->DeleteLocalRef(billingClass);
  266. }
  267. void InAppPurchasesAndroid::RestorePurchasedProducts() const
  268. {
  269. }
  270. void InAppPurchasesAndroid::ConsumePurchase(const AZStd::string& purchaseToken) const
  271. {
  272. JNIEnv* env = AZ::Android::JNI::GetEnv();
  273. jclass billingClass = env->GetObjectClass(m_billingInstance);
  274. jmethodID mid = env->GetMethodID(billingClass, "ConsumePurchase", "(Ljava/lang/String;)V");
  275. jstring jpurchaseToken = env->NewStringUTF(purchaseToken.c_str());
  276. env->CallVoidMethod(m_billingInstance, mid, jpurchaseToken);
  277. env->DeleteLocalRef(jpurchaseToken);
  278. env->DeleteLocalRef(billingClass);
  279. }
  280. void InAppPurchasesAndroid::FinishTransaction(const AZStd::string& transactionId, bool downloadHostedContent) const
  281. {
  282. }
  283. InAppPurchasesCache* InAppPurchasesAndroid::GetCache()
  284. {
  285. return &m_cache;
  286. }
  287. }