Pārlūkot izejas kodu

Merge pull request #39050 from timoschwarzer/google-play-billing-4.0

(4.0) Re-implement GodotPayment Android plugin using the Google Play Billing library
Rémi Verschelde 5 gadi atpakaļ
vecāks
revīzija
24ad4894cc
15 mainītis faili ar 213 papildinājumiem un 1916 dzēšanām
  1. 1 1
      platform/android/java/plugins/godotpayment/build.gradle
  2. 0 281
      platform/android/java/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl
  3. 0 116
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java
  4. 145 164
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java
  5. 0 93
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java
  6. 0 70
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java
  7. 0 403
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java
  8. 0 118
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java
  9. 0 140
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java
  10. 0 143
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java
  11. 0 72
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/CustomSSLSocketFactory.java
  12. 66 0
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/GodotPaymentUtils.java
  13. 0 230
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/HttpRequester.java
  14. 0 84
      platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/RequestParams.java
  15. 1 1
      platform/android/plugin/godot_plugin_config.h

+ 1 - 1
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")

+ 0 - 281
platform/android/java/plugins/godotpayment/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl

@@ -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<String> 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<String> - 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<String> containing the list of SKUs
-     *         "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information
-     *         "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> 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);
-}

+ 0 - 116
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ConsumeTask.java

@@ -1,116 +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<String, String, String> {
-		private WeakReference<ConsumeTask> 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);
-}

+ 145 - 164
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/GodotPayment.java

@@ -32,214 +32,177 @@ 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 {
-	private Long purchaseCallbackId = 0L;
-	private String accessToken;
-	private String purchaseValidationUrlPrefix;
-	private String transactionId;
-	private final PaymentsManager mPaymentManager;
-	private final Dictionary mSkuDetails = new Dictionary();
+public class GodotPayment extends GodotPlugin implements PurchasesUpdatedListener, BillingClientStateListener {
+	private final BillingClient billingClient;
+	private final HashMap<String, SkuDetails> skuDetailsCache = new HashMap<>(); // sku → SkuDetails
 
 	public GodotPayment(Godot godot) {
 		super(godot);
-		mPaymentManager = new PaymentsManager(godot, this);
-		mPaymentManager.initService();
-	}
 
-	@Override
-	public void onMainActivityResult(int requestCode, int resultCode, Intent data) {
-		if (requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE) {
-			mPaymentManager.processPurchaseResponse(resultCode, data);
-		}
+		billingClient = BillingClient
+								.newBuilder(getGodot())
+								.enablePendingPurchases()
+								.setListener(this)
+								.build();
 	}
 
-	@Override
-	public void onMainDestroy() {
-		super.onMainDestroy();
-		if (mPaymentManager != null) {
-			mPaymentManager.destroy();
-		}
+	public void startConnection() {
+		billingClient.startConnection(this);
 	}
 
-	public void purchase(final String sku, final String transactionId) {
-		runOnUiThread(new Runnable() {
-			@Override
-			public void run() {
-				mPaymentManager.requestPurchase(sku, transactionId);
-			}
-		});
-	}
-
-	public void consumeUnconsumedPurchases() {
-		runOnUiThread(new Runnable() {
-			@Override
-			public void run() {
-				mPaymentManager.consumeUnconsumedPurchases();
-			}
-		});
+	public void endConnection() {
+		billingClient.endConnection();
 	}
 
-	private String signature;
-
-	public String getSignature() {
-		return this.signature;
+	public boolean isReady() {
+		return this.billingClient.isReady();
 	}
 
-	public void callbackSuccess(String ticket, String signature, String sku) {
-		GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku });
-	}
+	public Dictionary queryPurchases(String type) {
+		Purchase.PurchasesResult result = billingClient.queryPurchases(type);
 
-	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;
-	}
+		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 {
+			returnValue.put("status", 1); // FAILED = 1
+			returnValue.put("response_code", result.getBillingResult().getResponseCode());
+			returnValue.put("debug_message", result.getBillingResult().getDebugMessage());
+		}
 
-	public String getPurchaseValidationUrlPrefix() {
-		return this.purchaseValidationUrlPrefix;
+		return returnValue;
 	}
 
-	public void setPurchaseValidationUrlPrefix(String url) {
-		this.purchaseValidationUrlPrefix = url;
-	}
+	public void querySkuDetails(final String[] list, String type) {
+		List<String> skuList = Arrays.asList(list);
 
-	public String getAccessToken() {
-		return accessToken;
-	}
+		SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder()
+												  .setSkusList(skuList)
+												  .setType(type);
 
-	public void setAccessToken(String accessToken) {
-		this.accessToken = accessToken;
+		billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
+			@Override
+			public void onSkuDetailsResponse(BillingResult billingResult,
+					List<SkuDetails> 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 setTransactionId(String transactionId) {
-		this.transactionId = transactionId;
+	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 String getTransactionId() {
-		return this.transactionId;
-	}
+	public void consumePurchase(String purchaseToken) {
+		ConsumeParams consumeParams = ConsumeParams.newBuilder()
+											  .setPurchaseToken(purchaseToken)
+											  .build();
 
-	// request purchased items are not consumed
-	public void requestPurchased() {
-		runOnUiThread(new Runnable() {
+		billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
 			@Override
-			public void run() {
-				mPaymentManager.requestPurchased();
+			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);
+				}
 			}
 		});
 	}
 
-	// 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[] {});
+	@Override
+	public void onBillingSetupFinished(BillingResult billingResult) {
+		if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
+			emitSignal("connected");
+		} else {
+			emitSignal("connect_error", billingResult.getResponseCode(), billingResult.getDebugMessage());
+		}
 	}
 
-	public void callbackConnected() {
-		GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {});
+	@Override
+	public void onBillingServiceDisconnected() {
+		emitSignal("disconnected");
 	}
 
-	// true if connected, false otherwise
-	public boolean isConnected() {
-		return mPaymentManager.isConnected();
-	}
+	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!");
+		}
 
-	// consume item automatically after purchase. default is true.
-	public void setAutoConsume(boolean autoConsume) {
-		mPaymentManager.setAutoConsume(autoConsume);
-	}
+		SkuDetails skuDetails = skuDetailsCache.get(sku);
+		BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
+												   .setSkuDetails(skuDetails)
+												   .build();
 
-	// consume a specific item
-	public void consume(String sku) {
-		mPaymentManager.consume(sku);
-	}
+		BillingResult result = billingClient.launchBillingFlow(getGodot(), purchaseParams);
 
-	// query in app item detail info
-	public void querySkuDetails(String[] list) {
-		List<String> nKeys = Arrays.asList(list);
-		List<String> cKeys = Arrays.asList(mSkuDetails.get_keys());
-		ArrayList<String> fKeys = new ArrayList<String>();
-		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.getResponseCode() == BillingClient.BillingResponseCode.OK) {
+			returnValue.put("status", 0); // OK = 0
 		} else {
-			completeSkuDetail();
-		}
-	}
-
-	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();
+			returnValue.put("status", 1); // FAILED = 1
+			returnValue.put("response_code", result.getResponseCode());
+			returnValue.put("debug_message", result.getDebugMessage());
 		}
-	}
 
-	public void completeSkuDetail() {
-		GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails });
+		return returnValue;
 	}
 
-	public void errorSkuDetail(String errorMessage) {
-		GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[] { errorMessage });
+	@Override
+	public void onPurchasesUpdated(final BillingResult billingResult, @Nullable final List<Purchase> 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 +214,26 @@ public class GodotPayment extends GodotPlugin {
 	@NonNull
 	@Override
 	public List<String> 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<SignalInfo> getPluginSignals() {
+		Set<SignalInfo> 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;
 	}
 }

+ 0 - 93
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/HandlePurchaseTask.java

@@ -1,93 +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();
-}

+ 0 - 70
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsCache.java

@@ -1,70 +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);
-	}
-}

+ 0 - 403
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PaymentsManager.java

@@ -1,403 +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<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
-					final ArrayList<String> 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<String> skuList = new ArrayList<String>(Arrays.asList(list));
-				if (skuList.size() == 0) {
-					return;
-				}
-				// Split the sku list in blocks of no more than 20 elements.
-				ArrayList<ArrayList<String>> packs = new ArrayList<ArrayList<String>>();
-				ArrayList<String> tempList;
-				int n = skuList.size() / 20;
-				int mod = skuList.size() % 20;
-				for (int i = 0; i < n; i++) {
-					tempList = new ArrayList<String>();
-					for (String s : skuList.subList(i * 20, i * 20 + 20)) {
-						tempList.add(s);
-					}
-					packs.add(tempList);
-				}
-				if (mod != 0) {
-					tempList = new ArrayList<String>();
-					for (String s : skuList.subList(n * 20, n * 20 + mod)) {
-						tempList.add(s);
-					}
-					packs.add(tempList);
-				}
-				for (ArrayList<String> 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<String> 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();
-	}
-}

+ 0 - 118
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/PurchaseTask.java

@@ -1,118 +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();
-}

+ 0 - 140
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ReleaseAllConsumablesTask.java

@@ -1,140 +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<String, String, String> {
-		private WeakReference<ReleaseAllConsumablesTask> 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<ReleaseAllConsumablesTask>(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<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
-				final ArrayList<String> 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();
-}

+ 0 - 143
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/ValidateTask.java

@@ -1,143 +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<String, String, String> {
-		private WeakReference<ValidateTask> 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();
-}

+ 0 - 72
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/CustomSSLSocketFactory.java

@@ -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 <[email protected]>
- */
-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();
-	}
-}

+ 66 - 0
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<Purchase> 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> skuDetails) {
+		Object[] skuDetailsDictionaries = new Object[skuDetails.size()];
+
+		for (int i = 0; i < skuDetails.size(); i++) {
+			skuDetailsDictionaries[i] = GodotPaymentUtils.convertSkuDetailsToDictionary(skuDetails.get(i));
+		}
+
+		return skuDetailsDictionaries;
+	}
+}

+ 0 - 230
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/HttpRequester.java

@@ -1,230 +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 <[email protected]>
- */
-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();
-	}
-}

+ 0 - 84
platform/android/java/plugins/godotpayment/src/main/java/org/godotengine/godot/plugin/payment/utils/RequestParams.java

@@ -1,84 +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 <[email protected]>
- */
-public class RequestParams {
-	private HashMap<String, String> params;
-	private String url;
-
-	public RequestParams() {
-		params = new HashMap<String, String>();
-	}
-
-	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<NameValuePair> toPairsList() {
-		List<NameValuePair> fields = new ArrayList<NameValuePair>();
-
-		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;
-	}
-}

+ 1 - 1
platform/android/plugin/godot_plugin_config.h

@@ -94,7 +94,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 =*/{}
 };