From 8373c0792a91d2cd152efeba64a38a02d90cad66 Mon Sep 17 00:00:00 2001 From: Timo Schwarzer Date: Mon, 25 May 2020 12:44:08 +0200 Subject: [PATCH] Re-implement GodotPayment Android plugin using the Google Play Billing library --- .../java/plugins/godotpayment/build.gradle | 2 +- .../vending/billing/IInAppBillingService.aidl | 281 ------------ .../godot/plugin/payment/ConsumeTask.java | 118 ----- .../godot/plugin/payment/GodotPayment.java | 350 +++++++-------- .../plugin/payment/HandlePurchaseTask.java | 94 ---- .../godot/plugin/payment/PaymentsCache.java | 71 --- .../godot/plugin/payment/PaymentsManager.java | 408 ------------------ .../godot/plugin/payment/PurchaseTask.java | 119 ----- .../payment/ReleaseAllConsumablesTask.java | 144 ------- .../godot/plugin/payment/ValidateTask.java | 144 ------- .../payment/utils/CustomSSLSocketFactory.java | 72 ---- .../payment/utils/GodotPaymentUtils.java | 66 +++ .../plugin/payment/utils/HttpRequester.java | 231 ---------- .../plugin/payment/utils/RequestParams.java | 85 ---- platform/android/plugin/godot_plugin_config.h | 2 +- 15 files changed, 234 insertions(+), 1953 deletions(-) delete mode 100644 platform/android/java/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl delete mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java delete mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java delete mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java delete mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java delete mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java delete mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java delete mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java delete mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/CustomSSLSocketFactory.java create mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/GodotPaymentUtils.java delete mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/HttpRequester.java delete mode 100644 platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/RequestParams.java diff --git a/platform/android/java/plugins/godotpayment/build.gradle b/platform/android/java/plugins/godotpayment/build.gradle index ffab86e26e8..fb3aa8bba2b 100644 --- a/platform/android/java/plugins/godotpayment/build.gradle +++ b/platform/android/java/plugins/godotpayment/build.gradle @@ -3,7 +3,6 @@ apply plugin: 'com.android.library' android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools - useLibrary 'org.apache.http.legacy' defaultConfig { minSdkVersion versions.minSdk @@ -21,6 +20,7 @@ android { dependencies { implementation libraries.supportCoreUtils implementation libraries.v4Support + implementation 'com.android.billingclient:billing:2.2.1' if (rootProject.findProject(":lib")) { compileOnly project(":lib") diff --git a/platform/android/java/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl b/platform/android/java/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl deleted file mode 100644 index 0f2bcae3382..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.vending.billing; - -import android.os.Bundle; - -/** - * InAppBillingService is the service that provides in-app billing version 3 and beyond. - * This service provides the following features: - * 1. Provides a new API to get details of in-app items published for the app including - * price, type, title and description. - * 2. The purchase flow is synchronous and purchase information is available immediately - * after it completes. - * 3. Purchase information of in-app purchases is maintained within the Google Play system - * till the purchase is consumed. - * 4. An API to consume a purchase of an inapp item. All purchases of one-time - * in-app items are consumable and thereafter can be purchased again. - * 5. An API to get current purchases of the user immediately. This will not contain any - * consumed purchases. - * - * All calls will give a response code with the following possible values - * RESULT_OK = 0 - success - * RESULT_USER_CANCELED = 1 - User pressed back or canceled a dialog - * RESULT_SERVICE_UNAVAILABLE = 2 - The network connection is down - * RESULT_BILLING_UNAVAILABLE = 3 - This billing API version is not supported for the type requested - * RESULT_ITEM_UNAVAILABLE = 4 - Requested SKU is not available for purchase - * RESULT_DEVELOPER_ERROR = 5 - Invalid arguments provided to the API - * RESULT_ERROR = 6 - Fatal error during the API action - * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned - * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned - */ -interface IInAppBillingService { - /** - * Checks support for the requested billing API version, package and in-app type. - * Minimum API version supported by this interface is 3. - * @param apiVersion billing API version that the app is using - * @param packageName the package name of the calling app - * @param type type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @return RESULT_OK(0) on success and appropriate response code on failures. - */ - int isBillingSupported(int apiVersion, String packageName, String type); - - /** - * Provides details of a list of SKUs - * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle - * with a list JSON strings containing the productId, price, title and description. - * This API can be called with a maximum of 20 SKUs. - * @param apiVersion billing API version that the app is using - * @param packageName the package name of the calling app - * @param type of the in-app items ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "DETAILS_LIST" with a StringArrayList containing purchase information - * in JSON format similar to: - * '{ "productId" : "exampleSku", - * "type" : "inapp", - * "price" : "$5.00", - * "price_currency": "USD", - * "price_amount_micros": 5000000, - * "title : "Example Title", - * "description" : "This is an example description" }' - */ - Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle); - - /** - * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, - * the type, a unique purchase token and an optional developer payload. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response - * codes on failures. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - */ - Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type, - String developerPayload); - - /** - * Returns the current SKUs owned by the user of the type and package name specified along with - * purchase information and a signature of the data to be validated. - * This will return all SKUs that have been purchased in V3 and managed items purchased using - * V1 and V2 that have not been consumed. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param type of the in-app items being requested ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param continuationToken to be set as null for the first call, if the number of owned - * skus are too many, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - on failures. - * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken); - - /** - * Consume the last purchase of the given SKU. This will result in this item being removed - * from all subsequent responses to getPurchases() and allow re-purchase of this item. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param purchaseToken token in the purchase information JSON that identifies the purchase - * to be consumed - * @return RESULT_OK(0) if consumption succeeded, appropriate response codes on failures. - */ - int consumePurchase(int apiVersion, String packageName, String purchaseToken); - - /** - * This API is currently under development. - */ - int stub(int apiVersion, String packageName, String type); - - /** - * Returns a pending intent to launch the purchase flow for upgrading or downgrading a - * subscription. The existing owned SKU(s) should be provided along with the new SKU that - * the user is upgrading or downgrading to. - * @param apiVersion billing API version that the app is using, must be 5 or later - * @param packageName package name of the calling app - * @param oldSkus the SKU(s) that the user is upgrading or downgrading from, - * if null or empty this method will behave like {@link #getBuyIntent} - * @param newSku the SKU that the user is upgrading or downgrading to - * @param type of the item being purchased, currently must be "subs" - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response - * codes on failures. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - */ - Bundle getBuyIntentToReplaceSkus(int apiVersion, String packageName, - in List oldSkus, String newSku, String type, String developerPayload); - - /** - * Returns a pending intent to launch the purchase flow for an in-app item. This method is - * a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams} - * parameter. This parameter is a Bundle of optional keys and values that affect the - * operation of the method. - * @param apiVersion billing API version that the app is using, must be 6 or later - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param developerPayload optional argument to be sent back with the purchase information - * @extraParams a Bundle with the following optional keys: - * "skusToReplace" - List - an optional list of SKUs that the user is - * upgrading or downgrading from. - * Pass this field if the purchase is upgrading or downgrading - * existing subscriptions. - * The specified SKUs are replaced with the SKUs that the user is - * purchasing. Google Play replaces the specified SKUs at the start of - * the next billing cycle. - * "replaceSkusProration" - Boolean - whether the user should be credited for any unused - * subscription time on the SKUs they are upgrading or downgrading. - * If you set this field to true, Google Play swaps out the old SKUs - * and credits the user with the unused value of their subscription - * time on a pro-rated basis. - * Google Play applies this credit to the new subscription, and does - * not begin billing the user for the new subscription until after - * the credit is used up. - * If you set this field to false, the user does not receive credit for - * any unused subscription time and the recurrence date does not - * change. - * Default value is true. Ignored if you do not pass skusToReplace. - * "accountId" - String - an optional obfuscated string that is uniquely - * associated with the user's account in your app. - * If you pass this value, Google Play can use it to detect irregular - * activity, such as many devices making purchases on the same - * account in a short period of time. - * Do not use the developer ID or the user's Google ID for this field. - * In addition, this field should not contain the user's ID in - * cleartext. - * We recommend that you use a one-way hash to generate a string from - * the user's ID, and store the hashed string in this field. - * "vr" - Boolean - an optional flag indicating whether the returned intent - * should start a VR purchase flow. The apiVersion must also be 7 or - * later to use this flag. - */ - Bundle getBuyIntentExtraParams(int apiVersion, String packageName, String sku, - String type, String developerPayload, in Bundle extraParams); - - /** - * Returns the most recent purchase made by the user for each SKU, even if that purchase is - * expired, canceled, or consumed. - * @param apiVersion billing API version that the app is using, must be 6 or later - * @param packageName package name of the calling app - * @param type of the in-app items being requested ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param continuationToken to be set as null for the first call, if the number of owned - * skus is too large, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @param extraParams a Bundle with extra params that would be appended into http request - * query string. Not used at this moment. Reserved for future functionality. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value: RESULT_OK(0) if success, - * {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures. - * - * "INAPP_PURCHASE_ITEM_LIST" - ArrayList containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - ArrayList containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- ArrayList containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - Bundle getPurchaseHistory(int apiVersion, String packageName, String type, - String continuationToken, in Bundle extraParams); - - /** - * This method is a variant of {@link #isBillingSupported}} that takes an additional - * {@code extraParams} parameter. - * @param apiVersion billing API version that the app is using, must be 7 or later - * @param packageName package name of the calling app - * @param type of the in-app item being purchased ("inapp" for one-time purchases and "subs" - * for subscriptions) - * @param extraParams a Bundle with the following optional keys: - * "vr" - Boolean - an optional flag to indicate whether {link #getBuyIntentExtraParams} - * supports returning a VR purchase flow. - * @return RESULT_OK(0) on success and appropriate response code on failures. - */ - int isBillingSupportedExtraParams(int apiVersion, String packageName, String type, - in Bundle extraParams); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java deleted file mode 100644 index 31c72fe7f85..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java +++ /dev/null @@ -1,118 +0,0 @@ -/*************************************************************************/ -/* ConsumeTask.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.RemoteException; - -import com.android.vending.billing.IInAppBillingService; - -import java.lang.ref.WeakReference; - -abstract public class ConsumeTask { - - private Context context; - private IInAppBillingService mService; - - private String mSku; - private String mToken; - - private static class ConsumeAsyncTask extends AsyncTask { - - private WeakReference mTask; - - ConsumeAsyncTask(ConsumeTask consume) { - mTask = new WeakReference<>(consume); - } - - @Override - protected String doInBackground(String... strings) { - ConsumeTask consume = mTask.get(); - if (consume != null) { - return consume.doInBackground(strings); - } - return null; - } - - @Override - protected void onPostExecute(String param) { - ConsumeTask consume = mTask.get(); - if (consume != null) { - consume.onPostExecute(param); - } - } - } - - public ConsumeTask(IInAppBillingService mService, Context context) { - this.context = context; - this.mService = mService; - } - - public void consume(final String sku) { - mSku = sku; - PaymentsCache pc = new PaymentsCache(context); - Boolean isBlocked = pc.getConsumableFlag("block", sku); - mToken = pc.getConsumableValue("token", sku); - if (!isBlocked && mToken == null) { - // Consuming task is processing - } else if (!isBlocked) { - return; - } else if (mToken == null) { - this.error("No token for sku:" + sku); - return; - } - new ConsumeAsyncTask(this).execute(); - } - - private String doInBackground(String... params) { - try { - int response = mService.consumePurchase(3, context.getPackageName(), mToken); - if (response == 0 || response == 8) { - return null; - } - } catch (RemoteException e) { - return e.getMessage(); - } - return "Some error"; - } - - private void onPostExecute(String param) { - if (param == null) { - success(new PaymentsCache(context).getConsumableValue("ticket", mSku)); - } else { - error(param); - } - } - - abstract protected void success(String ticket); - abstract protected void error(String message); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java index e2b12c94a48..4b273f1278b 100644 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java @@ -32,214 +32,178 @@ package org.godotengine.godot.plugin.payment; import org.godotengine.godot.Dictionary; import org.godotengine.godot.Godot; -import org.godotengine.godot.GodotLib; import org.godotengine.godot.plugin.GodotPlugin; - -import android.content.Intent; -import android.util.Log; +import org.godotengine.godot.plugin.SignalInfo; +import org.godotengine.godot.plugin.payment.utils.GodotPaymentUtils; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.collection.ArraySet; + +import com.android.billingclient.api.AcknowledgePurchaseParams; +import com.android.billingclient.api.AcknowledgePurchaseResponseListener; +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingClientStateListener; +import com.android.billingclient.api.BillingFlowParams; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.ConsumeParams; +import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.PurchasesUpdatedListener; +import com.android.billingclient.api.SkuDetails; +import com.android.billingclient.api.SkuDetailsParams; +import com.android.billingclient.api.SkuDetailsResponseListener; -import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Set; -import org.json.JSONException; -import org.json.JSONObject; +public class GodotPayment extends GodotPlugin implements PurchasesUpdatedListener, BillingClientStateListener { -public class GodotPayment extends GodotPlugin { - private Long purchaseCallbackId = 0L; - private String accessToken; - private String purchaseValidationUrlPrefix; - private String transactionId; - private final PaymentsManager mPaymentManager; - private final Dictionary mSkuDetails = new Dictionary(); + private final BillingClient billingClient; + private final HashMap skuDetailsCache = new HashMap<>(); // sku → SkuDetails public GodotPayment(Godot godot) { super(godot); - mPaymentManager = new PaymentsManager(godot, this); - mPaymentManager.initService(); + + billingClient = BillingClient + .newBuilder(getGodot()) + .enablePendingPurchases() + .setListener(this) + .build(); } - @Override - public void onMainActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE) { - mPaymentManager.processPurchaseResponse(resultCode, data); - } + public void startConnection() { + billingClient.startConnection(this); } - @Override - public void onMainDestroy() { - super.onMainDestroy(); - if (mPaymentManager != null) { - mPaymentManager.destroy(); - } + public void endConnection() { + billingClient.endConnection(); } - public void purchase(final String sku, final String transactionId) { - runOnUiThread(new Runnable() { - @Override - public void run() { - mPaymentManager.requestPurchase(sku, transactionId); - } - }); + public boolean isReady() { + return this.billingClient.isReady(); } - public void consumeUnconsumedPurchases() { - runOnUiThread(new Runnable() { - @Override - public void run() { - mPaymentManager.consumeUnconsumedPurchases(); - } - }); - } + public Dictionary queryPurchases(String type) { + Purchase.PurchasesResult result = billingClient.queryPurchases(type); - private String signature; - - public String getSignature() { - return this.signature; - } - - public void callbackSuccess(String ticket, String signature, String sku) { - GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku }); - } - - public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku) { - Log.d(this.getClass().getName(), "callbackSuccessProductMassConsumed > " + ticket + "," + signature + "," + sku); - GodotLib.calldeferred(purchaseCallbackId, "consume_success", new Object[] { ticket, signature, sku }); - } - - public void callbackSuccessNoUnconsumedPurchases() { - GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[] {}); - } - - public void callbackFailConsume(String message) { - GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[] { message }); - } - - public void callbackFail(String message) { - GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[] { message }); - } - - public void callbackCancel() { - GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[] {}); - } - - public void callbackAlreadyOwned(String sku) { - GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[] { sku }); - } - - public long getPurchaseCallbackId() { - return purchaseCallbackId; - } - - public void setPurchaseCallbackId(long purchaseCallbackId) { - this.purchaseCallbackId = purchaseCallbackId; - } - - public String getPurchaseValidationUrlPrefix() { - return this.purchaseValidationUrlPrefix; - } - - public void setPurchaseValidationUrlPrefix(String url) { - this.purchaseValidationUrlPrefix = url; - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public void setTransactionId(String transactionId) { - this.transactionId = transactionId; - } - - public String getTransactionId() { - return this.transactionId; - } - - // request purchased items are not consumed - public void requestPurchased() { - runOnUiThread(new Runnable() { - @Override - public void run() { - mPaymentManager.requestPurchased(); - } - }); - } - - // callback for requestPurchased() - public void callbackPurchased(String receipt, String signature, String sku) { - GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[] { receipt, signature, sku }); - } - - public void callbackDisconnected() { - GodotLib.calldeferred(purchaseCallbackId, "iap_disconnected", new Object[] {}); - } - - public void callbackConnected() { - GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {}); - } - - // true if connected, false otherwise - public boolean isConnected() { - return mPaymentManager.isConnected(); - } - - // consume item automatically after purchase. default is true. - public void setAutoConsume(boolean autoConsume) { - mPaymentManager.setAutoConsume(autoConsume); - } - - // consume a specific item - public void consume(String sku) { - mPaymentManager.consume(sku); - } - - // query in app item detail info - public void querySkuDetails(String[] list) { - List nKeys = Arrays.asList(list); - List cKeys = Arrays.asList(mSkuDetails.get_keys()); - ArrayList fKeys = new ArrayList(); - for (String key : nKeys) { - if (!cKeys.contains(key)) { - fKeys.add(key); - } - } - if (fKeys.size() > 0) { - mPaymentManager.querySkuDetails(fKeys.toArray(new String[0])); + Dictionary returnValue = new Dictionary(); + if (result.getBillingResult().getResponseCode() == BillingClient.BillingResponseCode.OK) { + returnValue.put("status", 0); // OK = 0 + returnValue.put("purchases", GodotPaymentUtils.convertPurchaseListToDictionaryObjectArray(result.getPurchasesList())); } else { - completeSkuDetail(); + returnValue.put("status", 1); // FAILED = 1 + returnValue.put("response_code", result.getBillingResult().getResponseCode()); + returnValue.put("debug_message", result.getBillingResult().getDebugMessage()); + } + + return returnValue; + } + + public void querySkuDetails(final String[] list, String type) { + List skuList = Arrays.asList(list); + + SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder() + .setSkusList(skuList) + .setType(type); + + billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(BillingResult billingResult, + List skuDetailsList) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + for (SkuDetails skuDetails : skuDetailsList) { + skuDetailsCache.put(skuDetails.getSku(), skuDetails); + } + emitSignal("sku_details_query_completed", (Object)GodotPaymentUtils.convertSkuDetailsListToDictionaryObjectArray(skuDetailsList)); + } else { + emitSignal("sku_details_query_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), list); + } + } + }); + } + + public void acknowledgePurchase(final String purchaseToken) { + AcknowledgePurchaseParams acknowledgePurchaseParams = + AcknowledgePurchaseParams.newBuilder() + .setPurchaseToken(purchaseToken) + .build(); + billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() { + @Override + public void onAcknowledgePurchaseResponse(BillingResult billingResult) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + emitSignal("purchase_acknowledged", purchaseToken); + } else { + emitSignal("purchase_acknowledgement_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken); + } + } + }); + } + + public void consumePurchase(String purchaseToken) { + ConsumeParams consumeParams = ConsumeParams.newBuilder() + .setPurchaseToken(purchaseToken) + .build(); + + billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() { + @Override + public void onConsumeResponse(BillingResult billingResult, String purchaseToken) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + emitSignal("purchase_consumed", purchaseToken); + } else { + emitSignal("purchase_consumption_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken); + } + } + }); + } + + @Override + public void onBillingSetupFinished(BillingResult billingResult) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + emitSignal("connected"); + } else { + emitSignal("connect_error", billingResult.getResponseCode(), billingResult.getDebugMessage()); } } - public void addSkuDetail(String itemJson) { - JSONObject o = null; - try { - o = new JSONObject(itemJson); - Dictionary item = new Dictionary(); - item.put("type", o.optString("type")); - item.put("product_id", o.optString("productId")); - item.put("title", o.optString("title")); - item.put("description", o.optString("description")); - item.put("price", o.optString("price")); - item.put("price_currency_code", o.optString("price_currency_code")); - item.put("price_amount", 0.000001d * o.optLong("price_amount_micros")); - mSkuDetails.put(item.get("product_id").toString(), item); - } catch (JSONException e) { - e.printStackTrace(); + @Override + public void onBillingServiceDisconnected() { + emitSignal("disconnected"); + } + + public Dictionary purchase(String sku) { + if (!skuDetailsCache.containsKey(sku)) { + emitSignal("purchase_error", null, "You must query the sku details and wait for the result before purchasing!"); } + + SkuDetails skuDetails = skuDetailsCache.get(sku); + BillingFlowParams purchaseParams = BillingFlowParams.newBuilder() + .setSkuDetails(skuDetails) + .build(); + + BillingResult result = billingClient.launchBillingFlow(getGodot(), purchaseParams); + + Dictionary returnValue = new Dictionary(); + if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { + returnValue.put("status", 0); // OK = 0 + } else { + returnValue.put("status", 1); // FAILED = 1 + returnValue.put("response_code", result.getResponseCode()); + returnValue.put("debug_message", result.getDebugMessage()); + } + + return returnValue; } - public void completeSkuDetail() { - GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails }); - } - - public void errorSkuDetail(String errorMessage) { - GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[] { errorMessage }); + @Override + public void onPurchasesUpdated(final BillingResult billingResult, @Nullable final List list) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) { + emitSignal("purchases_updated", (Object)GodotPaymentUtils.convertPurchaseListToDictionaryObjectArray(list)); + } else { + emitSignal("purchase_error", billingResult.getResponseCode(), billingResult.getDebugMessage()); + } } @NonNull @@ -251,8 +215,26 @@ public class GodotPayment extends GodotPlugin { @NonNull @Override public List getPluginMethods() { - return Arrays.asList("purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", - "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", - "setAutoConsume", "consume", "querySkuDetails", "isConnected"); + return Arrays.asList("startConnection", "endConnection", "purchase", "querySkuDetails", "isReady", "queryPurchases", "acknowledgePurchase"); + } + + @NonNull + @Override + public Set getPluginSignals() { + Set signals = new ArraySet<>(); + + signals.add(new SignalInfo("connected")); + signals.add(new SignalInfo("disconnected")); + signals.add(new SignalInfo("connect_error", Integer.class, String.class)); + signals.add(new SignalInfo("purchases_updated", Object[].class)); + signals.add(new SignalInfo("purchase_error", Integer.class, String.class)); + signals.add(new SignalInfo("sku_details_query_completed", Object[].class)); + signals.add(new SignalInfo("sku_details_query_error", Integer.class, String.class, String[].class)); + signals.add(new SignalInfo("purchase_acknowledged", String.class)); + signals.add(new SignalInfo("purchase_acknowledgement_error", Integer.class, String.class, String.class)); + signals.add(new SignalInfo("purchase_consumed", String.class)); + signals.add(new SignalInfo("purchase_consumption_error", Integer.class, String.class, String.class)); + + return signals; } } diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java deleted file mode 100644 index 70a51fcb97f..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java +++ /dev/null @@ -1,94 +0,0 @@ -/*************************************************************************/ -/* HandlePurchaseTask.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.app.Activity; -import android.content.Intent; - -import org.json.JSONException; -import org.json.JSONObject; - -abstract public class HandlePurchaseTask { - - private Activity context; - - public HandlePurchaseTask(Activity context) { - this.context = context; - } - - public void handlePurchaseRequest(int resultCode, Intent data) { - //Log.d("XXX", "Handling purchase response"); - if (resultCode == Activity.RESULT_OK) { - try { - //int responseCode = data.getIntExtra("RESPONSE_CODE", 0); - PaymentsCache pc = new PaymentsCache(context); - - String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); - //Log.d("XXX", "Purchase data:" + purchaseData); - String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); - //Log.d("XXX", "Purchase signature:" + dataSignature); - //Log.d("SARLANGA", purchaseData); - - JSONObject jo = new JSONObject(purchaseData); - //String sku = jo.getString("productId"); - //alert("You have bought the " + sku + ". Excellent choice, aventurer!"); - //String orderId = jo.getString("orderId"); - //String packageName = jo.getString("packageName"); - String productId = jo.getString("productId"); - //Long purchaseTime = jo.getLong("purchaseTime"); - //Integer state = jo.getInt("purchaseState"); - String developerPayload = jo.getString("developerPayload"); - String purchaseToken = jo.getString("purchaseToken"); - - if (!pc.getConsumableValue("validation_hash", productId).equals(developerPayload)) { - error("Untrusted callback"); - return; - } - //Log.d("XXX", "Este es el product ID:" + productId); - pc.setConsumableValue("ticket_signautre", productId, dataSignature); - pc.setConsumableValue("ticket", productId, purchaseData); - pc.setConsumableFlag("block", productId, true); - pc.setConsumableValue("token", productId, purchaseToken); - - success(productId, dataSignature, purchaseData); - return; - } catch (JSONException e) { - error(e.getMessage()); - } - } else if (resultCode == Activity.RESULT_CANCELED) { - canceled(); - } - } - - abstract protected void success(String sku, String signature, String ticket); - abstract protected void error(String message); - abstract protected void canceled(); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java deleted file mode 100644 index d5919e3d9db..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java +++ /dev/null @@ -1,71 +0,0 @@ -/*************************************************************************/ -/* PaymentsCache.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.content.Context; -import android.content.SharedPreferences; - -public class PaymentsCache { - - public Context context; - - public PaymentsCache(Context context) { - this.context = context; - } - - public void setConsumableFlag(String set, String sku, Boolean flag) { - SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putBoolean(sku, flag); - editor.apply(); - } - - public boolean getConsumableFlag(String set, String sku) { - SharedPreferences sharedPref = context.getSharedPreferences( - "consumables_" + set, Context.MODE_PRIVATE); - return sharedPref.getBoolean(sku, false); - } - - public void setConsumableValue(String set, String sku, String value) { - SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putString(sku, value); - //Log.d("XXX", "Setting asset: consumables_" + set + ":" + sku); - editor.apply(); - } - - public String getConsumableValue(String set, String sku) { - SharedPreferences sharedPref = context.getSharedPreferences( - "consumables_" + set, Context.MODE_PRIVATE); - //Log.d("XXX", "Getting asset: consumables_" + set + ":" + sku); - return sharedPref.getString(sku, null); - } -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java deleted file mode 100644 index 0393c0b06e3..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java +++ /dev/null @@ -1,408 +0,0 @@ -/*************************************************************************/ -/* PaymentsManager.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.text.TextUtils; -import android.util.Log; - -import com.android.vending.billing.IInAppBillingService; - -import java.util.ArrayList; -import java.util.Arrays; - -import org.json.JSONException; -import org.json.JSONObject; - -public class PaymentsManager { - - public static final int BILLING_RESPONSE_RESULT_OK = 0; - public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001; - private static boolean auto_consume = true; - - private final Activity activity; - private final GodotPayment godotPayment; - IInAppBillingService mService; - - PaymentsManager(Activity activity, GodotPayment godotPayment) { - this.activity = activity; - this.godotPayment = godotPayment; - } - - public PaymentsManager initService() { - Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); - intent.setPackage("com.android.vending"); - activity.bindService( - intent, - mServiceConn, - Context.BIND_AUTO_CREATE); - return this; - } - - public void destroy() { - if (mService != null) { - activity.unbindService(mServiceConn); - } - } - - ServiceConnection mServiceConn = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) { - mService = null; - - // At this stage, godotPayment might not have been initialized yet. - if (godotPayment != null) { - godotPayment.callbackDisconnected(); - } - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - mService = IInAppBillingService.Stub.asInterface(service); - - // At this stage, godotPayment might not have been initialized yet. - if (godotPayment != null) { - godotPayment.callbackConnected(); - } - } - }; - - public void requestPurchase(final String sku, String transactionId) { - new PurchaseTask(mService, activity) { - @Override - protected void error(String message) { - godotPayment.callbackFail(message); - } - - @Override - protected void canceled() { - godotPayment.callbackCancel(); - } - - @Override - protected void alreadyOwned() { - godotPayment.callbackAlreadyOwned(sku); - } - } - .purchase(sku, transactionId); - } - - public boolean isConnected() { - return mService != null; - } - - public void consumeUnconsumedPurchases() { - new ReleaseAllConsumablesTask(mService, activity) { - @Override - protected void success(String sku, String receipt, String signature, String token) { - godotPayment.callbackSuccessProductMassConsumed(receipt, signature, sku); - } - - @Override - protected void error(String message) { - Log.d("godot", "consumeUnconsumedPurchases :" + message); - godotPayment.callbackFailConsume(message); - } - - @Override - protected void notRequired() { - Log.d("godot", "callbackSuccessNoUnconsumedPurchases :"); - godotPayment.callbackSuccessNoUnconsumedPurchases(); - } - } - .consumeItAll(); - } - - public void requestPurchased() { - try { - PaymentsCache pc = new PaymentsCache(activity); - - String continueToken = null; - - do { - Bundle bundle = mService.getPurchases(3, activity.getPackageName(), "inapp", continueToken); - - if (bundle.getInt("RESPONSE_CODE") == 0) { - - final ArrayList myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); - final ArrayList mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); - - if (myPurchases == null || myPurchases.size() == 0) { - godotPayment.callbackPurchased("", "", ""); - return; - } - - for (int i = 0; i < myPurchases.size(); i++) { - - try { - String receipt = myPurchases.get(i); - JSONObject inappPurchaseData = new JSONObject(receipt); - String sku = inappPurchaseData.getString("productId"); - String token = inappPurchaseData.getString("purchaseToken"); - String signature = mySignatures.get(i); - - pc.setConsumableValue("ticket_signautre", sku, signature); - pc.setConsumableValue("ticket", sku, receipt); - pc.setConsumableFlag("block", sku, true); - pc.setConsumableValue("token", sku, token); - - godotPayment.callbackPurchased(receipt, signature, sku); - } catch (JSONException e) { - } - } - } - continueToken = bundle.getString("INAPP_CONTINUATION_TOKEN"); - Log.d("godot", "continue token = " + continueToken); - } while (!TextUtils.isEmpty(continueToken)); - } catch (Exception e) { - Log.d("godot", "Error requesting purchased products:" + e.getClass().getName() + ":" + e.getMessage()); - } - } - - public void processPurchaseResponse(int resultCode, Intent data) { - new HandlePurchaseTask(activity) { - @Override - protected void success(final String sku, final String signature, final String ticket) { - godotPayment.callbackSuccess(ticket, signature, sku); - - if (auto_consume) { - new ConsumeTask(mService, activity) { - @Override - protected void success(String ticket) { - } - - @Override - protected void error(String message) { - godotPayment.callbackFail(message); - } - } - .consume(sku); - } - } - - @Override - protected void error(String message) { - godotPayment.callbackFail(message); - } - - @Override - protected void canceled() { - godotPayment.callbackCancel(); - } - } - .handlePurchaseRequest(resultCode, data); - } - - public void validatePurchase(String purchaseToken, final String sku) { - - new ValidateTask(activity, godotPayment) { - @Override - protected void success() { - - new ConsumeTask(mService, activity) { - @Override - protected void success(String ticket) { - godotPayment.callbackSuccess(ticket, null, sku); - } - - @Override - protected void error(String message) { - godotPayment.callbackFail(message); - } - } - .consume(sku); - } - - @Override - protected void error(String message) { - godotPayment.callbackFail(message); - } - - @Override - protected void canceled() { - godotPayment.callbackCancel(); - } - } - .validatePurchase(sku); - } - - public void setAutoConsume(boolean autoConsume) { - auto_consume = autoConsume; - } - - public void consume(final String sku) { - new ConsumeTask(mService, activity) { - @Override - protected void success(String ticket) { - godotPayment.callbackSuccessProductMassConsumed(ticket, "", sku); - } - - @Override - protected void error(String message) { - godotPayment.callbackFailConsume(message); - } - } - .consume(sku); - } - - // Workaround to bug where sometimes response codes come as Long instead of Integer - int getResponseCodeFromBundle(Bundle b) { - Object o = b.get("RESPONSE_CODE"); - if (o == null) { - //logDebug("Bundle with null response code, assuming OK (known issue)"); - return BILLING_RESPONSE_RESULT_OK; - } else if (o instanceof Integer) - return ((Integer)o).intValue(); - else if (o instanceof Long) - return (int)((Long)o).longValue(); - else { - //logError("Unexpected type for bundle response code."); - //logError(o.getClass().getName()); - throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName()); - } - } - - /** - * Returns a human-readable description for the given response code. - * - * @param code The response code - * @return A human-readable string explaining the result code. - * It also includes the result code numerically. - */ - public static String getResponseDesc(int code) { - String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" - + - "3:Billing Unavailable/4:Item unavailable/" - + - "5:Developer Error/6:Error/7:Item Already Owned/" - + - "8:Item not owned") - .split("/"); - String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" - + - "-1002:Bad response received/" - + - "-1003:Purchase signature verification failed/" - + - "-1004:Send intent failed/" - + - "-1005:User cancelled/" - + - "-1006:Unknown purchase response/" - + - "-1007:Missing token/" - + - "-1008:Unknown error/" - + - "-1009:Subscriptions not available/" - + - "-1010:Invalid consumption attempt") - .split("/"); - - if (code <= -1000) { - int index = -1000 - code; - if (index >= 0 && index < iabhelper_msgs.length) - return iabhelper_msgs[index]; - else - return String.valueOf(code) + ":Unknown IAB Helper Error"; - } else if (code < 0 || code >= iab_msgs.length) - return String.valueOf(code) + ":Unknown"; - else - return iab_msgs[code]; - } - - public void querySkuDetails(final String[] list) { - (new Thread(new Runnable() { - @Override - public void run() { - ArrayList skuList = new ArrayList(Arrays.asList(list)); - if (skuList.size() == 0) { - return; - } - // Split the sku list in blocks of no more than 20 elements. - ArrayList> packs = new ArrayList>(); - ArrayList tempList; - int n = skuList.size() / 20; - int mod = skuList.size() % 20; - for (int i = 0; i < n; i++) { - tempList = new ArrayList(); - for (String s : skuList.subList(i * 20, i * 20 + 20)) { - tempList.add(s); - } - packs.add(tempList); - } - if (mod != 0) { - tempList = new ArrayList(); - for (String s : skuList.subList(n * 20, n * 20 + mod)) { - tempList.add(s); - } - packs.add(tempList); - } - for (ArrayList skuPartList : packs) { - Bundle querySkus = new Bundle(); - querySkus.putStringArrayList("ITEM_ID_LIST", skuPartList); - Bundle skuDetails = null; - try { - skuDetails = mService.getSkuDetails(3, activity.getPackageName(), "inapp", querySkus); - if (!skuDetails.containsKey("DETAILS_LIST")) { - int response = getResponseCodeFromBundle(skuDetails); - if (response != BILLING_RESPONSE_RESULT_OK) { - godotPayment.errorSkuDetail(getResponseDesc(response)); - } else { - godotPayment.errorSkuDetail("No error but no detail list."); - } - return; - } - - ArrayList responseList = skuDetails.getStringArrayList("DETAILS_LIST"); - - for (String thisResponse : responseList) { - Log.d("godot", "response = " + thisResponse); - godotPayment.addSkuDetail(thisResponse); - } - } catch (RemoteException e) { - e.printStackTrace(); - godotPayment.errorSkuDetail("RemoteException error!"); - } - } - godotPayment.completeSkuDetail(); - } - })) - .start(); - } -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java deleted file mode 100644 index 4894e4020fe..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java +++ /dev/null @@ -1,119 +0,0 @@ -/*************************************************************************/ -/* PurchaseTask.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.Intent; -import android.content.IntentSender.SendIntentException; -import android.os.Bundle; -import android.os.RemoteException; -import android.util.Log; - -import com.android.vending.billing.IInAppBillingService; - -abstract public class PurchaseTask { - - private Activity context; - - private IInAppBillingService mService; - public PurchaseTask(IInAppBillingService mService, Activity context) { - this.context = context; - this.mService = mService; - } - - private boolean isLooping = false; - - public void purchase(final String sku, final String transactionId) { - Log.d("XXX", "Starting purchase for: " + sku); - PaymentsCache pc = new PaymentsCache(context); - Boolean isBlocked = pc.getConsumableFlag("block", sku); - /* - if(isBlocked){ - Log.d("XXX", "Is awaiting payment confirmation"); - error("Awaiting payment confirmation"); - return; - } - */ - final String hash = transactionId; - - Bundle buyIntentBundle; - try { - buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, "inapp", hash); - } catch (RemoteException e) { - //Log.d("XXX", "Error: " + e.getMessage()); - error(e.getMessage()); - return; - } - Object rc = buyIntentBundle.get("RESPONSE_CODE"); - int responseCode = 0; - if (rc == null) { - responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK; - } else if (rc instanceof Integer) { - responseCode = ((Integer)rc).intValue(); - } else if (rc instanceof Long) { - responseCode = (int)((Long)rc).longValue(); - } - //Log.d("XXX", "Buy intent response code: " + responseCode); - if (responseCode == 1 || responseCode == 3 || responseCode == 4) { - canceled(); - return; - } - if (responseCode == 7) { - alreadyOwned(); - return; - } - - PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT"); - pc.setConsumableValue("validation_hash", sku, hash); - try { - if (context == null) { - //Log.d("XXX", "No context!"); - } - if (pendingIntent == null) { - //Log.d("XXX", "No pending intent"); - } - //Log.d("XXX", "Starting activity for purchase!"); - context.startIntentSenderForResult( - pendingIntent.getIntentSender(), - PaymentsManager.REQUEST_CODE_FOR_PURCHASE, - new Intent(), - Integer.valueOf(0), Integer.valueOf(0), - Integer.valueOf(0)); - } catch (SendIntentException e) { - error(e.getMessage()); - } - } - - abstract protected void error(String message); - abstract protected void canceled(); - abstract protected void alreadyOwned(); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java deleted file mode 100644 index 006688a4501..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java +++ /dev/null @@ -1,144 +0,0 @@ -/*************************************************************************/ -/* ReleaseAllConsumablesTask.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import android.content.Context; -import android.os.AsyncTask; -import android.os.Bundle; -import android.util.Log; - -import com.android.vending.billing.IInAppBillingService; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; - -import org.json.JSONException; -import org.json.JSONObject; - -abstract public class ReleaseAllConsumablesTask { - - private Context context; - private IInAppBillingService mService; - - private static class ReleaseAllConsumablesAsyncTask extends AsyncTask { - - private WeakReference mTask; - private String mSku; - private String mReceipt; - private String mSignature; - private String mToken; - - ReleaseAllConsumablesAsyncTask(ReleaseAllConsumablesTask task, String sku, String receipt, String signature, String token) { - mTask = new WeakReference(task); - - mSku = sku; - mReceipt = receipt; - mSignature = signature; - mToken = token; - } - - @Override - protected String doInBackground(String... params) { - ReleaseAllConsumablesTask consume = mTask.get(); - if (consume != null) { - return consume.doInBackground(mToken); - } - return null; - } - - @Override - protected void onPostExecute(String param) { - ReleaseAllConsumablesTask consume = mTask.get(); - if (consume != null) { - consume.success(mSku, mReceipt, mSignature, mToken); - } - } - } - - public ReleaseAllConsumablesTask(IInAppBillingService mService, Context context) { - this.context = context; - this.mService = mService; - } - - public void consumeItAll() { - try { - //Log.d("godot", "consumeItall for " + context.getPackageName()); - Bundle bundle = mService.getPurchases(3, context.getPackageName(), "inapp", null); - - if (bundle.getInt("RESPONSE_CODE") == 0) { - - final ArrayList myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); - final ArrayList mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); - - if (myPurchases == null || myPurchases.size() == 0) { - //Log.d("godot", "No purchases!"); - notRequired(); - return; - } - - //Log.d("godot", "# products to be consumed:" + myPurchases.size()); - for (int i = 0; i < myPurchases.size(); i++) { - - try { - String receipt = myPurchases.get(i); - JSONObject inappPurchaseData = new JSONObject(receipt); - String sku = inappPurchaseData.getString("productId"); - String token = inappPurchaseData.getString("purchaseToken"); - String signature = mySignatures.get(i); - //Log.d("godot", "A punto de consumir un item con token:" + token + "\n" + receipt); - new ReleaseAllConsumablesAsyncTask(this, sku, receipt, signature, token).execute(); - } catch (JSONException e) { - } - } - } - } catch (Exception e) { - Log.d("godot", "Error releasing products:" + e.getClass().getName() + ":" + e.getMessage()); - } - } - - private String doInBackground(String token) { - try { - //Log.d("godot", "Requesting to consume an item with token ." + token); - int response = mService.consumePurchase(3, context.getPackageName(), token); - //Log.d("godot", "consumePurchase response: " + response); - if (response == 0 || response == 8) { - return null; - } - } catch (Exception e) { - Log.d("godot", "Error " + e.getClass().getName() + ":" + e.getMessage()); - } - return null; - } - - abstract protected void success(String sku, String receipt, String signature, String token); - abstract protected void error(String message); - abstract protected void notRequired(); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java deleted file mode 100644 index 4ee7b5a0f81..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java +++ /dev/null @@ -1,144 +0,0 @@ -/*************************************************************************/ -/* ValidateTask.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment; - -import org.godotengine.godot.plugin.payment.utils.HttpRequester; -import org.godotengine.godot.plugin.payment.utils.RequestParams; - -import android.app.Activity; -import android.app.ProgressDialog; -import android.os.AsyncTask; - -import java.lang.ref.WeakReference; - -import org.json.JSONException; -import org.json.JSONObject; - -abstract public class ValidateTask { - - private Activity context; - private GodotPayment godotPayments; - private ProgressDialog dialog; - private String mSku; - - private static class ValidateAsyncTask extends AsyncTask { - private WeakReference mTask; - - ValidateAsyncTask(ValidateTask task) { - mTask = new WeakReference<>(task); - } - - @Override - protected void onPreExecute() { - ValidateTask task = mTask.get(); - if (task != null) { - task.onPreExecute(); - } - } - - @Override - protected String doInBackground(String... params) { - ValidateTask task = mTask.get(); - if (task != null) { - return task.doInBackground(params); - } - return null; - } - - @Override - protected void onPostExecute(String response) { - ValidateTask task = mTask.get(); - if (task != null) { - task.onPostExecute(response); - } - } - } - - public ValidateTask(Activity context, GodotPayment godotPayments) { - this.context = context; - this.godotPayments = godotPayments; - } - - public void validatePurchase(final String sku) { - mSku = sku; - new ValidateAsyncTask(this).execute(); - } - - private void onPreExecute() { - dialog = ProgressDialog.show(context, null, "Please wait..."); - } - - private String doInBackground(String... params) { - PaymentsCache pc = new PaymentsCache(context); - String url = godotPayments.getPurchaseValidationUrlPrefix(); - RequestParams param = new RequestParams(); - param.setUrl(url); - param.put("ticket", pc.getConsumableValue("ticket", mSku)); - param.put("purchaseToken", pc.getConsumableValue("token", mSku)); - param.put("sku", mSku); - //Log.d("XXX", "Haciendo request a " + url); - //Log.d("XXX", "ticket: " + pc.getConsumableValue("ticket", sku)); - //Log.d("XXX", "purchaseToken: " + pc.getConsumableValue("token", sku)); - //Log.d("XXX", "sku: " + sku); - param.put("package", context.getApplicationContext().getPackageName()); - HttpRequester requester = new HttpRequester(); - String jsonResponse = requester.post(param); - //Log.d("XXX", "Validation response:\n"+jsonResponse); - return jsonResponse; - } - - private void onPostExecute(String response) { - if (dialog != null) { - dialog.dismiss(); - dialog = null; - } - JSONObject j; - try { - j = new JSONObject(response); - if (j.getString("status").equals("OK")) { - success(); - return; - } else if (j.getString("status") != null) { - error(j.getString("message")); - } else { - error("Connection error"); - } - } catch (JSONException e) { - error(e.getMessage()); - } catch (Exception e) { - error(e.getMessage()); - } - } - - abstract protected void success(); - abstract protected void error(String message); - abstract protected void canceled(); -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/CustomSSLSocketFactory.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/CustomSSLSocketFactory.java deleted file mode 100644 index 55b87b49e5c..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/CustomSSLSocketFactory.java +++ /dev/null @@ -1,72 +0,0 @@ -/*************************************************************************/ -/* CustomSSLSocketFactory.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment.utils; - -import java.io.IOException; -import java.net.Socket; -import java.net.UnknownHostException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; - -import org.apache.http.conn.ssl.SSLSocketFactory; - -/** - * - * @author Luis Linietsky - */ -public class CustomSSLSocketFactory extends SSLSocketFactory { - SSLContext sslContext = SSLContext.getInstance("TLS"); - - public CustomSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - super(truststore); - - TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); - tmf.init(truststore); - - sslContext.init(null, tmf.getTrustManagers(), null); - } - - @Override - public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { - return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); - } - - @Override - public Socket createSocket() throws IOException { - return sslContext.getSocketFactory().createSocket(); - } -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/GodotPaymentUtils.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/GodotPaymentUtils.java new file mode 100644 index 00000000000..f569c1b8bf9 --- /dev/null +++ b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/GodotPaymentUtils.java @@ -0,0 +1,66 @@ +package org.godotengine.godot.plugin.payment.utils; + +import org.godotengine.godot.Dictionary; + +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.SkuDetails; + +import java.util.List; + +public class GodotPaymentUtils { + public static Dictionary convertPurchaseToDictionary(Purchase purchase) { + Dictionary dictionary = new Dictionary(); + dictionary.put("order_id", purchase.getOrderId()); + dictionary.put("package_name", purchase.getPackageName()); + dictionary.put("purchase_state", Integer.valueOf(purchase.getPurchaseState())); + dictionary.put("purchase_time", Long.valueOf(purchase.getPurchaseTime())); + dictionary.put("purchase_token", purchase.getPurchaseToken()); + dictionary.put("signature", purchase.getSignature()); + dictionary.put("sku", purchase.getSku()); + dictionary.put("is_acknowledged", Boolean.valueOf(purchase.isAcknowledged())); + dictionary.put("is_auto_renewing", Boolean.valueOf(purchase.isAutoRenewing())); + return dictionary; + } + + public static Dictionary convertSkuDetailsToDictionary(SkuDetails details) { + Dictionary dictionary = new Dictionary(); + dictionary.put("sku", details.getSku()); + dictionary.put("title", details.getTitle()); + dictionary.put("description", details.getDescription()); + dictionary.put("price", details.getPrice()); + dictionary.put("price_currency_code", details.getPriceCurrencyCode()); + dictionary.put("price_amount_micros", Long.valueOf(details.getPriceAmountMicros())); + dictionary.put("free_trial_period", details.getFreeTrialPeriod()); + dictionary.put("icon_url", details.getIconUrl()); + dictionary.put("introductory_price", details.getIntroductoryPrice()); + dictionary.put("introductory_price_amount_micros", Long.valueOf(details.getIntroductoryPriceAmountMicros())); + dictionary.put("introductory_price_cycles", details.getIntroductoryPriceCycles()); + dictionary.put("introductory_price_period", details.getIntroductoryPricePeriod()); + dictionary.put("original_price", details.getOriginalPrice()); + dictionary.put("original_price_amount_micros", Long.valueOf(details.getOriginalPriceAmountMicros())); + dictionary.put("subscription_period", details.getSubscriptionPeriod()); + dictionary.put("type", details.getType()); + dictionary.put("is_rewarded", Boolean.valueOf(details.isRewarded())); + return dictionary; + } + + public static Object[] convertPurchaseListToDictionaryObjectArray(List purchases) { + Object[] purchaseDictionaries = new Object[purchases.size()]; + + for (int i = 0; i < purchases.size(); i++) { + purchaseDictionaries[i] = GodotPaymentUtils.convertPurchaseToDictionary(purchases.get(i)); + } + + return purchaseDictionaries; + } + + public static Object[] convertSkuDetailsListToDictionaryObjectArray(List skuDetails) { + Object[] skuDetailsDictionaries = new Object[skuDetails.size()]; + + for (int i = 0; i < skuDetails.size(); i++) { + skuDetailsDictionaries[i] = GodotPaymentUtils.convertSkuDetailsToDictionary(skuDetails.get(i)); + } + + return skuDetailsDictionaries; + } +} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/HttpRequester.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/HttpRequester.java deleted file mode 100644 index acd17f10f2d..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/HttpRequester.java +++ /dev/null @@ -1,231 +0,0 @@ -/*************************************************************************/ -/* HttpRequester.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment.utils; - -import org.godotengine.godot.utils.Crypt; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.security.KeyStore; -import java.util.Date; - -import org.apache.http.HttpResponse; -import org.apache.http.HttpVersion; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.PlainSocketFactory; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpProtocolParams; -import org.apache.http.protocol.HTTP; -import org.apache.http.util.EntityUtils; - -/** - * - * @author Luis Linietsky - */ -public class HttpRequester { - - private Context context; - private static final int TTL = 600000; // 10 minutos - private long cttl = 0; - - public HttpRequester() { - //Log.d("XXX", "Creando http request sin contexto"); - } - - public HttpRequester(Context context) { - this.context = context; - //Log.d("XXX", "Creando http request con contexto"); - } - - public String post(RequestParams params) { - HttpPost httppost = new HttpPost(params.getUrl()); - try { - httppost.setEntity(new UrlEncodedFormEntity(params.toPairsList())); - return request(httppost); - } catch (UnsupportedEncodingException e) { - return null; - } - } - - public String get(RequestParams params) { - String response = getResponseFromCache(params.getUrl()); - if (response == null) { - //Log.d("XXX", "Cache miss!"); - HttpGet httpget = new HttpGet(params.getUrl()); - long timeInit = new Date().getTime(); - response = request(httpget); - long delay = new Date().getTime() - timeInit; - Log.d("HttpRequest::get(url)", "Url: " + params.getUrl() + " downloaded in " + String.format("%.03f", delay / 1000.0f) + " seconds"); - if (response == null || response.length() == 0) { - response = ""; - } else { - saveResponseIntoCache(params.getUrl(), response); - } - } - Log.d("XXX", "Req: " + params.getUrl()); - Log.d("XXX", "Resp: " + response); - return response; - } - - private String request(HttpUriRequest request) { - //Log.d("XXX", "Haciendo request a: " + request.getURI() ); - Log.d("PPP", "Haciendo request a: " + request.getURI()); - long init = new Date().getTime(); - HttpClient httpclient = getNewHttpClient(); - HttpParams httpParameters = httpclient.getParams(); - HttpConnectionParams.setConnectionTimeout(httpParameters, 0); - HttpConnectionParams.setSoTimeout(httpParameters, 0); - HttpConnectionParams.setTcpNoDelay(httpParameters, true); - try { - HttpResponse response = httpclient.execute(request); - Log.d("PPP", "Fin de request (" + (new Date().getTime() - init) + ") a: " + request.getURI()); - //Log.d("XXX1", "Status:" + response.getStatusLine().toString()); - if (response.getStatusLine().getStatusCode() == 200) { - String strResponse = EntityUtils.toString(response.getEntity()); - //Log.d("XXX2", strResponse); - return strResponse; - } else { - Log.d("XXX3", "Response status code:" + response.getStatusLine().getStatusCode() + "\n" + EntityUtils.toString(response.getEntity())); - return null; - } - - } catch (ClientProtocolException e) { - Log.d("XXX3", e.getMessage()); - } catch (IOException e) { - Log.d("XXX4", e.getMessage()); - } - return null; - } - - private HttpClient getNewHttpClient() { - try { - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(null, null); - - SSLSocketFactory sf = new CustomSSLSocketFactory(trustStore); - sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - - HttpParams params = new BasicHttpParams(); - HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); - HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); - - SchemeRegistry registry = new SchemeRegistry(); - registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); - registry.register(new Scheme("https", sf, 443)); - - ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry); - - return new DefaultHttpClient(ccm, params); - } catch (Exception e) { - return new DefaultHttpClient(); - } - } - - private static String convertStreamToString(InputStream is) { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - StringBuilder sb = new StringBuilder(); - String line = null; - try { - while ((line = reader.readLine()) != null) { - sb.append((line + "\n")); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - is.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - return sb.toString(); - } - - public void saveResponseIntoCache(String request, String response) { - if (context == null) { - //Log.d("XXX", "No context, cache failed!"); - return; - } - SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPref.edit(); - editor.putString("request_" + Crypt.md5(request), response); - editor.putLong("request_" + Crypt.md5(request) + "_ttl", new Date().getTime() + getTtl()); - editor.apply(); - } - - public String getResponseFromCache(String request) { - if (context == null) { - Log.d("XXX", "No context, cache miss"); - return null; - } - SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE); - long ttl = getResponseTtl(request); - if (ttl == 0l || (new Date().getTime() - ttl) > 0l) { - Log.d("XXX", "Cache invalid ttl:" + ttl + " vs now:" + new Date().getTime()); - return null; - } - return sharedPref.getString("request_" + Crypt.md5(request), null); - } - - public long getResponseTtl(String request) { - SharedPreferences sharedPref = context.getSharedPreferences( - "http_get_cache", Context.MODE_PRIVATE); - return sharedPref.getLong("request_" + Crypt.md5(request) + "_ttl", 0l); - } - - public long getTtl() { - return cttl > 0 ? cttl : TTL; - } - - public void setTtl(long ttl) { - this.cttl = (ttl * 1000) + new Date().getTime(); - } -} diff --git a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/RequestParams.java b/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/RequestParams.java deleted file mode 100644 index 023fd87f68b..00000000000 --- a/platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/RequestParams.java +++ /dev/null @@ -1,85 +0,0 @@ -/*************************************************************************/ -/* RequestParams.java */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -package org.godotengine.godot.plugin.payment.utils; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import org.apache.http.NameValuePair; -import org.apache.http.message.BasicNameValuePair; - -/** - * - * @author Luis Linietsky - */ -public class RequestParams { - - private HashMap params; - private String url; - - public RequestParams() { - params = new HashMap(); - } - - public void put(String key, String value) { - params.put(key, value); - } - - public String get(String key) { - return params.get(key); - } - - public void remove(Object key) { - params.remove(key); - } - - public boolean has(String key) { - return params.containsKey(key); - } - - public List toPairsList() { - List fields = new ArrayList(); - - for (String key : params.keySet()) { - fields.add(new BasicNameValuePair(key, this.get(key))); - } - return fields; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } -} diff --git a/platform/android/plugin/godot_plugin_config.h b/platform/android/plugin/godot_plugin_config.h index 9ad7de12022..2151451bc87 100644 --- a/platform/android/plugin/godot_plugin_config.h +++ b/platform/android/plugin/godot_plugin_config.h @@ -91,7 +91,7 @@ static const PluginConfig GODOT_PAYMENT = { /*.binary_type =*/"local", /*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar", /*.local_dependencies =*/{}, - /*.remote_dependencies =*/{}, + /*.remote_dependencies =*/String("com.android.billingclient:billing:2.2.1").split("|"), /*.custom_maven_repos =*/{} };