主頁 > 移動端開發 > Android_Google Play結算庫(應用內支付)billing 3.0接入實戰

Android_Google Play結算庫(應用內支付)billing 3.0接入實戰

2021-04-27 18:11:17 移動端開發

一、接入摘要

  • 時間:2021-04-19
  • 版本:billing 3.0
  • 語言:java
  • 內容:一次性消耗型商品
  • 老版本比較:當前客戶端接入版本對比aidl方式區別巨大,支付透傳欄位也被廢棄,需要開發者做好訂單和google訂單關聯;服務端支付驗證也改了流程,開發者需要做更多的操作,

關于google 支付新版本,了解到好多開發者還是用的老版本,為什么不更新?哈哈,因為太坑了,不過現在強制更新,給了具體的時間限制
在這里插入圖片描述

二、接入流程

  1. Google Play開發者后臺創建應用(Google Play)
  2. 開發者后臺對應專案配置相關資訊
  3. 安卓端接入(結算庫接入檔案)
  4. 后端商品驗證

三、客戶端接入

集成依賴庫

module的 build.gradle 添加下面代碼

dependencies {
	...
    implementation "com.android.billingclient:billing:3.0.3"
	...
}

支付流程

  1. 初始化 BillingClient
  2. 與 Google Play 建立連接
  3. 自己服務端生成訂單再調起 google 購買操作
  4. 購買成功拿到相關資訊去服務端驗證購買合法性
  5. 服務端驗證商品后進行發貨并回傳客戶端資訊,客戶端消耗商品
初始化 BillingClient
/**
 * 初始化billingClient
 */
private void initBillingClient() {
    mBillingClient = BillingClient.newBuilder(this)
            .setListener(new PurchasesUpdatedListener() {
                @Override
                public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases) {
                    //交易更新將會在這里回呼
                    int responseCode = billingResult.getResponseCode();
                    if (responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
                        for (Purchase purchase : purchases) {
                            String googlePayOrderId = purchase.getOrderId();
                            String purchaseToken = purchase.getPurchaseToken();
                            //服務器驗證
                            verifyPayment(orderId, googlePayOrderId, productId, purchaseToken);
                        }
                    } else if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
                        //取消支付
                    } else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
                        //已存在這個未完成訂單,查詢訂單驗證然后消耗掉
                        queryPurchases();
                    } else {
                        //還有很多其他狀態,判斷進行相應處理
                    }
                }
            })
            .enablePendingPurchases()
            .build();

}

與 Google Play 建立連接

/**
* 與Google Play建立連接
*/
private void startConnection() {
   mBillingClient.startConnection(new BillingClientStateListener() {
       @Override
       public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
           //鏈接成功最好去查詢訂單,做掉單處理
           if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
               queryPurchases();
           }
       }

       @Override
       public void onBillingServiceDisconnected() {
           // Try to restart the connection on the next request to
           // Google Play by calling the startConnection() method.
           //建議斷開時重連或在使用時判斷連接狀態,沒有連接就手動再調一次 startConnection,確保在執行任何方法時都與 BillingClient 保持連接,
       }
   });
}

發起購買

先展示商品再發起購買

/**
* 購買商品
*/
private void purchase() {
  //先展示商品
  List<String> skuList = new ArrayList<>();
  skuList.add(productId);
  SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
  params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);//INAPP應用內支付
  mBillingClient.querySkuDetailsAsync(params.build(),
          new SkuDetailsResponseListener() {
              @Override
              public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                  if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
                      for (SkuDetails skuDetails : skuDetailsList) {
                          String sku = skuDetails.getSku();
                          if (productId.equals(sku)) {
                              //啟動購買
                              BillingFlowParams purchaseParams =
                                      BillingFlowParams.newBuilder()
                                              .setSkuDetails(skuDetails)
                                              .build();
                              mBillingClient.launchBillingFlow(TestBilling.this, purchaseParams);
                              //購買狀態將在PurchasesUpdatedListener.onPurchasesUpdated回傳
                          }
                      }
                  }
              }
          });
  //
}

服務端驗證

這一步驟具體邏輯在服務端,接入是跟服務端人員溝通配合完成

/**
* 驗證支付,需要后端處理
*
* @param orderId       我們自己的訂單號,一般客戶端調起支付前會在自己服務端下單
* @param gpOrderId     google商品訂單號
* @param productId     商品id
* @param purchaseToken 商品token
*/
    private void verifyPayment(String orderId, String gpOrderId, String productId, String purchaseToken) {
        //這一步操作就是寫個網路請求,把支付相關資訊傳到后端進行驗證合法性,后端驗證回傳客戶端,驗證成功將消耗商品

    }

查詢購買

/**
 * 查詢購買交易,以確保所有購買交易都得到成功處理,如購買未發貨,或者未消耗
 */
private void queryPurchases() {
    if (mBillingClient != null && mBillingClient.isReady()) {
        Purchase.PurchasesResult result = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
        List<Purchase> purchasesList = result.getPurchasesList();
        if (purchasesList != null) {
            for (int i = 0; i < purchasesList.size(); i++) {
                if (purchasesList.get(i).isAcknowledged()) {
                    //已確認/已驗證,消耗即可
                    consume(purchasesList.get(i).getPurchaseToken());
                } else {
                    //TODO 因google支付新版沒有透傳欄位,所以我們的訂單號需要手動關聯,
                    // 資料庫查詢gp訂單對應的我方訂單號或者服務端進行訂單關聯
                    // 關于這一塊后續看是否google有新的改動優化
                    Purchase purchase = purchasesList.get(i);
                    String gpOrderId = purchase.getOrderId();
                    String orderId = "";//我們自己的訂單號,如果是程式剛啟動進來補單的,那么我們的訂單就拿不到,因為google不攜帶透傳,自己做處理吧
                    String sku = purchase.getSku();
                    String purchaseToken = purchase.getPurchaseToken();
                    verifyPayment(orderId, gpOrderId, sku, purchaseToken);
                }
            }
        }

    }
}

消耗商品

/**
* 消耗商品
*
* @param purchaseToken 商品token
*/
private void consume(String purchaseToken) {
   if (mBillingClient != null && mBillingClient.isReady()) {
       ConsumeParams consumeParams =
               ConsumeParams.newBuilder()
                       .setPurchaseToken(purchaseToken)
                       .build();
       ConsumeResponseListener listener = new ConsumeResponseListener() {
           @Override
           public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
               if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                   // Handle the success of the consume operation.
               }
           }
       };
       mBillingClient.consumeAsync(consumeParams, listener);
   }
}

四、服務端驗證流程

初次接入新版本也是相當麻煩,如不懂流程在官方檔案上會摸不著頭腦
總結有幾個步驟

1、創建 API 專案

Google Play 開發者后臺對應專案新建API專案(Google Play Developer API)并啟動API服務和關聯到Google Play 專案
在這里插入圖片描述
參考地址:https://developers.google.cn/android-publisher/getting_started

2、創建 OAuth 客戶端

API 專案下新建OAuth 客戶端,應用型別選擇網頁應用,其他的看情況填寫,確認創建之后獲取到客戶端ID(client_id)客戶端秘鑰(client_secret)還有client_secret_xxx.json檔案,內容如下:

{
    "web":{
        "client_id":"916683014888-479gobska5u85hho2hnb03296lcr9pii.apps.googleusercontent.com",
        "project_id":"api-8972885819834575872-739232",
        "auth_uri":"https://accounts.google.com/o/oauth2/auth",
        "token_uri":"https://oauth2.googleapis.com/token",
        "auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
        "client_secret":"5J8jjMVx4o5bvvkqzku1DzMl",
        "redirect_uris":[
            "xxx"
        ],
        "javascript_origins":[
            "xxx"
        ]
    }
}

以上引數都是用在服務端呼叫google api,客戶端ID和客戶端秘鑰并非Android客戶端引數,服務端這里的所以引數都跟Android端沒有啥關系

3、獲取code

這一步的操作是需要開發者賬號登錄網頁,然后在網頁打開這個鏈接(https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=xxx&client_id=xxx)進行授權,redirect_uri就是創建 OAuth 客戶端填寫的重定向鏈接,授權成功后會把code通過redirect_uri回傳,如果redirect_uri是隨便填的,訪問出現404頁面或者無法訪問的提示,這時候請將地址欄中的鏈接地址復制出來,把code=4/xxx的值取出來,這里就獲得了code的值4/wtedvcqw-yui5CNNb-m2iI83KQx1d.yp6198ti5Zc7dJ3UXOl0T3aRLxWrtgbn

4、 通過 code 獲取 refresh_token

重要的事情說三遍 保存 保存 保存 第一次授權的時候才會回傳refresh_token(長令牌,一般情況下永久有效) ,請妥善保存,
不過在除錯階段沒保存的小伙伴也不用太過擔心,可以在 OAuth 客戶端選擇重置秘鑰或者新建 OAuth 客戶端,記得服務端把引數替換哦,這時候再走一遍流程就可以再次授權獲得refresh_token

  • POST請求到https://accounts.google.com/o/oauth2/token

  • 請求引數:

    grant_type:authorization_code
    code:步驟 3 獲取到的code
    client_id:客戶端id
    client_secret:客戶端秘鑰
    redirect_uri:重定向地址

結果回傳:

{
    "access_token":"xxx",
    "expires_in":3599,
    "refresh_token":"1//xxx",
    "scope":"https://www.googleapis.com/auth/androidpublisher",
    "token_type":"Bearer",
    "created":16193255555
}

5、通過 refresh_token 獲取 access_token

  • POST方式呼叫https://accounts.google.com/o/oauth2/token

  • 請求引數:

    grant_type:refresh_token
    client_id:客戶端id
    client_secret:客戶端秘鑰
    refresh_token:步驟 4 獲取的refresh_token值

    結果回傳:

{
    "access_token":"xxx",
    "token_type":"Bearer",
    "expires_in":3600
}

6、查詢訂單資訊

其實就是所謂的驗證
一次性消耗型商品參考官網地址:https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.products/get

  • GET方法呼叫以下介面: https://www.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{token}?access_token=access_token

  • packageName:該應用的包名, 如com.google.demo

  • productId:商品ID

  • token:Android端充值獲取的token值

  • access_token:步驟 5 獲取的access_token

如果訂單有效會回傳相關資訊

{
  "kind": “androidpublisher#productPurchase”,
  "purchaseTimeMillis": “支付時間”,
  "purchaseState": 0,// 是否付費: 0 已支付, 1 取消
  "consumptionState": 0, // 是否被消費: 0 未消費, 1 已消費,
  "developerPayload": "",//透傳欄位,新版這個客戶端沒法傳了
  "orderId": “GPA-XXX”,//google的訂單號
  "purchaseType": 0, // 支付型別:  0 測驗, 1 真實
  "acknowledgementState": 0,//商品的確認狀態, 0 尚未被確認, 1 確認
  "purchaseToken": "token",//購買令牌,即客戶端支付商品token
  "productId": “sss”,//商品id
  "quantity": 1,//數量
  "obfuscatedExternalAccountId": “”,
  "obfuscatedExternalProfileId": “”,
  "regionCode": “”
}

可根據回傳的資訊作相應的驗證

如果訂單無效會回傳400錯誤

如果回傳403錯誤
1、檢查api專案啟動狀態
2、檢查api專案關聯狀態
3、OAuth 客戶端引數與服務端使用是否一致
4、谷歌服務的 bug,這時候只要在該應用的商店內,隨意增加一個內購商品,再試一次看看是否可行,新增的內購商品可以洗掉,

{
	"error": {
		"errors": [{
			"domain": "androidpublisher",
			"reason": "projectNotLinked",
			"message": "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console."
		}],
		"code": 403,
		"message": "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console."
	}
}

五、收工

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/280726.html

標籤:其他

上一篇:全面復盤Android開發者容易忽視的Backup功能

下一篇:android最全面的冷啟動優化方案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more