国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

App 出海實(shí)踐:Google Play 結(jié)算系統(tǒng)

這篇具有很好參考價(jià)值的文章主要介紹了App 出海實(shí)踐:Google Play 結(jié)算系統(tǒng)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

作者:業(yè)志陳

現(xiàn)如今,App 出海熱度不減,是很多公司和個(gè)人開(kāi)發(fā)者選擇的一個(gè)市場(chǎng)方向。App 為了實(shí)現(xiàn)盈利,除了接入廣告這種最常見(jiàn)的變現(xiàn)方式外,就是通過(guò)提供各類虛擬商品或者是會(huì)員服務(wù)來(lái)吸引用戶付費(fèi)了,此時(shí) Google Play 結(jié)算系統(tǒng)(Google Play’s billing system)就是 Android 端應(yīng)用必須使用到的一個(gè)支付渠道了

Google 對(duì) Google Play 結(jié)算系統(tǒng)的簡(jiǎn)介:Google Play’s billing system is a service that enables you to sell digital products and content in your Android app, whether you want to monetize through one-time purchases or offer subscriptions to your services. Google Play offers a full set of APIs for integration with both your Android app and your server backend that unlock the familiarity and safety of Google Play purchases for your users.

也就是說(shuō):Google Play 結(jié)算系統(tǒng)是一項(xiàng)可以讓我們?cè)?Android 應(yīng)用中銷售數(shù)字商品和內(nèi)容的服務(wù)。無(wú)論是要通過(guò)一次性購(gòu)買交易創(chuàng)收,還是要為用戶提供訂閱服務(wù),它都能幫我們搞定。Google Play 提供了一整套 API,可集成到 Android 應(yīng)用和服務(wù)器后端中,從而為用戶提供熟悉又安全的 Google Play 購(gòu)買交易服務(wù)

在最近的一年多時(shí)間里,我一直在負(fù)責(zé)一個(gè)海外項(xiàng)目的開(kāi)發(fā)工作,這個(gè)過(guò)程中也接入了 Google Play 結(jié)算系統(tǒng)。在剛開(kāi)始時(shí),由于對(duì)當(dāng)中的各個(gè)概念不夠了解,其整體支付流程又和國(guó)內(nèi)常用的各類支付服務(wù)相差挺大的,導(dǎo)致我走了不少的彎路

這里我就來(lái)寫一篇文章,對(duì) Google Play 結(jié)算系統(tǒng)進(jìn)行詳細(xì)介紹,希望對(duì)你有所幫助

一、概述

想要通過(guò) Google Play 結(jié)算系統(tǒng)向用戶展示并售賣商品,自然需要先創(chuàng)建商品,創(chuàng)建商品的方式有兩種:

  • 在 Google Play Console 手動(dòng)創(chuàng)建
  • 通過(guò) Google Play Developer API 以代碼的方式創(chuàng)建

在 Google Play 中創(chuàng)建的商品都屬于虛擬商品,每個(gè)商品代表的都是 App 給用戶提供的一種權(quán)益,而每個(gè)商品都包含一個(gè)唯一標(biāo)識(shí),也即 ProductId,我們?cè)跇I(yè)務(wù)上就需要根據(jù) ProductId 的命名規(guī)則來(lái)定義商品所代表的具體權(quán)益類型

每個(gè)商品又可以分為兩種類型:

  • 一次性商品。用戶通過(guò)單次付費(fèi)獲得的商品,屬于買斷制,對(duì)應(yīng) Google Play 結(jié)算庫(kù)中的 BillingClient.ProductType.INAPP
  • 訂閱型商品。用戶以固定周期不斷重復(fù)付費(fèi)的商品,屬于訂閱制,對(duì)應(yīng) Google Play 結(jié)算庫(kù)中的 BillingClient.ProductType.SUBS

當(dāng)用戶購(gòu)買了商品后,App 還需要對(duì)這筆訂單進(jìn)行核銷。處理流程和商品類型有關(guān),分為兩種:

  • 確認(rèn)交易。不管購(gòu)買的商品是什么類型,App 都需要先對(duì)這筆交易進(jìn)行 確認(rèn),如果在限定的時(shí)間內(nèi)未完成確認(rèn),Google Play 就會(huì)自動(dòng)撤銷這筆交易并向用戶退款?!按_認(rèn)交易” 這個(gè)操作應(yīng)該是 Google Play 為了讓 App 確定已經(jīng)向用戶提供了權(quán)益,盡量避免出現(xiàn)用戶已付款但 App 沒(méi)有向用戶下發(fā)權(quán)益這種情況。確認(rèn)操作可以由服務(wù)端或者移動(dòng)端來(lái)實(shí)現(xiàn),對(duì)應(yīng) acknowledgePurchase 操作
  • 消耗商品。消耗商品針對(duì)的是一次性商品中的消耗型商品,也即對(duì)其執(zhí)行 消耗 操作。通過(guò)執(zhí)行消耗操作,使得用戶后續(xù)可以再次購(gòu)買此商品。消耗操作可以由服務(wù)端或者移動(dòng)端來(lái)實(shí)現(xiàn),對(duì)應(yīng) consumePurchase 操作

二、一次性商品

一次性商品也稱為應(yīng)用內(nèi)商品,屬于一次性買斷的商品,具體又可以細(xì)分為兩種子類型:

  • 消耗型商品。也即是說(shuō),此商品在購(gòu)買后可以被消耗,從而使得用戶可以重復(fù)購(gòu)買。例如,該商品可以用于表示游戲中的金幣,用戶在使用完金幣后該商品代表的權(quán)益就失效了,用戶需要再次購(gòu)買商品才能再次獲得金幣
  • 非消耗型商品。也即是說(shuō),此商品在購(gòu)買后是不可消耗的,用戶可以永久獲得該商品代表的權(quán)益。例如,該商品可以用于表示某課程的觀看權(quán)益,用戶只要購(gòu)買商品后,就可以永久享有該課程的觀看權(quán)益

一次性商品到底屬于 消耗型 還是 非消耗型 都取決于 App 在業(yè)務(wù)上的定義,在 Google Play Console 中都統(tǒng)一將其稱為 應(yīng)用內(nèi)商品,在創(chuàng)建一次性商品時(shí)也沒(méi)有區(qū)分子類型的選項(xiàng)

假設(shè)我們對(duì)一件一次性商品在業(yè)務(wù)上的定義是消耗型的,那么就可以在適當(dāng)?shù)臅r(shí)候通過(guò)執(zhí)行 consumePurchase 來(lái)對(duì)其執(zhí)行 “消耗” 操作。例如,用戶通過(guò)購(gòu)買某個(gè)一次性商品獲得了游戲金幣,用戶在后續(xù)過(guò)程中使用這些金幣來(lái)購(gòu)買游戲道具,那么開(kāi)發(fā)者就需要同時(shí)執(zhí)行 consumePurchase 來(lái)消耗掉商品,從而使得該商品變?yōu)闊o(wú)效狀態(tài),這樣用戶后續(xù)也可以再次購(gòu)買此商品

而對(duì)于非消耗型商品,在業(yè)務(wù)上代表的是用戶可以永久享有的某個(gè)權(quán)益,只要買了該商品權(quán)益就不會(huì)丟失,因此用戶也不應(yīng)該再次購(gòu)買,自然也就不需要也不能執(zhí)行消耗操作了

三、訂閱型商品

訂閱型商品,也即需要用戶以固定周期定期進(jìn)行付費(fèi)的商品,在付費(fèi)周期內(nèi)用戶均能享有該商品代表的權(quán)益。最常見(jiàn)的應(yīng)用場(chǎng)景就是各類會(huì)員服務(wù):用戶按月付費(fèi),App 在每個(gè)訂閱周期內(nèi)向用戶提供會(huì)員獨(dú)有的功能,直至用戶取消訂閱

訂閱型商品包含四個(gè)比較重要的概念:

  • 基礎(chǔ)方案
  • 續(xù)訂類型
  • 優(yōu)惠
  • 定價(jià)階段

基礎(chǔ)方案

基礎(chǔ)方案,也稱為 BasePaln,每個(gè)訂閱型商品都必須包含一個(gè)或多個(gè)基礎(chǔ)方案才能讓用戶購(gòu)買

基礎(chǔ)方案就用于定義商品的售賣規(guī)則,包括結(jié)算周期、續(xù)訂類型、訂閱價(jià)格、優(yōu)惠策略等。例如,一個(gè)訂閱型商品可以同時(shí)提供 按月付費(fèi)按年付費(fèi) 這兩個(gè)基礎(chǔ)方案供用戶選擇,每個(gè)周期分別設(shè)定不同的價(jià)格,用戶根據(jù)喜好來(lái)選擇不同的方案進(jìn)行訂閱

續(xù)訂類型

每個(gè)基礎(chǔ)方案均需要指定續(xù)訂類型,用于指定用戶的付費(fèi)方式

續(xù)訂類型分為兩種:

  • 自動(dòng)續(xù)訂。在每個(gè)結(jié)算周期即將結(jié)束時(shí)主動(dòng)向用戶扣款,從而自動(dòng)延長(zhǎng)權(quán)益使用權(quán)的期限。付費(fèi)操作對(duì)于用戶來(lái)說(shuō)是被動(dòng)的
  • 預(yù)付費(fèi)。不會(huì)自動(dòng)續(xù)訂和扣款,用戶需要通過(guò)主動(dòng)付款來(lái)推遲權(quán)益使用權(quán)的結(jié)束日期,以此保持不間斷地享有訂閱內(nèi)容。付費(fèi)操作對(duì)于用戶來(lái)說(shuō)是主動(dòng)的

優(yōu)惠

優(yōu)惠,也稱為 Offer,只有 自動(dòng)續(xù)訂型 的基礎(chǔ)方案才能設(shè)定優(yōu)惠

每個(gè)自動(dòng)續(xù)訂型的基礎(chǔ)方案可以同時(shí)設(shè)定多個(gè)優(yōu)惠,讓用戶可以在訂閱初期享受一定的價(jià)格折扣或者是直接就免費(fèi)使用,從而吸引用戶購(gòu)買

Offer 的類型分為三種,也即分為三種優(yōu)惠策略。例如,假設(shè)現(xiàn)在有一個(gè)按月訂閱的基礎(chǔ)方案,我們就可以為其添加以下三個(gè) Offer 供用戶選擇:

  • 免費(fèi)試訂。用戶在前七天內(nèi)免費(fèi)試用,在七天后再正式進(jìn)行按月付費(fèi)
  • 單次付款。用戶一次性預(yù)付三個(gè)月的訂閱費(fèi)用,總價(jià)享受七折折扣,三個(gè)月后再按原價(jià)進(jìn)行按月訂閱
  • 周期性付款折扣。用戶還是按月訂閱,但前三個(gè)月每次付費(fèi)時(shí)均能享受八折折扣,三個(gè)月后再按原價(jià)進(jìn)行按月訂閱

價(jià)格階段

價(jià)格階段,也稱為 PricingPhases,可以看做是 Offer 的一個(gè)內(nèi)部屬性

由于一個(gè) Offer 可以同時(shí)包含多個(gè)優(yōu)惠策略,所以當(dāng)用戶在享用某個(gè) Offer 時(shí),其需要支出的價(jià)格就會(huì)隨時(shí)間發(fā)生多次變動(dòng),每個(gè)時(shí)間段分別對(duì)應(yīng)的不同的價(jià)格,PricingPhases 就用于表示 Offer 在每一個(gè)時(shí)間段的收費(fèi)規(guī)則

例如,某個(gè)按月自動(dòng)續(xù)訂的基礎(chǔ)方案包含一個(gè) Offer,此 Offer 包含一個(gè)七天免費(fèi)試訂的優(yōu)惠策略。那么,此 Offer 的價(jià)格階段就分別是:

  • 用戶先享受七天的免費(fèi)試訂
  • 七天后,用戶再按原價(jià)按月付費(fèi)

假如為這個(gè) Offer 再添加一個(gè) “折扣為七折,為期一個(gè)月的周期性付款” 的優(yōu)惠策略,此時(shí) Offer 的價(jià)格階段就變成了:

  • 用戶先享受七天的免費(fèi)試訂
  • 七天后,用戶按原價(jià)的七折進(jìn)行付費(fèi),獲得一個(gè)月的訂閱期
  • 一個(gè)月后,用戶再按原價(jià)按月付費(fèi)

所以說(shuō),價(jià)格階段就決定了用戶在不同時(shí)間段下所需要支出的費(fèi)用,每個(gè) Offer 最多允許添加兩個(gè)價(jià)格階段,也即最多發(fā)生三次價(jià)格變動(dòng),用戶會(huì)按順序來(lái)接收價(jià)格變化

總結(jié)

Google Play 設(shè)定 BasePlan 和 Offer 的自由度很高。自動(dòng)續(xù)訂的 BasePlan 的付費(fèi)周期可以從一周到一年,預(yù)付費(fèi)的 BasePlan 的付費(fèi)周期可以從一天到一年。每種優(yōu)惠策略的優(yōu)惠周期和優(yōu)惠價(jià)也都可以很靈活地設(shè)定。我們可以通過(guò)設(shè)定多種不同的周期時(shí)長(zhǎng)和優(yōu)惠策略供用戶選擇,從而盡量提高用戶的付費(fèi)率

此外,每個(gè)訂閱型商品最多可以創(chuàng)建 250 個(gè)基礎(chǔ)方案和優(yōu)惠,但同時(shí)啟用的基礎(chǔ)方案和優(yōu)惠不能超過(guò) 50 個(gè),多出的基礎(chǔ)方案和優(yōu)惠必須處于草稿或未啟用狀態(tài)

四、Billing SDK

了解了以上的基礎(chǔ)概念后,再來(lái)看這些概念如何和 Billing SDK 對(duì)應(yīng)起來(lái)

本文所有的代碼示例使用的均是當(dāng)前 Google Play 結(jié)算系統(tǒng)在 Android 端最新版本的 SDK,且是協(xié)程版本,讀者需要對(duì)協(xié)程有一定了解

dependencies {
    val billingVersion = "6.0.1"
    implementation("com.android.billingclient:billing-ktx:$billingVersion")
}

整個(gè)支付流程可以總結(jié)為以下幾點(diǎn):

  1. 通過(guò) BillingClient 和 Google Play 建立連接,同時(shí)綁定用于回調(diào)支付結(jié)果的 PurchasesUpdatedListener 接口
  2. 通過(guò) BillingClient 查詢到本地化處理的商品信息,也即 ProductDetails,從而拿到 商品描述、基礎(chǔ)方案、價(jià)格信息、優(yōu)惠策略 等屬性
  3. 根據(jù)查到的 ProductDetails,向 BillingClient 發(fā)起支付請(qǐng)求,調(diào)起支付彈窗
  4. 在 PurchasesUpdatedListener 里拿到支付結(jié)果,判斷用戶的支付狀態(tài)
  5. 當(dāng)確定用戶支付成功后,根據(jù)商品類型擇機(jī)對(duì)商品進(jìn)行 確認(rèn)消耗

BillingClient

BillingClient 是 Google Play 結(jié)算庫(kù)與 App 進(jìn)行通信的主接口,App 在執(zhí)行任何與支付相關(guān)的操作之前,都需要先通過(guò) BillingClient 和 Google Play 建立連接。在初始化 BillingClient 實(shí)例時(shí),需要同時(shí)綁定 PurchasesUpdatedListener,以便得到支付結(jié)果的回調(diào)通知。也正因?yàn)槿绱耍珹pp 在同一時(shí)間段最多只能保持一個(gè)活躍的 BillingClient 連接,以免同一個(gè)支付事件同時(shí)回調(diào)多個(gè) PurchasesUpdatedListener

private val purchasesUpdatedListener =
    PurchasesUpdatedListener { billingResult: BillingResult, purchases: List<Purchase>? ->

    }

private lateinit var billingClient: BillingClient

suspend fun startConnection(context: Context) {
    billingClient = buildBillingClient(context = context, purchasesUpdatedListener)
    startConnection(billingClient = mBillingClient)
}

private fun buildBillingClient(
    context: Context,
    listener: PurchasesUpdatedListener
): BillingClient {
    return BillingClient.newBuilder(context)
        .setListener(listener)
        .enablePendingPurchases()
        .build()
}

private suspend fun startConnection(billingClient: BillingClient): BillingResult? {
    return withContext(context = Dispatchers.Default) {
        if (billingClient.isReady) {
            return@withContext null
        }
        return@withContext suspendCancellableCoroutine { continuation ->
            billingClient.startConnection(object : BillingClientStateListener {
                override fun onBillingSetupFinished(billingResult: BillingResult) {
                    if (!continuation.isCompleted) {
                        continuation.resume(value = billingResult)
                    }
                }

                override fun onBillingServiceDisconnected() {
                    if (!continuation.isCompleted) {
                        continuation.resume(value = null)
                    }
                }
            })
        }
    }
}

ProductDetails

ProductDetails 也即商品詳情,不管是一次性商品還是訂閱型商品,都通過(guò) ProductDetails 來(lái)承載具體的商品信息

查詢 ProductDetails 需要兩個(gè)查詢參數(shù):ProductId 和 商品類型,商品類型也即 一次性商品 INAPP訂閱型商品 SUBS 兩種

private suspend fun queryProductDetails() {
    //查詢一次性商品
    queryProductDetails(
        billingClient = mBillingClient,
        productIdList = setOf("1", "2"),
        productType = BillingClient.ProductType.INAPP
    )
    //查詢訂閱型商品
    queryProductDetails(
        billingClient = mBillingClient,
        productIdList = setOf("1", "2"),
        productType = BillingClient.ProductType.SUBS
    )
}

private suspend fun queryProductDetails(
    billingClient: BillingClient,
    productIdList: Set<String>,
    productType: String
): List<ProductDetails>? {
    return withContext(context = Dispatchers.Default) {
        if (!billingClient.isReady || productIdList.isEmpty()) {
            return@withContext null
        }
        val productDetailParamsList = productIdList.map {
            QueryProductDetailsParams
                .Product
                .newBuilder()
                .setProductId(it)
                .setProductType(productType)
                .build()
        }
        val queryProductDetailsParams = QueryProductDetailsParams
            .newBuilder()
            .setProductList(productDetailParamsList)
            .build()
        val productDetailsResult = billingClient.queryProductDetails(queryProductDetailsParams)
        productDetailsResult.productDetailsList
    }
}

ProductDetails 的數(shù)據(jù)結(jié)構(gòu)如下所示,我們可以依靠這些信息來(lái)向用戶展示商品詳情。oneTimePurchaseOfferDetails 和 subscriptionOfferDetails 這兩個(gè)字段就分別用來(lái)承載一次性商品和訂閱型商品的價(jià)格信息

{
	"productId": "",
	"productType": "",
	"title": "",
	"name": "",
	"description": "",
	"oneTimePurchaseOfferDetails": {},
	"subscriptionOfferDetails": []
}

oneTimePurchaseOfferDetails

oneTimePurchaseOfferDetails 對(duì)應(yīng)的是一次性商品的詳情,數(shù)據(jù)結(jié)構(gòu)比較簡(jiǎn)單,主要就是價(jià)格信息了

{
	"priceAmountMicros": 548000000,
	"priceCurrencyCode": "HKD",
	"formattedPrice": "HK$548.00"
}

需要注意,Google Play 返回的價(jià)格信息都是做了本地化處理的,會(huì)自動(dòng)根據(jù)當(dāng)前設(shè)備的 Google Play 賬號(hào)所對(duì)應(yīng)的國(guó)家地區(qū)來(lái)返回詳情,所以商品的價(jià)格貨幣代號(hào) priceCurrencyCode 和格式化好的商品價(jià)格 formattedPrice 都會(huì)因?qū)嶋H情況而變化

subscriptionOfferDetails

subscriptionOfferDetails 對(duì)應(yīng)的是訂閱型商品的詳情

由于訂閱型商品是可以包含多個(gè) BasePlan 的,每個(gè) BasePlan 又可以包含多個(gè) Offer,所以 subscriptionOfferDetails 字段在 ProductDetails 中對(duì)應(yīng)的數(shù)據(jù)類型是 List<SubscriptionOfferDetails>。每個(gè) SubscriptionOfferDetails 都對(duì)應(yīng)一個(gè) Offer,每個(gè) Offer 又關(guān)聯(lián)一個(gè) BasePlan,Google Play 以 Offer 為單位來(lái)返回價(jià)格信息

[
    {
        "basePlanId": "yearly",
        "offerId": null,
        "offerToken": "xxx",
        "pricingPhases": {
            "pricingPhaseList": [
                {
                    "formattedPrice": "HK$469.00",
                    "priceAmountMicros": 469000000,
                    "priceCurrencyCode": "HKD",
                    "billingPeriod": "P1Y",
                    "billingCycleCount": 0,
                    "recurrenceMode": 1
                }
            ]
        }
    },
    {
        "basePlanId": "yearly",
        "offerId": "xxx",
        "offerToken": "xxx",
        "pricingPhases": {
            "pricingPhaseList": [
                {
                    "formattedPrice": "免費(fèi)",
                    "priceAmountMicros": 0,
                    "priceCurrencyCode": "HKD",
                    "billingPeriod": "P1W",
                    "billingCycleCount": 1,
                    "recurrenceMode": 2
                },
                {
                    "formattedPrice": "HK$469.00",
                    "priceAmountMicros": 469000000,
                    "priceCurrencyCode": "HKD",
                    "billingPeriod": "P1Y",
                    "billingCycleCount": 0,
                    "recurrenceMode": 1
                }
            ]
        }
    }
]

上文有講到,Offer 是包含價(jià)格階段 PricingPhases 這個(gè)概念的,這個(gè)概念就體現(xiàn)在以上 Json 中,當(dāng)中就可以解讀出以下商品信息:

  • 該商品包含一個(gè) Id 為 yearly 的 basePlan,一共包含兩個(gè) Offer
  • offerToken 用于唯一標(biāo)識(shí)每一個(gè) Offer,具有唯一性
  • billingPeriod 用于表示計(jì)費(fèi)周期,以 ISO 8601 格式來(lái)指定。例如,P1W 表示一周,P1Y 表示一年,P1M3D 表示一個(gè)月加三天
  • billingCycleCount 用于表示計(jì)費(fèi)周期的周期數(shù)。例如,以上的第二個(gè) Offer 的第一個(gè) PricingPhases,就表示允許用戶免費(fèi)試用一周;假如 billingCycleCount 是 2,就表示允許用戶免費(fèi)試用兩周
  • recurrenceMode 用于表示價(jià)格階段的重復(fù)模式,當(dāng)值為 1 或 3 時(shí),billingCycleCount 值都會(huì)是 0
    • 值為 1 就表示將在無(wú)限的計(jì)費(fèi)周期內(nèi)重復(fù)進(jìn)行,除非用戶主動(dòng)取消
    • 值為 2 就表示將在 billingCycleCount 指定的周期內(nèi)重復(fù)扣費(fèi)
    • 值為 3 表示是一次性收費(fèi),不會(huì)重復(fù)
  • 第一個(gè) Offer 的 offerId 為 null,說(shuō)明此 Offer 不包含實(shí)際的優(yōu)惠策略,代表的其實(shí)是 BasePlan 的原價(jià),所以 pricingPhaseList 也會(huì)只有一個(gè)值。且由于 billingPeriod 是 P1Y,說(shuō)明關(guān)聯(lián)的 BasePlan 的付費(fèi)周期是一年。選中此 Offer 后用戶就需要直接付 HK$469.00 的原價(jià)來(lái)進(jìn)行訂閱
  • 第二個(gè) Offer 的 offerId 不為 null,說(shuō)明此 Offer 包含真實(shí)的優(yōu)惠策略,所以 pricingPhaseList 的大小就會(huì)大于一。該 Offer 允許用戶先免費(fèi)試用一周,然后再和第一個(gè) Offer 同樣的價(jià)格和周期來(lái)進(jìn)行訂閱

所以說(shuō),想要解讀出 BasePlan 的定價(jià)策略和 Offer 的優(yōu)惠策略,就需要結(jié)合所有字段來(lái)進(jìn)行解析。首先,不管我們?cè)趧?chuàng)建 BasePlan 時(shí)有沒(méi)有為其指定優(yōu)惠策略,Google Play 都會(huì)將 BasePlan 的原價(jià)視為一個(gè) Offer 并返回,這種情況下 Offer 也只會(huì)有一個(gè)定價(jià)階段。而對(duì)于真實(shí)的優(yōu)惠策略,其 offerId 是必須設(shè)定的,自然也就不會(huì)為 null,也會(huì)有最多三個(gè)定價(jià)階段。我們要區(qū)分出 “虛假的” Offer 和 "真實(shí)的” Offer。然后,再通過(guò) pricingPhases 來(lái)解析出 BasePlan 的訂閱周期和價(jià)格、Offer 的優(yōu)惠策略、Offer 的價(jià)格階段具體是如何設(shè)定的。這樣我們才能向用戶完整展示整個(gè)商品的價(jià)格信息

launchBillingFlow

launchBillingFlow 用于調(diào)起支付彈窗發(fā)起支付操作,根據(jù)商品類型,其調(diào)用方式分為兩種

假如要購(gòu)買的是一次性商品,支付參數(shù)僅需要 ProductDetails 即可

private suspend fun launchBilling(
    activity: Activity,
    billingClient: BillingClient,
    productDetails: ProductDetails
): BillingResult {
    return withContext(context = Dispatchers.Main.immediate) {
        val productDetailsParams = BillingFlowParams
            .ProductDetailsParams
            .newBuilder()
            .setProductDetails(productDetails)
            .build()
        val billingFlowParams = BillingFlowParams
            .newBuilder()
            .setProductDetailsParamsList(listOf(productDetailsParams))
            .build()
        billingClient.launchBillingFlow(activity, billingFlowParams)
    }
}

假如要購(gòu)買的是訂閱型商品,則需要同時(shí)傳遞 ProductDetails 和 offerToken

由于一個(gè)訂閱型商品可能同時(shí)包含多個(gè) BasePlan 和多個(gè) Offer,每個(gè) Offer 的優(yōu)惠策略又各不相同。因此 App 在發(fā)起支付操作時(shí),就需要通過(guò) offerToken 來(lái)標(biāo)明用戶想要購(gòu)買的到底是哪個(gè) BasePlan,選中的又是哪個(gè) Offer。而由于 Google Play 也會(huì)將 BasePlan 的原價(jià)視為一個(gè) Offer 并返回,所以我們是可以自主選擇要不要讓用戶享用優(yōu)惠的,自由度還是比較高的

private suspend fun launchBilling(
    activity: Activity,
    billingClient: BillingClient,
    productDetails: ProductDetails,
    offerToken: String
): BillingResult {
    return withContext(context = Dispatchers.Main.immediate) {
        val productDetailsParams = BillingFlowParams
            .ProductDetailsParams
            .newBuilder()
            .setProductDetails(productDetails)
            .setOfferToken(offerToken)
            .build()
        val billingFlowParams = BillingFlowParams
            .newBuilder()
            .setProductDetailsParamsList(listOf(productDetailsParams))
            .build()
        billingClient.launchBillingFlow(activity, billingFlowParams)
    }
}

之后,我們?cè)?PurchasesUpdatedListener 回調(diào)里來(lái)獲取用戶的支付結(jié)果

假如用戶已支付成功,Purchase 就包含了此筆訂單的具體信息,包括 ProductId、OrderId、Quantity、PurchaseTime 等

private val purchasesUpdatedListener =
    PurchasesUpdatedListener { billingResult: BillingResult, purchases: List<Purchase>? ->
        when (billingResult.responseCode) {
            BillingClient.BillingResponseCode.OK -> {
                if (!purchases.isNullOrEmpty()) {
                    purchases.forEach {
                        when (it.purchaseState) {
                            Purchase.PurchaseState.PURCHASED -> {
                                //用戶支付成功
                            }

                            Purchase.PurchaseState.PENDING -> {
                                //用戶僅是預(yù)創(chuàng)建了訂單,還未真正付款
                            }

                            Purchase.PurchaseState.UNSPECIFIED_STATE -> {
                                //未知
                            }
                        }
                    }
                }
            }

            BillingClient.BillingResponseCode.USER_CANCELED -> {
                //用戶取消支付
            }

            else -> {

            }
        }
    }

acknowledgePurchase

用戶支付成功后,就需要對(duì)訂單進(jìn)行確認(rèn)了,否則 Google Play 會(huì)在限定時(shí)間內(nèi)退款給用戶

private suspend fun acknowledgePurchase(
    billingClient: BillingClient,
    purchase: Purchase
): Boolean {
    return withContext(context = Dispatchers.Default) {
        if (purchase.purchaseState != Purchase.PurchaseState.PURCHASED) {
            return@withContext false
        }
        if (purchase.isAcknowledged) {
            return@withContext true
        }
        if (!billingClient.isReady) {
            return@withContext false
        }
        val acknowledgePurchaseParams = AcknowledgePurchaseParams
            .newBuilder()
            .setPurchaseToken(purchase.purchaseToken)
            .build()
        val acknowledgePurchase = billingClient.acknowledgePurchase(acknowledgePurchaseParams)
        acknowledgePurchase.responseCode == BillingClient.BillingResponseCode.OK
    }
}

consumePurchase

如果用戶購(gòu)買的是消耗型的一次性商品,那么就需要根據(jù)實(shí)際業(yè)務(wù)擇機(jī)對(duì)訂單執(zhí)行消耗操作了

private suspend fun consumePurchase(
    billingClient: BillingClient,
    purchase: Purchase
): Boolean {
    return withContext(context = Dispatchers.Default) {
        if (purchase.purchaseState != Purchase.PurchaseState.PURCHASED) {
            return@withContext false
        }
        if (!billingClient.isReady) {
            return@withContext false
        }
        val consumeParams = ConsumeParams
            .newBuilder()
            .setPurchaseToken(purchase.purchaseToken)
            .build()
        val consumeResult = billingClient.consumePurchase(consumeParams)
        consumeResult.billingResult.responseCode == BillingClient.BillingResponseCode.OK
    }
}

五、鑒權(quán)

當(dāng)用戶購(gòu)買商品后,就需要來(lái)考慮如何對(duì)用戶進(jìn)行鑒權(quán)了。如果鑒權(quán)失敗或者是鑒權(quán)錯(cuò)了,不僅會(huì)給用戶帶來(lái)不良體驗(yàn),引來(lái)用戶投訴,也有可能會(huì)給項(xiàng)目帶來(lái)不可估量的資金損失

按照一般情況,App 在供用戶使用時(shí),App 都會(huì)為當(dāng)前用戶創(chuàng)建一個(gè)自己賬戶體系下的用戶身份,我們可以稱之為 appUser。當(dāng)用戶購(gòu)買商品后,這筆訂單也會(huì)和當(dāng)前設(shè)備付款的 Google Play 賬號(hào)綁定在一起,我們可以稱之為 gpUser

如此一來(lái),這筆訂單就會(huì)和兩個(gè)不同角度下的用戶產(chǎn)生關(guān)聯(lián)。這也就連鎖帶來(lái)一個(gè)問(wèn)題:商品代表的權(quán)益應(yīng)該掛載在哪個(gè)用戶的名下?appUser 還是 gpUser ?

這兩個(gè)選擇都各有優(yōu)缺點(diǎn)

掛載在 appUser 名下:

  • 優(yōu)點(diǎn):用戶權(quán)益清晰明確,可以精準(zhǔn)隔離用戶的權(quán)益狀態(tài)
  • 缺點(diǎn):在國(guó)外,以游客身份來(lái)購(gòu)買虛擬商品是很常見(jiàn)的情況,假如 App 只允許正式用戶(綁定了郵箱或者電話號(hào)碼)才能購(gòu)買商品的話,很有可能會(huì)流失大部分的潛在付費(fèi)用戶。因此,如果 appUser 是游客的話,當(dāng)用戶卸載應(yīng)用、更換或者重置設(shè)備后,就有可能導(dǎo)致已付費(fèi)的用戶再也找不回這筆訂單了

掛載在 gpUser 名下:

  • 優(yōu)點(diǎn):即使用戶卸載應(yīng)用、更換或者重置設(shè)備,只要當(dāng)前設(shè)備登錄的就是付款時(shí)的 Google Play 賬號(hào),App 都能通過(guò) Billing SDK 的 queryPurchasesAsync 方法重新找回該賬號(hào)名下所有的訂單信息,不用擔(dān)心出現(xiàn)權(quán)益丟失的情況。同個(gè) Google Play 賬號(hào)在不同設(shè)備上也能共同享有 App 的權(quán)益,用戶體驗(yàn)是最好的
  • 缺點(diǎn):App 是無(wú)法拿到 gpUser 的唯一身份標(biāo)識(shí)的,容易出現(xiàn)賬號(hào)倒賣的情況,多個(gè)用戶通過(guò)共享同一個(gè) Google Play 賬號(hào)來(lái)一起享有同一筆訂單的權(quán)益

所以說(shuō),App 需要根據(jù)自己的業(yè)務(wù)類型和用戶屬性,來(lái)決定是否要允許游客也能進(jìn)行購(gòu)買操作,用戶應(yīng)該以哪種維度來(lái)進(jìn)行身份鑒權(quán),當(dāng)發(fā)現(xiàn)同筆訂單在多臺(tái)設(shè)備上生效時(shí),又應(yīng)該如何避免資產(chǎn)損失

六、最后

本文主要是以移動(dòng)端的角度來(lái)進(jìn)行闡述,雖然 Google Play 結(jié)算系統(tǒng)也允許在沒(méi)有 App 后端服務(wù)參與的情況下就直接完成整個(gè)支付流程并完成用戶鑒權(quán),但為了安全性考慮,最好還是需要將訂單信息同步保存到服務(wù)端,并由服務(wù)端對(duì)訂單進(jìn)行校驗(yàn)后再?zèng)Q定是否要下發(fā)權(quán)益。此外,用戶是可以在不經(jīng)過(guò) App 的情況下,直接從 Google Play 中取消訂閱或者恢復(fù)訂閱,App 無(wú)法實(shí)時(shí)獲知該筆訂單的狀態(tài)變化,此時(shí) Google Play 也只會(huì)通過(guò) 開(kāi)發(fā)者實(shí)時(shí)通知 將這種變化通知給服務(wù)端,這種情況下也需要服務(wù)端的參與才能完整記錄下用戶的整個(gè)付費(fèi)狀態(tài)變化

Android 學(xué)習(xí)筆錄

Android 性能優(yōu)化篇:https://qr18.cn/FVlo89
Android 車載篇:https://qr18.cn/F05ZCM
Android 逆向安全學(xué)習(xí)筆記:https://qr18.cn/CQ5TcL
Android Framework底層原理篇:https://qr18.cn/AQpN4J
Android 音視頻篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(內(nèi)含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源碼解析筆記:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知識(shí)體:https://qr18.cn/CyxarU
Android 核心筆記:https://qr21.cn/CaZQLo
Android 往年面試題錦:https://qr18.cn/CKV8OZ
2023年最新Android 面試題集:https://qr18.cn/CgxrRy
Android 車載開(kāi)發(fā)崗位面試習(xí)題:https://qr18.cn/FTlyCJ
音視頻面試題錦:https://qr18.cn/AcV6Ap文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-702918.html

到了這里,關(guān)于App 出海實(shí)踐:Google Play 結(jié)算系統(tǒng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 網(wǎng)易互娛出海之旅:大數(shù)據(jù)平臺(tái)上云架構(gòu)設(shè)計(jì)與實(shí)踐

    網(wǎng)易互娛出海之旅:大數(shù)據(jù)平臺(tái)上云架構(gòu)設(shè)計(jì)與實(shí)踐

    2020 年初,隨著網(wǎng)易互娛的海外業(yè)務(wù)增長(zhǎng)與海外數(shù)據(jù)合規(guī)的需求,我們開(kāi)始了網(wǎng)易互娛大數(shù)據(jù)離線計(jì)算平臺(tái)遷移出海的工作。前期,我們采取了云主機(jī)裸機(jī)加上高性能 EBS 塊存儲(chǔ)的方案。但是,這個(gè)方案存儲(chǔ)費(fèi)用高昂,成本是國(guó)內(nèi)自建機(jī)房的數(shù)十倍。 于是,我們決定在公有云

    2024年02月13日
    瀏覽(24)
  • 【Unity】Attribute meta-data#com.google.android.play.billingclient.version 多版本庫(kù)沖突

    【Unity】Attribute meta-data#com.google.android.play.billingclient.version 多版本庫(kù)沖突

    1、Unity 2021.3.9f1 2、Max由6.0.1至最新版本6.1.0 錯(cuò)誤信息 Attribute meta-data#com.google.android.play.billingclient.version@value value=(6.1.0) from [com.android.billingclient:billing:6.1.0] AndroidManifest.xml:21:13-34 is also present at [:billing-5.2.1:] AndroidManifest.xml:25:13-34 value=(5.2.1). Suggestion: add ‘tools:replace=“android:val

    2024年01月18日
    瀏覽(35)
  • Android Studio Error: Google Play requires that apps target API level 30 or higher.

    Android Studio Error: Google Play requires that apps target API level 30 or higher.

    Android Studio Error: Google Play requires that apps target API level 30 or higher. flyfish 在build.gradle增加

    2024年02月15日
    瀏覽(23)
  • 百度交易中臺(tái)之內(nèi)容分潤(rùn)結(jié)算系統(tǒng)架構(gòu)淺析

    百度交易中臺(tái)之內(nèi)容分潤(rùn)結(jié)算系統(tǒng)架構(gòu)淺析

    作者 | 交易中臺(tái)團(tuán)隊(duì) 導(dǎo)讀 隨著公司內(nèi)容生態(tài)的蓬勃發(fā)展,內(nèi)容產(chǎn)出方和流量提供方最關(guān)注的“收益結(jié)算”的工作,也就成為重中之重。本文基于內(nèi)容分潤(rùn)結(jié)算業(yè)務(wù)為入口,介紹了實(shí)現(xiàn)過(guò)程中的重難點(diǎn),比如千萬(wàn)級(jí)和百萬(wàn)級(jí)數(shù)據(jù)量下的技術(shù)選型和最終實(shí)現(xiàn),滿足了業(yè)務(wù)需求的

    2024年02月07日
    瀏覽(23)
  • Google Play 安全提示方案

    Google Play 安全提示方案

    Google Play 保護(hù)機(jī)制可以保護(hù)從 Google Play 以外的來(lái)源安裝的應(yīng)用。當(dāng)用戶嘗試安裝應(yīng)用時(shí),Play 保護(hù)機(jī)制會(huì)根據(jù) Google Play 保護(hù)機(jī)制已編入目錄的已知有害或惡意樣本對(duì)應(yīng)用進(jìn)行實(shí)時(shí)檢查。此外,會(huì)向用戶發(fā)出警告,或在極端情況下阻止安裝。在你的應(yīng)用沒(méi)有上架Google 市場(chǎng)或者

    2024年02月03日
    瀏覽(30)
  • 國(guó)內(nèi)手機(jī)安裝 Google Play 服務(wù) (GMS/Google Mobile Services)

    GMS(英語(yǔ): Google Mobile Services), 是 Google 應(yīng)用程序和 API 的集合。這些應(yīng)用程序可以跨設(shè)備的無(wú)縫協(xié)作, 給您的設(shè)備提供出色的用戶體驗(yàn)。 下載需注意 cpu 指令集架構(gòu), 如果不知道本機(jī)的可以下載通用架構(gòu)版本 (Universal)。 可以從可靠的網(wǎng)站下載軟件包, 到 www.apkmirror.com(需扶墻)下載

    2024年02月08日
    瀏覽(33)
  • 共赴 Google Cloud 2022 中國(guó)出海數(shù)字峰會(huì),探索更多可能

    共赴 Google Cloud 2022 中國(guó)出海數(shù)字峰會(huì),探索更多可能

    后疫情時(shí)代,得益于多年構(gòu)筑的供應(yīng)鏈、日趨成熟的數(shù)字化技術(shù),中國(guó)企業(yè)出海發(fā)展,有力助推了經(jīng)濟(jì)增速“轉(zhuǎn)正”。在國(guó)際國(guó)內(nèi)雙循環(huán)發(fā)展格局下,出海賽道未來(lái)將會(huì)有更多看點(diǎn)與掘金點(diǎn),賽道內(nèi)企業(yè)如何構(gòu)建綜合競(jìng)爭(zhēng)力?肩負(fù)成本與效率兩座大山,出海企業(yè)如何巧妙借力

    2024年02月05日
    瀏覽(21)
  • 如何快速下載Google play里軟件APK

    如何快速下載Google play里軟件APK

    ? 可能有些小伙伴為了某個(gè)APP而去安裝Google應(yīng)用商店下載,我之前也折騰過(guò),但實(shí)在是太麻煩了。如果只是為了下載某個(gè)APP,不建議這樣折騰。 下面分享一個(gè)快速方法 先找到你要下載的APP 然后復(fù)制你Google Play的鏈接 ?接著打開(kāi)這個(gè)下載APK的網(wǎng)站 https://apps.evozi.com/apk-downloade

    2024年02月05日
    瀏覽(33)
  • 華為手機(jī)配置google play的幾種方式

    介紹幾種常見(jiàn)的方式 1、華為自帶的谷歌商店,通過(guò)手機(jī)設(shè)置開(kāi)啟 。具體步驟如下: 1、進(jìn)入華為手機(jī)設(shè)置界面,找到Googel, 2、點(diǎn)擊Google,進(jìn)入設(shè)置界面,點(diǎn)擊解除即打開(kāi)Google Play服務(wù), Google Play 前名為Android Market,是一個(gè)由Google為Android設(shè)備開(kāi)發(fā)的在線 華為自帶的谷歌商店 2、在第三

    2024年02月11日
    瀏覽(24)
  • 安卓集成Google Play支付(谷歌支付)最新版本

    安卓集成Google Play支付(谷歌支付)最新版本

    Google Play 支付文檔:https://developer.android.com/google/play/billing/integrate Google Play 支付庫(kù)已經(jīng)升級(jí)到5.0了,相對(duì)之前的版本有不少的變化,現(xiàn)在記錄下! 接入Google Play 流程還是和之前一樣: 1.去Google console 申請(qǐng)開(kāi)發(fā)者賬號(hào)? https://play.google.com/console/?? 2.創(chuàng)建項(xiàng)目,上傳APK 3.去Googl

    2024年02月09日
    瀏覽(26)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包