summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java172
-rw-r--r--platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java255
2 files changed, 279 insertions, 148 deletions
diff --git a/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java b/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java
index fde752acc9..b7de31ada3 100644
--- a/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java
+++ b/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java
@@ -28,99 +28,94 @@
/*************************************************************************/
package org.godotengine.godot;
-import org.godotengine.godot.Dictionary;
import android.app.Activity;
import android.util.Log;
+import org.godotengine.godot.payments.PaymentsManager;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
public class GodotPaymentV3 extends Godot.SingletonBase {
private Godot activity;
-
private Integer purchaseCallbackId = 0;
-
private String accessToken;
-
private String purchaseValidationUrlPrefix;
-
private String transactionId;
+ private PaymentsManager mPaymentManager;
+ private Dictionary mSkuDetails = new Dictionary();
- public void purchase( String _sku, String _transactionId) {
- final String sku = _sku;
- final String transactionId = _transactionId;
- activity.getPaymentsManager().setBaseSingleton(this);
+ public void purchase(final String sku, final String transactionId) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
- activity.getPaymentsManager().requestPurchase(sku, transactionId);
+ mPaymentManager.requestPurchase(sku, transactionId);
}
});
}
-
-/* public string requestPurchasedTicket(){
- activity.getPaymentsManager()
- }
-*/
- static public Godot.SingletonBase initialize(Activity p_activity) {
+ static public Godot.SingletonBase initialize(Activity p_activity) {
- return new GodotPaymentV3(p_activity);
- }
+ return new GodotPaymentV3(p_activity);
+ }
-
public GodotPaymentV3(Activity p_activity) {
- registerClass("GodotPayments", new String[] {"purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume"});
- activity=(Godot) p_activity;
+ registerClass("GodotPayments", new String[]{"purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume", "querySkuDetails"});
+ activity = (Godot) p_activity;
+ mPaymentManager = activity.getPaymentsManager();
+ mPaymentManager.setBaseSingleton(this);
}
- public void consumeUnconsumedPurchases(){
- activity.getPaymentsManager().setBaseSingleton(this);
+ public void consumeUnconsumedPurchases() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
- activity.getPaymentsManager().consumeUnconsumedPurchases();
+ mPaymentManager.consumeUnconsumedPurchases();
}
});
}
private String signature;
- public String getSignature(){
- return this.signature;
+
+ public String getSignature() {
+ return this.signature;
}
-
-
- public void callbackSuccess(String ticket, String signature, String sku){
-// Log.d(this.getClass().getName(), "PRE-Send callback to purchase success");
+
+ public void callbackSuccess(String ticket, String signature, String sku) {
GodotLib.callobject(purchaseCallbackId, "purchase_success", new Object[]{ticket, signature, sku});
-// Log.d(this.getClass().getName(), "POST-Send callback to purchase success");
-}
+ }
+
+ public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku) {
+ Log.d(this.getClass().getName(), "callbackSuccessProductMassConsumed > " + ticket + "," + signature + "," + sku);
+ GodotLib.calldeferred(purchaseCallbackId, "consume_success", new Object[]{ticket, signature, sku});
+ }
- public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku){
-// Log.d(this.getClass().getName(), "PRE-Send callback to consume success");
- Log.d(this.getClass().getName(), "callbackSuccessProductMassConsumed > "+ticket+","+signature+","+sku);
- GodotLib.calldeferred(purchaseCallbackId, "consume_success", new Object[]{ticket, signature, sku});
-// Log.d(this.getClass().getName(), "POST-Send callback to consume success");
+ public void callbackSuccessNoUnconsumedPurchases() {
+ GodotLib.calldeferred(purchaseCallbackId, "consume_not_required", new Object[]{});
}
- public void callbackSuccessNoUnconsumedPurchases(){
- GodotLib.calldeferred(purchaseCallbackId, "no_validation_required", new Object[]{});
+ public void callbackFailConsume() {
+ GodotLib.calldeferred(purchaseCallbackId, "consume_fail", new Object[]{});
}
-
- public void callbackFail(){
+
+ public void callbackFail() {
GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[]{});
-// GodotLib.callobject(purchaseCallbackId, "purchase_fail", new Object[]{});
}
-
- public void callbackCancel(){
+
+ public void callbackCancel() {
GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[]{});
-// GodotLib.callobject(purchaseCallbackId, "purchase_cancel", new Object[]{});
}
-
- public void callbackAlreadyOwned(String sku){
+
+ public void callbackAlreadyOwned(String sku) {
GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[]{sku});
}
-
+
public int getPurchaseCallbackId() {
return purchaseCallbackId;
}
@@ -129,11 +124,11 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
this.purchaseCallbackId = purchaseCallbackId;
}
- public String getPurchaseValidationUrlPrefix(){
- return this.purchaseValidationUrlPrefix ;
+ public String getPurchaseValidationUrlPrefix() {
+ return this.purchaseValidationUrlPrefix;
}
- public void setPurchaseValidationUrlPrefix(String url){
+ public void setPurchaseValidationUrlPrefix(String url) {
this.purchaseValidationUrlPrefix = url;
}
@@ -144,39 +139,80 @@ public class GodotPaymentV3 extends Godot.SingletonBase {
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
-
- public void setTransactionId(String transactionId){
+
+ public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
-
- public String getTransactionId(){
+
+ public String getTransactionId() {
return this.transactionId;
}
-
+
// request purchased items are not consumed
- public void requestPurchased(){
- activity.getPaymentsManager().setBaseSingleton(this);
+ public void requestPurchased() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
- activity.getPaymentsManager().requestPurchased();
+ mPaymentManager.requestPurchased();
}
});
}
-
+
// callback for requestPurchased()
- public void callbackPurchased(String receipt, String signature, String sku){
+ public void callbackPurchased(String receipt, String signature, String sku) {
GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[]{receipt, signature, sku});
}
-
+
// consume item automatically after purchase. default is true.
- public void setAutoConsume(boolean autoConsume){
- activity.getPaymentsManager().setAutoConsume(autoConsume);
+ public void setAutoConsume(boolean autoConsume) {
+ mPaymentManager.setAutoConsume(autoConsume);
}
-
+
// consume a specific item
- public void consume(String sku){
- activity.getPaymentsManager().consume(sku);
+ public void consume(String sku) {
+ mPaymentManager.consume(sku);
+ }
+
+ // query in app item detail info
+ public void querySkuDetails(String[] list) {
+ List<String> nKeys = Arrays.asList(list);
+ List<String> cKeys = Arrays.asList(mSkuDetails.get_keys());
+ ArrayList<String> fKeys = new ArrayList<String>();
+ for (String key : nKeys) {
+ if (!cKeys.contains(key)) {
+ fKeys.add(key);
+ }
+ }
+ if (fKeys.size() > 0) {
+ mPaymentManager.querySkuDetails(fKeys.toArray(new String[0]));
+ } else {
+ completeSkuDetail();
+ }
+ }
+
+ public void addSkuDetail(String itemJson) {
+ JSONObject o = null;
+ try {
+ o = new JSONObject(itemJson);
+ Dictionary item = new Dictionary();
+ item.put("type", o.optString("type"));
+ item.put("product_id", o.optString("productId"));
+ item.put("title", o.optString("title"));
+ item.put("description", o.optString("description"));
+ item.put("price", o.optString("price"));
+ item.put("price_currency_code", o.optString("price_currency_code"));
+ item.put("price_amount", 0.000001d * o.optLong("price_amount_micros"));
+ mSkuDetails.put(item.get("product_id").toString(), item);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void completeSkuDetail() {
+ GodotLib.calldeferred(purchaseCallbackId, "sku_details_complete", new Object[]{mSkuDetails});
+ }
+
+ public void errorSkuDetail(String errorMessage) {
+ GodotLib.calldeferred(purchaseCallbackId, "sku_details_error", new Object[]{errorMessage});
}
}
-
diff --git a/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java b/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java
index 35676e333e..753c0a6f93 100644
--- a/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java
+++ b/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java
@@ -28,119 +28,120 @@
/*************************************************************************/
package org.godotengine.godot.payments;
-import java.util.ArrayList;
-import java.util.List;
-
-import android.os.RemoteException;
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 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.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 static final int BILLING_RESPONSE_RESULT_OK = 0;
public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001;
private static boolean auto_consume = true;
-
+
private Activity activity;
IInAppBillingService mService;
- public void setActivity(Activity activity){
+ public void setActivity(Activity activity) {
this.activity = activity;
}
- public static PaymentsManager createManager(Activity activity){
+ public static PaymentsManager createManager(Activity activity) {
PaymentsManager manager = new PaymentsManager(activity);
return manager;
}
-
- private PaymentsManager(Activity activity){
+
+ private PaymentsManager(Activity activity) {
this.activity = activity;
}
-
- public PaymentsManager initService(){
+
+ public PaymentsManager initService() {
Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
intent.setPackage("com.android.vending");
activity.bindService(
- intent,
- mServiceConn,
+ intent,
+ mServiceConn,
Context.BIND_AUTO_CREATE);
return this;
}
- public void destroy(){
+ public void destroy() {
if (mService != null) {
- activity.unbindService(mServiceConn);
- }
+ activity.unbindService(mServiceConn);
+ }
}
-
+
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);
- }
+ }
};
-
- public void requestPurchase(final String sku, String transactionId){
+
+ public void requestPurchase(final String sku, String transactionId) {
new PurchaseTask(mService, Godot.getInstance()) {
-
+
@Override
protected void error(String message) {
godotPaymentV3.callbackFail();
-
+
}
-
+
@Override
protected void canceled() {
godotPaymentV3.callbackCancel();
}
-
+
@Override
protected void alreadyOwned() {
godotPaymentV3.callbackAlreadyOwned(sku);
}
-
+
}.purchase(sku, transactionId);
}
- public void consumeUnconsumedPurchases(){
+ public void consumeUnconsumedPurchases() {
new ReleaseAllConsumablesTask(mService, activity) {
-
+
@Override
protected void success(String sku, String receipt, String signature, String token) {
godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku);
}
-
+
@Override
protected void error(String message) {
- godotPaymentV3.callbackFail();
-
+ Log.d("godot", "consumeUnconsumedPurchases :" + message);
+ godotPaymentV3.callbackFailConsume();
+
}
@Override
protected void notRequired() {
+ Log.d("godot", "callbackSuccessNoUnconsumedPurchases :");
godotPaymentV3.callbackSuccessNoUnconsumedPurchases();
-
+
}
}.consumeItAll();
}
@@ -190,113 +191,207 @@ public class PaymentsManager {
Log.d("godot", "Error requesting purchased products:" + e.getClass().getName() + ":" + e.getMessage());
}
}
-
+
public void processPurchaseResponse(int resultCode, Intent data) {
- new HandlePurchaseTask(activity){
+ new HandlePurchaseTask(activity) {
@Override
protected void success(final String sku, final String signature, final String ticket) {
godotPaymentV3.callbackSuccess(ticket, signature, sku);
- if (auto_consume){
+ if (auto_consume) {
new ConsumeTask(mService, activity) {
-
+
@Override
protected void success(String ticket) {
-// godotPaymentV3.callbackSuccess("");
}
-
+
@Override
protected void error(String message) {
godotPaymentV3.callbackFail();
-
+
}
}.consume(sku);
}
-
-// godotPaymentV3.callbackSuccess(new PaymentsCache(activity).getConsumableValue("ticket", sku),signature);
-// godotPaymentV3.callbackSuccess(ticket);
- //validatePurchase(purchaseToken, sku);
}
@Override
protected void error(String message) {
godotPaymentV3.callbackFail();
-
}
@Override
protected void canceled() {
godotPaymentV3.callbackCancel();
-
}
}.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
protected void success() {
-
+
new ConsumeTask(mService, activity) {
-
+
@Override
protected void success(String ticket) {
godotPaymentV3.callbackSuccess(ticket, null, sku);
-
}
-
+
@Override
protected void error(String message) {
godotPaymentV3.callbackFail();
-
}
}.consume(sku);
-
+
}
@Override
protected void error(String message) {
godotPaymentV3.callbackFail();
-
}
@Override
protected void canceled() {
godotPaymentV3.callbackCancel();
-
}
}.validatePurchase(sku);
}
-
- public void setAutoConsume(boolean autoConsume){
+
+ public void setAutoConsume(boolean autoConsume) {
auto_consume = autoConsume;
}
-
- public void consume(final String sku){
+
+ public void consume(final String sku) {
new ConsumeTask(mService, activity) {
-
+
@Override
protected void success(String ticket) {
godotPaymentV3.callbackSuccessProductMassConsumed(ticket, "", sku);
-
}
-
+
@Override
protected void error(String message) {
- godotPaymentV3.callbackFail();
-
+ godotPaymentV3.callbackFailConsume();
}
}.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;
-
+
public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
this.godotPaymentV3 = godotPaymentV3;
}
}
-