http://www.bubuko.com/infodetail-930440.html

[html] view plaincopy
  1. 如billing开发文档所说,要在你的应用中实现In-app Billing只需要完成以下几步就可以了。

[html] view plaincopy
  1. 第一,把你上篇下载的AIDL文件添加到你的工程里,第二,把
<uses-permission android:name="com.android.vending.BILLING" />

这个权限加到你工程的AndroidManifest.xml文件中,第三,创建一个ServiceConnection,并把它绑定到IInAppBillingService中。完成上面三条后就可以使用支付了。当然这只是一个简单的介绍。其实Google的这个支付,大部分都是你手机上的Google Play来进行处理的,你只需要处理购买请求,处理购买结果就行了。文档写的很好,先把这个文档看完,就知道支付流程了。

正文:

1.内购商品相关

针对我的项目而言,我们在Google后台设置的是受管理可消耗的商品("managed per user account"),具体的就是游戏里的水晶,玩家可以多次购买。但是Google后台的这种可重复购买商品(还有一种是只能购买一次的商品"subscription")有个要求,就是你购买成功后需要主动向Google Play请求消耗这个商品,等消耗成功后你才可以再次下单购买。因此,在游戏里的支付会多出一个操作步骤就是请求消耗购买成功的商品。

2.检测设备是否支持Google Play Service

在正式开启支付前,Google billing会检查你的手机是否支持Google billing,这个下面会讲到。为了更好的用户体验,建议在Google billing检测之前,可以先检测一下用户的设备是否支持Google Play Service,其中基本要求就是安装了Google Play应用商店和Google Play Service。如果用户的设备不具备这两个,就可以弹出提示引导用户去安装。这里有两种方式可以用,一种是通过Google Play Service来进行检测,就是上篇下载的那个Service扩展包,一种是自己写代码,遍历设备上安装的应用程序,检查是否有安装Google Play。先说第一种。

(1)Google Play Service

上篇下载的Service包里会有一个库工程

把这个库工程导入你的eclipse,引用到你的工程里就可以用了,具体操作可以参加docs下的文档,so easy!导入成功后,调用其中的一个方法就可以了。

/*** Check the device to make sure it has the Google Play Services APK.If* it doesn‘t, display a dialog that allows users to download the APK from* the Google Play Store or enable it in the device‘s system settings*/private boolean checkPlayServices(){int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);if(resultCode != ConnectionResult.SUCCESS){if(GooglePlayServicesUtil.isUserRecoverableError(resultCode)){GooglePlayServicesUtil.getErrorDialog(resultCode, this,PLAY_SERVICES_RESOLUTION_REQUEST).show();}else{Log.i(TAG, "This device is not supported");finish();}return false;}return true;}

如果当前设备的Google Service不可用,就会弹出提示,引导用户去设置安装。如果此设备不支持的话,就也不需要检测Google billing是否可用了。多说一句,Google Play Service可以做很多事的,如果觉得只用上面的功能太简单的话,就可以考虑把应用自动更新也加上,当你在Google Play上传了新版程序后,Google Play会帮你提示用户更新程序。还有一个比较好玩的就是如果引入了这个库工程后,就可以加GCM了(Google Cloud Messaging),就是消息推送推送功能,当然这个比较麻烦,有兴趣的可以去加加看。

(2)遍历包名

Google Play的程序包名是"com.Android.vending",运行在设备上的Google Play Service的包名是"com.google.android.gms",可以在程序启动的时候遍历下设备上的包名,如果没有这两个东西就引导用户去安装。

遍历包名方法

//Check Google Playprotected boolean isHaveGooglePlay(Context context, String packageName){//Get PackageManagerfinal PackageManager packageManager = context.getPackageManager();//Get The All Install App Package NameList<PackageInfo> pInfo = packageManager.getInstalledPackages(0);//Create Name ListList<String> pName = new ArrayList<String>();//Add Package Name into Name Listif(pInfo != null){for(int i=0; i<pInfo.size(); i++){String pn = pInfo.get(i).packageName;pName.add(pn);//Log.v("Package Name", "PackAgeName: = " + pn);}}//Check return pName.contains(packageName);}

提示安装方法

 Uri uri = Uri.parse("market://details?id=" + "要安装程序的包名");Intent it = new Intent(Intent.ACTION_VIEW, uri); startActivity(it);

上面这个方法会打开你手机上的应用商店,定位到要安装的程序。

不过还是推荐用Google Play Service来检测,貌似第二种,即使有的用户装了Google Play(像国内用户),也不支持Google Play Service的。

3.添加代码(终于要加支付代码了)

把上篇下载的samples里util的代码全部拷到你的工程里,可以新建一个包,放到里面。

这个说明一下,其实这个例子的代码还是不错的,本着天下代码一大抄和拿来主义,就直接拿来用吧!当然如果你觉得这个代码写的不好,或者不适用你的工程,你就可以依据文档自己写适用的代码。当然文档里说过,为了防止别人破解你的游戏,最好把里面的变量和方法都改下名字,毕竟这里的代码任何人都看得到。我的做法是照搬过来了,只是把IabHelper.Java改造了下,因为这个是整个支付的关键,其他都是辅助的,可以不管。

把这里的代码拷完,把该import的都import了,你就可以照samples中的代码开写自己的支付了。针对单机游戏,就需要考虑这个代码改造和本地的验证,加密了。针对网络游戏就要简单了。因为我其实对java不太熟悉技术分享,所以单机的加密,base验证,混淆什么的就不做介绍了。下面主要说网络游戏。

(1)IabHelper.java

这个是支付的关键代码,其中已经把设置billing,商品查询,商品购买,商品回调,商品验证以及回调方法都写好了,你直接参照samples用就可以了。

01.设置billing

就是开篇所说的绑定ServiceConnection到IInAppBillingService。功能很完善,包括成功和失败都有回调,还有各种异常。在你程序的启动Activity里检测完设备是否Google Play Service后,就可以new一个IabHelper,来调用这个方法,根据不同的回调里做相应的处理。

/*** Starts the setup process. This will start up the setup process asynchronously.* You will be notified through the listener when the setup process is complete.* This method is safe to call from a UI thread.** @param listener The listener to notify when the setup process is complete.*/public void startSetup(final OnIabSetupFinishedListener listener) {// If already set up, can‘t do it again.checkNotDisposed();if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");// Connection to IAB servicelogDebug("Starting in-app billing setup.");mServiceConn = new ServiceConnection() {@Overridepublic void onServiceDisconnected(ComponentName name) {logDebug("Billing service disconnected.");mService = null;}@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {if (mDisposed) return;logDebug("Billing service connected.");mService = IInAppBillingService.Stub.asInterface(service);String packageName = mContext.getPackageName();try {logDebug("Checking for in-app billing 3 support.");// check for in-app billing v3 supportint response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);if (response != BILLING_RESPONSE_RESULT_OK) {if (listener != null) listener.onIabSetupFinished(new IabResult(response,"Error checking for billing v3 support."));// if in-app purchases aren‘t supported, neither are subscriptions.mSubscriptionsSupported = false;return;}logDebug("In-app billing version 3 supported for " + packageName);// check for v3 subscriptions supportresponse = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);if (response == BILLING_RESPONSE_RESULT_OK) {logDebug("Subscriptions AVAILABLE.");mSubscriptionsSupported = true;}else {logDebug("Subscriptions NOT AVAILABLE. Response: " + response);}mSetupDone = true;}catch (RemoteException e) {if (listener != null) {listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,"RemoteException while setting up in-app billing."));}e.printStackTrace();return;}if (listener != null) {listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));}}};Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");serviceIntent.setPackage("com.android.vending");if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {// service available to handle that IntentmContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);}else {// no service available to handle that Intentif (listener != null) {listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,"Billing service unavailable on device."));}}}

[html] view plaincopy
  1. samples中的代码
// Create the helper, passing it our context and the public key to verify signatures withLog.d(TAG, "Creating IAB helper.");mHelper = new IabHelper(this, base64EncodedPublicKey);// enable debug logging (for a production application, you should set this to false).mHelper.enableDebugLogging(true);// Start setup. This is asynchronous and the specified listener// will be called once setup completes.Log.d(TAG, "Starting setup.");mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {public void onIabSetupFinished(IabResult result) {Log.d(TAG, "Setup finished.");if (!result.isSuccess()) {// Oh noes, there was a problem.complain("Problem setting up in-app billing: " + result);return;}// Have we been disposed of in the meantime? If so, quit.if (mHelper == null) return;// IAB is fully set up. Now, let‘s get an inventory of stuff we own.Log.d(TAG, "Setup successful. Querying inventory.");mHelper.queryInventoryAsync(mGotInventoryListener);}});}

02.查询商品

在setup方法的最后有一个

mHelper.queryInventoryAsync(mGotInventoryListener);

是用来查询你目前拥有的商品的。其中的回调的代码如下

// Listener that‘s called when we finish querying the items and subscriptions we ownIabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {public void onQueryInventoryFinished(IabResult result, Inventory inventory) {Log.d(TAG, "Query inventory finished.");// Have we been disposed of in the meantime? If so, quit.if (mHelper == null) return;// Is it a failure?if (result.isFailure()) {complain("Failed to query inventory: " + result);return;}Log.d(TAG, "Query inventory was successful.");/** Check for items we own. Notice that for each purchase, we check* the developer payload to see if it‘s correct! See* verifyDeveloperPayload().*/// Do we have the premium upgrade?Purchase premiumPurchase = inventory.getPurchase(SKU_PREMIUM);mIsPremium = (premiumPurchase != null && verifyDeveloperPayload(premiumPurchase));Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));// Do we have the infinite gas plan?Purchase infiniteGasPurchase = inventory.getPurchase(SKU_INFINITE_GAS);mSubscribedToInfiniteGas = (infiniteGasPurchase != null &&verifyDeveloperPayload(infiniteGasPurchase));Log.d(TAG, "User " + (mSubscribedToInfiniteGas ? "HAS" : "DOES NOT HAVE")+ " infinite gas subscription.");if (mSubscribedToInfiniteGas) mTank = TANK_MAX;// Check for gas delivery -- if we own gas, we should fill up the tank immediatelyPurchase gasPurchase = inventory.getPurchase(SKU_GAS);if (gasPurchase != null && verifyDeveloperPayload(gasPurchase)) {Log.d(TAG, "We have gas. Consuming it.");mHelper.consumeAsync(inventory.getPurchase(SKU_GAS), mConsumeFinishedListener);return;}updateUi();setWaitScreen(false);Log.d(TAG, "Initial inventory query finished; enabling main UI.");}};

因为目前我们的内购商品是可重复购买的,所以在成功查询到我们已经购买的商品后进行了消耗商品操作。消耗的代码在这里

 // Check for gas delivery -- if we own gas, we should fill up the tank immediatelyPurchase gasPurchase = inventory.getPurchase(SKU_GAS);if (gasPurchase != null && verifyDeveloperPayload(gasPurchase)) {Log.d(TAG, "We have gas. Consuming it.");mHelper.consumeAsync(inventory.getPurchase(SKU_GAS), mConsumeFinishedListener);return;}

在讲消耗前,先解释下以上这么操作的原因。在内购商品那里讲过,如果是设置的是可重复商品,当你在成功购买这个商品后是需要主动消耗的,只有消耗成功后才可以再次购买。可能有些人觉得这种设定不好,我的商品本来就是可重复购买的,为什么还要在买成功后通知Google Play消耗掉商品呢(可能本身商品没用消耗掉,这只是一种叫法)?我个人觉得这样设定,第一,可以避免用户重复下单购买,第二,可以保证每笔消费订单的唯一。有了以上两点就可以很好地处理漏单。 so,上面代码在成功设置billing后,第一个操作就是查询拥有的商品,就是做的漏单处理。因为支付过程其实就是你的应用程序----->Google Play程序(通过Google Play Service)------>Google服务器------->Google Play程序(通过Google Play Service)------>你的应用程序。这样一个交互过程,还需要网络支持,所以每次支付操作不会保证百分百成功,这样就会产生漏单现象,就是用户付费成功了,但是Google Play在通知你的应用程序支付结果时,因为某些原因断掉了,这样你的程序就不知道支付是否操作成功了,so,只好在下次进游戏时查查有没有已经购买的,但是还没有消耗的商品,有的话就消耗掉,然后再把商品发送给用户。因为这个商品在消耗之前,用户是无法再次购买的,所以单个用户就只会对应单一的漏单,不会有重复的漏单。这些信息都是存到Google服务器上的,直接调代码里的查询代码就可以了。

02.消耗商品

消耗商品会在两个地方出现。一,查询商品中所说的漏单中,二,就是你每次购买成功后的消耗。消耗商品也有一个回调,如下

// Called when consumption is completeIabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {public void onConsumeFinished(Purchase purchase, IabResult result) {Log.d(TAG, "Consumption finished. Purchase: " + purchase + ", result: " + result);// if we were disposed of in the meantime, quit.if (mHelper == null) return;// We know this is the "gas" sku because it‘s the only one we consume,// so we don‘t check which sku was consumed. If you have more than one// sku, you probably should check...if (result.isSuccess()) {// successfully consumed, so we apply the effects of the item in our// game world‘s logic, which in our case means filling the gas tank a bitLog.d(TAG, "Consumption successful. Provisioning.");mTank = mTank == TANK_MAX ? TANK_MAX : mTank + 1;saveData();alert("You filled 1/4 tank. Your tank is now " + String.valueOf(mTank) + "/4 full!");}else {complain("Error while consuming: " + result);}updateUi();setWaitScreen(false);Log.d(TAG, "End consumption flow.");}};

代码比较简单,针对自己的游戏逻辑,在里面稍做改动即可。

03.购买商品

按重要程度,购买商品应该排在第一位的,只是按支付流程走的话,购买商品却不是第一位,这里就根据支付流程来走吧。

 /*** Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,* which will involve bringing up the Google Play screen. The calling activity will be paused while* the user interacts with Google Play, and the result will be delivered via the activity‘s* {@link android.app.Activity#onActivityResult} method, at which point you must call* this object‘s {@link #handleActivityResult} method to continue the purchase flow. This method* MUST be called from the UI thread of the Activity.** @param act The calling activity.* @param sku The sku of the item to purchase.* @param itemType indicates if it‘s a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)* @param requestCode A request code (to differentiate from other responses --*     as in {@link android.app.Activity#startActivityForResult}).* @param listener The listener to notify when the purchase process finishes* @param extraData Extra data (developer payload), which will be returned with the purchase data*     when the purchase completes. This extra data will be permanently bound to that purchase*     and will always be returned when the purchase is queried.*/public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,OnIabPurchaseFinishedListener listener, String extraData) {checkNotDisposed();checkSetupDone("launchPurchaseFlow");flagStartAsync("launchPurchaseFlow");IabResult result;if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,"Subscriptions are not available.");flagEndAsync();if (listener != null) listener.onIabPurchaseFinished(r, null);return;}try {logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);int response = getResponseCodeFromBundle(buyIntentBundle);if (response != BILLING_RESPONSE_RESULT_OK) {logError("Unable to buy item, Error response: " + getResponseDesc(response));flagEndAsync();result = new IabResult(response, "Unable to buy item");if (listener != null) listener.onIabPurchaseFinished(result, null);return;}PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);mRequestCode = requestCode;mPurchaseListener = listener;mPurchasingItemType = itemType;act.startIntentSenderForResult(pendingIntent.getIntentSender(),requestCode, new Intent(),Integer.valueOf(0), Integer.valueOf(0),Integer.valueOf(0));}catch (SendIntentException e) {logError("SendIntentException while launching purchase flow for sku " + sku);e.printStackTrace();flagEndAsync();result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");if (listener != null) listener.onIabPurchaseFinished(result, null);}catch (RemoteException e) {logError("RemoteException while launching purchase flow for sku " + sku);e.printStackTrace();flagEndAsync();result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");if (listener != null) listener.onIabPurchaseFinished(result, null);}}

以上是IabHelper中的支付购买代码,其中包括了重复购买商品类型和一次购买商品类型的处理。主要的代码是try里面的这一块

try {logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);int response = getResponseCodeFromBundle(buyIntentBundle);if (response != BILLING_RESPONSE_RESULT_OK) {logError("Unable to buy item, Error response: " + getResponseDesc(response));flagEndAsync();result = new IabResult(response, "Unable to buy item");if (listener != null) listener.onIabPurchaseFinished(result, null);return;}PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);mRequestCode = requestCode;mPurchaseListener = listener;mPurchasingItemType = itemType;act.startIntentSenderForResult(pendingIntent.getIntentSender(),requestCode, new Intent(),Integer.valueOf(0), Integer.valueOf(0),Integer.valueOf(0));}

[html] view plaincopy
  1. <br style="padding: 0px;" />一,调用In-app Billing中的getBuyIntent方法,会传几个参数,第一个参数 3 代表的是当前所用的支付API的版本,第二个参数是你的包名,第三个参数就是你内购商品的ID,第四个参数是这次购买的类型,“inapp”和"subs",我们用的是第一个,第二个是只能购买一次的类型,第五个参数是订单号。需要讲的只有第三个和第五个参数。

第三个参数,商品Id,就是你在Google开发者后台上设置的内购商品的名字。每个商品的名字要唯一。推荐用商品名字加下划线加价格的组合,比如"crystal_0.99",这样你一看名字就知道这个商品的价格是0.99美金,商品是水晶。

第三个参数,订单号。如果本地有支付服务器的话,这个订单号可以由支付服务器生成,然后再传给客户端,这样的话本地服务器也可以记录下订单信息,方便以后的查询和操作。订单号的格式推荐用时间戳加商品名字和价格,这样也可以容易看出订单信息。这个订单号会传给Google,购买成功后Google会原样传给你,所以也可以在其中加个标示信息,可以做下比对。

二,在getBuyIntent成功后,返回的Bundle中会有个BILLING_RESPONSE_RESULT_OK返回码,这就代表成功了。然后再用这个Bundle得到一个PendingIntent.如上面代码演示。

三,进行支付

act.startIntentSenderForResult(pendingIntent.getIntentSender(),requestCode, new Intent(),Integer.valueOf(0), Integer.valueOf(0),Integer.valueOf(0));

这个方法是Activity中的一个方法,调用这个方法后,回有一个回调来接收结果。除了第一个PengdingIntent参数外,其他的可以按参数类型随便写。

四,支付完成

 /*** Handles an activity result that‘s part of the purchase flow in in-app billing. If you* are calling {@link #launchPurchaseFlow}, then you must call this method from your* Activity‘s {@link android.app.Activity@onActivityResult} method. This method* MUST be called from the UI thread of the Activity.** @param requestCode The requestCode as you received it.* @param resultCode The resultCode as you received it.* @param data The data (Intent) as you received it.* @return Returns true if the result was related to a purchase flow and was handled;*     false if the result was not related to a purchase, in which case you should*     handle it normally.*/public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {IabResult result;if (requestCode != mRequestCode) return false;checkNotDisposed();checkSetupDone("handleActivityResult");// end of async purchase operation that started on launchPurchaseFlowflagEndAsync();if (data == null) {logError("Null data in IAB activity result.");result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);return true;}int responseCode = getResponseCodeFromIntent(data);String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {logDebug("Successful resultcode from purchase activity.");logDebug("Purchase data: " + purchaseData);logDebug("Data signature: " + dataSignature);logDebug("Extras: " + data.getExtras());logDebug("Expected item type: " + mPurchasingItemType);if (purchaseData == null || dataSignature == null) {logError("BUG: either purchaseData or dataSignature is null.");logDebug("Extras: " + data.getExtras().toString());result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);return true;}Purchase purchase = null;try {purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature);String sku = purchase.getSku();// Verify signatureif (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {logError("Purchase signature verification FAILED for sku " + sku);result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, purchase);return true;}logDebug("Purchase signature successfully verified.");}catch (JSONException e) {logError("Failed to parse purchase data.");e.printStackTrace();result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);return true;}if (mPurchaseListener != null) {mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);}}else if (resultCode == Activity.RESULT_OK) {// result code was OK, but in-app billing response was not OK.logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));if (mPurchaseListener != null) {result = new IabResult(responseCode, "Problem purchashing item.");mPurchaseListener.onIabPurchaseFinished(result, null);}}else if (resultCode == Activity.RESULT_CANCELED) {logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);}else {logError("Purchase failed. Result code: " + Integer.toString(resultCode)+ ". Response: " + getResponseDesc(responseCode));result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);}return true;}public Inventory queryInventory(boolean querySkuDetails, List<String> moreSkus) throws IabException {return queryInventory(querySkuDetails, moreSkus, null);}

支付结果返回后会调用上面这个方法,对于支付失败和其中的错误,代码写的很清楚,可以自行处理。现在来关注支付成功后的结果验证。在上面方法中会从支付结果的数据中取得两个json数据。

        int responseCode = getResponseCodeFromIntent(data);String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);

就是purchaseData和dataSignature。验证支付就是需要这两个参数和publicKey,例子里的验证方法是写在Security.java里的。里面写了三个方法来完成支付结果的验证。

对于有本地支付服务器的游戏来说,这个操作就可以放到服务端了,客户端只需要把purchaseData和dataSignature传给支付服务器即可。然后有支付服务器把验证结果传给客户端,再做成功和失败的处理。成功后则要进行消耗商品的操作。对于没有支付服务器的游戏来说,我个人觉得本地的操作要达到安全,还是比较难的。不过对于服务器验证支付结果,也是存在风险的,只是风险要小。

/*** Verifies that the data was signed with the given signature, and returns* the verified purchase. The data is in JSON format and signed* with a private key. The data also contains the {@link PurchaseState}* and product ID of the purchase.* @param base64PublicKey the base64-encoded public key to use for verifying.* @param signedData the signed JSON string (signed, not encrypted)* @param signature the signature for the data, signed with the private key*/public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||TextUtils.isEmpty(signature)) {Log.e(TAG, "Purchase verification failed: missing data.");return false;}PublicKey key = Security.generatePublicKey(base64PublicKey);return Security.verify(key, signedData, signature);}/*** Generates a PublicKey instance from a string containing the* Base64-encoded public key.** @param encodedPublicKey Base64-encoded public key* @throws IllegalArgumentException if encodedPublicKey is invalid*/public static PublicKey generatePublicKey(String encodedPublicKey) {try {byte[] decodedKey = Base64.decode(encodedPublicKey);KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));} catch (NoSuchAlgorithmException e) {throw new RuntimeException(e);} catch (InvalidKeySpecException e) {Log.e(TAG, "Invalid key specification.");throw new IllegalArgumentException(e);} catch (Base64DecoderException e) {Log.e(TAG, "Base64 decoding failed.");throw new IllegalArgumentException(e);}}/*** Verifies that the signature from the server matches the computed* signature on the data.  Returns true if the data is correctly signed.** @param publicKey public key associated with the developer account* @param signedData signed data from server* @param signature server signature* @return true if the data and signature match*/public static boolean verify(PublicKey publicKey, String signedData, String signature) {Signature sig;try {sig = Signature.getInstance(SIGNATURE_ALGORITHM);sig.initVerify(publicKey);sig.update(signedData.getBytes());if (!sig.verify(Base64.decode(signature))) {Log.e(TAG, "Signature verification failed.");return false;}return true;} catch (NoSuchAlgorithmException e) {Log.e(TAG, "NoSuchAlgorithmException.");} catch (InvalidKeyException e) {Log.e(TAG, "Invalid key specification.");} catch (SignatureException e) {Log.e(TAG, "Signature exception.");} catch (Base64DecoderException e) {Log.e(TAG, "Base64 decoding failed.");}return false;}

PublicKey:

这个PublicKey是用来验证支付结果的,所以这绝对是个Key,不可以让其他人知道的,这个Key放到支付服务器端,本地不存。

samples里的这段代码写的很有意思,能看出笑点不?

单机游戏的话,想办法把这个key存到某个地方加个密什么的,最好不要直接写到代码里。(其实对于单机游戏,如果没有自己的服务器来验证支付结果,本地不管如何操作,都是很容易被破解的,如果游戏比较大卖,推荐自己写个支付服务器端来验证支付结果)。

/* base64EncodedPublicKey should be YOUR APPLICATION‘S PUBLIC KEY* (that you got from the Google Play developer console). This is not your* developer public key, it‘s the *app-specific* public key.** Instead of just storing the entire literal string here embedded in the* program,  construct the key at runtime from pieces or* use bit manipulation (for example, XOR with some other string) to hide* the actual key.  The key itself is not secret information, but we don‘t* want to make it easy for an attacker to replace the public key with one* of their own and then fake messages from the server.*/String base64EncodedPublicKey = "CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE";// Some sanity checks to see if the developer (that‘s you!) really followed the// instructions to run this sample (don‘t put these checks on your app!)if (base64EncodedPublicKey.contains("CONSTRUCT_YOUR")) {throw new RuntimeException("Please put your app‘s public key in MainActivity.java. See README.");}if (getPackageName().startsWith("com.example")) {throw new RuntimeException("Please change the sample‘s package name! See README.");}

本地服务器验证补充:

关于支付结果的验证,本地服务器除了用publicKey做签名验证外,还可以到Google后台请求下支付结果验证。这个需要本地服务器和Google服务器交互通信。可以参考这个文档。

不过对于国内的开发者而言,在Google日益被封锁加重的情况下,在与Google服务器通信上绝对会有障碍,因为通信阻碍,会导致你验证失败,所以这个功能可选,有兴趣的可以添加上。

补充1:

如果是直接用samples的代码的话还需要注意几点。第一,把错误提示改成用户友好型的。因为samples的错误提示主要是给开发者看的,所以提示的很详细,但是用户不需要,你只要告诉用户成功,失败以及简单的失败原因就行了。第二,在发布正式版时把打印信息关掉。第三,修改类名和变量名。

补充2:

如果在测试支付时遇到一下错误,可做的处理。

1.当前应用程序不支持购买此商品:确定你手机上装的程序包名和签名和后台上传的一致。p.s.上传后台后APK需要等一段时间才能生效。

2.购买的商品不存在 :确保你代码里的商品名字和后台的一致,如果一致,则可能需要等一两个小时再测试,Google后台的问题。

3.loading了很长时间,最后给你提示未知错误:这个不管它,Google后台的问题,等会再测。

最后国内开发者确保是在vpn下进行测试!!!!

写在后面:

以上就是Google In-app Billing的代码添加了,其实就是把samples讲了一下技术分享,所以还是推荐去看下官方文档和samples吧,在那里你会学到更多。

Google play billing(Google play 内支付)相关推荐

  1. Google play billing(Google play 内支付) 上篇

    写在前面: 最近Google貌似又被全面封杀了,幸好在此之前,把Google play billing弄完了,现在写篇 博客来做下记录.这篇博客一是自己做个记录,二是帮助其他有需要的人.因为现在基本登 ...

  2. Google play billing(Google play 内支付) 下篇

    开篇: 如billing开发文档所说,要在你的应用中实现In-app Billing只需要完成以下几步就可以了. 第一,把你上篇下载的AIDL文件添加到你的工程里,第二,把<uses-permi ...

  3. Google in app billing 应用内支付

    一 简介 Google in app billing 是google play 商店的应用内支付,他是一种应用内的虚拟的道具支付服务,支持应用内支付(inapp)和订阅(subs)两种模式; 在中国, ...

  4. SDK接入(之Android Google Play内支付(in-app Billing)接入的细节

    翻了很多文档,大致流程 懂了,实际上就能上手了,长篇大论是很好,但是关键要素也是决定效率的因素,有些小问题,决定是否成功与否. 首先 下载sdk,在github.com 的google sample里 ...

  5. SDK接入(2)之Android Google Play内支付(in-app Billing)接入

    SDK接入(2)之Android Google Play内支付(in-app Billing)接入 SDK接入(2)之Android Google Play内支付(in-app Billing)接入 ...

  6. google应用内支付 in-app billing(二)

    在写<google应用内支付 in-app billing(二)>的时候我并没有写这篇文章的前传<google应用内支付 in-app billing(一)>,留到后面再写吧. ...

  7. 安卓集成google内支付

    Google Play Console创建应用 我的应用是后台人员完成的,具体的可以参考Google Play Console创建应用其中需要注意的是添加测试人员只是测试APK包,需要进行内支付测试则 ...

  8. 集成Google应用内 支付 或者 订阅

    要集成Google应用内支付 先要了解文档 集成文档: https://developer.android.google.cn/google/play/billing/integrate 1.创建Go ...

  9. google pay billing 支付嵌入中需要注意的问题

    看这篇博客前,强烈建议先看下面这篇博客 Android支付接入(七):Google In-app-Billing 由于项目需求,加入google pay billing, 由于没有过加google p ...

最新文章

  1. [Sdoi2008]沙拉公主的困惑
  2. php脚本查杀,无敌强大的Shell脚本查杀各种PHP方便之门和Webshell
  3. 关于如何能够快速找到某个文本在所有文件中的出现位置
  4. Ubuntu/Debian交叉编译安装ARM平台版本的ffmpeg
  5. matlab armax 无法识别,求教:Java调用系统辨识工具箱内的armax函数出错
  6. OpenFlow和SDN的历史和原理介绍
  7. 微信小程序正确的异步request请求,根据经纬度获取地理位置信息
  8. 基于百度地图API的微信周边搜索
  9. UCan下午茶武汉站,为你全面挖宝分布式存储
  10. 进程中堆栈向下增长的原因
  11. atitit 英文与中文与阿拉伯文的简化解决方案.docx
  12. leaflet+vue:色斑图的实现大体步骤
  13. 一款度盘高速下载工具
  14. Qt学习之QDir的使用(使用QDir创建文件夹)
  15. JVM类加载机制(加载,初始化,类加载器和双亲委派机制)
  16. 六轴机器人直角坐标系建立_工业机器人六种坐标系详解(图)
  17. mysql外键设置不成功_MySQL数据库建立外键失败的原因总结
  18. 搭建自已的turn服务器
  19. UML用例图-用例图
  20. 关系数据库设计 函数依赖 逻辑蕴含

热门文章

  1. load styles.php,wordpress后台管理超时没反应:load-scripts.php载入缓慢出错
  2. hcia是什么等级的证书_华为的HCNA,HCNP,HCIE认证证书都有什么用?
  3. php上传图片完成后的截图,php实现粘贴截图并完成上传功能
  4. python nan_python [吐槽]关于nan类型时遇到的问题
  5. mysql查询正在执行的存储过程,[转]ms sql server 存储过程,查看正在执行的sql语句...
  6. mysql+地图网格数据下载_echarts 中国各省市 echarts地图数据,含世界地图
  7. django模板系统Template
  8. mysql存储过程while 遍历游标
  9. sw接口是什么意思啊_为什么有些任务栏上的按钮一点就消失了
  10. Linux通过第三方应用提权实战总结