今天跟大家一起看下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接入過程,接下來會跟大家一起走一遍亞馬遜支付,如有疑問請留言。
