Google支付和服務端驗證


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

導入google結算庫

google結算服務接入地址

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

接入支付

流程:

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

這時google支付的准備工作已完成,下面就可以發起支付了

  1. 使用google后台配置商品id進行支付
  2. 支付完成后通知服務器驗證訂單合法性並發貨
  3. 客戶端消費商品

下面咋們上代碼

step1
初始化並連接到google服務

    // init方法 public synchronized void init(Activity mActivity){ //創建BillingClient 對面,查詢 消費 支付都會使用這個對象 this.mBillingClient = BillingClient.newBuilder(mActivity) .setListener(new PurchasesUpdatedListener() {//設置支付回調,這里其實是商品狀態發生變化時就會回調 @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); //通知服務器支付成功,服務端驗證后,消費商品 } //TODO客戶端同步回調支付成功 } 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服務 payEnable = true; queryPurchases(); } } @Override public void onBillingServiceDisconnected() { //未鏈接到google服務 payEnable = false; connectBillPay(); } } 

setp2
查詢已支付的商品,並通知服務器后消費(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); //這里處理已經支付過的訂單,通知服務器去驗證 } } }; mBillingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, mPurchasesResponseListener); } 

setp3
發起支付

    /** * * @param cpOrder 你自己的訂單號或者用戶id,用於關聯到對應的用戶,發放道具時使用 * @param productId google后台配置產品ID */ public void pay(final String cpOrder, final String productId) { if(mBillingClient == null || wrActivity.get() == null || !payEnable){ //TODO客戶端同步回調支付失敗,原因是為鏈接到google或者google的支付服務不能使用 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())){ //發起支付 launchBillingFlow(cpOrder, skuDetails); } } } } }); } //吊起google支付頁面 void launchBillingFlow(String cpOrder, SkuDetails skuDetails){ mBillingClient.launchBillingFlow( wrActivity.get(), BillingFlowParams .newBuilder() .setSkuDetails(skuDetails) .setObfuscatedAccountId(cpOrder)//這里本來的意思存放用戶信息,類似於國內的透傳參數,我這里傳的我們的訂單號。老版本使用DeveloperPayload字段,最新版本中這個字段已不可用了 .build() ); } 

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

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); } 

服務端驗證

做服務端驗證前,需要做一下准備工作

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

下面咋們上操作截圖

setp1
創建api項目google api console

 
創建api項目

 

setp2
開啟Google Play Android Developer API

 
Api和服務菜單

 

 
庫菜單

搜索“Google Play Android Developer API”


 
image.png

開啟“Google Play Android Developer API”


 
image.png

setp3
開啟同意屏幕

 
填上必填項

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

 

setp4
創建oauth2客戶端id

 
image.png

 

創建頁面和創建成功后的修改頁面可以獲取到clientId和clientSecret


 
image.png

到這里api項目就已經創建好了

setp5
google play后台關聯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={創建的clientId}
將上面的{XX}替換成創建api項目時填寫的重定向地址,和clientId,然后將連接放到瀏覽器中打開,就會吊起授權界面,使用你的開發者賬號授權登錄
請求方式:瀏覽器中打開

 
image.png

 
image.png

 

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

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

 
image.png

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

 

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

 
image.png

 

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

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

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

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

關於RefreshToken過期問題

  • api項目-同意屏幕,發布狀態為測試(有效期7天)
  • RefreshToken 6個月都未使用,這個要維護accessToken的有效性,應該可以不必考慮
  • 授權賬號改密碼了(筆者未測試,修改開發者賬號密碼是否會導致過期)
  • 授權超過50個刷新令牌,最先的刷新令牌就會失效(這里50個應該夠用了,除了測試時,可能會授權多個)
  • 取消了授權
  • 屬於具有有效會話控制策略的 Google Cloud Platform 組織


作者:Ray206
鏈接:https://www.jianshu.com/p/76416ebc0db0
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM