Complete the implementation of the GodotPayment plugin.

Move the remaining plugin components within the plugin source code.

(cherry picked from commit 99173c5fc4)
This commit is contained in:
fhuya 2020-03-17 10:16:49 -07:00 committed by Rémi Verschelde
parent cd2b2bf8bf
commit 9a035efe62
11 changed files with 39 additions and 175 deletions

View file

@ -0,0 +1,281 @@
/*
* 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);
}

View file

@ -0,0 +1,116 @@
/*************************************************************************/
/* 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);
}

View file

@ -30,6 +30,7 @@
package org.godotengine.godot.plugin.payment;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.util.Log;
import java.util.ArrayList;
@ -38,28 +39,40 @@ import java.util.List;
import org.godotengine.godot.Dictionary;
import org.godotengine.godot.Godot;
import org.godotengine.godot.GodotLib;
import org.godotengine.godot.payments.GodotPaymentInterface;
import org.godotengine.godot.payments.PaymentsManager;
import org.godotengine.godot.plugin.GodotPlugin;
import org.json.JSONException;
import org.json.JSONObject;
public class GodotPayment extends GodotPlugin implements GodotPaymentInterface {
public class GodotPayment extends GodotPlugin {
private Integer purchaseCallbackId = 0;
private String accessToken;
private String purchaseValidationUrlPrefix;
private String transactionId;
private PaymentsManager mPaymentManager;
private Dictionary mSkuDetails = new Dictionary();
private final PaymentsManager mPaymentManager;
private final Dictionary mSkuDetails = new Dictionary();
public GodotPayment(Godot godot) {
super(godot);
mPaymentManager = godot.getPaymentsManager();
mPaymentManager.setBaseSingleton(this);
mPaymentManager = new PaymentsManager(godot, this);
mPaymentManager.initService();
}
@Override
public void onMainActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE) {
mPaymentManager.processPurchaseResponse(resultCode, data);
}
}
@Override
public void onMainDestroy() {
super.onMainDestroy();
if (mPaymentManager != null) {
mPaymentManager.destroy();
}
}
public void purchase(final String sku, final String transactionId) {
runOnUiThread(new Runnable() {
@Override
@ -69,7 +82,6 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface {
});
}
@Override
public void consumeUnconsumedPurchases() {
runOnUiThread(new Runnable() {
@Override
@ -81,89 +93,72 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface {
private String signature;
@Override
public String getSignature() {
return this.signature;
}
@Override
public void callbackSuccess(String ticket, String signature, String sku) {
GodotLib.calldeferred(purchaseCallbackId, "purchase_success", new Object[] { ticket, signature, sku });
}
@Override
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 });
}
@Override
public void callbackSuccessNoUnconsumedPurchases() {
GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[] {});
}
@Override
public void callbackFailConsume(String message) {
GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[] { message });
}
@Override
public void callbackFail(String message) {
GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[] { message });
}
@Override
public void callbackCancel() {
GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[] {});
}
@Override
public void callbackAlreadyOwned(String sku) {
GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[] { sku });
}
@Override
public int getPurchaseCallbackId() {
return purchaseCallbackId;
}
@Override
public void setPurchaseCallbackId(int purchaseCallbackId) {
this.purchaseCallbackId = purchaseCallbackId;
}
@Override
public String getPurchaseValidationUrlPrefix() {
return this.purchaseValidationUrlPrefix;
}
@Override
public void setPurchaseValidationUrlPrefix(String url) {
this.purchaseValidationUrlPrefix = url;
}
@Override
public String getAccessToken() {
return accessToken;
}
@Override
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
@Override
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
@Override
public String getTransactionId() {
return this.transactionId;
}
// request purchased items are not consumed
@Override
public void requestPurchased() {
runOnUiThread(new Runnable() {
@Override
@ -174,41 +169,34 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface {
}
// callback for requestPurchased()
@Override
public void callbackPurchased(String receipt, String signature, String sku) {
GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[] { receipt, signature, sku });
}
@Override
public void callbackDisconnected() {
GodotLib.calldeferred(purchaseCallbackId, "iap_disconnected", new Object[] {});
}
@Override
public void callbackConnected() {
GodotLib.calldeferred(purchaseCallbackId, "iap_connected", new Object[] {});
}
// true if connected, false otherwise
@Override
public boolean isConnected() {
return mPaymentManager.isConnected();
}
// consume item automatically after purchase. default is true.
@Override
public void setAutoConsume(boolean autoConsume) {
mPaymentManager.setAutoConsume(autoConsume);
}
// consume a specific item
@Override
public void consume(String sku) {
mPaymentManager.consume(sku);
}
// query in app item detail info
@Override
public void querySkuDetails(String[] list) {
List<String> nKeys = Arrays.asList(list);
List<String> cKeys = Arrays.asList(mSkuDetails.get_keys());
@ -225,7 +213,6 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface {
}
}
@Override
public void addSkuDetail(String itemJson) {
JSONObject o = null;
try {
@ -244,12 +231,10 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface {
}
}
@Override
public void completeSkuDetail() {
GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[] { mSkuDetails });
}
@Override
public void errorSkuDetail(String errorMessage) {
GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[] { errorMessage });
}
@ -263,6 +248,8 @@ public class GodotPayment extends GodotPlugin implements GodotPaymentInterface {
@NonNull
@Override
public List<String> getPluginMethods() {
return Arrays.asList("purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume", "querySkuDetails", "isConnected");
return Arrays.asList("purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix",
"setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased",
"setAutoConsume", "consume", "querySkuDetails", "isConnected");
}
}

View file

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

View file

@ -0,0 +1,71 @@
/*************************************************************************/
/* 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);
}
}

View file

@ -0,0 +1,405 @@
/*************************************************************************/
/* 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();
}
}

View file

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

View file

@ -0,0 +1,141 @@
/*************************************************************************/
/* 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();
}

View file

@ -0,0 +1,141 @@
/*************************************************************************/
/* 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 android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import java.lang.ref.WeakReference;
import org.godotengine.godot.utils.HttpRequester;
import org.godotengine.godot.utils.RequestParams;
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();
}