diff options
| author | volzhs <volzhs@gmail.com> | 2016-06-26 02:46:40 +0900 | 
|---|---|---|
| committer | volzhs <volzhs@gmail.com> | 2016-07-18 23:45:58 +0900 | 
| commit | 79cb91dc842eded0fcbb562f127996759abeddc7 (patch) | |
| tree | d67e8e8ab6964c6138d9f0f930074cab90dd6f96 /platform/android/java/src | |
| parent | f26f181ba964d4fa4903ab936c26a27c65a1e525 (diff) | |
Add querying details of IAP items for android
Diffstat (limited to 'platform/android/java/src')
| -rw-r--r-- | platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java | 172 | ||||
| -rw-r--r-- | platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java | 255 | 
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;  	}  } -  |