Google支付和服務(wù)端驗證

因為公司業(yè)務(wù)需求,需要使用google的登錄和支付。google支付分為訂閱和應(yīng)用內(nèi)購買兩種,筆者使用的是應(yīng)用內(nèi)購買這種方式,這里將整個google支付和支付驗證的流程記錄下來。

導(dǎo)入google結(jié)算庫

google結(jié)算服務(wù)接入地址

def billing_version = "4.0.0"
implementation "com.android.billingclient:billing-ktx:$billing_version"

接入支付

流程:

  1. 初始化鏈接到google支付服務(wù),如果不能鏈接到說明設(shè)備環(huán)境有問題,要么是沒有翻墻,要么是google套件(google paly 、server)沒有安裝完整,國內(nèi)手機都是閹割過的,所以需要重新安裝google套件
  2. 查詢上次未消費的商品,如果有未消費的商品通知服務(wù)器,然后消費掉。因為國外的支付環(huán)境和國內(nèi)不一樣,他們可以線上下單,然后到便利店去支付,所以有未消費的這種情況。

這時google支付的準(zhǔn)備工作已完成,下面就可以發(fā)起支付了

  1. 使用google后臺配置商品id進(jìn)行支付
  2. 支付完成后通知服務(wù)器驗證訂單合法性并發(fā)貨
  3. 客戶端消費商品

下面咋們上代碼

step1
初始化并連接到google服務(wù)

    // init方法
    public synchronized void init(Activity mActivity){
        //創(chuàng)建BillingClient 對面,查詢 消費  支付都會使用這個對象
        this.mBillingClient = BillingClient.newBuilder(mActivity)
                .setListener(new PurchasesUpdatedListener() {//設(shè)置支付回調(diào),這里其實是商品狀態(tài)發(fā)生變化時就會回調(diào)
                    @Override
                    public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
                        LogUtils.d("call onPurchasesUpdated");
                        if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) {//支付成功
                            for (Purchase purchase : purchases) {
                                if(purchase == null || purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED) continue;
                                OrderManager.getInstance().paySuccess(purchase);
                                //通知服務(wù)器支付成功,服務(wù)端驗證后,消費商品
                            }
                            //TODO客戶端同步回調(diào)支付成功
                        } else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) {//支付取消
                          
                        } else {//支付失敗
                        }
                    }
                })
                .enablePendingPurchases()
                .build();
        //鏈接到google play
        this.connectBillPay();
    }       
                  
    private void connectBillPay(){
        mBillingClient.startConnection(new BillConnectListener());
    }

    class BillConnectListener implements BillingClientStateListener {

        @Override
        public void onBillingSetupFinished(BillingResult billingResult) {
            if (billingResult.getResponseCode() ==  BillingClient.BillingResponseCode.OK) {
                //鏈接到google服務(wù)
                payEnable = true;
                queryPurchases();
            }
        }

        @Override
        public void onBillingServiceDisconnected() {
            //未鏈接到google服務(wù)
            payEnable = false;
            connectBillPay();
        }
    }

setp2
查詢已支付的商品,并通知服務(wù)器后消費(google的支付里面,沒有消費的商品,不能再次購買)

    private void queryPurchases(){
        PurchasesResponseListener mPurchasesResponseListener = new PurchasesResponseListener() {
            @Override
            public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List<Purchase> purchasesResult) {
                if(billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK || purchasesResult == null) return;
                for (Purchase purchase : purchasesResult) {
                    if(purchase == null || purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED) continue;
                    OrderManager.getInstance().paySuccess(purchase);
                    //這里處理已經(jīng)支付過的訂單,通知服務(wù)器去驗證
                }
            }
        };
        mBillingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, mPurchasesResponseListener);
    }

setp3
發(fā)起支付

    /**
     * 
     * @param cpOrder 你自己的訂單號或者用戶id,用于關(guān)聯(lián)到對應(yīng)的用戶,發(fā)放道具時使用
     * @param productId google后臺配置產(chǎn)品ID
     */
    public void pay(final String cpOrder, final String productId) {
        if(mBillingClient == null || wrActivity.get() == null || !payEnable){
             //TODO客戶端同步回調(diào)支付失敗,原因是為鏈接到google或者google的支付服務(wù)不能使用
            return;
        }
        //查詢商品詳情
        querySkuDetailsAsync(cpOrder, productId);
    }

     //查詢商品詳情
    void querySkuDetailsAsync(final String cpOrder, final String productId){
        List<String> skuList = new ArrayList<>();
        skuList.add(productId);
        SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
        mBillingClient.querySkuDetailsAsync(params.build(),
                new SkuDetailsResponseListener() {
                    @Override
                    public void onSkuDetailsResponse(BillingResult billingResult,
                                                     List<SkuDetails> skuDetailsList) {
                        if (skuDetailsList != null && billingResult.getResponseCode() ==  BillingClient.BillingResponseCode.OK){
                            for(SkuDetails skuDetails : skuDetailsList){
                                if(productId.equals(skuDetails.getSku())){
                                    //發(fā)起支付
                                    launchBillingFlow(cpOrder, skuDetails);
                                }
                            }
                        }
                    }
                });
    }
    
  //吊起google支付頁面
    void launchBillingFlow(String cpOrder, SkuDetails skuDetails){
        mBillingClient.launchBillingFlow(
        wrActivity.get(),
        BillingFlowParams
                .newBuilder()
                .setSkuDetails(skuDetails)
                .setObfuscatedAccountId(cpOrder)//這里本來的意思存放用戶信息,類似于國內(nèi)的透傳參數(shù),我這里傳的我們的訂單號。老版本使用DeveloperPayload字段,最新版本中這個字段已不可用了
                .build()
        );
    }

服務(wù)器支付驗證操作較為復(fù)雜,咋們在下面單獨提出來做一個小節(jié)

setp5
消費商品

    public void consumePurchase(final Purchase purchase){
        if(mBillingClient == null || purchase == null || purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED) return;
        LogUtils.d("消耗商品:\n商品id:" + purchase.getSkus() + "\n商品OrderId:" + purchase.getOrderId() + "\ntoken:" + purchase.getPurchaseToken());
        LogUtils.d("消耗商品:" + purchase.getAccountIdentifiers().getObfuscatedAccountId());
        ConsumeParams consumeParams = ConsumeParams.newBuilder()
                .setPurchaseToken(purchase.getPurchaseToken())
                .build();
        ConsumeResponseListener listener = new ConsumeResponseListener() {
            @Override
            public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
                if (billingResult.getResponseCode() == BillingResponseCode.ERROR) {
                    //消費失敗將商品重新放入消費隊列
                    OrderManager.getInstance().consumeFinal(purchase);
                    return;
                }
                LogUtils.d("消費成功");
            }
        };
        mBillingClient.consumeAsync(consumeParams, listener);
    }

服務(wù)端驗證

做服務(wù)端驗證前,需要做一下準(zhǔn)備工作

  1. 創(chuàng)建api項目這個和登錄用的項目不是同一個
  2. 開啟Google Play Android Developer API
  3. 設(shè)置oauth同意屏幕(就是拉起開發(fā)者授權(quán)賬號登錄時的登錄頁面)
  4. 創(chuàng)建web應(yīng)用的oauth客戶端ID
  5. google play開發(fā)者后臺,API權(quán)限菜單中關(guān)聯(lián)剛剛創(chuàng)建的項目,一個google play賬號只需要也只能關(guān)聯(lián)一個api項目就行了,這個項目可以查詢關(guān)聯(lián)賬號中的所有應(yīng)用的訂單
  6. 拉起授權(quán)頁面,使用google開發(fā)者賬號給項目授權(quán),得到code
  7. 通過code,拿到refreshToken,這個token只有第一次才會返回需要永久儲存(這個refreshtoken很重要,需要保存下來),如果弄丟,只有重新創(chuàng)建一個oauth客戶端ID,然后重復(fù)步驟6,7,拿到新的refreshtoken
  8. 刷新refreshToken, 得到accessToken,通過accesstoken就可以去查詢訂單狀態(tài)了,這里的accessToken一般只有5分鐘左右,5分鐘后需要重新用refreshToken換取新的accessToken

下面咋們上操作截圖

setp1
創(chuàng)建api項目google api console

創(chuàng)建api項目

setp2
開啟Google Play Android Developer API

Api和服務(wù)菜單

庫菜單

搜索“Google Play Android Developer API”


image.png

開啟“Google Play Android Developer API”


image.png

setp3
開啟同意屏幕

填上必填項

這里填上必填項就行了,這個授權(quán)同意屏幕,請求code時拉起來給咋們開發(fā)人員開的,填啥都無所謂

setp4
創(chuàng)建oauth2客戶端id

image.png

創(chuàng)建頁面和創(chuàng)建成功后的修改頁面可以獲取到clientId和clientSecret


image.png

到這里api項目就已經(jīng)創(chuàng)建好了

setp5
google play后臺關(guān)聯(lián)api項目

image.png

setp6
獲取code
地址:https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri={填寫的重定向地址}&client_id={創(chuàng)建的clientId}
將上面的{XX}替換成創(chuàng)建api項目時填寫的重定向地址,和clientId,然后將連接放到瀏覽器中打開,就會吊起授權(quán)界面,使用你的開發(fā)者賬號授權(quán)登錄
請求方式:瀏覽器中打開

image.png

image.png

這里可以看到,重定向地址上有兩個參數(shù)code和scope,我們只需要code就行了,這里的code是urlencode后的,使用時需要decode

setp7
使用code換取refreshToken
地址:https://accounts.google.com/o/oauth2/token
請求方式:post
參數(shù):grant_type=authorization_code
code=獲取到的code(需要看看code中是否有%號,如果有需要urldecode)
client_id=創(chuàng)建api項目是的clientId(客戶端ID)
client_secret=創(chuàng)建api項目時的clientSecret(客戶端密鑰)
redirect_uri=創(chuàng)建api項目時的重定向地址

image.png

這里就獲取到refreshToken了,重點重點重點,refreshToken保存下來,它只會在第一次請求中返回,后續(xù)用在發(fā)一樣的請求不會返回refreshtoken,如果不慎弄丟了,需要去重新創(chuàng)建一個WebClientId

setp8
使用refreshToken獲取accessToken
地址:https://accounts.google.com/o/oauth2/token
請求方式:post
參數(shù):grant_type=refresh_token
refresh_token=剛剛獲取到的refreshToken
client_id=創(chuàng)建api項目是的clientId(客戶端ID)
client_secret=創(chuàng)建api項目時的clientSecret(客戶端密鑰)

image.png

setp9
查詢訂單狀態(tài)
https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{token}?access_token={access_token}
packageName:app包名,必須是創(chuàng)建登錄api項目時,創(chuàng)建android客戶端Id使用包名
productId:對應(yīng)購買商品的商品ID
token:購買成功后Purchase對象的getPurchaseToken()
access_token:上面咋們獲取到的accessToken
請求方式:get
返回值:

{
  "purchaseTimeMillis": "1623980699933",//購買產(chǎn)品的時間,自紀(jì)元(1970 年 1 月 1 日)以來的毫秒數(shù)。
  "purchaseState": 0,//訂單的購買狀態(tài)??赡艿闹禐椋?. 已購買 1. 已取消 2. 待定
  "consumptionState": 0,//產(chǎn)品的消費狀態(tài)??赡艿闹禐椋?0. 尚未消耗 1. 已消耗
  "developerPayload": "",
  "orderId": "GPA.3398-6726-1036-80298",//google訂單號
  "purchaseType": 0,
  "acknowledgementState": 0,
  "kind": "androidpublisher#productPurchase",
  "obfuscatedExternalAccountId": "SDK2106180944530041",//上面客戶支付時的透傳字段,google指導(dǎo)是用來存放用戶信息的,不能過長,否則客戶端不能支付
  "obfuscatedExternalProfileId": "",
  "regionCode": "HK"
}

到這里整個支付驗證流程就已經(jīng)走完了,這里總結(jié)哈筆者這次試用過程中走過的一些坑:

  • google應(yīng)用必須要在封閉測試狀態(tài)下,并審核通過的應(yīng)用才能支付,文檔說的是內(nèi)部測試就可以了,筆者每次都弄到封閉測試狀態(tài)下才可以支付。
  • 在firebase中創(chuàng)建了項目,會自動同步到google api后臺,不用再去單獨創(chuàng)建登錄使用的項目
  • 登錄使用的api項目和查詢支付使用的api項目是兩個不同的項目相互不干擾,查詢支付的api項目一個google play賬號對應(yīng)一個項目,這個google play賬號中所有的應(yīng)用,都可以通過這個查詢支付的api項目去查詢
  • 獲取code授權(quán)api項目時,要使用google play后臺的開發(fā)者賬號授權(quán)

關(guān)于RefreshToken過期問題

  • api項目-同意屏幕,發(fā)布狀態(tài)為測試(有效期7天)
  • RefreshToken 6個月都未使用,這個要維護(hù)accessToken的有效性,應(yīng)該可以不必考慮
  • 授權(quán)賬號改密碼了(筆者未測試,修改開發(fā)者賬號密碼是否會導(dǎo)致過期)
  • 授權(quán)超過50個刷新令牌,最先的刷新令牌就會失效(這里50個應(yīng)該夠用了,除了測試時,可能會授權(quán)多個)
  • 取消了授權(quán)
  • 屬于具有有效會話控制策略的 Google Cloud Platform 組織
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容