googlepay-谷歌支付-一次性消耗inapp
1.参考:https://zhuanlan.zhihu.com/p/506636775
2.官方文档:https://developer.android.google.cn/google/play/billing/integrate
3.Pub/sub api:https://cloud.google.com/pubsub/docs/apis
4.Pub/sub参考 https://blog.csdn.net/qq_35754073/article/details/110634585
5.配置Pub/sub:https://blog.csdn.net/wujiesunlirong/article/details/122173321
6.https://blog.csdn.net/yangbin0513/article/details/123591922?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166363667516800192227449%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=166363667516800192227449&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-123591922-null-null.142^v47^pc_rank_34_default_23,201^v3^control_1&utm_term=Googlepay&spm=1018.2226.3001.4187
本文章仅作学习参考
接入过程
名词解释
首先解释三对概念来帮助理解 Google 支付的逻辑。
一次性商品 vs 订阅型商品
一次性商品是通过单次购买获得的商品。一次性商品又分为消耗型商品和非消耗型商品,消耗型商品顾名思义是可以被消耗的商品,例如 App 提供的金币或虚拟货币,用户可以重复购买。非消耗型商品是通过一次购买可以得到的永久权益,例如付费升级的内容。
订阅型商品是指会定期发生购买行为的商品,如会员服务等,订阅会自动续期,直至取消。
本文的讨论仅限于消耗型商品,不涉及其他类型的商品。
Consume vs Acknowledge
Consume 和 Acknowledge 均有完成支付后进行确认的含义,但两者并不完全相同。
Acknowledge 是实际意义上的确认操作,进行了 Acknowledge 会使得订单不被退款,Acknowledge 可由客户端 API acknowledge() 执行,也可由 Google 服务端 API acknowledge() 完成。 Google 会对已支付但未确认的订单在三天后进行自动退款处理。
Consume 是专门针对消耗型商品的操作,Consume 不仅包含确认的含义,并使得商品可以重复购买。Consume 可以看成是包含了 Acknowledge 操作,Consume 仅可由客户端 API consumeAsync() 完成,不能通过服务端 API 进行。
业务服务端 vs Google 服务端
本文中将多次提到关于服务端的操作,分别使用业务服务端和 Google 服务端来进行区分,避免混淆。业务服务端指的是 App 业务逻辑的服务端。Google 服务端在本文特指 Google 应用内支付的服务端,由 Google 提供。
交易流程概览
从业务的角度出发,一次交易流程可以大体用下图表示:
https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13211245181/0261/e4ee/a827/33c0931dec2ed68844aff39b367e5638.png
但在实际交易的过程中,充满了不确定的因素,如用户的网络环境不稳定、误操作等等。由于交易业务的敏感性,既不能让用户多付钱,也不能少付、错付。因此需要全面考虑各种可能出现的情况,对可能导致订单和支付状态异常的因素做充分的考量和处置。站在技术的视角,完整的交易流程如下:
https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13250165151/35cd/03e6/3bdb/da476a0ebc7220fb7682441e1ed77e61.png
交易重点流程详解
创建订单
创建订单这里指的是创建业务服务端的订单。订单创建完成后,会将业务订单 ID 和 对应的 Google 商品 ID 传递到后续的步骤中。业务服务端会管理维护自身的商品以及订单,这与 Google 的商品和订单并不相同,且需要建立和维护它们之间的关联关系。
建立连接
在进行 Google 支付前,需要与 Google Play 建立连接,连接的桥梁是 BillingClient。BillingClient 是 Google Play Billing Library 提供的重要工具,支付相关的操作都与之有关。倘若由于网络等原因连接断开,必须要重连才能继续后续操作。
查询商品
对于一个商品,需要提前在 Google 后台完成创建。查询商品是查询 Google 后台配置的商品信息,确认商品的信息无误,取得商品详情,为发起支付提供必要的数据。
发起支付
发起支付是通过 BillingClient 的 API launchBillingFlow()
调用 Google 的支付界面,此时对应的 Google 支付的服务端订单被创建。业务服务端订单和 Google 服务端订单需关联起来,常规的方式是客户端在后续发起订单校验时,告知业务服务端对应 Google 订单 ID,现实中客户端可能由于某些原因没有收到支付结果,在由 Google 开发者实时通知回调业务服务端的场景下,同样需要足够的信息来关联。在这里将业务服务端 ID 通过混淆的方式传入,目的是使得业务服务端凭借混淆 ID 将 Google 订单和业务服务端订单关联起来,完成后续的确认和履约。
订单确认
收到支付成功的回调后,客户端主动调用 BillingClient 提供的 consumeAsync()
方法,确保订单已确认不被退款,并且可再次被购买。对于未确认的订单,Google 会在三天后自动退款。此外,需要主动向业务服务端发起订单的检查。
订单履约
按照流程,客户端发起订单的校验,服务端确认了订单的有效无误后需要进行履约,业务上通常表现为发放金币、余额增加等。服务端必须保证此操作的幂等性,履约一次且仅且履约一次,这是订单补偿机制的前提。
订单补偿
一个支付的流程可能被打断,需要重点关注的是是用户支付完成但是没有收到权益的情况,此过程容易造成客诉,需要通过 订单补偿 作为兜底。
订单补偿分为两个场景,一个是业务服务端通过 Google 提供的实时开发者通知,接收到 Pub/Sub 消息,得知订单支付状态发生变化,此时检查订单的状态,若是未履约态,会进行履约保障用户的权益,并且调用服务端 acknowledge API 确认订单,确保不出现自动退款而造成资损的情况。
另一个场景是由客户端主动发起订单补偿机制,在合适的时机,获取已支付但未确认的订单进行后续的 Consume 和履约过程。主动补偿可以在启动 App、进入充值购买页面等多个时机进行,可根据业务场景自行决定。同时也会将第一种场景逻辑完善,第一个场景下服务端 Acknowledge 完成,权益得到发放,但是该商品却无法再次购买,此时由客户端完成 Consume,使得商品可重复购买,形成整体逻辑的闭环。
技术实现
https://p5.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13211034378/455e/3689/d4d5/3a169de7485c7e2bdced5c2631fe708e.png
交易流程的一个重要的特点是事件驱动。在技术上表现为,需要在大量的回调方法中决定下一步的操作。按照面向过程的方式,代码会层层回调嵌套。这样的代码逻辑不够清晰、难以理解,无数的回调会导致异常处理复杂,排查问题也比较困难。为了解决这个问题,可以将整个交易流程看成是一个 pipeline,将每一步抽象成子模块。
Google Play Billing Library 直接提供的回调是无法组成 pipeline 的,需要进行一层转换。Kotlin Coroutine 提供的 CallbackFlow 是一个 Flow 构建器,可以将基于回调的 API 转换成 Flow。
逻辑封装
https://p6.music.126.net/obj/wonDlsKUwrLClGjCm8Kx/13200827987/2b92/0b0f/44b7/4fc22901ce8c123430a8530d9261ebcb.png
整个交易的流程拆分层若干的子模块,使得子模块是可衔接和可复用的。每个子模块有特定的输入和输出,上一个子模块的输出是下一个子模块的输入,例如查询商品子模块的输出是商品详情信息,而商品详情作为发起支付的输入,展示在输入面板上。并且,每个子模块需保证自身逻辑内聚,仅关心当前流程需要完成的工作,不关心接下来的流程。
我们将拆分后的独立的子模块包装成 CallbackFlow。操作成功后通过 offer 方法在协程外发送数据到后续的 Flow 中,出现错误可以终止 close() 或取消 cancel() 当前 Flow。拆分的颗粒度由操作和回调共同决定,原则是模块功能单一且内聚。此外,在整个操作过程中,都需要用到 BillingClient 的实例,输入时将传递该实例。
fun queryPurchasesFlow(client: BillingClient?): Flow<List<Purchase>> =
callbackFlow {
client?.queryPurchasesAsync(
BillingClient.SkuType.INAPP
) { p0, p1 ->
when (p0.responseCode) {
BillingClient.BillingResponseCode.OK -> {
// emit the value to the flow
offer(p1)
}
else -> {
// close the flow
close()
}
}
}
awaitClose {
// log & release resources
}
}
pipeline 组建
在完成单个操作的封装后,需要将这些 Flow 组装起来。CallbackFlow 提供了操作符用于串联和转换 Flow,其中 flatMapConcat 是对上游的元素进行转换、拍扁并返回新的 Flow。flatMapConcat 是适用于当前场景的。一个串联的例子如下,通过建立 Google 连接后,获取商品的信息,校验无误后调用支付面板。
startConnectionFlow(client).flatMapConcat {
querySkuDetailFlow(client, request)
}.flatMapConcat {
launchBillingFlow(activity, client, it, request)
}.catch { e ->
// catch exception
e.printStackTrace()
}.collect {
processNext(it)
}
此外,还可以灵活的编配 Flow,以实现不同的业务逻辑。订单补偿流程和正常的支付流程不完全相同,需要重新编排对应的 Flow,相同的流程可以与支付逻辑复用,无需重新开发。