Android整合Google支付,以及遇到的坑、坑
Google商店的應用被下架,應用內購買必須走Google支付,還要扣去百分之三十的手續費,而且有些國家還會收一定的銷售稅最高達27%,其實Google支付只是自己集成了Paypal支付和銀行卡支付,然後Google收手續費。使用者使用Google正常支付退款時間是48小時,退款只會在商家賬號通知。
我們來談一談整合Google支付吧:
要在您的應用中實現應用內購買結算,您需要執行以下操作:
- 將應用內購買結算庫新增到您的專案中。
- 更新您的
AndroidManifest.xml
檔案。 - 建立
ServiceConnection
並將其繫結到IInAppBillingService
。 - 從您的應用傳送應用內購買結算請求至
IInAppBillingService
- 處理來自 Google Play 的應用內購買結算請求響應。
將 AIDL 檔案新增到您的專案中
IInAppBillingService.aidl
是一種定義應用內購買結算版本 3 服務介面的 Android 介面定義語言 (AIDL) 檔案。 您可以使用此介面通過呼叫 IPC 方法呼叫來發送結算請求。
要獲取 AIDL 檔案,請執行以下操作:
- 在 SDK 管理器中,展開
Extras
部分。 - 選擇 Google Play Billing Library。
- 點選 Install packages 完成下載。
IInAppBillingService.aidl
檔案將安裝到 <sdk>/extras/google/play_billing/
要將 AIDL 新增到您的專案,請執行以下操作:
- 首先,下載 Google Play Billing Library 到您的 Android 專案:
- 選擇 Tools > Android > SDK Manager。
- 在 Appearance & Behavior > System Settings > Android SDK 下面,選擇 SDK Tools 標籤以選擇並下載 Google Play Billing Library。
- 接下來,複製
IInAppBillingService.aidl
檔案到您的專案。- 如果您使用的是 Android Studio,請執行以下操作:
- 導航至 Project 工具視窗中的
src/main
- 選擇 File > New > Directory,然後在 New Directory 視窗中輸入
aidl
,再選擇 OK。 - 選擇 File > New > Package,然後在 New Package 視窗中輸入
com.android.vending.billing
,再選擇 OK。 - 使用您的作業系統檔案資源管理器,導航至
<sdk>/extras/google/play_billing/
,複製IInAppBillingService.aidl
檔案,然後將其貼上到專案中的com.android.vending.billing
軟體包。
- 導航至 Project 工具視窗中的
- 如果您在非 Android Studio 環境中開發,請執行以下操作:建立目錄
/src/com/android/vending/billing
,並將IInAppBillingService.aidl
檔案複製到此目錄。 將 AIDL 檔案新增到您的專案中並使用 Gradle 工具構建專案,從而生成IInAppBillingService.java
檔案。
- 如果您使用的是 Android Studio,請執行以下操作:
- 開發您的應用。您會在專案的
/gen
目錄中看到名為IInAppBillingService.java
的生成檔案。
更新您的應用清單
應用內購買結算依賴於 Google Play 應用,後者將處理應用與 Google Play 伺服器之間的所有通訊。 要使用 Google Play 應用,您的應用必須請求適當的許可權。 您可以通過將 com.android.vending.BILLING
許可權新增到 AndroidManifest.xml 檔案執行此操作。 如果您的應用未宣告應用內購買結算許可權,但試圖傳送結算請求,Google Play 將拒絕請求並使用錯誤響應。
要為您的應用授予必要的許可權,請在 AndroidManifest.xml
檔案中新增以下程式碼行:
<uses-permission android:name="com.android.vending.BILLING" />
這個許可權是一定得加的。按照例子,先把所需Google aidl放好,位置一定不能錯。
IInAppBillingService.aidl
還有所需的Util,都拷貝到專案中:
然後Clean一下,不讓IInAppBillingService不能用。
下面開始程式碼整合:
先把所需要的常量定義一下:
//google支付部分:
// 宣告屬性The helper object
private IabHelper mHelper;
private String TAG = "MyLog1";
/**
* Google是否初始化成功:
*/
boolean iap_is_ok = false;
/**
* Google支付需要的
* 購買產品的id
*/
static String purchaseId = "";
// (arbitrary) request code for the purchase flow
//購買請求回撥requestcode
static final int RC_REQUEST = 1001;
//base64EncodedPublicKey是在Google開發者後臺複製過來的:要整合的應用——>服務和API——>此應用的許可金鑰(自己去複製)
String base64EncodedPublicKey = "MIIBIjANBgkqh******************************DAQAB";
在onCreate中初始化:
mHelper = new IabHelper(this, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
MyLog.i(TAG, "初始化完成.");
if (!result.isSuccess()) {
// Oh noes, there was a problem.
complain("Problem setting up in-app billing:初始化失敗 " + result);
return;
}
iap_is_ok = true;
if (mHelper == null) return;
MyLog.i(TAG, "Google初始化成功.");
if (iap_is_ok) {
try {
mHelper.queryInventoryAsync(mGotInventoryListener);
} catch (IabHelper.IabAsyncInProgressException e) {
e.printStackTrace();
}
}else {
MyLog.i(TAG, "Google Play初始化失敗,當前無法進行支付,請確定您所在地區支援Google Play支付或重啟遊戲再試!");
// toast("Google Play初始化失敗,當前無法進行支付,請確定您所在地區支援Google Play支付或重啟遊戲再試!");
}
}
});
初始化的時候會去查詢一下Google 後臺自己所建立的產品,查詢監聽:
// 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) {
MyLog.i(TAG, "查詢庫存完成.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;
// Is it a failure?
if (result.isFailure()) {
complain("查詢庫存失敗: " + result);
return;
}
MyLog.i(TAG, "查詢庫存成功.");
/*
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it's correct! See
* verifyDeveloperPayload().
*/
// Check for gas delivery -- if we own gas, we should fill up the tank immediately
//查詢你的產品是否存在沒有消耗的,要是沒有消耗,先去消耗,再購買
Purchase gasPurchase = inventory.getPurchase(purchaseId);
if (gasPurchase != null && verifyDeveloperPayload(gasPurchase))
{
try {
mHelper.consumeAsync(inventory.getPurchase(purchaseId), mConsumeFinishedListener);
} catch (IabHelper.IabAsyncInProgressException e) {
complain("Error consuming gas. Another async operation in progress.");
}
return;
}
MyLog.i(TAG, "初始庫存查詢完成;啟用主使用者介面.");
}
};
然後是購買產品的點選事件中新增購買事件:
/**
* 去購買Google產品
* purchaseId Google產品id
*
* 點選購買的時候,才去初始化產品,看看是否有這個產品,是否消耗
*
*/
private void toBuyGooglepay(){
// launch the gas purchase UI flow.
// We will be notified of completion via mPurchaseFinishedListener
MyLog.i(TAG, "開始購買");
/* TODO: for security, generate your payload here for verification. See the comments on
* verifyDeveloperPayload() for more info. Since this is a SAMPLE, we just use
* an empty string, but on a production app you should carefully generate this. */
//這個payload是要給Google傳送的備註資訊,自定義引數,購買完成之後的訂單中也有該欄位
String payload = Contants.userID;
try {
mHelper.launchPurchaseFlow(TypeActivity.this, purchaseId, RC_REQUEST,mPurchaseFinishedListener, payload);
} catch (Exception e) {
Toast.makeText(TypeActivity.this,"無法完成谷歌支付",Toast.LENGTH_SHORT).show();
}
}
下面是幾個所需的購買過程中事件監聽:
1:購買完成的回撥事件監聽:
// Callback for when a purchase is finished購買完成的回撥
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
MyLog.i(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;
if (result.isFailure()) {
complain("Error purchasing: " + result);
return;
}
if (!verifyDeveloperPayload(purchase)) {
complain("Error purchasing. Authenticity verification failed.");
return;
}
MyLog.i("MyLog1", "購買完成.");
//購買完成時候就能獲取到訂單的詳細資訊:purchase.getOriginalJson(),要是想要什麼就去purchase中get
//根據獲取到產品的Id去判斷是哪一項產品
if (purchase.getSku().equals(purchaseId)) {
MyLog.i(TAG, "購買的是"+purchase.getSku());
try {
//購買完成之後去消耗產品
mHelper.consumeAsync(purchase, mConsumeFinishedListener);
} catch (IabHelper.IabAsyncInProgressException e) {
complain("Error consuming gas. Another async operation in progress.");
return;
}
}
}
};
購買完成事件中有產品消耗事件監聽,也就是說,購買完成時候,一定要去消耗一下產品,不然不能進行下次購買。
Google給出的宣告:應用內商品一經購買,就會被視為“被擁有”且無法從 Google Play 購買。 您必須對應用內商品傳送消耗請求,然後 Google Play 才能允許再次購買。可以消耗託管的應用內商品,但不能消耗訂閱。下面是消耗監聽:
// Called when consumption is complete 消耗產品的回撥
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase, IabResult result) {
MyLog.i(TAG, "消耗完。購買(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
MyLog.i(TAG, "消費成功。Provisioning.");
}else {
complain("Error while consuming: " + result);
}
}
};
下面是幾個用到的輔助類:
/** Verifies the developer payload of a purchase. */
boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
/*
* TODO: verify that the developer payload of the purchase is correct. It will be
* the same one that you sent when initiating the purchase.
*
* WARNING: Locally generating a random string when starting a purchase and
* verifying it here might seem like a good approach, but this will fail in the
* case where the user purchases an item on one device and then uses your app on
* a different device, because on the other device you will not have access to the
* random string you originally generated.
*
* So a good developer payload has these characteristics:
*
* 1. If two different users purchase an item, the payload is different between them,
* so that one user's purchase can't be replayed to another user.
*
* 2. The payload must be such that you can verify it even when the app wasn't the
* one who initiated the purchase flow (so that items purchased by the user on
* one device work on other devices owned by the user).
*
* Using your own server to store and verify developer payloads across app
* installations is recommended.
*/
return true;
}
void complain(String message) {
MyLog.i(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);
MyLog.i(TAG, "Showing alert dialog: " + message);
bld.create().show();
}
在onActivityResult中新增:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (mHelper == null) return;
// Pass on the activity result to the helper for handling
try {
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
}else {
MyLog.i("MyLog1", "onActivityResult handled by IABUtil.");
}
} catch (Exception e) {
e.printStackTrace();
}
if (requestCode == 1001) {
int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
//訂單資訊
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
MyLog.i("MyLog1", "responseCode:: " + responseCode );
MyLog.i("MyLog1", "purchaseData:: " + purchaseData );
MyLog.i("MyLog1", "dataSignature:: " + dataSignature );
if (resultCode == RESULT_OK) {
try {
JSONObject jo = new JSONObject(purchaseData);
//訂單Id
String sku = jo.getString("productId");
System.out.println("You have bought the " + sku + ". Excellent choice,adventurer!");
}
catch (JSONException e) {
MyLog.i("MyLog1","Failed to parse purchase data.");
System.out.println("Failed to parse purchase data.");
e.printStackTrace();
}
}
}
}
}
完成購買之後一定要解除應用內購買服務,在onDestroy中新增
@Override
protected void onDestroy() {
super.onDestroy();
if (mHelper != null) {
try {
mHelper.dispose();
} catch (Exception e) {
e.printStackTrace();
}
}
mHelper = null;
}
注:國內部分手機由於不能啟動Google play service ,所以在解綁的時候可能會出錯,(呼叫mHelper.dispose();方法時報錯),解決辦法:
在IabHelp.java中的300行左右把:
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
修改為:
新建一個繫結的判斷:
public boolean isBound;
isBound = mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
然後在dispose();方法中修改:
public void dispose() throws IabAsyncInProgressException {
synchronized (mAsyncInProgressLock) {
if (mAsyncInProgress) {
throw new IabAsyncInProgressException("Can't dispose because an async operation " +
"(" + mAsyncOperation + ") is in progress.");
}
}
logDebug("Disposing.");
mSetupDone = false;
if (mServiceConn != null) {
logDebug("Unbinding from service.");
if (mContext != null && isBound){
mContext.unbindService(mServiceConn);
isBound = false;
}
}
mDisposed = true;
mContext = null;
mServiceConn = null;
mService = null;
mPurchaseListener = null;
}
程式碼整合到此結束,下面是測試:
首先你得有一個能安裝Google play的手機,還要有個Gmail郵箱(不能和Google開發者的Gmail郵箱一樣),測試支付不產生訂單id,要是需要檢視訂單id就得真實支付(需要支援雙幣的信用卡或者paypal賬號)。測試用的app一定要跟上傳到Google的測試版的包名、版本code、name、簽名一致,否則無法進行支付測試。