Google play billing(Google play 内支付)
http://www.bubuko.com/infodetail-930440.html
?
[html]?view plaincopy?
[html]?view plaincopy這個(gè)權(quán)限加到你工程的AndroidManifest.xml文件中,第三,創(chuàng)建一個(gè)ServiceConnection,并把它綁定到IInAppBillingService中。完成上面三條后就可以使用支付了。當(dāng)然這只是一個(gè)簡(jiǎn)單的介紹。其實(shí)Google的這個(gè)支付,大部分都是你手機(jī)上的Google Play來(lái)進(jìn)行處理的,你只需要處理購(gòu)買(mǎi)請(qǐng)求,處理購(gòu)買(mǎi)結(jié)果就行了。文檔寫(xiě)的很好,先把這個(gè)文檔看完,就知道支付流程了。
?
正文:
1.內(nèi)購(gòu)商品相關(guān)
針對(duì)我的項(xiàng)目而言,我們?cè)贕oogle后臺(tái)設(shè)置的是受管理可消耗的商品("managed per user account"),具體的就是游戲里的水晶,玩家可以多次購(gòu)買(mǎi)。但是Google后臺(tái)的這種可重復(fù)購(gòu)買(mǎi)商品(還有一種是只能購(gòu)買(mǎi)一次的商品"subscription")有個(gè)要求,就是你購(gòu)買(mǎi)成功后需要主動(dòng)向Google Play請(qǐng)求消耗這個(gè)商品,等消耗成功后你才可以再次下單購(gòu)買(mǎi)。因此,在游戲里的支付會(huì)多出一個(gè)操作步驟就是請(qǐng)求消耗購(gòu)買(mǎi)成功的商品。
2.檢測(cè)設(shè)備是否支持Google Play Service
在正式開(kāi)啟支付前,Google billing會(huì)檢查你的手機(jī)是否支持Google billing,這個(gè)下面會(huì)講到。為了更好的用戶體驗(yàn),建議在Google billing檢測(cè)之前,可以先檢測(cè)一下用戶的設(shè)備是否支持Google Play Service,其中基本要求就是安裝了Google Play應(yīng)用商店和Google Play Service。如果用戶的設(shè)備不具備這兩個(gè),就可以彈出提示引導(dǎo)用戶去安裝。這里有兩種方式可以用,一種是通過(guò)Google Play Service來(lái)進(jìn)行檢測(cè),就是上篇下載的那個(gè)Service擴(kuò)展包,一種是自己寫(xiě)代碼,遍歷設(shè)備上安裝的應(yīng)用程序,檢查是否有安裝Google Play。先說(shuō)第一種。
(1)Google Play Service
上篇下載的Service包里會(huì)有一個(gè)庫(kù)工程
把這個(gè)庫(kù)工程導(dǎo)入你的eclipse,引用到你的工程里就可以用了,具體操作可以參加docs下的文檔,so easy!導(dǎo)入成功后,調(diào)用其中的一個(gè)方法就可以了。
/*** 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;}如果當(dāng)前設(shè)備的Google Service不可用,就會(huì)彈出提示,引導(dǎo)用戶去設(shè)置安裝。如果此設(shè)備不支持的話,就也不需要檢測(cè)Google billing是否可用了。多說(shuō)一句,Google Play Service可以做很多事的,如果覺(jué)得只用上面的功能太簡(jiǎn)單的話,就可以考慮把應(yīng)用自動(dòng)更新也加上,當(dāng)你在Google Play上傳了新版程序后,Google Play會(huì)幫你提示用戶更新程序。還有一個(gè)比較好玩的就是如果引入了這個(gè)庫(kù)工程后,就可以加GCM了(Google Cloud Messaging),就是消息推送推送功能,當(dāng)然這個(gè)比較麻煩,有興趣的可以去加加看。
?
(2)遍歷包名
Google Play的程序包名是"com.Android.vending",運(yùn)行在設(shè)備上的Google Play Service的包名是"com.google.android.gms",可以在程序啟動(dòng)的時(shí)候遍歷下設(shè)備上的包名,如果沒(méi)有這兩個(gè)東西就引導(dǎo)用戶去安裝。
遍歷包名方法
//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);?上面這個(gè)方法會(huì)打開(kāi)你手機(jī)上的應(yīng)用商店,定位到要安裝的程序。
?
不過(guò)還是推薦用Google Play Service來(lái)檢測(cè),貌似第二種,即使有的用戶裝了Google Play(像國(guó)內(nèi)用戶),也不支持Google Play Service的。
3.添加代碼(終于要加支付代碼了)
把上篇下載的samples里util的代碼全部拷到你的工程里,可以新建一個(gè)包,放到里面。
這個(gè)說(shuō)明一下,其實(shí)這個(gè)例子的代碼還是不錯(cuò)的,本著天下代碼一大抄和拿來(lái)主義,就直接拿來(lái)用吧!當(dāng)然如果你覺(jué)得這個(gè)代碼寫(xiě)的不好,或者不適用你的工程,你就可以依據(jù)文檔自己寫(xiě)適用的代碼。當(dāng)然文檔里說(shuō)過(guò),為了防止別人破解你的游戲,最好把里面的變量和方法都改下名字,畢竟這里的代碼任何人都看得到。我的做法是照搬過(guò)來(lái)了,只是把IabHelper.Java改造了下,因?yàn)檫@個(gè)是整個(gè)支付的關(guān)鍵,其他都是輔助的,可以不管。
把這里的代碼拷完,把該import的都import了,你就可以照samples中的代碼開(kāi)寫(xiě)自己的支付了。針對(duì)單機(jī)游戲,就需要考慮這個(gè)代碼改造和本地的驗(yàn)證,加密了。針對(duì)網(wǎng)絡(luò)游戲就要簡(jiǎn)單了。因?yàn)槲移鋵?shí)對(duì)java不太熟悉,所以單機(jī)的加密,base驗(yàn)證,混淆什么的就不做介紹了。下面主要說(shuō)網(wǎng)絡(luò)游戲。
(1)IabHelper.java
這個(gè)是支付的關(guān)鍵代碼,其中已經(jīng)把設(shè)置billing,商品查詢,商品購(gòu)買(mǎi),商品回調(diào),商品驗(yàn)證以及回調(diào)方法都寫(xiě)好了,你直接參照samples用就可以了。
01.設(shè)置billing
就是開(kāi)篇所說(shuō)的綁定ServiceConnection到IInAppBillingService。功能很完善,包括成功和失敗都有回調(diào),還有各種異常。在你程序的啟動(dòng)Activity里檢測(cè)完設(shè)備是否Google Play Service后,就可以new一個(gè)IabHelper,來(lái)調(diào)用這個(gè)方法,根據(jù)不同的回調(diào)里做相應(yīng)的處理。
/*** 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?
02.查詢商品
在setup方法的最后有一個(gè)
mHelper.queryInventoryAsync(mGotInventoryListener);是用來(lái)查詢你目前擁有的商品的。其中的回調(diào)的代碼如下
// 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.");}};因?yàn)槟壳拔覀兊膬?nèi)購(gòu)商品是可重復(fù)購(gòu)買(mǎi)的,所以在成功查詢到我們已經(jīng)購(gòu)買(mǎi)的商品后進(jìn)行了消耗商品操作。消耗的代碼在這里
// 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;}在講消耗前,先解釋下以上這么操作的原因。在內(nèi)購(gòu)商品那里講過(guò),如果是設(shè)置的是可重復(fù)商品,當(dāng)你在成功購(gòu)買(mǎi)這個(gè)商品后是需要主動(dòng)消耗的,只有消耗成功后才可以再次購(gòu)買(mǎi)。可能有些人覺(jué)得這種設(shè)定不好,我的商品本來(lái)就是可重復(fù)購(gòu)買(mǎi)的,為什么還要在買(mǎi)成功后通知Google Play消耗掉商品呢(可能本身商品沒(méi)用消耗掉,這只是一種叫法)?我個(gè)人覺(jué)得這樣設(shè)定,第一,可以避免用戶重復(fù)下單購(gòu)買(mǎi),第二,可以保證每筆消費(fèi)訂單的唯一。有了以上兩點(diǎn)就可以很好地處理漏單。 so,上面代碼在成功設(shè)置billing后,第一個(gè)操作就是查詢擁有的商品,就是做的漏單處理。因?yàn)橹Ц哆^(guò)程其實(shí)就是你的應(yīng)用程序----->Google Play程序(通過(guò)Google Play Service)------>Google服務(wù)器------->Google Play程序(通過(guò)Google Play Service)------>你的應(yīng)用程序。這樣一個(gè)交互過(guò)程,還需要網(wǎng)絡(luò)支持,所以每次支付操作不會(huì)保證百分百成功,這樣就會(huì)產(chǎn)生漏單現(xiàn)象,就是用戶付費(fèi)成功了,但是Google Play在通知你的應(yīng)用程序支付結(jié)果時(shí),因?yàn)槟承┰驍嗟袅?#xff0c;這樣你的程序就不知道支付是否操作成功了,so,只好在下次進(jìn)游戲時(shí)查查有沒(méi)有已經(jīng)購(gòu)買(mǎi)的,但是還沒(méi)有消耗的商品,有的話就消耗掉,然后再把商品發(fā)送給用戶。因?yàn)檫@個(gè)商品在消耗之前,用戶是無(wú)法再次購(gòu)買(mǎi)的,所以單個(gè)用戶就只會(huì)對(duì)應(yīng)單一的漏單,不會(huì)有重復(fù)的漏單。這些信息都是存到Google服務(wù)器上的,直接調(diào)代碼里的查詢代碼就可以了。
?
02.消耗商品
消耗商品會(huì)在兩個(gè)地方出現(xiàn)。一,查詢商品中所說(shuō)的漏單中,二,就是你每次購(gòu)買(mǎi)成功后的消耗。消耗商品也有一個(gè)回調(diào),如下
// 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.");}};代碼比較簡(jiǎn)單,針對(duì)自己的游戲邏輯,在里面稍做改動(dòng)即可。
?
03.購(gòu)買(mǎi)商品
按重要程度,購(gòu)買(mǎi)商品應(yīng)該排在第一位的,只是按支付流程走的話,購(gòu)買(mǎi)商品卻不是第一位,這里就根據(jù)支付流程來(lái)走吧。
/*** 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中的支付購(gòu)買(mǎi)代碼,其中包括了重復(fù)購(gòu)買(mǎi)商品類型和一次購(gòu)買(mǎi)商品類型的處理。主要的代碼是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第三個(gè)參數(shù),商品Id,就是你在Google開(kāi)發(fā)者后臺(tái)上設(shè)置的內(nèi)購(gòu)商品的名字。每個(gè)商品的名字要唯一。推薦用商品名字加下劃線加價(jià)格的組合,比如"crystal_0.99",這樣你一看名字就知道這個(gè)商品的價(jià)格是0.99美金,商品是水晶。
第三個(gè)參數(shù),訂單號(hào)。如果本地有支付服務(wù)器的話,這個(gè)訂單號(hào)可以由支付服務(wù)器生成,然后再傳給客戶端,這樣的話本地服務(wù)器也可以記錄下訂單信息,方便以后的查詢和操作。訂單號(hào)的格式推薦用時(shí)間戳加商品名字和價(jià)格,這樣也可以容易看出訂單信息。這個(gè)訂單號(hào)會(huì)傳給Google,購(gòu)買(mǎi)成功后Google會(huì)原樣傳給你,所以也可以在其中加個(gè)標(biāo)示信息,可以做下比對(duì)。
二,在getBuyIntent成功后,返回的Bundle中會(huì)有個(gè)BILLING_RESPONSE_RESULT_OK返回碼,這就代表成功了。然后再用這個(gè)Bundle得到一個(gè)PendingIntent.如上面代碼演示。
三,進(jìn)行支付
act.startIntentSenderForResult(pendingIntent.getIntentSender(),requestCode, new Intent(),Integer.valueOf(0), Integer.valueOf(0),Integer.valueOf(0));?這個(gè)方法是Activity中的一個(gè)方法,調(diào)用這個(gè)方法后,回有一個(gè)回調(diào)來(lái)接收結(jié)果。除了第一個(gè)PengdingIntent參數(shù)外,其他的可以按參數(shù)類型隨便寫(xiě)。
?
四,支付完成
?
/*** 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);}支付結(jié)果返回后會(huì)調(diào)用上面這個(gè)方法,對(duì)于支付失敗和其中的錯(cuò)誤,代碼寫(xiě)的很清楚,可以自行處理。現(xiàn)在來(lái)關(guān)注支付成功后的結(jié)果驗(yàn)證。在上面方法中會(huì)從支付結(jié)果的數(shù)據(jù)中取得兩個(gè)json數(shù)據(jù)。
int responseCode = getResponseCodeFromIntent(data);String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);就是purchaseData和dataSignature。驗(yàn)證支付就是需要這兩個(gè)參數(shù)和publicKey,例子里的驗(yàn)證方法是寫(xiě)在Security.java里的。里面寫(xiě)了三個(gè)方法來(lái)完成支付結(jié)果的驗(yàn)證。
?
對(duì)于有本地支付服務(wù)器的游戲來(lái)說(shuō),這個(gè)操作就可以放到服務(wù)端了,客戶端只需要把purchaseData和dataSignature傳給支付服務(wù)器即可。然后有支付服務(wù)器把驗(yàn)證結(jié)果傳給客戶端,再做成功和失敗的處理。成功后則要進(jìn)行消耗商品的操作。對(duì)于沒(méi)有支付服務(wù)器的游戲來(lái)說(shuō),我個(gè)人覺(jué)得本地的操作要達(dá)到安全,還是比較難的。不過(guò)對(duì)于服務(wù)器驗(yàn)證支付結(jié)果,也是存在風(fēng)險(xiǎn)的,只是風(fēng)險(xiǎn)要小。
/*** 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:
這個(gè)PublicKey是用來(lái)驗(yàn)證支付結(jié)果的,所以這絕對(duì)是個(gè)Key,不可以讓其他人知道的,這個(gè)Key放到支付服務(wù)器端,本地不存。
samples里的這段代碼寫(xiě)的很有意思,能看出笑點(diǎn)不?
單機(jī)游戲的話,想辦法把這個(gè)key存到某個(gè)地方加個(gè)密什么的,最好不要直接寫(xiě)到代碼里。(其實(shí)對(duì)于單機(jī)游戲,如果沒(méi)有自己的服務(wù)器來(lái)驗(yàn)證支付結(jié)果,本地不管如何操作,都是很容易被破解的,如果游戲比較大賣,推薦自己寫(xiě)個(gè)支付服務(wù)器端來(lái)驗(yàn)證支付結(jié)果)。
/* 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.");}本地服務(wù)器驗(yàn)證補(bǔ)充:
?
關(guān)于支付結(jié)果的驗(yàn)證,本地服務(wù)器除了用publicKey做簽名驗(yàn)證外,還可以到Google后臺(tái)請(qǐng)求下支付結(jié)果驗(yàn)證。這個(gè)需要本地服務(wù)器和Google服務(wù)器交互通信。可以參考這個(gè)文檔。
不過(guò)對(duì)于國(guó)內(nèi)的開(kāi)發(fā)者而言,在Google日益被封鎖加重的情況下,在與Google服務(wù)器通信上絕對(duì)會(huì)有障礙,因?yàn)橥ㄐ抛璧K,會(huì)導(dǎo)致你驗(yàn)證失敗,所以這個(gè)功能可選,有興趣的可以添加上。
補(bǔ)充1:
如果是直接用samples的代碼的話還需要注意幾點(diǎn)。第一,把錯(cuò)誤提示改成用戶友好型的。因?yàn)閟amples的錯(cuò)誤提示主要是給開(kāi)發(fā)者看的,所以提示的很詳細(xì),但是用戶不需要,你只要告訴用戶成功,失敗以及簡(jiǎn)單的失敗原因就行了。第二,在發(fā)布正式版時(shí)把打印信息關(guān)掉。第三,修改類名和變量名。
補(bǔ)充2:
如果在測(cè)試支付時(shí)遇到一下錯(cuò)誤,可做的處理。
1.當(dāng)前應(yīng)用程序不支持購(gòu)買(mǎi)此商品:確定你手機(jī)上裝的程序包名和簽名和后臺(tái)上傳的一致。p.s.上傳后臺(tái)后APK需要等一段時(shí)間才能生效。
2.購(gòu)買(mǎi)的商品不存在 :確保你代碼里的商品名字和后臺(tái)的一致,如果一致,則可能需要等一兩個(gè)小時(shí)再測(cè)試,Google后臺(tái)的問(wèn)題。
3.loading了很長(zhǎng)時(shí)間,最后給你提示未知錯(cuò)誤:這個(gè)不管它,Google后臺(tái)的問(wèn)題,等會(huì)再測(cè)。
最后國(guó)內(nèi)開(kāi)發(fā)者確保是在vpn下進(jìn)行測(cè)試!!!!
寫(xiě)在后面:
以上就是Google In-app Billing的代碼添加了,其實(shí)就是把samples講了一下,所以還是推薦去看下官方文檔和samples吧,在那里你會(huì)學(xué)到更多。
總結(jié)
以上是生活随笔為你收集整理的Google play billing(Google play 内支付)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 第一部分 Mysql的基础
- 下一篇: Java中避免if-else-if:策略