1. 程式人生 > >Android整合Google支付,以及遇到的坑、坑

Android整合Google支付,以及遇到的坑、坑

Google商店的應用被下架,應用內購買必須走Google支付,還要扣去百分之三十的手續費,而且有些國家還會收一定的銷售稅最高達27%,其實Google支付只是自己集成了Paypal支付和銀行卡支付,然後Google收手續費。使用者使用Google正常支付退款時間是48小時,退款只會在商家賬號通知。

我們來談一談整合Google支付吧:

要在您的應用中實現應用內購買結算,您需要執行以下操作:

  1. 將應用內購買結算庫新增到您的專案中。
  2. 更新您的 AndroidManifest.xml 檔案。
  3. 建立 ServiceConnection 並將其繫結到 IInAppBillingService
  4. 從您的應用傳送應用內購買結算請求至IInAppBillingService
  5. 處理來自 Google Play 的應用內購買結算請求響應。

將 AIDL 檔案新增到您的專案中

IInAppBillingService.aidl 是一種定義應用內購買結算版本 3 服務介面的 Android 介面定義語言 (AIDL) 檔案。 您可以使用此介面通過呼叫 IPC 方法呼叫來發送結算請求。

要獲取 AIDL 檔案,請執行以下操作:

  1. 在 SDK 管理器中,展開 Extras 部分。
  2. 選擇 Google Play Billing Library
  3. 點選 Install packages 完成下載。

IInAppBillingService.aidl 檔案將安裝到 <sdk>/extras/google/play_billing/

要將 AIDL 新增到您的專案,請執行以下操作:

  1. 首先,下載 Google Play Billing Library 到您的 Android 專案:
    1. 選擇 Tools > Android > SDK Manager
    2. 在 Appearance & Behavior > System Settings > Android SDK 下面,選擇 SDK Tools 標籤以選擇並下載 Google Play Billing Library
  2. 接下來,複製 IInAppBillingService.aidl 檔案到您的專案。
    • 如果您使用的是 Android Studio,請執行以下操作:
      1. 導航至 Project 工具視窗中的 src/main
      2. 選擇 File > New > Directory,然後在 New Directory 視窗中輸入 aidl,再選擇 OK
      3. 選擇 File > New > Package,然後在 New Package 視窗中輸入 com.android.vending.billing,再選擇 OK
      4. 使用您的作業系統檔案資源管理器,導航至 <sdk>/extras/google/play_billing/,複製 IInAppBillingService.aidl 檔案,然後將其貼上到專案中的 com.android.vending.billing 軟體包。
    • 如果您在非 Android Studio 環境中開發,請執行以下操作:建立目錄 /src/com/android/vending/billing,並將IInAppBillingService.aidl 檔案複製到此目錄。 將 AIDL 檔案新增到您的專案中並使用 Gradle 工具構建專案,從而生成IInAppBillingService.java 檔案。
  3. 開發您的應用。您會在專案的 /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、簽名一致,否則無法進行支付測試。