Google play billing(Google play 內支付)
http://www.bubuko.com/infodetail-930440.html
如billing開發文件所說,要在你的應用中實現In-app Billing只需要完成以下幾步就可以了。
第一,把你上篇下載的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 Play protected boolean isHaveGooglePlay(Context context, String packageName) { //Get PackageManager final PackageManager packageManager = context.getPackageManager(); //Get The All Install App Package Name List<PackageInfo> pInfo = packageManager.getInstalledPackages(0); //Create Name List List<String> pName = new ArrayList<String>(); //Add Package Name into Name List if(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 service logDebug("Starting in-app billing setup."); mServiceConn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { logDebug("Billing service disconnected."); mService = null; } @Override public 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 support int 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 support response = 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 Intent mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); } else { // no service available to handle that Intent if (listener != null) { listener.onIabSetupFinished( new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE, "Billing service unavailable on device.")); } } }
samples中的程式碼
// Create the helper, passing it our context and the public key to verify signatures with Log.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 own IabHelper.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 immediately Purchase 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 immediately Purchase 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 complete IabHelper.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 bit Log.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)); }
<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 [email protected]} 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 launchPurchaseFlow flagEndAsync(); 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 signature if (!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 contai