你好。我是zm soft,去年(2023年底)注册为开发者并开始发布应用。我们还计划发布一款面向开发者的应用,通过开发者之间的合作一起通过封闭测试,欢迎有兴趣的朋友查看。
大家有没有实现应用内购买呢?对于应用开发者来说,让用户满意并获得相应报酬是最好的事,也是开发努力得到回报的时刻。这次我想写一篇关于如何实现这一点的文章。
什么是应用内购买
如大家所知,应用内购买分为以下两类:
- 消耗型商品
- 订阅型商品
两者都通过Google Play Billing Library访问Play Store中预先设置的商品,并将获得收入的一部分作为手续费支付给Google。从实现角度来看两者类似,但订阅型商品有其独特的注意事项。我将结合个人实际经验来说明注意事项。
Play Store设置
商品注册
首先,请注册商品,因为没有注册就无法显示。价格、名称等以及是否实际使用该商品都可以后续更改,所以填写临时数据也没关系。但是,只有ID无法更改,因此如果您考虑将测试用商品修改后直接用于生产环境,最好使用[product_1,2,3...]或[sub_1,2,3...]这样可重复使用的ID进行注册。注册可在[Monetize]中进行,其中[In-app products]和[Subscriptions]分别对应消耗型商品和订阅型商品。每个商品可通过[Create xx]按钮进行注册。

基本上按照输入界面的指引进行设置即可,但让我困惑的是订阅型商品的价格设置。起初我不知道如何一次性为所有地区设置价格。我以为必须逐个地区分别输入,但实际上可以按[Set prices] - [Country / region] - [Set price]的顺序操作。

点击按钮后会弹出以下对话框,可以一次性进行设置。

测试账号注册
接下来注册测试账号。如果没有注册就进行测试购买,将会产生实际费用,请务必注意。测试账号的注册通过注册邮件列表来完成。可以在登录Play Console后的第一个界面(开发者界面)通过[Settings] - [License testing]进行设置。

使用邮件列表中注册的用户执行应用购买流程时,付款信息会显示为"测试卡",可以在不产生实际费用的情况下进行测试。
实现工作
应用端的实现需要以下工作:
- 引入库
- 连接商店并获取商品信息
- 实现商品展示界面
- 发起购买请求
- 购买完成时的处理
以下将分别详细说明。
引入库
引入库需要以下操作:
- 修改build.gradle
- 修改android.manifest
build.gradle修改示例:
dependencies {
implementation "com.android.billingclient:6.0.0"
}
manifest修改示例:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="com.android.vending.BILLING" />
<application
关于版本等信息,请参考官方文档。
连接商店并获取商品信息
初始化BillingClient并执行startConnection后,即可与商店进行通信。可通过queryProductDetailsAsync获取各商品信息。详情请参考官方文档。我的情况是,不知为何批量获取无法正常工作,因此先获取商品列表,再通过queryProductDetailAsync逐个获取商品详情。获取到的商品(SKU)是消耗型还是订阅型,可以通过其类型inapp或subs来区分。
实现商品展示界面
获取商品信息后,以列表形式展示供用户选择购买。在处理订阅型商品时,需要符合Subscription Policy,我的情况是因以下两个原因导致应用更新连续被拒:
- 价格和条款的本地化不完整
- 优惠条款说明不完整
首先是价格和条款的本地化不完整:原因是期限的显示方式(例:$10/month)。问题在于,在不支持翻译的语言的地区设置下运行时,"month"部分未被翻译。通过BillingClient获取信息时,价格金额以包含单位信息的formattedPrice字符串形式获取,无需在应用中进行翻译。而期限信息以_P1M_这样的ISO 8601格式通知,需要在应用中进行翻译。通过为每种语言添加如下转换处理来解决:
fun formatBillingPeriod(billingPeriod: String, languageCode: String): String {
return when(languageCode) {
"en" -> {
when (billingPeriod) {
"P1W" -> "weekly"
"P1M" -> "monthly"
"P3M" -> "every 3 months"
"P6M" -> "every 6 months"
"P1Y" -> "annually"
else -> "unknown"
}
}
"ja" -> {
when (billingPeriod) {
"P1W" -> "週間"
"P1M" -> "月額"
"P3M" -> "3ヶ月ごと"
"P6M" -> "6ヶ月ごと"
"P1Y" -> "年額"
else -> "不明"
}
}
"fr" -> {
when (billingPeriod) {
"P1W" -> "hebdomadaire"
"P1M" -> "mensuel"
"P3M" -> "tous les 3 mois"
"P6M" -> "tous les 6 mois"
"P1Y" -> "annuel"
else -> "inconnu"
}
}
"es" -> {
when (billingPeriod) {
"P1W" -> "semanal"
"P1M" -> "mensual"
"P3M" -> "cada 3 meses"
"P6M" -> "cada 6 meses"
"P1Y" -> "anual"
else -> "desconocido"
}
}
"de" -> {
when (billingPeriod) {
"P1W" -> "wöchentlich"
"P1M" -> "monatlich"
"P3M" -> "alle 3 Monate"
"P6M" -> "alle 6 Monate"
"P1Y" -> "jährlich"
else -> "unbekannt"
}
}
else -> "unknown"
}
}
其次是优惠条款说明不完整:

我理解了问题所在,但不知道具体应该写什么样的措辞来解决。以下是我收到拒绝通知的邮件原文的摘录:
Issue found: Violation of Subscriptions policy
Your app does not comply with the Subscriptions policy.
- Your offer does not clearly and accurately describe the terms of your trial offer or introductory pricing, including when a free trial will convert to a paid subscription, how much the paid subscription will cost, and that a user can cancel if they do not want to convert to a paid subscription.
作为解决方案,我参考了其他应用的措辞。因为这大部分看起来都是固定套语,所以我认为参照已有成功案例比从零开始思考更好。
发起购买请求
用户在界面上选好商品后,终于到了商品购买处理环节。对获取到的商品执行购买流程,即可调出实际的购买界面。
测试
使用最初准备的测试账号登录后进行测试,可以在不产生实际费用的情况下完成测试。这里有以下两个注意点:
- 购买流程需在已在商店注册的应用中执行
- 订阅型商品的期限会被自定义设置
首先,关于购买流程的执行,如果应用是debug构建版本,购买界面会显示错误,无法执行流程。
其次,关于订阅型商品,进入实际购买界面后会显示测试卡的付款信息。此时购买界面上会显示续费期限,但显示的期限与商品实际设置的期限不同。我的情况是显示了5分钟。这似乎是为了订阅续费行为测试而显示极短固定期限的规格设计。(我以为自己哪里设置错了,回顾了一段时间的设置。)
总结
起初看起来很难,但一旦理解整体框架,应用内购买本身可以通过官方文档等公开信息出人意料地轻松实现。但是,对于订阅型商品,存在一些不实际尝试就无法发现的坑,希望我踩过的那些坑能对今后实现该功能的开发者有所参考。