Slide 1

Slide 1 text

DroidKaigi 2023, Electric Eel - 2023/09/14 14:20-15:00 Ryo Yamazaki & Yuya Sato iOSとAndroidで 定期購入の意図しない解約を防ぐ 1

Slide 2

Slide 2 text

Caution アプリ内課金はユーザーにとってセンシティブであり、 販売者の利益に直結する重要なシステムです。 サービスのリリース前には必ずSandboxで検証してください。 2

Slide 3

Slide 3 text

● Ryo Yamazaki / Android Engineer ○ GitHub: @ymnder ● Yuya Sato / iOS Engineer ○ GitHub: @yutosa261 Speakers 3

Slide 4

Slide 4 text

● 日本経済新聞社ではiOS / Androidエンジニアの募集中です!!! ○ https://hack.nikkei.com/jobs/ios/ ○ https://hack.nikkei.com/jobs/android/ We are Hiring :)) 4

Slide 5

Slide 5 text

● 非自発的解約を防ぐための機能について解説する ○ Grace PeriodとAccount Hold ● 非自発的解約を防ぐための実装について説明する ○ Google Play ストア(以降Play Store)とApp Storeの仕様差 ○ アプリとサーバーのAPIについて 本セッションのスコープ 5

Slide 6

Slide 6 text

● Android ○ Google Play Billing Library 6.0の詳細な実装・解説 ○ Amazonアプリストアのアプリ内課金について ● iOS ○ StoreKit 2の詳細な実装・解説 本セッションで扱わないもの 6

Slide 7

Slide 7 text

定期購入の解約とは 7

Slide 8

Slide 8 text

定期購入の解約の種類と原因(Play Store) ストアでの解約 アカウントの削除 サポートへの解約の申し込み Google Play Developer API での解約 注文管理による定期購入の解約 自発的解約 支払いに問題があった(残高の不足やアカ ウントの削除など) 価格変更の確認が行われなかった 非自発的解約 8

Slide 9

Slide 9 text

● Grace Period ○ 支払いが失敗した場合に購読を解約せず、購読状態を一定期間保ち続ける機能 ■ ユーザーが定期購入失敗時に自動解約されなくなる ■ ユーザーに支払い情報の修正を促すことができる ● Account Hold ○ 支払いの失敗が解決されなかった場合にアカウントを一時停止する機能 ■ ユーザーは有料機能は使えないが、支払いを修正すればすぐ再開できる ■ サービスの利用期間のカウントがリセットされない ○ iOSではBilling RetryのうちGrace Periodを除いた期間に相当 ■ 本セッションでは便宜的にAccount Holdと呼ぶ 非自発的解約を減らすための機能 9

Slide 10

Slide 10 text

有効な定期購入 Grace Period 有料機能を維持 Account Hold 有料機能を制限 解約 Grace PeriodとAccount Hold 有効な定期購入 Grace Period 有料機能を維持 Billing retry state 有料機能を制限 解約 Play Store App Store 10 支払いに問題が発生 💥 支払いに問題が発生 💥

Slide 11

Slide 11 text

Grace PeriodとAccount Holdの効果は高い > Developers on Google Play who use a grace period see a 57% higher recovery rate from renewal declines. > ref: https://medium.com/googleplaydev/how-to-win-back-subscribers-who-cancel-9960731adeb > For example, account hold has helped developers achieve 8% lower involuntary churn and 35% higher payment decline recovery rate compared to developers without account hold. > ref: https://android-developers.googleblog.com/2020/06/new-features-to-acquire-and-retain-subscri bers.html note: 引用文のBoldは発表者が追加した 11

Slide 12

Slide 12 text

Grace PeriodとAccount Holdの期間 Play Store App Store Grace Period 3, 7, 14, 30日間 3, 6, 16, 28 日間 Account Hold 30日間 60日間 12

Slide 13

Slide 13 text

Grace PeriodとAccount Holdの期間 Play Store App Store Grace Period 3, 7, 14, 30日間 3, 6, 16, 28 日間 Account Hold 30日間 60日間 Play StoreはデフォルトON、App StoreはデフォルトOFF 13

Slide 14

Slide 14 text

Grace PeriodとAccount Holdの期間 Play Store App Store Grace Period 3, 7, 14, 30日間 3, 6, 16, 28 日間 Account Hold 30日間 60日間 設定がなく常に有効である 14

Slide 15

Slide 15 text

● Play StoreとApp Storeでのそれぞれの実行イメージを紹介する ● 説明に使う例の状況設定 ○ 請求対象期間は1か月 ○ Grace Period ■ Play Store: 14日 ■ App Store: 16日 Grace PeriodとAccount Holdの期間例 15

Slide 16

Slide 16 text

16 正常に購入が更新されているケース 有効な定期購入 4/1 5/1 有効な定期購入 6/1 有効な定期購入 7/1 有効な定期購入 請求対象期間は1か月なため、毎月1日から定 期購入が開始されている

Slide 17

Slide 17 text

Grace Periodがoffの場合(Play Store) 有効な定期購入 Account Hold (30 days) 解約 4/1 5/1 有効な定期購入 5/20 6/20 17 支払いに問題が発生 💥 支払いを修正

Slide 18

Slide 18 text

Grace Periodがoffの場合(Play Store) 有効な定期購入 Account Hold (30 days) 解約 4/1 5/1 有効な定期購入 5/20 6/20 新しい定期購入期間となる 18

Slide 19

Slide 19 text

Grace Periodがonの場合(Play Store) 有効な定期購入 Grace Period Account Hold (30 days) 解約 4/1 5/1 5/14 有効な定期購入 5/10 6/1 19 支払いを修正 支払いに問題が発生 💥

Slide 20

Slide 20 text

Grace Periodがonの場合(Play Store) 有効な定期購入 Grace Period Account Hold (30 days) 解約 4/1 5/1 5/14 有効な定期購入 5/10 6/1 Grace Periodの期間が算入 定期購入期間に変更はない 20

Slide 21

Slide 21 text

Grace Periodがoffの場合(App Store) 有効な定期購入 Billing retry state (60days) 解約 4/1 5/1 有効な定期購入 5/20 6/20 21 支払いを修正 支払いに問題が発生 💥

Slide 22

Slide 22 text

Grace Periodがonの場合(App Store) 有効な定期購入 解約 4/1 5/1 5/16 有効な定期購入 5/10 6/1 Grace Period (16 days) Billing retry state (60 days) 22 支払いを修正 支払いに問題が発生 💥

Slide 23

Slide 23 text

いざ実装 23

Slide 24

Slide 24 text

の前に 24

Slide 25

Slide 25 text

検証環境の整備 25

Slide 26

Slide 26 text

1. ライセンステスターの登録をする a. Play Consoleで[設定] > [ライセンス テスト] を選択 b. メーリング リストからライセンステスターを選択する c. メールアドレスを追加する 2. ライセンステスターアカウントでPlay ストアアプリにログインする a. 注意:Play ストアアプリに複数のアカウントでログインしている場合、 最初にログインしたアカウントがテスト対象となる テストができる環境を整える(Play Store) 26

Slide 27

Slide 27 text

3. Play Consoleの基本プランから猶予期間の設定を有効にする テストができる環境を整える(Play Store) App Storeと異なり、Sandbox 環境でのみ提供するオプショ ンは存在しない 27

Slide 28

Slide 28 text

4. 定期購入後、Play ストアアプリでメインのお支払い方法を「テストカード、常 に不承認」に更新する テストができる環境を整える(Play Store) 28

Slide 29

Slide 29 text

テストサイクル(Play Store) 有効な定期購入 Grace Period Account Hold 解約 00:00 00:05 00:10 00:01 常に不承認を選択 00:20 29

Slide 30

Slide 30 text

テストサイクル(Play Store) 有効な定期購入 Grace Period Account Hold 解約 00:00 00:05 00:10 00:01 常に不承認を選択 00:20 30 Account Holdまで検証するには 20分かかる

Slide 31

Slide 31 text

テストサイクル(Play Store) 有効な定期購入 Grace Period Account Hold 解約 00:00 00:05 00:10 00:01 常に不承認を選択 00:20 Play ストア アプリから通 知とメールがくる Play ストア アプリから通 知とメールがくる 31

Slide 32

Slide 32 text

● Sandbox環境でのテスト方法を紹介 ● StoreKitTestというフレームワークを用いることで、App Store Connet を介さずテストすることも可能 ○ WWDC2020: Introducing StoreKit Testing in Xcode テストができる環境を整える(App Store) 32

Slide 33

Slide 33 text

1. Sandboxテスターアカウントを作成する a. App Store Connectで [ユーザーとアクセ ス] > [Sandboxテスター] を選択 b. メールアドレスなど、必要情報を入力しアカウ ント作成 テストができる環境を整える(App Store) 33

Slide 34

Slide 34 text

2. デバイスでログインする a. iPhoneの [設定] > [App Store] を選択 b. メールアドレス、パスワードを入力しログイン テストができる環境を整える(App Store) 34

Slide 35

Slide 35 text

3. 猶予期間の設定 a. App Store Connectで [請求 の猶予期間] に移動 b. 猶予期間を何日にするか設定 c. サーバー環境を選択することが でき、Sandbox環境のみで設 定可能 テストができる環境を整える(App Store) 35

Slide 36

Slide 36 text

4. 定期購入後、Sandboxアカウント設定の [購入と更新を許可する] をオフにする テストができる環境を整える(App Store) 36

Slide 37

Slide 37 text

テストのサイクル(App Store) 有効な定期購入 Grace Period Billing retry state 解約 00:00 00:05 00:10 00:01 [購入と更新を許可する] をオフにする 00:15 ● ユーザー単位で更新間隔を変更できる ● 更新間隔に応じてGrace PeriodとBilling retry stateの時間も変わる ● テスト中に契約状態が変更されたことを知らせ るメールはこない 37

Slide 38

Slide 38 text

購入ステータスを確認する 38

Slide 39

Slide 39 text

● 購入ステータスを確認するAPIが用意されている ○ deprecated: purchases.subscriptions.get ■ 2022年5月に新機能がリリースされたがサポートがない ○ purchases.subscriptionsv2.get ■ すべての機能が移行されているわけではないため、2022 年 5 月の定期購入変更ガイドを参照すること Google Play Developer API (Play Store) 39

Slide 40

Slide 40 text

Google Play Developer API (Play Store) 40

Slide 41

Slide 41 text

Google Play Developer API (Play Store) 41 V1は購入ステータスはレスポンスの値 を組み合わせて判断していた V2は判定が簡潔になった

Slide 42

Slide 42 text

deprecated: purchases.subscriptions.get ● expiryTimeMillis ○ Grace Periodは有効な定期購入 として扱われ延長される ● paymentState ○ 0は支払い保留中を示す ● autoRenewing ○ 自動更新されるかどうか 42

Slide 43

Slide 43 text

● subscriptionStateをチェックする ○ SUBSCRIPTION_STATE_ACTIVE ○ SUBSCRIPTION_STATE_IN_GRACE_PERIOD ○ SUBSCRIPTION_STATE_ON_HOLD ○ SUBSCRIPTION_STATE_CANCELED ○ SUBSCRIPTION_STATE_EXPIRED ○ … purchases.subscriptionsv2.get 複雑な判定を行わず に済むようになった 43

Slide 44

Slide 44 text

● 購入ステータスを確認するため次のAPIを呼ぶ ○ deprecated: verifyReceipt ○ Get All Subscription Statuses ● StoreKit 2、Get All Subscription Statuses、App Store Server Notifications V2の登場により従来のverifyReceiptと状況が変わった ○ クライアントでレシートが検証されているため、購入ステータスに集中 できるようになった App Store Server API (App Store) 44

Slide 45

Slide 45 text

verifyReceipt (App Store) 45

Slide 46

Slide 46 text

verifyReceipt (App Store) レシートを検証する APIである 購入ステータスはレ スポンスの値を組み 合わせて判断する 46

Slide 47

Slide 47 text

● is_in_billing_retry_period ○ 1はbilling retryの状態を示す ● expiration_intent ○ 2はBilling errorで支払い情報が有効でな い場合を示す ● grace_period_expires_date_ms > 現在時刻 ○ expires_date_msはGrace Periodで延長 されない verifyReceipt (App Store) 47

Slide 48

Slide 48 text

Get All Subscription Statuses 48

Slide 49

Slide 49 text

Get All Subscription Statuses StoreKit 2ではレシートの 検証をクライアントででき る 49

Slide 50

Slide 50 text

Get All Subscription Statuses 購入ステータスを問い合 わせるAPIとなっている 50

Slide 51

Slide 51 text

● lastTransactionsItem.statusの値をチェックする ○ 1: 定期購入は有効である ○ 2: 定期購入は有効期限切れである ○ 3: 定期購入はAccount Holdである ○ 4: 定期購入はGrace Periodである ○ … Get All Subscription Statuses 複雑な判定を行わず に済むようになった 51

Slide 52

Slide 52 text

Server Notification 52

Slide 53

Slide 53 text

● subscriptionNotification.notificationType ○ (5)SUBSCRIPTION_ON_HOLD ○ (6)SUBSCRIPTION_IN_GRACE_PERIOD ● 通知で取得できる情報は一部である ○ 必要な場合Google Play Developer APIを呼ぶ Real-time developer notifications 53

Slide 54

Slide 54 text

● アプリ > 収益化 > 収益化のセットアップを開きGoogle Play 請求サービ スの入力欄にCloud Pub/Subを入力する Real-time developer notifications 54

Slide 55

Slide 55 text

● subTypeを見ることで現在の購入ステータスを把握できる ○ BILLING_RETRY ○ GRACE_PERIOD ● アプリ > アプリ情報 > App Storeサーバ通知から設定する ○ プロダクション環境とSandbox環境でURLを分けられる ○ V1とV2のバージョンを切り替えて検証できる App Store Server Notifications V2 55

Slide 56

Slide 56 text

App Store Server Notifications V2 56

Slide 57

Slide 57 text

アプリのUI/UX 57

Slide 58

Slide 58 text

● Grace Periodを有効にした場合、ストアが自動的にサポートしてくれる機 能がある ● Play Store ○ Play ストアアプリから通知とメールが送られてくる ● App Store ○ App Storeからメールが送られてくる アプリの外でのコミュニケーション 58

Slide 59

Slide 59 text

● Play Store ○ showInAppMessages ● App Store ○ StoreKit2 Message API アプリの中で支払いを修正できる機能 59

Slide 60

Slide 60 text

● Play Store ○ showInAppMessages ● App Store ○ StoreKit2 Message API アプリの中で支払いを修正できる機能 60 通常はPlay Store / App Storeの アプリを開いて修正を行う必要が ある

Slide 61

Slide 61 text

● Google Play Billing Library 4.1から追加された ○ Grace PeriodとAccount Hold中に1日1回だけお支払い方法を修 正するUIを表示する ○ アプリ内から直接支払いの問題を修正できる ● 注意 ○ 1日1回表示したら再表示されない ○ テストカードでも回数制限がかかる ○ 判定と表示の機能はまとめて提供されている showInAppMessagesを実装する 61

Slide 62

Slide 62 text

実装コード val params = InAppMessageParams.newBuilder() .addInAppMessageCategoryToShow(InAppMessageParams. InAppMessageCategoryId .TRANSACTIONAL) .build() billingClient.showInAppMessages(activity , params) { inAppMessageResult -> when(inAppMessageResult. responseCode) { InAppMessageResult. InAppMessageResponseCode .NO_ACTION_NEEDED -> { // The flow has finished and there is no action needed from developers. } InAppMessageResult. InAppMessageResponseCode .SUBSCRIPTION_STATUS_UPDATED -> { // The subscription status changed. For example, a subscription // has been recovered from a suspend state. Developers should // expect the purchase token to be returned with this response // code and use the purchase token with the Google Play // Developer API. } } } 62

Slide 63

Slide 63 text

63

Slide 64

Slide 64 text

● WWDC2022で発表された機能 ● 支払いの問題が発生した際に、アプリ内で支払い方法 の更新を促すシートが表示 ● アプリ内で支払いに関する問題を解決できる ● アプリ側の対応は不要で、iOS 16.4 / iPadOS 16.4 以 降のユーザーに表示される StoreKit2 Message APIについて 64

Slide 65

Slide 65 text

https://developer.apple.com/videos/play/wwdc2022/10007/ Messageを表示するまでの流れ 65

Slide 66

Slide 66 text

● 基本的にはアプリがフォアグラウンドに入ったタイミング ● リスナーを設定することで表示タイミングを遅延することが可能 ○ UIKitの場合は、display(in:) を使用 ○ SwiftUIの場合は、DisplayMessageAction を使用 ● 支払いに関する問題が解消された場合、Messageは表示されない Messageが表示されるタイミング 66

Slide 67

Slide 67 text

Messageが表示される頻度 Billing retry interval Message frequency 1~3日目 24時間毎 4~16日目 72時間毎 17~30日目 96時間毎 31~60日目 120時間毎 67 メッセージの表示頻度はだんだん少なくなる

Slide 68

Slide 68 text

独自に訴求するUI例 68

Slide 69

Slide 69 text

● アプリの中で修正を行う機能は便利 ○ しかし訴求できる内容に制限がある ● 独自にダイアログやバナーを表示する ○ ただ購入ステータスの判定はサー バーと連携した実装が必要 サーバーから契約情報を返しアプリで表示する 69

Slide 70

Slide 70 text

● 独自UIの場合、アプリの中で支払いを修正できないた めストアへ誘導する必要がある ● Play Store ○ https://play.google.com/store/account/su bscriptions?sku=your-sub-product-id&pac kage=your-app-package ● App Store ○ https://apps.apple.com/account/billing サーバーから契約情報を返しアプリで表示する 70

Slide 71

Slide 71 text

まとめ 「どこまで、何を、実装するか」 71

Slide 72

Slide 72 text

● Step1 ○ 何もしない。ストアの通知・メールに任せる ○ アプリ内では何も表示されない。ユーザーに気が付かれにくい。 ● Step2 ○ 公式ライブラリのアプリ内での支払い修正機能を実装する ○ シンプルに訴求できる。表示内容や頻度に制限がある。 ● Step3 ○ アプリ内で独自のダイアログやバナーを表示する ○ 自由に訴求できる。実装コストがかかる。アプリ外での支払い修正となる。 Grace Period / Account HoldのUI/UX対応 72 前提:API側でのハンドリングを行う

Slide 73

Slide 73 text

非自発的解約対応やっていこう💪 73