Re-implement GodotPayment Android plugin using the Google Play Billing library
This commit is contained in:
parent
7c332d9386
commit
8373c0792a
|
@ -3,7 +3,6 @@ apply plugin: 'com.android.library'
|
||||||
android {
|
android {
|
||||||
compileSdkVersion versions.compileSdk
|
compileSdkVersion versions.compileSdk
|
||||||
buildToolsVersion versions.buildTools
|
buildToolsVersion versions.buildTools
|
||||||
useLibrary 'org.apache.http.legacy'
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion versions.minSdk
|
minSdkVersion versions.minSdk
|
||||||
|
@ -21,6 +20,7 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation libraries.supportCoreUtils
|
implementation libraries.supportCoreUtils
|
||||||
implementation libraries.v4Support
|
implementation libraries.v4Support
|
||||||
|
implementation 'com.android.billingclient:billing:2.2.1'
|
||||||
|
|
||||||
if (rootProject.findProject(":lib")) {
|
if (rootProject.findProject(":lib")) {
|
||||||
compileOnly project(":lib")
|
compileOnly project(":lib")
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* ConsumeTask.java */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
package org.godotengine.godot.plugin.payment;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
|
|
||||||
import com.android.vending.billing.IInAppBillingService;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
abstract public class ConsumeTask {
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private IInAppBillingService mService;
|
|
||||||
|
|
||||||
private String mSku;
|
|
||||||
private String mToken;
|
|
||||||
|
|
||||||
private static class ConsumeAsyncTask extends AsyncTask<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);
|
|
||||||
}
|
|
|
@ -32,214 +32,178 @@ package org.godotengine.godot.plugin.payment;
|
||||||
|
|
||||||
import org.godotengine.godot.Dictionary;
|
import org.godotengine.godot.Dictionary;
|
||||||
import org.godotengine.godot.Godot;
|
import org.godotengine.godot.Godot;
|
||||||
import org.godotengine.godot.GodotLib;
|
|
||||||
import org.godotengine.godot.plugin.GodotPlugin;
|
import org.godotengine.godot.plugin.GodotPlugin;
|
||||||
|
import org.godotengine.godot.plugin.SignalInfo;
|
||||||
import android.content.Intent;
|
import org.godotengine.godot.plugin.payment.utils.GodotPaymentUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
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.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.json.JSONException;
|
public class GodotPayment extends GodotPlugin implements PurchasesUpdatedListener, BillingClientStateListener {
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class GodotPayment extends GodotPlugin {
|
private final BillingClient billingClient;
|
||||||
private Long purchaseCallbackId = 0L;
|
private final HashMap<String, SkuDetails> skuDetailsCache = new HashMap<>(); // sku → SkuDetails
|
||||||
private String accessToken;
|
|
||||||
private String purchaseValidationUrlPrefix;
|
|
||||||
private String transactionId;
|
|
||||||
private final PaymentsManager mPaymentManager;
|
|
||||||
private final Dictionary mSkuDetails = new Dictionary();
|
|
||||||
|
|
||||||
public GodotPayment(Godot godot) {
|
public GodotPayment(Godot godot) {
|
||||||
super(godot);
|
super(godot);
|
||||||
mPaymentManager = new PaymentsManager(godot, this);
|
|
||||||
mPaymentManager.initService();
|
billingClient = BillingClient
|
||||||
|
.newBuilder(getGodot())
|
||||||
|
.enablePendingPurchases()
|
||||||
|
.setListener(this)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void startConnection() {
|
||||||
public void onMainActivityResult(int requestCode, int resultCode, Intent data) {
|
billingClient.startConnection(this);
|
||||||
if (requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE) {
|
|
||||||
mPaymentManager.processPurchaseResponse(resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void endConnection() {
|
||||||
public void onMainDestroy() {
|
billingClient.endConnection();
|
||||||
super.onMainDestroy();
|
|
||||||
if (mPaymentManager != null) {
|
|
||||||
mPaymentManager.destroy();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void purchase(final String sku, final String transactionId) {
|
public boolean isReady() {
|
||||||
runOnUiThread(new Runnable() {
|
return this.billingClient.isReady();
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mPaymentManager.requestPurchase(sku, transactionId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void consumeUnconsumedPurchases() {
|
public Dictionary queryPurchases(String type) {
|
||||||
runOnUiThread(new Runnable() {
|
Purchase.PurchasesResult result = billingClient.queryPurchases(type);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mPaymentManager.consumeUnconsumedPurchases();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private String signature;
|
Dictionary returnValue = new Dictionary();
|
||||||
|
if (result.getBillingResult().getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||||
public String getSignature() {
|
returnValue.put("status", 0); // OK = 0
|
||||||
return this.signature;
|
returnValue.put("purchases", GodotPaymentUtils.convertPurchaseListToDictionaryObjectArray(result.getPurchasesList()));
|
||||||
}
|
|
||||||
|
|
||||||
public void callbackSuccess(String ticket, String signature, String sku) {
|
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku });
|
|
||||||
}
|
|
||||||
|
|
||||||
public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku) {
|
|
||||||
Log.d(this.getClass().getName(), "callbackSuccessProductMassConsumed > " + ticket + "," + signature + "," + sku);
|
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "consume_success", new Object[] { ticket, signature, sku });
|
|
||||||
}
|
|
||||||
|
|
||||||
public void callbackSuccessNoUnconsumedPurchases() {
|
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void callbackFailConsume(String message) {
|
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[] { message });
|
|
||||||
}
|
|
||||||
|
|
||||||
public void callbackFail(String message) {
|
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[] { message });
|
|
||||||
}
|
|
||||||
|
|
||||||
public void callbackCancel() {
|
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void callbackAlreadyOwned(String sku) {
|
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[] { sku });
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getPurchaseCallbackId() {
|
|
||||||
return purchaseCallbackId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPurchaseCallbackId(long purchaseCallbackId) {
|
|
||||||
this.purchaseCallbackId = purchaseCallbackId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPurchaseValidationUrlPrefix() {
|
|
||||||
return this.purchaseValidationUrlPrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPurchaseValidationUrlPrefix(String url) {
|
|
||||||
this.purchaseValidationUrlPrefix = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAccessToken() {
|
|
||||||
return accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccessToken(String accessToken) {
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTransactionId(String transactionId) {
|
|
||||||
this.transactionId = transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTransactionId() {
|
|
||||||
return this.transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// request purchased items are not consumed
|
|
||||||
public void requestPurchased() {
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mPaymentManager.requestPurchased();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// callback for requestPurchased()
|
|
||||||
public void callbackPurchased(String receipt, String signature, String sku) {
|
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[] { receipt, signature, sku });
|
|
||||||
}
|
|
||||||
|
|
||||||
public void callbackDisconnected() {
|
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "iap_disconnected", new Object[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void callbackConnected() {
|
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// true if connected, false otherwise
|
|
||||||
public boolean isConnected() {
|
|
||||||
return mPaymentManager.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
// consume item automatically after purchase. default is true.
|
|
||||||
public void setAutoConsume(boolean autoConsume) {
|
|
||||||
mPaymentManager.setAutoConsume(autoConsume);
|
|
||||||
}
|
|
||||||
|
|
||||||
// consume a specific item
|
|
||||||
public void consume(String sku) {
|
|
||||||
mPaymentManager.consume(sku);
|
|
||||||
}
|
|
||||||
|
|
||||||
// query in app item detail info
|
|
||||||
public void querySkuDetails(String[] list) {
|
|
||||||
List<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]));
|
|
||||||
} else {
|
} else {
|
||||||
completeSkuDetail();
|
returnValue.put("status", 1); // FAILED = 1
|
||||||
|
returnValue.put("response_code", result.getBillingResult().getResponseCode());
|
||||||
|
returnValue.put("debug_message", result.getBillingResult().getDebugMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void querySkuDetails(final String[] list, String type) {
|
||||||
|
List<String> skuList = Arrays.asList(list);
|
||||||
|
|
||||||
|
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder()
|
||||||
|
.setSkusList(skuList)
|
||||||
|
.setType(type);
|
||||||
|
|
||||||
|
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
|
||||||
|
@Override
|
||||||
|
public void onSkuDetailsResponse(BillingResult billingResult,
|
||||||
|
List<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 acknowledgePurchase(final String purchaseToken) {
|
||||||
|
AcknowledgePurchaseParams acknowledgePurchaseParams =
|
||||||
|
AcknowledgePurchaseParams.newBuilder()
|
||||||
|
.setPurchaseToken(purchaseToken)
|
||||||
|
.build();
|
||||||
|
billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
|
||||||
|
@Override
|
||||||
|
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
|
||||||
|
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||||
|
emitSignal("purchase_acknowledged", purchaseToken);
|
||||||
|
} else {
|
||||||
|
emitSignal("purchase_acknowledgement_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void consumePurchase(String purchaseToken) {
|
||||||
|
ConsumeParams consumeParams = ConsumeParams.newBuilder()
|
||||||
|
.setPurchaseToken(purchaseToken)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
|
||||||
|
@Override
|
||||||
|
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
|
||||||
|
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||||
|
emitSignal("purchase_consumed", purchaseToken);
|
||||||
|
} else {
|
||||||
|
emitSignal("purchase_consumption_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBillingSetupFinished(BillingResult billingResult) {
|
||||||
|
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||||
|
emitSignal("connected");
|
||||||
|
} else {
|
||||||
|
emitSignal("connect_error", billingResult.getResponseCode(), billingResult.getDebugMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSkuDetail(String itemJson) {
|
@Override
|
||||||
JSONObject o = null;
|
public void onBillingServiceDisconnected() {
|
||||||
try {
|
emitSignal("disconnected");
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void completeSkuDetail() {
|
public Dictionary purchase(String sku) {
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails });
|
if (!skuDetailsCache.containsKey(sku)) {
|
||||||
|
emitSignal("purchase_error", null, "You must query the sku details and wait for the result before purchasing!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void errorSkuDetail(String errorMessage) {
|
SkuDetails skuDetails = skuDetailsCache.get(sku);
|
||||||
GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[] { errorMessage });
|
BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
|
||||||
|
.setSkuDetails(skuDetails)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
BillingResult result = billingClient.launchBillingFlow(getGodot(), purchaseParams);
|
||||||
|
|
||||||
|
Dictionary returnValue = new Dictionary();
|
||||||
|
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
|
||||||
|
returnValue.put("status", 0); // OK = 0
|
||||||
|
} else {
|
||||||
|
returnValue.put("status", 1); // FAILED = 1
|
||||||
|
returnValue.put("response_code", result.getResponseCode());
|
||||||
|
returnValue.put("debug_message", result.getDebugMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
@NonNull
|
||||||
|
@ -251,8 +215,26 @@ public class GodotPayment extends GodotPlugin {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public List<String> getPluginMethods() {
|
public List<String> getPluginMethods() {
|
||||||
return Arrays.asList("purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix",
|
return Arrays.asList("startConnection", "endConnection", "purchase", "querySkuDetails", "isReady", "queryPurchases", "acknowledgePurchase");
|
||||||
"setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased",
|
}
|
||||||
"setAutoConsume", "consume", "querySkuDetails", "isConnected");
|
|
||||||
|
@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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* HandlePurchaseTask.java */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
package org.godotengine.godot.plugin.payment;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
abstract public class HandlePurchaseTask {
|
|
||||||
|
|
||||||
private Activity context;
|
|
||||||
|
|
||||||
public HandlePurchaseTask(Activity context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handlePurchaseRequest(int resultCode, Intent data) {
|
|
||||||
//Log.d("XXX", "Handling purchase response");
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
try {
|
|
||||||
//int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
|
|
||||||
PaymentsCache pc = new PaymentsCache(context);
|
|
||||||
|
|
||||||
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
|
|
||||||
//Log.d("XXX", "Purchase data:" + purchaseData);
|
|
||||||
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
|
|
||||||
//Log.d("XXX", "Purchase signature:" + dataSignature);
|
|
||||||
//Log.d("SARLANGA", purchaseData);
|
|
||||||
|
|
||||||
JSONObject jo = new JSONObject(purchaseData);
|
|
||||||
//String sku = jo.getString("productId");
|
|
||||||
//alert("You have bought the " + sku + ". Excellent choice, aventurer!");
|
|
||||||
//String orderId = jo.getString("orderId");
|
|
||||||
//String packageName = jo.getString("packageName");
|
|
||||||
String productId = jo.getString("productId");
|
|
||||||
//Long purchaseTime = jo.getLong("purchaseTime");
|
|
||||||
//Integer state = jo.getInt("purchaseState");
|
|
||||||
String developerPayload = jo.getString("developerPayload");
|
|
||||||
String purchaseToken = jo.getString("purchaseToken");
|
|
||||||
|
|
||||||
if (!pc.getConsumableValue("validation_hash", productId).equals(developerPayload)) {
|
|
||||||
error("Untrusted callback");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//Log.d("XXX", "Este es el product ID:" + productId);
|
|
||||||
pc.setConsumableValue("ticket_signautre", productId, dataSignature);
|
|
||||||
pc.setConsumableValue("ticket", productId, purchaseData);
|
|
||||||
pc.setConsumableFlag("block", productId, true);
|
|
||||||
pc.setConsumableValue("token", productId, purchaseToken);
|
|
||||||
|
|
||||||
success(productId, dataSignature, purchaseData);
|
|
||||||
return;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
error(e.getMessage());
|
|
||||||
}
|
|
||||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
|
||||||
canceled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract protected void success(String sku, String signature, String ticket);
|
|
||||||
abstract protected void error(String message);
|
|
||||||
abstract protected void canceled();
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* PaymentsCache.java */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
package org.godotengine.godot.plugin.payment;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
public class PaymentsCache {
|
|
||||||
|
|
||||||
public Context context;
|
|
||||||
|
|
||||||
public PaymentsCache(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConsumableFlag(String set, String sku, Boolean flag) {
|
|
||||||
SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE);
|
|
||||||
SharedPreferences.Editor editor = sharedPref.edit();
|
|
||||||
editor.putBoolean(sku, flag);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getConsumableFlag(String set, String sku) {
|
|
||||||
SharedPreferences sharedPref = context.getSharedPreferences(
|
|
||||||
"consumables_" + set, Context.MODE_PRIVATE);
|
|
||||||
return sharedPref.getBoolean(sku, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConsumableValue(String set, String sku, String value) {
|
|
||||||
SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE);
|
|
||||||
SharedPreferences.Editor editor = sharedPref.edit();
|
|
||||||
editor.putString(sku, value);
|
|
||||||
//Log.d("XXX", "Setting asset: consumables_" + set + ":" + sku);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getConsumableValue(String set, String sku) {
|
|
||||||
SharedPreferences sharedPref = context.getSharedPreferences(
|
|
||||||
"consumables_" + set, Context.MODE_PRIVATE);
|
|
||||||
//Log.d("XXX", "Getting asset: consumables_" + set + ":" + sku);
|
|
||||||
return sharedPref.getString(sku, null);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,408 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* PaymentsManager.java */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
package org.godotengine.godot.plugin.payment;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.vending.billing.IInAppBillingService;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class PaymentsManager {
|
|
||||||
|
|
||||||
public static final int BILLING_RESPONSE_RESULT_OK = 0;
|
|
||||||
public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001;
|
|
||||||
private static boolean auto_consume = true;
|
|
||||||
|
|
||||||
private final Activity activity;
|
|
||||||
private final GodotPayment godotPayment;
|
|
||||||
IInAppBillingService mService;
|
|
||||||
|
|
||||||
PaymentsManager(Activity activity, GodotPayment godotPayment) {
|
|
||||||
this.activity = activity;
|
|
||||||
this.godotPayment = godotPayment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PaymentsManager initService() {
|
|
||||||
Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
|
|
||||||
intent.setPackage("com.android.vending");
|
|
||||||
activity.bindService(
|
|
||||||
intent,
|
|
||||||
mServiceConn,
|
|
||||||
Context.BIND_AUTO_CREATE);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroy() {
|
|
||||||
if (mService != null) {
|
|
||||||
activity.unbindService(mServiceConn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ServiceConnection mServiceConn = new ServiceConnection() {
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
|
||||||
mService = null;
|
|
||||||
|
|
||||||
// At this stage, godotPayment might not have been initialized yet.
|
|
||||||
if (godotPayment != null) {
|
|
||||||
godotPayment.callbackDisconnected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
|
||||||
mService = IInAppBillingService.Stub.asInterface(service);
|
|
||||||
|
|
||||||
// At this stage, godotPayment might not have been initialized yet.
|
|
||||||
if (godotPayment != null) {
|
|
||||||
godotPayment.callbackConnected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public void requestPurchase(final String sku, String transactionId) {
|
|
||||||
new PurchaseTask(mService, activity) {
|
|
||||||
@Override
|
|
||||||
protected void error(String message) {
|
|
||||||
godotPayment.callbackFail(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void canceled() {
|
|
||||||
godotPayment.callbackCancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void alreadyOwned() {
|
|
||||||
godotPayment.callbackAlreadyOwned(sku);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.purchase(sku, transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isConnected() {
|
|
||||||
return mService != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void consumeUnconsumedPurchases() {
|
|
||||||
new ReleaseAllConsumablesTask(mService, activity) {
|
|
||||||
@Override
|
|
||||||
protected void success(String sku, String receipt, String signature, String token) {
|
|
||||||
godotPayment.callbackSuccessProductMassConsumed(receipt, signature, sku);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void error(String message) {
|
|
||||||
Log.d("godot", "consumeUnconsumedPurchases :" + message);
|
|
||||||
godotPayment.callbackFailConsume(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void notRequired() {
|
|
||||||
Log.d("godot", "callbackSuccessNoUnconsumedPurchases :");
|
|
||||||
godotPayment.callbackSuccessNoUnconsumedPurchases();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.consumeItAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void requestPurchased() {
|
|
||||||
try {
|
|
||||||
PaymentsCache pc = new PaymentsCache(activity);
|
|
||||||
|
|
||||||
String continueToken = null;
|
|
||||||
|
|
||||||
do {
|
|
||||||
Bundle bundle = mService.getPurchases(3, activity.getPackageName(), "inapp", continueToken);
|
|
||||||
|
|
||||||
if (bundle.getInt("RESPONSE_CODE") == 0) {
|
|
||||||
|
|
||||||
final ArrayList<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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* PurchaseTask.java */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
package org.godotengine.godot.plugin.payment;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentSender.SendIntentException;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.vending.billing.IInAppBillingService;
|
|
||||||
|
|
||||||
abstract public class PurchaseTask {
|
|
||||||
|
|
||||||
private Activity context;
|
|
||||||
|
|
||||||
private IInAppBillingService mService;
|
|
||||||
public PurchaseTask(IInAppBillingService mService, Activity context) {
|
|
||||||
this.context = context;
|
|
||||||
this.mService = mService;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isLooping = false;
|
|
||||||
|
|
||||||
public void purchase(final String sku, final String transactionId) {
|
|
||||||
Log.d("XXX", "Starting purchase for: " + sku);
|
|
||||||
PaymentsCache pc = new PaymentsCache(context);
|
|
||||||
Boolean isBlocked = pc.getConsumableFlag("block", sku);
|
|
||||||
/*
|
|
||||||
if(isBlocked){
|
|
||||||
Log.d("XXX", "Is awaiting payment confirmation");
|
|
||||||
error("Awaiting payment confirmation");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
final String hash = transactionId;
|
|
||||||
|
|
||||||
Bundle buyIntentBundle;
|
|
||||||
try {
|
|
||||||
buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, "inapp", hash);
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
//Log.d("XXX", "Error: " + e.getMessage());
|
|
||||||
error(e.getMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Object rc = buyIntentBundle.get("RESPONSE_CODE");
|
|
||||||
int responseCode = 0;
|
|
||||||
if (rc == null) {
|
|
||||||
responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK;
|
|
||||||
} else if (rc instanceof Integer) {
|
|
||||||
responseCode = ((Integer)rc).intValue();
|
|
||||||
} else if (rc instanceof Long) {
|
|
||||||
responseCode = (int)((Long)rc).longValue();
|
|
||||||
}
|
|
||||||
//Log.d("XXX", "Buy intent response code: " + responseCode);
|
|
||||||
if (responseCode == 1 || responseCode == 3 || responseCode == 4) {
|
|
||||||
canceled();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (responseCode == 7) {
|
|
||||||
alreadyOwned();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
|
|
||||||
pc.setConsumableValue("validation_hash", sku, hash);
|
|
||||||
try {
|
|
||||||
if (context == null) {
|
|
||||||
//Log.d("XXX", "No context!");
|
|
||||||
}
|
|
||||||
if (pendingIntent == null) {
|
|
||||||
//Log.d("XXX", "No pending intent");
|
|
||||||
}
|
|
||||||
//Log.d("XXX", "Starting activity for purchase!");
|
|
||||||
context.startIntentSenderForResult(
|
|
||||||
pendingIntent.getIntentSender(),
|
|
||||||
PaymentsManager.REQUEST_CODE_FOR_PURCHASE,
|
|
||||||
new Intent(),
|
|
||||||
Integer.valueOf(0), Integer.valueOf(0),
|
|
||||||
Integer.valueOf(0));
|
|
||||||
} catch (SendIntentException e) {
|
|
||||||
error(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract protected void error(String message);
|
|
||||||
abstract protected void canceled();
|
|
||||||
abstract protected void alreadyOwned();
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* ReleaseAllConsumablesTask.java */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
package org.godotengine.godot.plugin.payment;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.vending.billing.IInAppBillingService;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
abstract public class ReleaseAllConsumablesTask {
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private IInAppBillingService mService;
|
|
||||||
|
|
||||||
private static class ReleaseAllConsumablesAsyncTask extends AsyncTask<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();
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* ValidateTask.java */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
package org.godotengine.godot.plugin.payment;
|
|
||||||
|
|
||||||
import org.godotengine.godot.plugin.payment.utils.HttpRequester;
|
|
||||||
import org.godotengine.godot.plugin.payment.utils.RequestParams;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
abstract public class ValidateTask {
|
|
||||||
|
|
||||||
private Activity context;
|
|
||||||
private GodotPayment godotPayments;
|
|
||||||
private ProgressDialog dialog;
|
|
||||||
private String mSku;
|
|
||||||
|
|
||||||
private static class ValidateAsyncTask extends AsyncTask<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();
|
|
||||||
}
|
|
|
@ -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 <luis.linietsky@gmail.com>
|
|
||||||
*/
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,231 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* HttpRequester.java */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
package org.godotengine.godot.plugin.payment.utils;
|
|
||||||
|
|
||||||
import org.godotengine.godot.utils.Crypt;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.HttpVersion;
|
|
||||||
import org.apache.http.client.ClientProtocolException;
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.apache.http.client.methods.HttpPost;
|
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
|
||||||
import org.apache.http.conn.ClientConnectionManager;
|
|
||||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
|
||||||
import org.apache.http.conn.scheme.Scheme;
|
|
||||||
import org.apache.http.conn.scheme.SchemeRegistry;
|
|
||||||
import org.apache.http.conn.ssl.SSLSocketFactory;
|
|
||||||
import org.apache.http.impl.client.DefaultHttpClient;
|
|
||||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
|
||||||
import org.apache.http.params.BasicHttpParams;
|
|
||||||
import org.apache.http.params.HttpConnectionParams;
|
|
||||||
import org.apache.http.params.HttpParams;
|
|
||||||
import org.apache.http.params.HttpProtocolParams;
|
|
||||||
import org.apache.http.protocol.HTTP;
|
|
||||||
import org.apache.http.util.EntityUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Luis Linietsky <luis.linietsky@gmail.com>
|
|
||||||
*/
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*************************************************************************/
|
|
||||||
/* RequestParams.java */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* This file is part of: */
|
|
||||||
/* GODOT ENGINE */
|
|
||||||
/* https://godotengine.org */
|
|
||||||
/*************************************************************************/
|
|
||||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
||||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
|
||||||
/* */
|
|
||||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
||||||
/* a copy of this software and associated documentation files (the */
|
|
||||||
/* "Software"), to deal in the Software without restriction, including */
|
|
||||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
||||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
||||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
||||||
/* the following conditions: */
|
|
||||||
/* */
|
|
||||||
/* The above copyright notice and this permission notice shall be */
|
|
||||||
/* included in all copies or substantial portions of the Software. */
|
|
||||||
/* */
|
|
||||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
||||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
||||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
||||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
||||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
||||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
||||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
||||||
/*************************************************************************/
|
|
||||||
|
|
||||||
package org.godotengine.godot.plugin.payment.utils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.http.NameValuePair;
|
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Luis Linietsky <luis.linietsky@gmail.com>
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -91,7 +91,7 @@ static const PluginConfig GODOT_PAYMENT = {
|
||||||
/*.binary_type =*/"local",
|
/*.binary_type =*/"local",
|
||||||
/*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar",
|
/*.binary =*/"res://android/build/libs/plugins/GodotPayment.release.aar",
|
||||||
/*.local_dependencies =*/{},
|
/*.local_dependencies =*/{},
|
||||||
/*.remote_dependencies =*/{},
|
/*.remote_dependencies =*/String("com.android.billingclient:billing:2.2.1").split("|"),
|
||||||
/*.custom_maven_repos =*/{}
|
/*.custom_maven_repos =*/{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue