Android Google應(yīng)用內(nèi)支付

前言

之前閱讀過一篇 通過AIDL方式集成Google支付的文檔绑青,想看的适室,可以去閱讀一下。但是呢?這個(gè)文檔只能適用于API3.0以前的版本,不適用于新版的API叽奥,所以還是不推薦去閱讀。新的API已經(jīng)不再支持AIDL的方式痛侍。所以現(xiàn)在來說說現(xiàn)在新的集成方式朝氓,準(zhǔn)備工作這邊就不做介紹,有需要的可以參考一下官方Google支付文檔主届,該文檔主要講解的是Google 支付的訂閱功能赵哲。

準(zhǔn)備工作

1.準(zhǔn)備Google Console 官方開發(fā)者賬號(hào)
2.準(zhǔn)備支持Google 服務(wù),帶Google Play商店的手機(jī)

首先

準(zhǔn)備工作完成后君丁,這時(shí)候需要在Google Play Console 官方配置一下商品信息誓竿,同時(shí)在Google Play console頁面添加一下沙盒測(cè)試,推薦先使用沙盒測(cè)試谈截,然后再通過真實(shí)環(huán)境測(cè)試筷屡。

注:
   * 商品信息配置的時(shí)候,一旦商品生效后簸喂,將無法被刪除毙死,但是可以修改
   * 從未上傳Google 的App,上傳內(nèi)部測(cè)試時(shí)間幾乎和上正式版的時(shí)間差不多
   * 添加測(cè)試喻鳄,在應(yīng)用外扼倘,"許可測(cè)試"里需要添加人員和在內(nèi)部測(cè)試?yán)锾砑訙y(cè)試人員
支付流程

引入Google Play支付的包

implementation "com.android.billingclient:billing-ktx:3.0.1"

1.初始化 BillingClient

初始化一下支付方法,在初始化里添加一個(gè)監(jiān)聽,主要是用于接收應(yīng)用中所有交易的更新以及當(dāng)用戶支付完成后再菊,刷新一個(gè)購買信息狀態(tài)

private fun initBillingClient() {
   Log.d(TAG, "startDataSourceConnections")
        if (billingClient == null) {
            billingClient = BillingClient.newBuilder(context)
                .enablePendingPurchases()
                .setListener { billingResult, purchases ->
                    onPurchasesUpdated(billingResult, purchases)
                }
                .build()
        }
        connectToPlayBillingService()
    }
2.與 Google Play 建立連接

連接到 Google Play 的過程是異步操作爪喘,所以是需要使用BillingClientStateListener監(jiān)聽,確保能夠成功的連接到Google Play

    private fun connectToPlayBillingService(): Boolean {
        Log.d(TAG, "connectToPlayBillingService")
        if (billingClient?.isReady != true) {
            billingClient?.startConnection(object : BillingClientStateListener {
                override fun onBillingServiceDisconnected() {
                    Log.d(TAG, "onBillingServiceDisconnected")
                    connectToPlayBillingService()
                }

                override fun onBillingSetupFinished(billingResult: BillingResult) {
                    billingSetup(billingResult)
                }
            })
            return true
        }
        return false
    }
3.與Google Play連接結(jié)束

連接結(jié)束后纠拔,Google Play將會(huì)返回BillingResponseCode 官方文檔秉剑。連接成功后,展示當(dāng)前可以購買的商品(即商品信息)稠诲,消耗掉上一次購買操作未消耗的商品(也叫確認(rèn)交易)侦鹏。
注:連接成功后,必須要核銷一下商品臀叙,否則會(huì)導(dǎo)致商品購買不成功

fun billingSetup(billingResult: BillingResult) {
        when (billingResult.responseCode) {
            BillingClient.BillingResponseCode.OK -> {
                Log.d(TAG, "onBillingSetupFinished successfully")
                querySkuDetailsAsync(BillingClient.SkuType.SUBS, skuS)
                queryPurchasesHistory()
            }
            BillingClient.BillingResponseCode.BILLING_UNAVAILABLE -> {
                Log.d(TAG, billingResult.debugMessage)
            }
            else -> {
                Log.d(TAG, billingResult.debugMessage)
            }
        }
    }
4.展示可供購買的商品

根據(jù)SkuType 官方文檔類型來查詢對(duì)應(yīng)的商品詳情略水,查詢的結(jié)果將會(huì)返回本地化商品信息(即將查詢的信息賦值到本地的List)。
請(qǐng)求成功后劝萤,調(diào)用一下invoke方法渊涝,它是kotlin的中一個(gè)函數(shù),可能換個(gè)寫法床嫌,這個(gè)函數(shù)可能看起來比較懵驶赏,那就換個(gè)寫法,它就相當(dāng)于onSkuDetails(skuDetailsList)
注:在配置應(yīng)用內(nèi)商品時(shí)創(chuàng)建的唯一商品 ID 將用于向 Google Play 異步查詢應(yīng)用內(nèi)商品詳情

    private fun querySkuDetailsAsync(
        @BillingClient.SkuType skuType: String,
        skuList: List<String>
    ) {
        val params = SkuDetailsParams.newBuilder()
            .setSkusList(skuList)
            .setType(skuType)
            .build()
        Log.d(TAG, "querySkuDetailsAsync for $skuType")
        billingClient?.querySkuDetailsAsync(params) { billingResult, skuDetailsList ->
            when (billingResult.responseCode) {
                BillingClient.BillingResponseCode.OK -> {
                    if (skuDetailsList.orEmpty().isNotEmpty()) {
                        if (skuDetailsList != null) {
                            onSkuDetails?.invoke(skuDetailsList)
                        }
                    }
                }
                else -> {
                    Log.d(TAG, billingResult.debugMessage)
                }
            }
        }
    }
5.啟動(dòng)購買流程

從支付按鈕發(fā)起請(qǐng)求既鞠,調(diào)用成功后,頁面將會(huì)顯示Google Play支付頁面盖文,即如圖G1:
無法調(diào)起以下頁面原因:
1.該手機(jī)不支持Google服務(wù)
2.該手機(jī)未安裝Google服務(wù)三件套
3.Google Play商店“后臺(tái)彈出界面”的權(quán)限被關(guān)閉
4.Google Play商店獲取手機(jī)信息權(quán)限被關(guān)閉了

G1.png

區(qū)別:

這里和舊的API是不一樣的嘱蛋,舊的API,在這里是使用AIDL的方法五续,將userId綁定到商品信息里洒敏。
新API是可以直接通過setObfuscatedAccountId(String)的方法,將userId綁定到對(duì)應(yīng)的商品信息內(nèi)疙驾。

    fun launchBillingFlow(activity: Activity, skuDetails: SkuDetails, userId: String) {
        val purchaseParams = BillingFlowParams
            .newBuilder()
            .setObfuscatedAccountId(userId)
            //skuDetails對(duì)應(yīng)的商品
            .setSkuDetails(skuDetails)
            .build()
        Log.d(TAG, "launchBillingFlow ${skuDetails.originalJson}")
        billingClient?.launchBillingFlow(activity, purchaseParams)
    }
6.確認(rèn)購買,支付

Google Play在支付完成后會(huì)再調(diào)用onPurchasesUpdated()凶伙,然后會(huì)將結(jié)果通過接口返回,用戶支付成功后它碎,Google Play將會(huì)生成購買令牌(唯一標(biāo)識(shí))函荣,同時(shí)用戶將會(huì)收到對(duì)應(yīng)訂單的郵件,訂單ID(退款需要)扳肛,以及扣款通知傻挂,同時(shí)也可以在Google Play商店看到對(duì)應(yīng)的訂單。BillingResponseCode 官方文檔

private fun onPurchasesUpdated(
        billingResult: BillingResult,
        purchases: MutableList<Purchase>?
    ) {
        Log.d(TAG, "onPurchasesUpdated ${billingResult.responseCode} ${billingResult.debugMessage}")
        when (billingResult.responseCode) {
            BillingClient.BillingResponseCode.OK -> {
                purchases?.apply {
                    for (purchase in purchases) {
                        handlePurchase(purchase)
                    }
                }
            }
            BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
                //購買失敗挖息,因?yàn)樵撐锲芬驯粨碛薪鹁埽撋唐沸枰幌?                Log.d(TAG, billingResult.debugMessage)
                queryPurchasesHistory()
            }
            BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> {
              //購買失敗,Google服務(wù)斷開套腹,可能網(wǎng)絡(luò)原因 
                connectToPlayBillingService()
            }
            else -> {
                Log.d(TAG, billingResult.debugMessage)
            }
        }
    }

做到這一步绪抛,你是不是感覺已經(jīng)流程已經(jīng)結(jié)束了资铡,這邊可以準(zhǔn)確告訴你,它還沒有結(jié)束幢码,它還需要調(diào)用一個(gè)消耗交易操作(即確認(rèn)交易)笤休,如果沒有這一步,正常用戶會(huì)在3天內(nèi)退款蛤育,并且取消這一筆訂單宛官,測(cè)試用戶會(huì)在5分鐘內(nèi)取消訂單。


image.png
7.處理購買交易(消耗瓦糕,確認(rèn)交易)

在確認(rèn)交易之前要判斷一下PurchaseState 官方文檔底洗,當(dāng)購買交易只有處于PURCHASED的狀態(tài)下,才可以確認(rèn)交易咕娄,不在此狀態(tài)下的交易亥揖,是沒有辦法進(jìn)行確認(rèn)操作的
處理交易方式:
1.驗(yàn)證當(dāng)前購買交易
2.提供內(nèi)容給用戶,并且將"確認(rèn)內(nèi)容"傳送給用戶,并且標(biāo)志成已消費(fèi)
注:如果您在三天內(nèi)未確認(rèn)購買交易圣勒,用戶會(huì)自動(dòng)收到退款费变,并且 Google Play 會(huì)撤消該購買交易
Api2.0以后都是需要確認(rèn)交易,2.0以前是不需要消耗的

private fun handlePurchase(purchase: Purchase) {
        Log.d(TAG, "handlePurchase ${purchase} isAcknowledged: ${purchase.isAcknowledged}")
        if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
            GlobalScope.launch(Dispatchers.Main) {
                if (!purchase.isAcknowledged) {
                    val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                        .setPurchaseToken(purchase.purchaseToken)
                    withContext(Dispatchers.IO) {
                        billingClient?.acknowledgePurchase(acknowledgePurchaseParams.build()) {}
                    }
                }
            }
        }
    }
8.提取購買交易(查詢購買交易圣贸,也可以叫恢復(fù)購買)

當(dāng)用戶進(jìn)入App的時(shí)候挚歧,建議在在onResume()和onCreate()調(diào)用此方法,主要用于處理用戶在App以外購買成功吁峻,為什么這么說滑负?因?yàn)橛嗛営脩羰强梢栽贕oogle Play商店里,進(jìn)行續(xù)訂用含,購買等操作的矮慕,但是購買成功后,同事也會(huì)提示用戶需要重新回到App啄骇,進(jìn)行確認(rèn)交易的操作步驟痴鳄。

存在漏單多種原因:
1.在購買過程中出現(xiàn)網(wǎng)絡(luò)問題,收到購買交易的通知之前失去了網(wǎng)絡(luò)連接
2.多臺(tái)設(shè)備:用戶在一臺(tái)設(shè)備上購買了一件商品,然后在切換設(shè)備時(shí)看到該商品
3.處理在您的應(yīng)用外進(jìn)行的購買交易(在App外交易)

 private fun queryPurchasesHistory() {
        val result = billingClient?.queryPurchases(BillingClient.SkuType.SUBS)
        result?.purchasesList?.apply {
            forEach {
                handlePurchaseHistory(it)
            }
        }
        Log.d(TAG,"queryPurchasesHistory results:${result?.purchasesList} size: ${result?.purchasesList?.size} "
        )
    }
9.調(diào)用消耗方法

調(diào)用方法之前缸夹,先判斷一下該訂單是否處于可被消耗的狀態(tài)痪寻,在該訂單消耗之前,將purchaseToken發(fā)送給后端虽惭。

    private fun handlePurchaseHistory(purchase: Purchase) {
        Log.d(
            TAG,
            "handlePurchaseHistory $purchase handlePurchase.purchaseState: ${purchase.purchaseState} isAcknowledged: ${purchase.isAcknowledged}"
        )
        if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
             GlobalScope.launch(Dispatchers.Main) {
                if (!purchase.isAcknowledged) {
                  // 向后端發(fā)送purchaseToken
                          ·····
                    withContext(Dispatchers.IO) {
                        val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                            .setPurchaseToken(purchase.purchaseToken)
                        billingClient?.acknowledgePurchase(acknowledgePurchaseParams.build()) {}
                    }
                }
            }
        }
    }
總結(jié)

Google 支付看似流程簡(jiǎn)單槽华,但是實(shí)際上細(xì)節(jié)方面比較稍微有一點(diǎn)難處理,但是如果一點(diǎn)點(diǎn)捋下來的話s 趟妥,發(fā)現(xiàn)邏輯其實(shí)也不是很復(fù)雜猫态,但是比較折磨人。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市亲雪,隨后出現(xiàn)的幾起案子勇凭,更是在濱河造成了極大的恐慌,老刑警劉巖义辕,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虾标,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡灌砖,警方通過查閱死者的電腦和手機(jī)璧函,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來基显,“玉大人蘸吓,你說我怎么就攤上這事×糜模” “怎么了库继?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)窜醉。 經(jīng)常有香客問我宪萄,道長(zhǎng),這世上最難降的妖魔是什么榨惰? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任拜英,我火速辦了婚禮,結(jié)果婚禮上琅催,老公的妹妹穿的比我還像新娘居凶。我一直安慰自己,他們只是感情好恢暖,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著狰右,像睡著了一般杰捂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棋蚌,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天嫁佳,我揣著相機(jī)與錄音,去河邊找鬼谷暮。 笑死蒿往,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的湿弦。 我是一名探鬼主播瓤漏,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蔬充?” 一聲冷哼從身側(cè)響起蝶俱,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饥漫,沒想到半個(gè)月后榨呆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡庸队,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年积蜻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彻消。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡竿拆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出证膨,到底是詐尸還是另有隱情如输,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布央勒,位于F島的核電站不见,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏崔步。R本人自食惡果不足惜稳吮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望井濒。 院中可真熱鬧灶似,春花似錦、人聲如沸瑞你。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽者甲。三九已至春感,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虏缸,已是汗流浹背鲫懒。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刽辙,地道東北人窥岩。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像宰缤,于是被迫代替她去往敵國和親颂翼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晃洒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容