应用内购买实现指南

应用内购买实现指南

你好。我是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]按钮进行注册。

Play Console の Monetize メニューにあるアプリ内商品・定期購入セクション

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

Play Console の定期購入価格入力フォーム

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

全地域一括価格設定ダイアログ(Play Console)

测试账号注册

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

Play Console のライセンステスト設定画面(テストアカウント登録用)

使用邮件列表中注册的用户执行应用购买流程时,付款信息会显示为"测试卡",可以在不产生实际费用的情况下进行测试。

实现工作

应用端的实现需要以下工作:

  • 引入库
  • 连接商店并获取商品信息
  • 实现商品展示界面
  • 发起购买请求
  • 购买完成时的处理

以下将分别详细说明。

引入库

引入库需要以下操作:

  • 修改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分钟。这似乎是为了订阅续费行为测试而显示极短固定期限的规格设计。(我以为自己哪里设置错了,回顾了一段时间的设置。)

总结

起初看起来很难,但一旦理解整体框架,应用内购买本身可以通过官方文档等公开信息出人意料地轻松实现。但是,对于订阅型商品,存在一些不实际尝试就无法发现的坑,希望我踩过的那些坑能对今后实现该功能的开发者有所参考。