|
@@ -28,119 +28,120 @@
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
package org.godotengine.godot.payments;
|
|
package org.godotengine.godot.payments;
|
|
|
|
|
|
-import java.util.ArrayList;
|
|
|
|
-import java.util.List;
|
|
|
|
-
|
|
|
|
-import android.os.RemoteException;
|
|
|
|
import android.app.Activity;
|
|
import android.app.Activity;
|
|
import android.content.ComponentName;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.Intent;
|
|
import android.content.ServiceConnection;
|
|
import android.content.ServiceConnection;
|
|
|
|
+import android.os.Bundle;
|
|
import android.os.IBinder;
|
|
import android.os.IBinder;
|
|
|
|
+import android.os.RemoteException;
|
|
|
|
+import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.util.Log;
|
|
-import android.os.Bundle;
|
|
|
|
|
|
|
|
-import org.json.JSONException;
|
|
|
|
-import org.json.JSONObject;
|
|
|
|
-import org.json.JSONStringer;
|
|
|
|
|
|
+import com.android.vending.billing.IInAppBillingService;
|
|
|
|
|
|
-import org.godotengine.godot.Dictionary;
|
|
|
|
import org.godotengine.godot.Godot;
|
|
import org.godotengine.godot.Godot;
|
|
import org.godotengine.godot.GodotPaymentV3;
|
|
import org.godotengine.godot.GodotPaymentV3;
|
|
-import com.android.vending.billing.IInAppBillingService;
|
|
|
|
|
|
+import org.json.JSONException;
|
|
|
|
+import org.json.JSONObject;
|
|
|
|
+
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
|
public class PaymentsManager {
|
|
public class PaymentsManager {
|
|
|
|
|
|
public static final int BILLING_RESPONSE_RESULT_OK = 0;
|
|
public static final int BILLING_RESPONSE_RESULT_OK = 0;
|
|
public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001;
|
|
public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001;
|
|
private static boolean auto_consume = true;
|
|
private static boolean auto_consume = true;
|
|
-
|
|
|
|
|
|
+
|
|
private Activity activity;
|
|
private Activity activity;
|
|
IInAppBillingService mService;
|
|
IInAppBillingService mService;
|
|
|
|
|
|
- public void setActivity(Activity activity){
|
|
|
|
|
|
+ public void setActivity(Activity activity) {
|
|
this.activity = activity;
|
|
this.activity = activity;
|
|
}
|
|
}
|
|
|
|
|
|
- public static PaymentsManager createManager(Activity activity){
|
|
|
|
|
|
+ public static PaymentsManager createManager(Activity activity) {
|
|
PaymentsManager manager = new PaymentsManager(activity);
|
|
PaymentsManager manager = new PaymentsManager(activity);
|
|
return manager;
|
|
return manager;
|
|
}
|
|
}
|
|
-
|
|
|
|
- private PaymentsManager(Activity activity){
|
|
|
|
|
|
+
|
|
|
|
+ private PaymentsManager(Activity activity) {
|
|
this.activity = activity;
|
|
this.activity = activity;
|
|
}
|
|
}
|
|
-
|
|
|
|
- public PaymentsManager initService(){
|
|
|
|
|
|
+
|
|
|
|
+ public PaymentsManager initService() {
|
|
Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
|
|
Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
|
|
intent.setPackage("com.android.vending");
|
|
intent.setPackage("com.android.vending");
|
|
activity.bindService(
|
|
activity.bindService(
|
|
- intent,
|
|
|
|
- mServiceConn,
|
|
|
|
|
|
+ intent,
|
|
|
|
+ mServiceConn,
|
|
Context.BIND_AUTO_CREATE);
|
|
Context.BIND_AUTO_CREATE);
|
|
return this;
|
|
return this;
|
|
}
|
|
}
|
|
|
|
|
|
- public void destroy(){
|
|
|
|
|
|
+ public void destroy() {
|
|
if (mService != null) {
|
|
if (mService != null) {
|
|
- activity.unbindService(mServiceConn);
|
|
|
|
- }
|
|
|
|
|
|
+ activity.unbindService(mServiceConn);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
ServiceConnection mServiceConn = new ServiceConnection() {
|
|
ServiceConnection mServiceConn = new ServiceConnection() {
|
|
- @Override
|
|
|
|
- public void onServiceDisconnected(ComponentName name) {
|
|
|
|
- mService = null;
|
|
|
|
- }
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void onServiceDisconnected(ComponentName name) {
|
|
|
|
+ mService = null;
|
|
|
|
+ }
|
|
|
|
|
|
- @Override
|
|
|
|
- public void onServiceConnected(ComponentName name, IBinder service) {
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void onServiceConnected(ComponentName name, IBinder service) {
|
|
mService = IInAppBillingService.Stub.asInterface(service);
|
|
mService = IInAppBillingService.Stub.asInterface(service);
|
|
- }
|
|
|
|
|
|
+ }
|
|
};
|
|
};
|
|
-
|
|
|
|
- public void requestPurchase(final String sku, String transactionId){
|
|
|
|
|
|
+
|
|
|
|
+ public void requestPurchase(final String sku, String transactionId) {
|
|
new PurchaseTask(mService, Godot.getInstance()) {
|
|
new PurchaseTask(mService, Godot.getInstance()) {
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void error(String message) {
|
|
protected void error(String message) {
|
|
godotPaymentV3.callbackFail();
|
|
godotPaymentV3.callbackFail();
|
|
-
|
|
|
|
|
|
+
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void canceled() {
|
|
protected void canceled() {
|
|
godotPaymentV3.callbackCancel();
|
|
godotPaymentV3.callbackCancel();
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void alreadyOwned() {
|
|
protected void alreadyOwned() {
|
|
godotPaymentV3.callbackAlreadyOwned(sku);
|
|
godotPaymentV3.callbackAlreadyOwned(sku);
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
}.purchase(sku, transactionId);
|
|
}.purchase(sku, transactionId);
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- public void consumeUnconsumedPurchases(){
|
|
|
|
|
|
+ public void consumeUnconsumedPurchases() {
|
|
new ReleaseAllConsumablesTask(mService, activity) {
|
|
new ReleaseAllConsumablesTask(mService, activity) {
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void success(String sku, String receipt, String signature, String token) {
|
|
protected void success(String sku, String receipt, String signature, String token) {
|
|
godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku);
|
|
godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku);
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void error(String message) {
|
|
protected void error(String message) {
|
|
- godotPaymentV3.callbackFail();
|
|
|
|
-
|
|
|
|
|
|
+ Log.d("godot", "consumeUnconsumedPurchases :" + message);
|
|
|
|
+ godotPaymentV3.callbackFailConsume();
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
protected void notRequired() {
|
|
protected void notRequired() {
|
|
|
|
+ Log.d("godot", "callbackSuccessNoUnconsumedPurchases :");
|
|
godotPaymentV3.callbackSuccessNoUnconsumedPurchases();
|
|
godotPaymentV3.callbackSuccessNoUnconsumedPurchases();
|
|
-
|
|
|
|
|
|
+
|
|
}
|
|
}
|
|
}.consumeItAll();
|
|
}.consumeItAll();
|
|
}
|
|
}
|
|
@@ -190,113 +191,207 @@ public class PaymentsManager {
|
|
Log.d("godot", "Error requesting purchased products:" + e.getClass().getName() + ":" + e.getMessage());
|
|
Log.d("godot", "Error requesting purchased products:" + e.getClass().getName() + ":" + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
public void processPurchaseResponse(int resultCode, Intent data) {
|
|
public void processPurchaseResponse(int resultCode, Intent data) {
|
|
- new HandlePurchaseTask(activity){
|
|
|
|
|
|
+ new HandlePurchaseTask(activity) {
|
|
|
|
|
|
@Override
|
|
@Override
|
|
protected void success(final String sku, final String signature, final String ticket) {
|
|
protected void success(final String sku, final String signature, final String ticket) {
|
|
godotPaymentV3.callbackSuccess(ticket, signature, sku);
|
|
godotPaymentV3.callbackSuccess(ticket, signature, sku);
|
|
|
|
|
|
- if (auto_consume){
|
|
|
|
|
|
+ if (auto_consume) {
|
|
new ConsumeTask(mService, activity) {
|
|
new ConsumeTask(mService, activity) {
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void success(String ticket) {
|
|
protected void success(String ticket) {
|
|
-// godotPaymentV3.callbackSuccess("");
|
|
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void error(String message) {
|
|
protected void error(String message) {
|
|
godotPaymentV3.callbackFail();
|
|
godotPaymentV3.callbackFail();
|
|
-
|
|
|
|
|
|
+
|
|
}
|
|
}
|
|
}.consume(sku);
|
|
}.consume(sku);
|
|
}
|
|
}
|
|
-
|
|
|
|
-// godotPaymentV3.callbackSuccess(new PaymentsCache(activity).getConsumableValue("ticket", sku),signature);
|
|
|
|
-// godotPaymentV3.callbackSuccess(ticket);
|
|
|
|
- //validatePurchase(purchaseToken, sku);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
protected void error(String message) {
|
|
protected void error(String message) {
|
|
godotPaymentV3.callbackFail();
|
|
godotPaymentV3.callbackFail();
|
|
-
|
|
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
protected void canceled() {
|
|
protected void canceled() {
|
|
godotPaymentV3.callbackCancel();
|
|
godotPaymentV3.callbackCancel();
|
|
-
|
|
|
|
}
|
|
}
|
|
}.handlePurchaseRequest(resultCode, data);
|
|
}.handlePurchaseRequest(resultCode, data);
|
|
}
|
|
}
|
|
-
|
|
|
|
- public void validatePurchase(String purchaseToken, final String sku){
|
|
|
|
-
|
|
|
|
- new ValidateTask(activity, godotPaymentV3){
|
|
|
|
|
|
+
|
|
|
|
+ public void validatePurchase(String purchaseToken, final String sku) {
|
|
|
|
+
|
|
|
|
+ new ValidateTask(activity, godotPaymentV3) {
|
|
|
|
|
|
@Override
|
|
@Override
|
|
protected void success() {
|
|
protected void success() {
|
|
-
|
|
|
|
|
|
+
|
|
new ConsumeTask(mService, activity) {
|
|
new ConsumeTask(mService, activity) {
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void success(String ticket) {
|
|
protected void success(String ticket) {
|
|
godotPaymentV3.callbackSuccess(ticket, null, sku);
|
|
godotPaymentV3.callbackSuccess(ticket, null, sku);
|
|
-
|
|
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void error(String message) {
|
|
protected void error(String message) {
|
|
godotPaymentV3.callbackFail();
|
|
godotPaymentV3.callbackFail();
|
|
-
|
|
|
|
}
|
|
}
|
|
}.consume(sku);
|
|
}.consume(sku);
|
|
-
|
|
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
protected void error(String message) {
|
|
protected void error(String message) {
|
|
godotPaymentV3.callbackFail();
|
|
godotPaymentV3.callbackFail();
|
|
-
|
|
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
protected void canceled() {
|
|
protected void canceled() {
|
|
godotPaymentV3.callbackCancel();
|
|
godotPaymentV3.callbackCancel();
|
|
-
|
|
|
|
}
|
|
}
|
|
}.validatePurchase(sku);
|
|
}.validatePurchase(sku);
|
|
}
|
|
}
|
|
-
|
|
|
|
- public void setAutoConsume(boolean autoConsume){
|
|
|
|
|
|
+
|
|
|
|
+ public void setAutoConsume(boolean autoConsume) {
|
|
auto_consume = autoConsume;
|
|
auto_consume = autoConsume;
|
|
}
|
|
}
|
|
-
|
|
|
|
- public void consume(final String sku){
|
|
|
|
|
|
+
|
|
|
|
+ public void consume(final String sku) {
|
|
new ConsumeTask(mService, activity) {
|
|
new ConsumeTask(mService, activity) {
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void success(String ticket) {
|
|
protected void success(String ticket) {
|
|
godotPaymentV3.callbackSuccessProductMassConsumed(ticket, "", sku);
|
|
godotPaymentV3.callbackSuccessProductMassConsumed(ticket, "", sku);
|
|
-
|
|
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
protected void error(String message) {
|
|
protected void error(String message) {
|
|
- godotPaymentV3.callbackFail();
|
|
|
|
-
|
|
|
|
|
|
+ godotPaymentV3.callbackFailConsume();
|
|
}
|
|
}
|
|
}.consume(sku);
|
|
}.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) {
|
|
|
|
+ godotPaymentV3.errorSkuDetail(getResponseDesc(response));
|
|
|
|
+ } else {
|
|
|
|
+ godotPaymentV3.errorSkuDetail("No error but no detail list.");
|
|
|
|
+ }
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
|
|
|
|
+
|
|
|
|
+ for (String thisResponse : responseList) {
|
|
|
|
+ Log.d("godot", "response = "+thisResponse);
|
|
|
|
+ godotPaymentV3.addSkuDetail(thisResponse);
|
|
|
|
+ }
|
|
|
|
+ } catch (RemoteException e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ godotPaymentV3.errorSkuDetail("RemoteException error!");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ godotPaymentV3.completeSkuDetail();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })).start();
|
|
|
|
+ }
|
|
|
|
+
|
|
private GodotPaymentV3 godotPaymentV3;
|
|
private GodotPaymentV3 godotPaymentV3;
|
|
-
|
|
|
|
|
|
+
|
|
public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
|
|
public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
|
|
this.godotPaymentV3 = godotPaymentV3;
|
|
this.godotPaymentV3 = godotPaymentV3;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
-
|
|
|