|
@@ -33,6 +33,7 @@ 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.pm.ResolveInfo;
|
|
import android.content.ServiceConnection;
|
|
import android.content.ServiceConnection;
|
|
import android.os.Bundle;
|
|
import android.os.Bundle;
|
|
import android.os.IBinder;
|
|
import android.os.IBinder;
|
|
@@ -49,19 +50,73 @@ import org.json.JSONObject;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Arrays;
|
|
|
|
+import java.util.List;
|
|
|
|
|
|
public class PaymentsManager {
|
|
public class PaymentsManager {
|
|
|
|
|
|
private static String TAG = "PaymentsManager";
|
|
private static String TAG = "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 static boolean auto_consume = true;
|
|
|
|
|
|
- private Activity activity;
|
|
|
|
|
|
+ private GodotPaymentV3 godotPaymentV3;
|
|
|
|
+
|
|
|
|
+ // Is setup done?
|
|
|
|
+ private boolean mSetupDone = false;
|
|
|
|
+
|
|
|
|
+ // Has this object been disposed of? (If so, we should ignore callbacks, etc)
|
|
|
|
+ private boolean mDisposed = false;
|
|
|
|
+
|
|
|
|
+ // Connection to the service
|
|
IInAppBillingService mService;
|
|
IInAppBillingService mService;
|
|
|
|
+ ServiceConnection mServiceConn;
|
|
|
|
+
|
|
|
|
+ private Activity activity;
|
|
|
|
+ private Context context;
|
|
|
|
+
|
|
|
|
+ // Billing response codes
|
|
|
|
+ public static final int BILLING_RESPONSE_RESULT_OK = 0;
|
|
|
|
+ public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
|
|
|
|
+ public static final int BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE = 2;
|
|
|
|
+ public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
|
|
|
|
+ public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
|
|
|
|
+ public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
|
|
|
|
+ public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
|
|
|
|
+ public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
|
|
|
|
+ public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
|
|
|
|
+
|
|
|
|
+ // IAB Helper error codes
|
|
|
|
+ public static final int IABHELPER_ERROR_BASE = -1000;
|
|
|
|
+ public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
|
|
|
|
+ public static final int IABHELPER_BAD_RESPONSE = -1002;
|
|
|
|
+ public static final int IABHELPER_VERIFICATION_FAILED = -1003;
|
|
|
|
+ public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
|
|
|
|
+ public static final int IABHELPER_USER_CANCELLED = -1005;
|
|
|
|
+ public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
|
|
|
|
+ public static final int IABHELPER_MISSING_TOKEN = -1007;
|
|
|
|
+ public static final int IABHELPER_UNKNOWN_ERROR = -1008;
|
|
|
|
+ public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
|
|
|
|
+ public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
|
|
|
|
+ public static final int IABHELPER_SUBSCRIPTION_UPDATE_NOT_AVAILABLE = -1011;
|
|
|
|
+
|
|
|
|
+ // Keys for the responses from InAppBillingService
|
|
|
|
+ public static final String RESPONSE_CODE = "RESPONSE_CODE";
|
|
|
|
+ public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
|
|
|
|
+ public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
|
|
|
|
+ public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
|
|
|
|
+ public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
|
|
|
|
+ public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
|
|
|
|
+ public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
|
|
|
|
+ public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
|
|
|
|
+ public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
|
|
|
|
+
|
|
|
|
+ // Item types
|
|
|
|
+ public static final String ITEM_TYPE_INAPP = "inapp";
|
|
|
|
+ public static final String ITEM_TYPE_SUBS = "subs";
|
|
|
|
+
|
|
|
|
+ public static final int REQUEST_CODE_FOR_PURCHASE = 10001;
|
|
|
|
|
|
public void setActivity(Activity activity) {
|
|
public void setActivity(Activity activity) {
|
|
this.activity = activity;
|
|
this.activity = activity;
|
|
|
|
+ this.context = activity.getApplicationContext();
|
|
}
|
|
}
|
|
|
|
|
|
public static PaymentsManager createManager(Activity activity) {
|
|
public static PaymentsManager createManager(Activity activity) {
|
|
@@ -71,49 +126,88 @@ public class PaymentsManager {
|
|
|
|
|
|
private PaymentsManager(Activity activity) {
|
|
private PaymentsManager(Activity activity) {
|
|
this.activity = activity;
|
|
this.activity = activity;
|
|
|
|
+ this.context = activity.getApplicationContext();
|
|
}
|
|
}
|
|
|
|
|
|
- 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);
|
|
|
|
|
|
+ public void initService() {
|
|
|
|
+ // Cancel the service creation if it has already been created or it creating
|
|
|
|
+ if (mDisposed || mSetupDone) {
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- ServiceConnection mServiceConn = new ServiceConnection() {
|
|
|
|
- @Override
|
|
|
|
- public void onServiceDisconnected(ComponentName name) {
|
|
|
|
- mService = null;
|
|
|
|
|
|
+ mServiceConn = new ServiceConnection() {
|
|
|
|
+ @Override
|
|
|
|
+ public void onServiceDisconnected(ComponentName name) {
|
|
|
|
+ mService = null;
|
|
|
|
|
|
- // At this stage, godotPaymentV3 might not have been initialized yet.
|
|
|
|
- if (godotPaymentV3 != null) {
|
|
|
|
- godotPaymentV3.callbackDisconnected();
|
|
|
|
|
|
+ // At this stage, godotPaymentV3 might not have been initialized yet.
|
|
|
|
+ if (godotPaymentV3 != null) {
|
|
|
|
+ godotPaymentV3.callbackDisconnected();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- @Override
|
|
|
|
- public void onServiceConnected(ComponentName name, IBinder service) {
|
|
|
|
- mService = IInAppBillingService.Stub.asInterface(service);
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public void onServiceConnected(ComponentName name, IBinder service) {
|
|
|
|
+ if (mDisposed) return;
|
|
|
|
+
|
|
|
|
+ mService = IInAppBillingService.Stub.asInterface(service);
|
|
|
|
+
|
|
|
|
+ String packageName = context.getPackageName();
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // check for in-app billing v3 support
|
|
|
|
+ int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
|
|
|
|
+
|
|
|
|
+ if (response != BILLING_RESPONSE_RESULT_OK) {
|
|
|
|
+ Log.i(TAG, "Device does not support billing 3.");
|
|
|
|
+
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
- // At this stage, godotPaymentV3 might not have been initialized yet.
|
|
|
|
- if (godotPaymentV3 != null) {
|
|
|
|
- godotPaymentV3.callbackConnected();
|
|
|
|
|
|
+ mSetupDone = true;
|
|
|
|
+ } catch (RemoteException e) {
|
|
|
|
+ Log.d(TAG, "Error binding ServiceConnection:" + e.getMessage());
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // At this stage, godotPaymentV3 might not have been initialized yet.
|
|
|
|
+ if (godotPaymentV3 != null) {
|
|
|
|
+ godotPaymentV3.callbackConnected();
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
|
|
|
|
+ serviceIntent.setPackage("com.android.vending");
|
|
|
|
+
|
|
|
|
+ List<ResolveInfo> intentServices = context.getPackageManager().queryIntentServices(serviceIntent, 0);
|
|
|
|
+ if (intentServices != null && !intentServices.isEmpty()) {
|
|
|
|
+ // service available to handle that Intent
|
|
|
|
+ context.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
|
|
|
|
+ } else {
|
|
|
|
+ Log.i(TAG, "Billing service unavailable on device.");
|
|
}
|
|
}
|
|
- };
|
|
|
|
|
|
+
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void destroy() {
|
|
|
|
+ if (mService != null) {
|
|
|
|
+ activity.unbindService(mServiceConn);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ mSetupDone = false;
|
|
|
|
+ mDisposed = true;
|
|
|
|
+ mServiceConn = null;
|
|
|
|
+ mService = null;
|
|
|
|
+ activity = null;
|
|
|
|
+ context = null;
|
|
|
|
+ }
|
|
|
|
|
|
public void requestPurchase(final String sku, String transactionId) {
|
|
public void requestPurchase(final String sku, String transactionId) {
|
|
- new PurchaseTask(mService, Godot.getInstance()) {
|
|
|
|
|
|
+ if (!mSetupDone) return;
|
|
|
|
|
|
|
|
+ PurchaseTask purchaseTask = new PurchaseTask(mService, Godot.getInstance()) {
|
|
@Override
|
|
@Override
|
|
protected void error(String message) {
|
|
protected void error(String message) {
|
|
godotPaymentV3.callbackFail();
|
|
godotPaymentV3.callbackFail();
|
|
@@ -128,18 +222,17 @@ public class PaymentsManager {
|
|
protected void alreadyOwned() {
|
|
protected void alreadyOwned() {
|
|
godotPaymentV3.callbackAlreadyOwned(sku);
|
|
godotPaymentV3.callbackAlreadyOwned(sku);
|
|
}
|
|
}
|
|
|
|
+ };
|
|
|
|
|
|
- }
|
|
|
|
- .purchase(sku, transactionId);
|
|
|
|
|
|
+ purchaseTask.purchase(sku, transactionId);
|
|
}
|
|
}
|
|
|
|
|
|
public boolean isConnected() {
|
|
public boolean isConnected() {
|
|
- return mService != null;
|
|
|
|
|
|
+ return mSetupDone && mService != null;
|
|
}
|
|
}
|
|
|
|
|
|
public void consumeUnconsumedPurchases() {
|
|
public void consumeUnconsumedPurchases() {
|
|
- new ReleaseAllConsumablesTask(mService, activity) {
|
|
|
|
-
|
|
|
|
|
|
+ ReleaseAllConsumablesTask releaseAllConsumablesTask = 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);
|
|
@@ -156,8 +249,9 @@ public class PaymentsManager {
|
|
Log.d(TAG, "callbackSuccessNoUnconsumedPurchases :");
|
|
Log.d(TAG, "callbackSuccessNoUnconsumedPurchases :");
|
|
godotPaymentV3.callbackSuccessNoUnconsumedPurchases();
|
|
godotPaymentV3.callbackSuccessNoUnconsumedPurchases();
|
|
}
|
|
}
|
|
- }
|
|
|
|
- .consumeItAll();
|
|
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ releaseAllConsumablesTask.consumeItAll();
|
|
}
|
|
}
|
|
|
|
|
|
public void requestPurchased() {
|
|
public void requestPurchased() {
|
|
@@ -167,12 +261,12 @@ public class PaymentsManager {
|
|
String continueToken = null;
|
|
String continueToken = null;
|
|
|
|
|
|
do {
|
|
do {
|
|
- Bundle bundle = mService.getPurchases(3, activity.getPackageName(), "inapp", continueToken);
|
|
|
|
|
|
+ Bundle bundle = mService.getPurchases(3, activity.getPackageName(), ITEM_TYPE_INAPP, continueToken);
|
|
|
|
|
|
- if (bundle.getInt("RESPONSE_CODE") == 0) {
|
|
|
|
|
|
+ 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");
|
|
|
|
|
|
+ final ArrayList<String> myPurchases = bundle.getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST);
|
|
|
|
+ final ArrayList<String> mySignatures = bundle.getStringArrayList(RESPONSE_INAPP_SIGNATURE_LIST);
|
|
|
|
|
|
if (myPurchases == null || myPurchases.size() == 0) {
|
|
if (myPurchases == null || myPurchases.size() == 0) {
|
|
godotPaymentV3.callbackPurchased("", "", "");
|
|
godotPaymentV3.callbackPurchased("", "", "");
|
|
@@ -188,7 +282,7 @@ public class PaymentsManager {
|
|
String token = inappPurchaseData.getString("purchaseToken");
|
|
String token = inappPurchaseData.getString("purchaseToken");
|
|
String signature = mySignatures.get(i);
|
|
String signature = mySignatures.get(i);
|
|
|
|
|
|
- pc.setConsumableValue("ticket_signautre", sku, signature);
|
|
|
|
|
|
+ pc.setConsumableValue("ticket_signature", sku, signature);
|
|
pc.setConsumableValue("ticket", sku, receipt);
|
|
pc.setConsumableValue("ticket", sku, receipt);
|
|
pc.setConsumableFlag("block", sku, true);
|
|
pc.setConsumableFlag("block", sku, true);
|
|
pc.setConsumableValue("token", sku, token);
|
|
pc.setConsumableValue("token", sku, token);
|
|
@@ -198,7 +292,7 @@ public class PaymentsManager {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- continueToken = bundle.getString("INAPP_CONTINUATION_TOKEN");
|
|
|
|
|
|
+ continueToken = bundle.getString(INAPP_CONTINUATION_TOKEN);
|
|
Log.d(TAG, "continue token = " + continueToken);
|
|
Log.d(TAG, "continue token = " + continueToken);
|
|
} while (!TextUtils.isEmpty(continueToken));
|
|
} while (!TextUtils.isEmpty(continueToken));
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
@@ -207,7 +301,7 @@ public class PaymentsManager {
|
|
}
|
|
}
|
|
|
|
|
|
public void processPurchaseResponse(int resultCode, Intent data) {
|
|
public void processPurchaseResponse(int resultCode, Intent data) {
|
|
- new HandlePurchaseTask(activity) {
|
|
|
|
|
|
+ HandlePurchaseTask handlePurchaseTask = 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) {
|
|
@@ -238,8 +332,9 @@ public class PaymentsManager {
|
|
protected void canceled() {
|
|
protected void canceled() {
|
|
godotPaymentV3.callbackCancel();
|
|
godotPaymentV3.callbackCancel();
|
|
}
|
|
}
|
|
- }
|
|
|
|
- .handlePurchaseRequest(resultCode, data);
|
|
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ handlePurchaseTask.handlePurchaseRequest(resultCode, data);
|
|
}
|
|
}
|
|
|
|
|
|
public void validatePurchase(String purchaseToken, final String sku) {
|
|
public void validatePurchase(String purchaseToken, final String sku) {
|
|
@@ -299,7 +394,7 @@ public class PaymentsManager {
|
|
|
|
|
|
// Workaround to bug where sometimes response codes come as Long instead of Integer
|
|
// Workaround to bug where sometimes response codes come as Long instead of Integer
|
|
int getResponseCodeFromBundle(Bundle b) {
|
|
int getResponseCodeFromBundle(Bundle b) {
|
|
- Object o = b.get("RESPONSE_CODE");
|
|
|
|
|
|
+ Object o = b.get(RESPONSE_CODE);
|
|
if (o == null) {
|
|
if (o == null) {
|
|
//logDebug("Bundle with null response code, assuming OK (known issue)");
|
|
//logDebug("Bundle with null response code, assuming OK (known issue)");
|
|
return BILLING_RESPONSE_RESULT_OK;
|
|
return BILLING_RESPONSE_RESULT_OK;
|
|
@@ -395,8 +490,8 @@ public class PaymentsManager {
|
|
querySkus.putStringArrayList("ITEM_ID_LIST", skuPartList);
|
|
querySkus.putStringArrayList("ITEM_ID_LIST", skuPartList);
|
|
Bundle skuDetails = null;
|
|
Bundle skuDetails = null;
|
|
try {
|
|
try {
|
|
- skuDetails = mService.getSkuDetails(3, activity.getPackageName(), "inapp", querySkus);
|
|
|
|
- if (!skuDetails.containsKey("DETAILS_LIST")) {
|
|
|
|
|
|
+ skuDetails = mService.getSkuDetails(3, activity.getPackageName(), ITEM_TYPE_INAPP, querySkus);
|
|
|
|
+ if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
|
|
int response = getResponseCodeFromBundle(skuDetails);
|
|
int response = getResponseCodeFromBundle(skuDetails);
|
|
if (response != BILLING_RESPONSE_RESULT_OK) {
|
|
if (response != BILLING_RESPONSE_RESULT_OK) {
|
|
godotPaymentV3.errorSkuDetail(getResponseDesc(response));
|
|
godotPaymentV3.errorSkuDetail(getResponseDesc(response));
|
|
@@ -406,7 +501,7 @@ public class PaymentsManager {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST");
|
|
|
|
|
|
+ ArrayList<String> responseList = skuDetails.getStringArrayList(RESPONSE_GET_SKU_DETAILS_LIST);
|
|
|
|
|
|
for (String thisResponse : responseList) {
|
|
for (String thisResponse : responseList) {
|
|
Log.d(TAG, "response = " + thisResponse);
|
|
Log.d(TAG, "response = " + thisResponse);
|
|
@@ -423,8 +518,6 @@ public class PaymentsManager {
|
|
.start();
|
|
.start();
|
|
}
|
|
}
|
|
|
|
|
|
- private GodotPaymentV3 godotPaymentV3;
|
|
|
|
-
|
|
|
|
public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
|
|
public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
|
|
this.godotPaymentV3 = godotPaymentV3;
|
|
this.godotPaymentV3 = godotPaymentV3;
|
|
}
|
|
}
|