今天跟大家一起看下Google的in-app Billing V3支付。















3.集成Google Billing。


(2).Consuming In-app Products,消耗產品時的通信過程

(1).測試支付官方文檔鏈接http://developer.android.com/google/play/billing/billing_testing.html
(2).Testing with staticresponses,靜態測試,即當支付狀態為一下四種情況時游戲邏輯是否正確。
mHelper.launchPurchaseFlow(MainActivity.this,“android.test.purchased”,RC_REQUEST,mPurchaseFinishedListener);






1.IabHelper.OnIabPurchaseFinishedListener 支付完成的回調,如果是受管理的商品在此回調中直接可以將道具給用戶
2.IabHelper.OnConsumeFinishedListener 消耗完成的回調,當不受管理的商品被成功消耗進入此回調,此時將不受管理的商品給用戶
3.IabHelper.QueryInventoryFinishedListener 查詢完成的回調,RestoreOrder的時候用,當有訂單成功付款但由於種種原因(突然斷網、斷電等)沒收到Google支付成功的回調時,在這里可以查詢到此訂單,此時需要對訂單進行處理(給用戶道具等)。
四:測試用的app一定要跟上傳到Google的測試版的包名、版本code、name、簽名一致,否則無法進行支付測試。
1.當簽名不一致或者版本code、版本name不一致時錯誤界面如下:
2.當包名不一致時錯誤界面如下:
1.下載in-app-billing-v03,下載地址:http://pan.baidu.com/share/link?shareid=1387554851&uk=473193131將下載后的壓縮包解壓:

將src目錄下兩個包及包中的java文件引入工程,例如:

2.添加權限:
<uses-permissionandroid:name="com.android.vending.BILLING"/>
String base64EncodedPublicKey ="";此處填寫Google控制台添加新應用程序后的appid mHelper =new IabHelper(this, base64EncodedPublicKey); // enable debuglogging (for a production application, you should set this tofalse). mHelper.enableDebugLogging(false); // Start setup. This is asynchronous andthe specified listener // will be called oncesetup completes. Log.d(TAG,"Starting setup."); mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener(){ publicvoid onIabSetupFinished(IabResult result){ Log.d(TAG,"Setupfinished."); if (!result.isSuccess()) { // Ohnoes, there was a problem. complain("Problemsetting up in-app billing: " + result); return; } iap_is_ok = true; //Hooray, IAB is fully set up. Now, let's get an inventory of stuffwe own. Log.d(TAG, "Setup successful. Queryinginventory."); } });
調用支付接口:
if(iap_is_ok){ mHelper.launchPurchaseFlow(MainActivity.this,skus[1],RC_REQUEST, mPurchaseFinishedListener); }else { showMessage("提示", "GooglePlay初始化失敗,當前無法進行支付,請確定您所在地區支持Google Play支付或重啟游戲再試!"); }
調用查詢接口:
mHelper.queryInventoryAsync(mGotInventoryListener);
調用獲取道具價格接口:(因Google市場是根據不同國家顯示不同貨幣價格,所以顯示到游戲道具列表中的價格不是定值,而是動態獲取的)
billingservice =mHelper.getService(); Bundle querySkus = newBundle(); querySkus.putStringArrayList("ITEM_ID_LIST", skus); try { Bundle skuDetails = billingservice.getSkuDetails(3,MainActivity.this.getPackageName(),"inapp", querySkus); ArrayList<String> responseList =skuDetails.getStringArrayList("DETAILS_LIST"); if (null!=responseList) { for (String thisResponse :responseList) { try { SkuDetails d = newSkuDetails(thisResponse); for (int i = 0; i <sku_list.size(); i++) { if(sku_list.get(i).equals(d.getSku())) { price_list.set(i, d.getPrice()); } } iapHandler.sendEmptyMessage(0); }catch (JSONException e) { // TODO Auto-generated catchblock e.printStackTrace(); } } } }catch (RemoteException e) { // TODO Auto-generated catchblock e.printStackTrace(); }
三個回調:
// Callback for when a purchase is finished IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener =newIabHelper.OnIabPurchaseFinishedListener() { publicvoidonIabPurchaseFinished(IabResult result, Purchase purchase) { Log.d(TAG,"Purchase finished: " + result+", purchase: " +purchase); if (result.isFailure()) { // Oh noes! complain("Error purchasing: " + result); return; } Log.d(TAG,"Purchase successful."); if(purchase.getSku().equals("coins_100")||purchase.getSku().equals("android.test.purchased")){ mHelper.consumeAsync(purchase, mConsumeFinishedListener); }elseif (purchase.getSku().equals("double_income")) { //受管理的商品,開啟雙倍經驗 showMessage("支付成功","成功購買雙倍經驗"); } } }; // Called when consumption is complete IabHelper.OnConsumeFinishedListener mConsumeFinishedListener =newIabHelper.OnConsumeFinishedListener() { publicvoid onConsumeFinished(Purchasepurchase, IabResult result) { Log.d(TAG,"Consumption finished. Purchase: "+purchase + ", result: " +result); // We know this is the "gas"sku because it's the only onewe consume, // so we don't check whichsku was consumed. If you havemore than one //sku, you probably shouldcheck... if (result.isSuccess()) { //successfully consumed, so we apply the effects of the item inour //game world's logic, which in our case means filling the gas tank abit if(purchase.getSku().equals("coins_100")||purchase.getSku().equals("android.test.purchased")){ showMessage("支付成功","成功購買100貓幣"); } } else { complain("Error while consuming: " + result); } } }; // Listener that's called when we finish querying the items weown IabHelper.QueryInventoryFinishedListener mGotInventoryListener =newIabHelper.QueryInventoryFinishedListener() { publicvoidonQueryInventoryFinished(IabResult result, Inventory inventory){ Log.d(TAG,"Query inventory finished."); if (result.isFailure()) { complain("Failed to query inventory: " +result); return; } Log.d(TAG,"Query inventory was successful."); if(inventory.hasPurchase("double_income")) { //查詢到有受管理的商品支付成功需要將道具給用戶 showMessage("成功Restore雙倍金幣", "查詢到有雙倍金幣需要恢復"); }elseif(inventory.hasPurchase("cions_100")){ //查詢到不受管理的商品支付成功需要將道具消耗掉 showMessage("成功Restore100金幣","查詢到有100金幣需要恢復"); } } };
處理返回Activity后的數據:
@Override protectedvoid onActivityResult(int requestCode,int resultCode, Intent data) { // TODO Auto-generated methodstub Log.d(TAG, "onActivityResult("+ requestCode + "," +resultCode +"," + data); // Pass on theactivity result to the helper for handling if(!mHelper.handleActivityResult(requestCode, resultCode, data)){ // not handled, so handle it ourselves(here's where you'd // perform any handling of activityresults not related to in-app // billing... super.onActivityResult(requestCode,resultCode, data); } else { Log.d(TAG,"onActivityResult handled by IABUtil."); } }
退出游戲后銷毀IabHelper:
@Override protectedvoid onDestroy() { // TODO Auto-generated methodstub super.onDestroy(); if (mHelper !=null) mHelper.dispose(); mHelper =null; }
最后附上MainActivity.java完整文件,源碼下載地址:http://pan.baidu.com/share/link?shareid=1579953623&uk=473193131:
packagecn.catcap.together; importjava.util.ArrayList; importorg.json.JSONException; importcom.android.vending.billing.IInAppBillingService; importcom.example.android.trivialdrivesample.util.IabHelper; importcom.example.android.trivialdrivesample.util.IabResult; importcom.example.android.trivialdrivesample.util.Inventory; importcom.example.android.trivialdrivesample.util.Purchase; importcom.example.android.trivialdrivesample.util.SkuDetails; importandroid.os.Bundle; importandroid.os.Handler; importandroid.os.RemoteException; importandroid.app.Activity; importandroid.app.AlertDialog; importandroid.content.Intent; importandroid.util.Log; importandroid.view.View; importandroid.widget.TextView; public class MainActivityextends Activity { // The helper object IabHelper mHelper; // Debugtag, for logging static final String TAG = "TrivialDrive"; //Current amount of gas in tank, in units int mTank; //(arbitrary) request code for the purchase flow請求碼 static final int RC_REQUEST = 10001; private boolean iap_is_ok = false; //double_income為受管理商品,coins_100為不受管理商品 private String[] skus ={"android.test.purchased","double_income","coins_100"}; privateArrayList<String> sku_list; privateArrayList<String> price_list; private IInAppBillingService billingservice; private TextView tv; @Override protected voidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Stringbase64EncodedPublicKey = "";//此處填寫自己的appid mHelper =new IabHelper(this, base64EncodedPublicKey); // enabledebug logging (for a production application, you should set this tofalse). mHelper.enableDebugLogging(false); // Start setup. This isasynchronous and the specified listener // will becalled once setup completes. Log.d(TAG,"Starting setup."); mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public voidonIabSetupFinished(IabResult result) { Log.d(TAG, "Setup finished."); if (!result.isSuccess()) { // Ohnoes, there was a problem. complain("Problem setting up in-app billing: " + result); return; } iap_is_ok = true; // Hooray, IAB is fully set up. Now, let's getan inventory of stuff we own. Log.d(TAG, "Setup successful. Queryinginventory."); } }); //購買雙倍金幣(受管理商品) findViewById(R.id.button1).setOnClickListener(newView.OnClickListener() { @Override public void onClick(Viewv) { // TODO Auto-generatedmethod stub iapHandler.sendEmptyMessage(1); } }); //購買100貓幣(不受管理商品) findViewById(R.id.button2).setOnClickListener(newView.OnClickListener() { @Override public void onClick(Viewv) { // TODO Auto-generatedmethod stub iapHandler.sendEmptyMessage(2); } }); //RestoreOrder findViewById(R.id.button3).setOnClickListener(newView.OnClickListener() { @Override public void onClick(Viewv) { // TODO Auto-generatedmethod stub if (iap_is_ok) { mHelper.queryInventoryAsync(mGotInventoryListener); }else { showMessage("提示", "GooglePlay初始化失敗,當前無法進行支付,請確定您所在地區支持Google Play支付或重啟游戲再試!"); } } }); //獲取價格 findViewById(R.id.button4).setOnClickListener(newView.OnClickListener() { @Override public void onClick(Viewv) { // TODO Auto-generatedmethod stub sku_list = newArrayList<String>(); price_list = newArrayList<String>(); //添加默認值 sku_list.add("double_income"); price_list.add("HK$40"); sku_list.add("coins_100"); price_list.add("HK$8"); new Thread(new Runnable(){ @Override public void run() { // TODO Auto-generatedmethod stub getPrice(); } }).start(); } }); //測試訂單 findViewById(R.id.button5).setOnClickListener(newView.OnClickListener() { @Override public void onClick(Viewv) { // TODO Auto-generatedmethod stub iapHandler.sendEmptyMessage(3); } }); //顯示價格 tv =(TextView) findViewById(R.id.text); } //獲取價格 private voidgetPrice(){ ArrayList<String> skus = newArrayList<String>(); skus.add("double_income"); skus.add("coins_100"); billingservice =mHelper.getService(); Bundle querySkus = newBundle(); querySkus.putStringArrayList("ITEM_ID_LIST",skus); try { Bundle skuDetails =billingservice.getSkuDetails(3,MainActivity.this.getPackageName(),"inapp", querySkus); ArrayList<String> responseList =skuDetails.getStringArrayList("DETAILS_LIST"); if (null!=responseList){ for (String thisResponse :responseList) { try { SkuDetails d = newSkuDetails(thisResponse); for (int i = 0; i< sku_list.size(); i++) { if(sku_list.get(i).equals(d.getSku())) { price_list.set(i,d.getPrice()); } } iapHandler.sendEmptyMessage(0); } catch (JSONException e){ // TODO Auto-generatedcatch block e.printStackTrace(); } } } } catch (RemoteExceptione) { // TODO Auto-generatedcatch block e.printStackTrace(); } } Handler iapHandler = newHandler(){ public voidhandleMessage(android.os.Message msg) { switch(msg.what){ case 0: tv.setText(price_list.get(0)+"\n"+price_list.get(1)); break; case 1: if (iap_is_ok) { mHelper.launchPurchaseFlow(MainActivity.this, skus[1], RC_REQUEST, mPurchaseFinishedListener); }else { showMessage("提示", "GooglePlay初始化失敗,當前無法進行支付,請確定您所在地區支持Google Play支付或重啟游戲再試!"); } break; case 2: if (iap_is_ok) { mHelper.launchPurchaseFlow(MainActivity.this, skus[2], RC_REQUEST,mPurchaseFinishedListener); }else { showMessage("提示", "GooglePlay初始化失敗,當前無法進行支付,請確定您所在地區支持Google Play支付或重啟游戲再試!"); } break; case 3: if (iap_is_ok) { mHelper.launchPurchaseFlow(MainActivity.this, skus[0], RC_REQUEST,mPurchaseFinishedListener); }else { showMessage("提示", "GooglePlay初始化失敗,當前無法進行支付,請確定您所在地區支持Google Play支付或重啟游戲再試!"); } break; default: break; } }; }; // Callback for when apurchase is finished IabHelper.OnIabPurchaseFinishedListenermPurchaseFinishedListener = newIabHelper.OnIabPurchaseFinishedListener() { publicvoid onIabPurchaseFinished(IabResult result, Purchase purchase){ Log.d(TAG, "Purchasefinished: " + result + ", purchase: " + purchase); if (result.isFailure()) { // Oh noes! complain("Error purchasing: " + result); return; } Log.d(TAG, "Purchasesuccessful."); if(purchase.getSku().equals("coins_100")||purchase.getSku().equals("android.test.purchased")){ mHelper.consumeAsync(purchase, mConsumeFinishedListener); }else if(purchase.getSku().equals("double_income")) { //受管理的商品,開啟雙倍經驗 showMessage("支付成功","成功購買雙倍經驗"); } } }; // Called when consumption is complete IabHelper.OnConsumeFinishedListenermConsumeFinishedListener = newIabHelper.OnConsumeFinishedListener() { publicvoid onConsumeFinished(Purchase purchase, IabResult result) { Log.d(TAG, "Consumptionfinished. Purchase: " + purchase + ", result: " + result); // We know this is the "gas"sku because it's the only one we consume, // so we don't check whichsku was consumed. If you have more than one // sku, you probably shouldcheck... if (result.isSuccess()) { // successfully consumed, so we apply theeffects of the item in our // game world's logic, which in our case meansfilling the gas tank a bit if(purchase.getSku().equals("coins_100")||purchase.getSku().equals("android.test.purchased")){ showMessage("支付成功","成功購買100貓幣"); } } else { complain("Error while consuming: " +result); } } }; // Listener that's called when we finishquerying the items we own IabHelper.QueryInventoryFinishedListenermGotInventoryListener = newIabHelper.QueryInventoryFinishedListener() { publicvoid onQueryInventoryFinished(IabResult result, Inventoryinventory) { Log.d(TAG, "Query inventoryfinished."); if (result.isFailure()) { complain("Failed to query inventory: " +result); return; } Log.d(TAG, "Query inventorywas successful."); if(inventory.hasPurchase("double_income")) { //查詢到有受管理的商品支付成功需要將道具給用戶 showMessage("成功Restore雙倍金幣", "查詢到有雙倍金幣需要恢復"); }elseif(inventory.hasPurchase("cions_100")){ //查詢到不受管理的商品支付成功需要將道具消耗掉 showMessage("成功Restore100金幣","查詢到有100金幣需要恢復" ); } } }; @Override protected voidonActivityResult(int requestCode, int resultCode, Intent data){ // TODO Auto-generatedmethod stub Log.d(TAG,"onActivityResult(" + requestCode + "," + resultCode + "," +data); // Pass onthe activity result to the helper for handling if(!mHelper.handleActivityResult(requestCode, resultCode, data)){ // not handled, so handle itourselves (here's where you'd // perform any handling ofactivity results not related to in-app // billing... super.onActivityResult(requestCode, resultCode, data); } else { Log.d(TAG, "onActivityResulthandled by IABUtil."); } } @Override protected void onDestroy(){ // TODO Auto-generatedmethod stub super.onDestroy(); if (mHelper != null)mHelper.dispose(); mHelper =null; } void complain(Stringmessage) { Log.e(TAG,"**** TrivialDrive Error: " + message); alert("Error: " + message); } void alert(String message){ AlertDialog.Builder bld = new AlertDialog.Builder(this); bld.setMessage(message); bld.setNeutralButton("OK", null); Log.d(TAG,"Showing alert dialog: " + message); bld.create().show(); } private voidshowMessage(String title,String message){ newAlertDialog.Builder(MainActivity.this).setTitle(title).setMessage(message).setPositiveButton("確定",null).show(); } }
以上就是完整的Google in-appBilling接入過程,接下來會跟大家一起走一遍亞馬遜支付,如有疑問請留言。