1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-16 14:00:40 +00:00

Refactor PaymentsManager according to Google's guidelines

This commit is contained in:
Xavier Sellier
2018-04-28 12:39:48 -04:00
parent 83d5db294d
commit 72d81d5903
5 changed files with 163 additions and 68 deletions

View File

@@ -388,7 +388,9 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
result_callback = null; result_callback = null;
mPaymentsManager = PaymentsManager.createManager(this).initService(); mPaymentsManager = PaymentsManager.createManager(this);
mPaymentsManager.initService();
godot_initialized = true; godot_initialized = true;
} }

View File

@@ -59,8 +59,8 @@ abstract public class HandlePurchaseTask {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
PaymentsCache pc = new PaymentsCache(context); PaymentsCache pc = new PaymentsCache(context);
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); String purchaseData = data.getStringExtra(PaymentsManager.RESPONSE_INAPP_PURCHASE_DATA);
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); String dataSignature = data.getStringExtra(PaymentsManager.RESPONSE_INAPP_SIGNATURE);
try { try {
JSONObject jo = new JSONObject(purchaseData); JSONObject jo = new JSONObject(purchaseData);
@@ -73,7 +73,7 @@ abstract public class HandlePurchaseTask {
return; return;
} }
pc.setConsumableValue("ticket_signautre", productId, dataSignature); pc.setConsumableValue("ticket_signature", productId, dataSignature);
pc.setConsumableValue("ticket", productId, purchaseData); pc.setConsumableValue("ticket", productId, purchaseData);
pc.setConsumableFlag("block", productId, true); pc.setConsumableFlag("block", productId, true);
pc.setConsumableValue("token", productId, purchaseToken); pc.setConsumableValue("token", productId, purchaseToken);

View File

@@ -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() { public void initService() {
Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); // Cancel the service creation if it has already been created or it creating
intent.setPackage("com.android.vending"); if (mDisposed || mSetupDone) {
activity.bindService( return;
intent, }
mServiceConn,
Context.BIND_AUTO_CREATE); mServiceConn = new ServiceConnection() {
return this; @Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
// At this stage, godotPaymentV3 might not have been initialized yet.
if (godotPaymentV3 != null) {
godotPaymentV3.callbackDisconnected();
}
}
@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;
}
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() { public void destroy() {
if (mService != null) { if (mService != null) {
activity.unbindService(mServiceConn); activity.unbindService(mServiceConn);
} }
mSetupDone = false;
mDisposed = true;
mServiceConn = null;
mService = null;
activity = null;
context = null;
} }
ServiceConnection 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();
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IInAppBillingService.Stub.asInterface(service);
// At this stage, godotPaymentV3 might not have been initialized yet.
if (godotPaymentV3 != null) {
godotPaymentV3.callbackConnected();
}
}
};
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);
} }
};
} purchaseTask.purchase(sku, transactionId);
.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> myPurchases = bundle.getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST);
final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_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); skuDetails = mService.getSkuDetails(3, activity.getPackageName(), ITEM_TYPE_INAPP, querySkus);
if (!skuDetails.containsKey("DETAILS_LIST")) { 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;
} }

View File

@@ -70,12 +70,12 @@ abstract public class PurchaseTask {
Bundle buyIntentBundle; Bundle buyIntentBundle;
try { try {
buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, "inapp", hash); buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, PaymentsManager.ITEM_TYPE_INAPP, hash);
} catch (RemoteException e) { } catch (RemoteException e) {
error(e.getMessage()); error(e.getMessage());
return; return;
} }
Object rc = buyIntentBundle.get("RESPONSE_CODE"); Object rc = buyIntentBundle.get(PaymentsManager.RESPONSE_CODE);
int responseCode = 0; int responseCode = 0;
if (rc == null) { if (rc == null) {
responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK; responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK;
@@ -85,16 +85,16 @@ abstract public class PurchaseTask {
responseCode = (int)((Long)rc).longValue(); responseCode = (int)((Long)rc).longValue();
} }
if (responseCode == 1 || responseCode == 3 || responseCode == 4) { if (responseCode == PaymentsManager.BILLING_RESPONSE_RESULT_USER_CANCELED || responseCode == PaymentsManager.BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE || responseCode == PaymentsManager.BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE) {
canceled(); canceled();
return; return;
} }
if (responseCode == 7) { if (responseCode == PaymentsManager.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED) {
alreadyOwned(); alreadyOwned();
return; return;
} }
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT"); PendingIntent pendingIntent = buyIntentBundle.getParcelable(PaymentsManager.RESPONSE_BUY_INTENT);
pc.setConsumableValue("validation_hash", sku, hash); pc.setConsumableValue("validation_hash", sku, hash);
try { try {
context.startIntentSenderForResult( context.startIntentSenderForResult(

View File

@@ -57,7 +57,7 @@ abstract public class ReleaseAllConsumablesTask {
public void consumeItAll() { public void consumeItAll() {
try { try {
Bundle bundle = mService.getPurchases(3, context.getPackageName(), "inapp", null); Bundle bundle = mService.getPurchases(3, context.getPackageName(), PaymentsManager.ITEM_TYPE_INAPP, null);
// TODO: // TODO:
// Check if this loop is useful and remove it if not // Check if this loop is useful and remove it if not
@@ -67,8 +67,8 @@ abstract public class ReleaseAllConsumablesTask {
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> myPurchases = bundle.getStringArrayList(PaymentsManager.RESPONSE_INAPP_PURCHASE_DATA_LIST);
final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST"); final ArrayList<String> mySignatures = bundle.getStringArrayList(PaymentsManager.RESPONSE_INAPP_SIGNATURE_LIST);
if (myPurchases == null || myPurchases.size() == 0) { if (myPurchases == null || myPurchases.size() == 0) {
notRequired(); notRequired();