Upgrade to Pro — share decks privately, control downloads, hide ads and more …

アプリ内課金と自社の認証認可基盤を連携する複雑な課金ネットワークを構築する

Go Takagi
August 23, 2024

 アプリ内課金と自社の認証認可基盤を連携する複雑な課金ネットワークを構築する

iOSDC2024 Day 1 Track D 13:00〜
https://fortee.jp/iosdc-japan-2024/proposal/4275ed9f-73c4-4012-86b1-64bb7c01e38b

iOS 15から導入されたStoreKit2はアプリ内課金の実装を大幅に簡略化できます。オファー機能や複数のプラン提供を行いやすくするために導入・移行を検討している方は多いのではないでしょうか。
ただし課金機能がアプリで完結しない場合、StoreKit2と様々な基盤を連携する必要があります。たとえば有料機能の認証・認可API、課金ユーザー分析、iOSアプリ内課金以外の決済手段が挙げられます。

そのためのバックエンド構築にはApp Store Server APIやApp Store Server NotificationsというApple提供の仕組みが使えます。近年バージョン2といえる抜本的な改善がなされ、アプリ内課金の状態を自社サービスから詳細に分析できるようになりました。

本セッションでは、日本経済新聞社の電子版アプリで提供するサブスクリプションをStoreKit2ベースでフルリプレースして得た知見を紹介します。各種ツールの仕様・アプリ内課金全体の仕組みを理解、クライアントからバックエンドまでどのような手順で安全にアップデートしていくか注意点を確認しつつ解説します。クライアントだけでは把握しづらかった課金状態をAPIで把握したTipsも紹介していきます。

このトークを通して、参加者は自社のシステムにアプリ内課金を連携するための実践的な仕様を理解することができるでしょう。

Go Takagi

August 23, 2024
Tweet

More Decks by Go Takagi

Other Decks in Technology

Transcript

  1. Me ( Go Takagi ) ‣ID • bento.me/shimastripe ‣Work •

    日 本経済新聞社 ‣ 電 子 版 ・ 紙 面 ビューアー ‣Like • Swift / 自 動化 / 柴 犬 2 Accessibility for Swift Charts by Mika Day 2 Track D 1 3 : 5 5 ~
  2. Keynote ‣アプリ内課 金 の全体像 • より深く StoreKit 2 を使う •

    バックエンドの仕組みも学ぶ • それぞれの役割を理解し、V 1 と V 2 で課 金 の何が変わったかを理解する ‣早速使いたい!でも移 行 が必要...... • V 1 → V 2 へ安全に移 行 していくためにやれることを学ぶ ‣ 自 社システムとの複雑な連携 • 起きうるユースケース ・ 知っておくべき設計Tipsを学ぶ 3
  3. Adopt App Store Server API Enable App Store Server Noti

    fi cation V 2 StoreKit 2 によるモダンなアプリ内課 金 iOSDC 202 4 Day 0 1 7 : 2 0 ~ TrackB StoreKit 2 によるiOSのアプリ内課 金 リニューアル iOSDC 202 4 Day 2 1 3 : 0 0 ~ TrackB Catch up 4
  4. 過去の知 見 も絶対 見 よう! 5 アプリ内課 金 におけるトラブルを劇的に減らすための取り組み iOSDC

    202 3 20分間で振り返るIn-App Purchaseの歴史 iOSDC 202 2 StoreKit testingを使ってアプリ内課 金 のテストや検証を効率化する 方 法 iOSDC 202 3 StoreKit 2 を使った課 金 システムのフルリニューアル iOSDC 202 3 StoreKit 2 によるiOSのアプリ内課 金 のチュートリアル https://www.revenuecat.com/blog/engineering/ios-in-app-subscription-tutorial-with-storekit- 2 -and-swift-jp
  5. うっかりを避けたい課 金 は集合知の結晶 ‣「 自 分達はこうした」の積み重ね • 自 社のコンテキストに依存する決定も多い •

    同じような話は除きつつ、新しい話をできるようにします。 • 様々な知 見 の共有本当にありがとうございます🙏 6
  6. WWDC 24 Explore App Store server APIs for In-App Purchase

    9 https://developer.apple.com/jp/videos/play/wwdc 2 02 4 / 1 0 0 62 /
  7. (といいつつ) 実際には移 行 は 大 変 ‣ 自 社のシステムでアプリ内課 金

    がどう組み込まれているか • 状態を把握し、どういう移 行 計画を 立 てるか • 場合によって全体のアーキテクチャを刷新すべき可能性も ‣そのために、StoreKit 2 の世界観を確認する • V 1 → V 2 で結局何が変わったのか、どう改善したのか • バックエンド側への影響も加味 1 1
  8. 自 社基盤との連携時の課題 ‣IDシステムと認可連携 • 思ったよりも複雑 original_transaction_id! ‣外部経由も含めた課 金 イベントを把握! •

    人 はいつどこから課 金 をする? ‣(別課 金 システムとの) 二 重課 金 問題と対策 • 最適なUXを提供するために、起きうる課 金 フローを整理 1 2
  9. アプリ内課 金 ‣StoreKit SDK • クライアントアプリで課 金 を実 行 できる

    SDK • V 1 / V 2 (iOS 15 +) があり、V 1 の API が 2024年6 月 deprecated に ‣App Store Server API • バックエンドで課 金 情報の取得や検証が 行 える • StoreKit 2 に合わせて Receipts API を代替する形で登場 1 4 https://developer.apple.com/jp/videos/play/wwdc 2 02 4 / 1 0 0 62 /
  10. App Store Server API ‣App Store Server API • ユーザーの課

    金 情報をバックエンドで取得できる API ‣App Store Server Noti fi cations • 課 金 情報の Update が App Store から届くリアルタイムプッシュ通知 1 5 https://developer.apple.com/jp/videos/play/wwdc 2 02 4 / 1 0 0 62 /
  11. ユースケース ‣StoreKit • ユーザーの操作に応じて課 金 処理を実 行 • クライアントが依存し、購 入

    タイミングに依存する ‣App Store Server API (旧 Receipts API) • レシートやトランザクションから課 金 情報を取得 ・ 検証 • クライアントから送られてきたタイミングに依存する (ポーリングを除く) ‣App Store Server Noti fi cations • 課 金 に関する更新通知をリアルタイムに受信 • クライアントの操作に依存せずに App Store から最新の更新情報が届く 1 6
  12. スライド上の対応関係 1 7 V 1 V 2 Client StoreKit StoreKit

    2 Backend App Store Receipts API App Store Server API Backend App Store Server Noti fi cation V 1 App Store Server Noti fi cation V 2
  13. ユースケース 1 8 Event Timing V 2 Client アプリが依存する ユーザーイベントから発

    火 StoreKit 2 Backend バックエンドが依存する ユーザーイベントから発 火 App Store Server API Backend バックエンドが依存する App Store から発 火 App Store Server Noti fi cation V 2
  14. StoreKit 2 (iOS 1 5 +) ‣iOS 15 + から

    V 2 に • 2024年6 月 V 1 の API が deprecated に ‣API が 一 新 • 非 同期処理の刷新。Observer パターン → Swift Concurrency に • API 刷新に伴い、アプリ内課 金 と外部導線経由の課 金 が明確に区別できるように ‣JWS Transaction を 用 いた改ざん検証 方 法に刷新 • 従来のレシートを 用 いた verifyReceipt() は deprecated に • 課 金 状態や有効な Transaction を好きなタイミングで取得可能に 1 9
  15. API の変化 ‣StoreKit 1 • Observer パターン • Transaction の更新通知を購読してハンドリング

    • Transaction の情報と前後のコンテキストで状況を判断 2 0 extension TransactionObserver: SKPaymentTransactionObserver { func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { // ౉ͬͯ͘Δ transactions ͷঢ়ଶΛݟ֤ͯछϋϯυϦϯάΛߦ͏ } }
  16. StoreKit 2 の場合 ‣ユーザーの直接操作による購読 • await してそのまま取得可能 ‣ユーザーの直接操作でない 外部起因の継続的なUpdateEvent •

    AsyncSequence を購読 • サブスクリプションの更新 • オファーコード • 他端末経由の購 入 etc... 2 1 let result = try await product.purchase() switch result { case let .success(verificationResult): switch verificationResult { case .verified(let signedType): // վ͟Μ͞Ε͍ͯͳ͍͜ͱΛ֬ೝ case let .unverified(_, verificationError): } case .pending, .userCancelled: } Task { for await verificationResult in Transaction.updates { switch verificationResult { ... } } }
  17. 購 入 情報の改ざん検証 方 法 ‣購 入 情報に不正な改ざんがされていない検証をしておく • V

    1 : Receipt (全 Transaction を集約) • V 2 : Transaction (1つの購 入 情報) • V 1 はバックエンドを介した検証が推奨されていた (verifyReceipt()) ‣V 2 はどうして端末で検証できる? • データ形式が JWS (JSON Web Signature) になった • JSON形式のデータに App Store によるデジタル署名がついた • パブリック証明書を 用 いて検証が可能に (WEBからDL) 2 2
  18. JWS (JSON Web Signature) ‣JWT が改ざんされてないかを検証する仕組み 2 3 eyAbcdefghij.eyBcdefghijklmnopq.sssssssss Base64(header)

    Base64(payload) sign(B(h) + "." + B(p)) // header { "alg": "ES256", "X5c": [...] } // payload { "transactionId": "1098836", "originalTransactionId": "1082212", "purchaseDate": 1623081600000, ...... }
  19. (再掲) StoreKit 2 の場合 ‣ユーザーの直接操作による購読 • await してそのまま取得可能 ‣ユーザーの直接操作でない 外部起因の継続的なUpdateEvent

    • AsyncSequence を購読 • サブスクリプションの更新 • オファーコード • 他端末経由の購 入 etc... 2 4 let result = try await product.purchase() switch result { case let .success(verificationResult): switch verificationResult { case .verified(let signedType): // վ͟Μ͞Ε͍ͯͳ͍͜ͱΛ֬ೝ case let .unverified(_, verificationError): } case .pending, .userCancelled: } Task { for await verificationResult in Transaction.updates { switch verificationResult { ... } } }
  20. (従来) レシート検証 2 5 Client Your Server App Store Receipts

    Send Receipt Verify Receipt Receipt には全ての購 入 情報が 入 るため、 利 用 期間が 長 いユーザーほど、レシートサイズも増加する
  21. StoreKit 2 の検証 2 6 Client Your Server App Store

    Receipts Verify signedTransaction (JWS) API ・ ネットワークエラーの考慮が減らせる 検証するためのサーバーを 用 意しなくていい JWS の改ざん検証を端末で 行 える
  22. バックエンドと連携する場合でも検証が楽に ⭕ 2 7 Client Your Server App Store Receipts

    Verify signedTransaction (JWS) Send signedTransaction API へのリクエストが減る! Transactionは直近購 入 情報のみ、ペイロードが増加しない
  23. App Store Server API ‣V 1 : App Receipts API

    • verify receipt API を通して課 金 情報を 一 括取得 ‣V 2 相当: 用 途ごとに洗練された API (現在は 12 Endpoint) が整備 • JWS が返ってくるので改ざん検証 • App Store Server Noti fi cations の履歴の取得 • ユーザーのTransaction 履歴 ・ 各 Transaction の取得 • サブスクリプションステータスの取得 ・ 期間の延 長 • 返 金 申請 • etc ... 2 8 https://developer.apple.com/documentation/appstoreserverapi
  24. App Store Server Noti fi cations ‣こちらも V 2 EndPoint

    が登場 • Sandbox / Production それぞれで指定可能 • V 1 で既に動いている場合、まずは開発だけ V 2 が始められる ‣V 2 からは JWS で通知 • 改ざん検証して受信サーバー単体でそのまま利 用 可能 • V 1 時代は verifyReceipt() しなければいけなかった ‣情報量の増加 • 通知イベントの種類の追加 • ほぼどの通知にも TransactionInfo / RenewalInfo といった基本情報が常につく • 追加で API を叩かなくて済む 2 9 https://developer.apple.com/documentation/appstoreservernoti fi cations
  25. どんな通知が 飛 ぶか 3 0 ユーザー サブスクリプション 有効中 SUBSCRIBE: INITIAL_BUY

    ① 解約予約中 DID_CHANGE_RENEWAL_STATUS: AUTO_RENEW_DISABLED ③ 解約 EXPIRED: VOLUNRARY ④ DID_RENEW ②
  26. ‣通知タイプの洗練化 ⭕ • V 1 は10のシナリオ。通知イベント + 前の状態からの変化で判断 • V

    2 は28以上に増加。シナリオに対応した通知イベント事に毎回発 火 V 2 の進化 3 2 https://developer.apple.com/documentation/appstoreservernoti fi cations/noti fi cation_type https://developer.apple.com/documentation/appstoreservernoti fi cations/noti fi cationtype V 1 : 同じ通知でも状況によって実際のシナリオを判定 V 2 : 通知イベントとシナリオが 1:1 対応!
  27. Server Noti fi cations V 2 ‣V 2 ははるかに使いやすくなった •

    改ざん検証さえすれば、API の連携無しで 十 分な情報量を受信 ‣年間通して継続的なアップデートも 行 われている • 不 足 していたイベント ・足 りない情報も随時追加 • 例えば、O ff erType のステータスも取得可能に • V 2 で取れなくなって移 行 時に困ったときが過去にはあった • V 1 に機能追加の予定はもちろん無し 3 3
  28. Update 例 ‣ 2 0 2 3 / 10 /

    26 • サブスクリプションオファーの DiscountType が分かるように ‣ 2 0 2 4 / 02 / 29 • EU 周りの外部購 入 トークンのフィールド追加 ‣ 2 0 2 4 / 06 / 10 • Get Transaction History V 2 を追加 • 消耗型課 金 の購 入 時に ONE_TIME_CHARGE 通知の追加 ‣ 2 0 2 4 / 07 / 08 • 各種通知に win-back o ff er 周りのフィールド追加 3 4 https://developer.apple.com/documentation/appstoreservernoti fi cations/app_store_server_noti fi cations_changelog
  29. Server API / Server Noti fi cations まとめ ‣簡潔な仕組みに 大

    改善!でも検証周りはどうすれば!? • JWS の改ざん検証はバックエンドで 自 分で実装しないといけない? • 各種 API の Client や DataModel の 用 意が 面 倒...... • 使うフィールドもたくさんあるし、なおも増えてる...... 3 5 †App Store Server Library†
  30. App Store Server Library ‣Apple 公式 App Store Server API

    向け Wrapper Tool • App Store 署名 JWS の Veri fi cation ができる • 各種 API の Client + Response の DataModel が 用 意 • 2023年冬 1.0.0 🎉 3 6 https://github.com/apple/app-store-server-library-swift Meet the App Store Server Library WWDC 2 3 Explore App Store server APIs for In-App Purchase WWDC 2 4
  31. 幅広いバックエンド向け Language サポート ‣O ffi cial • github.com/apple/app-store-server-library-swift • github.com/apple/app-store-server-library-java

    • github.com/apple/app-store-server-library-python • github.com/apple/app-store-server-library-node ‣有志による別 言 語対応 • github.com/hoels/app-store-server-library-php • github.com/namecare/app-store-server-library-rust 3 7
  32. 他にも課 金 の様々な Utility をサポート ‣Receipt から transaction_id の抽出 •

    verifyReceipt() を代替する後 方 互換 方 法の 用 意 • Get Transaction History API 等と連携 ‣Promotion O ff er のサポート • O ff er に必要な署名 生 成もライブラリで可能に 3 8
  33. これまでの Receipt 形式 (再掲) 3 9 Client Your Server App

    Store Receipts Send Receipt Verify Receipt https://developer.apple.com/videos/play/wwdc 2 0 2 4 / 1 0 0 6 2 / V 1 DataModel V 1 Endpoint
  34. Library: Receipt から transaction_id を抽出 4 0 Client Your Server

    Send Receipt Extract transaction_id https://developer.apple.com/videos/play/wwdc 2 0 2 4 / 1 0 0 6 2 / App Store Receipts
  35. App Store Server API を使えば V 2 形式で取得できる 4 1

    Client Your Server Send Receipt App Store Server API Get Transaction History API Extract transaction_id https://developer.apple.com/videos/play/wwdc 2 0 2 4 / 1 0 0 6 2 / V 2 DataModel V 2 Endpoint の処理と共通化できる
  36. (それはそれとして) JWS になると...... ‣開発時はデータが 見 づらい 😭 4 2 {

    "signed_payload": "ey..........." } API の結果を 見 ても Token だけ...
  37. (それはそれとして) JWS になると...... ‣開発時にデータが 見 づらい 😭 4 3 {

    "signed_payload": { "data": { "signed_transaction_info": "ey...........", "signed_renewal_info": "ey..........." } } } { "signed_payload": "ey..........." }
  38. (それはそれとして) JWS になると...... ‣開発時にデータが 見 づらい 😭 4 4 {

    "signed_payload": { "data": { "signed_transaction_info": { "transactionId": "23456", "originalTransactionId": "12345", ... }, "signed_renewal_info": { "originalTransactionId": "12345", "renewalDate": 1698148850000, ... } } } } { "signed_payload": { "data": { "signed_transaction_info": "ey...........", "signed_renewal_info": "ey..........." } } }
  39. App Store Server API Viewer (OSS) ‣API Call + JWS

    検証を 行 って 自 動展開 • ユーザーの課 金行 動ログをシュッと 一 望 😉 4 6 ↑Server Noti fi cation の History を表 示 https://github.com/shimastripe/InAppPurchaseViewer/releases
  40. Migration にあたって ‣リスクが低い ・ 独 立 性が 高 い箇所から着 手

    したい • 個別リリースができるならそれに越したことはない • 初回は JWS への移 行 も 行 わなければいけない ‣定点監視できるものがあるとなおいい • 信頼できるデータソースにあたるものはあるか 4 8
  41. V 2 は個別にアップデート可能 4 9 V 1 V 2 Client

    StoreKit StoreKit 2 Backend App Store Receipts API App Store Server API Backend App Store Server Noti fi cation V 1 App Store Server Noti fi cation V 2
  42. V 2 は個別にアップデート可能 5 0 V 1 V 2 Client

    StoreKit StoreKit 2 Backend App Store Receipts API App Store Server API Backend App Store Server Noti fi cation V 1 App Store Server Noti fi cation V 2
  43. V 2 は個別にアップデート可能 5 1 V 1 V 2 Client

    StoreKit StoreKit 2 Backend App Store Receipts API App Store Server API Backend App Store Server Noti fi cation V 1 App Store Server Noti fi cation V 2
  44. V 2 は個別にアップデート可能 5 2 V 1 V 2 Client

    StoreKit StoreKit 2 Backend App Store Receipts API App Store Server API Backend App Store Server Noti fi cation V 1 App Store Server Noti fi cation V 2
  45. トリッキーな共存は危険 ‣StoreKit 1 と 2 の購 入 処理を混ぜてはいけない • StoreKit

    2 でpurchase()を実 行 しつつ、 あくまで Receipt を使って決済処理を進める 5 3 https://speakerdeck.com/yuheiito/storekit 2 woshi-tutake-jin-sisutemunohururiniyuaru?slide= 5 0
  46. つまり横向きに共存する購 入 フローは避ける 5 4 V 1 V 2 Client

    StoreKit StoreKit 2 Backend App Store Receipts API App Store Server API Backend App Store Server Noti fi cation V 1 App Store Server Noti fi cation V 2
  47. 自 社のシステムの利 用 用 途を確認する ( 日 経の場合) ‣StoreKit •

    クライアントアプリで定期購読型サブスクリプションを実 行 ‣App Store Receipts API • クライアントから Receipt を受け取って verifyReceipt() • ユーザーの購読情報の更新 ‣App Store Server Noti fi cation V 1 • 分析 用 課 金 ログ 5 5
  48. 自 社のシステムの利 用 用 途を確認する ( 日 経の場合) ‣StoreKit 2

    • クライアントアプリで定期購読型サブスクリプションを実 行 ‣App Store Server API • Transaction を受け取って、追加 API を叩く場合のみ使 用 • (ユーザーの購読情報の更新) ‣App Store Server Noti fi cation V 2 • 分析 用 課 金 ログ • ユーザーの購読情報の更新 5 6
  49. なぜ App Store Server Noti fi cation に移した? ‣クライアント操作に依存しない •

    アプリ内課 金 は外部動線 (OS の設定画 面 ) からも様々な操作が 行 える • 購読解除 ・ 再開 • 別期間のプランに移 行 ( 月 額 ↔ 年額) • アプリ越しだと、ユーザーが開かないと状態を把握できない • そのため、元々は記録した transaction_id をポーリングして確認していた ‣Push 型はイベントが起きたタイミングで受信できる • 調査時の振る舞い理解や問い合わせ時に有 用 ‣共通化とリスク軽減 • クライアントに依存しない Noti fi cation は V 1 V 2 で差が 生 まれない更新処理にできてよかった • Server API と StoreKit 2 は合わせて本番展開したため、事前移 行 できるものはあげたかった 5 7
  50. まずはバックエンドから V 2 対応 ‣具体的には、以下の粒度でリリースする 1 . AppStoreServerNoti fi cations

    を V 2 に上げる • 可能であれば、ログ監視 (信頼できるデータソースへ) を仕込む 2 . V 1 Endpoint の verifyReceipt() を移 行 • AppStoreServerLibrary を 用 いた後 方 互換対応 3 . Transaction を受け取る V 2 EndPoint を 用 意する 4 . StoreKit -> StoreKit 2 に移 行 5 8 ↑ Backend ↓ Client
  51. 再送ポリシーの改善 ‣通知は返却ステータスコードに応じて再送してくれる • 200 - 20 6 : successful •

    40 X, 5 0 X: retry ‣再送タイミング • V 1 : 6 , 24 , 48 時間後 • V 2 : 1 , 12 , 24 , 4 8 , 7 2 時間後 6 1 App Store Server Noti fi cation V 2 https://developer.apple.com/documentation/appstoreservernoti fi cations/responding_to_app_store_server_noti fi cations
  52. 移 行 直後の遅延に注意 ‣40分ほど切り替えラグがある (あった) • V 1 と V

    2 それぞれ受信できる状態が存在する • 安定後、V 1 実装の削除ができる • 1 EndPoint で両バージョン捌けるようにしておくか • 切替時に EndPoint ごと変えるか 6 2 App Store Server Noti fi cation V 2
  53. Noti fi cation 移 行 時にエラーが 生 じたら ‣Case 1

    すぐに修正できる場合 • 再送時に解消していることを確認する ‣Case 2 再送時間内に対応できなかった... • V 2 からは Noti fi cation History API が利 用 可能 • 取得可能範囲の制約はあるが、復元を試みることができる ‣Optional (Recommend) • 通知イベントのバックアップをまず取るようにしておくとベター 6 3 App Store Server Noti fi cation V 2
  54. API ・ Noti fi cation は仕様変更を想定しておく ‣追加変更を想定した緩い制約にしておく • API 側がアップデートしてエラーになると再送につながる

    ‣情報の追加 • currency ・ price (価格) • introductry_o ff er (無料体験の形式) ‣通知の追加 • consumption_request (返 金 要求) • onetime_noti fi cation (消耗型課 金 購 入 時もイベント通知されるように) 6 4 App Store Server Noti fi cation V 2
  55. Sandboxで検証成功、Productionで検証失敗 ‣特定の設定が不 足 していると Production だけコケる 6 6 App Store

    Server Library import AppStoreServerLibrary let bundleId = "com.example" let appleRootCAs = loadRootCAs() // Specific implementation may vary let appAppleId: Int64? = nil // appAppleId must be provided for the Production environment let enableOnlineChecks = true let environment = Environment.sandbox
  56. 改ざん検証に使うRoot 証明書は有効期限がある ‣Apple Root Certi fi cates • G 3

    が最新 • 更新があることを忘れずに • 有効期限をチェック 6 7 https://www.apple.com/certi fi cateauthority App Store Server Library
  57. 証明書の online check はそこそこコケる ‣証明書の期限切れ以外の理由による失効があるかチェック • 500エラーがそこそこ返る、確認するタイミングを調整するといい 6 8 App

    Store Server Library import AppStoreServerLibrary let bundleId = "com.example" let appleRootCAs = loadRootCAs() // Specific implementation may vary let appAppleId: Int64? = nil // appAppleId must be provided for the Production environment let enableOnlineChecks = true let environment = Environment.sandbox
  58. StoreKitTesting を 用 いたローカル課 金 機能の検証 ‣改ざん検証に Xcode Environment を指定する

    • ただし、これは内部的には検証無しで Pass しているだけに注意 • サーバーで許可すると検証無しで通せてしまう 6 9 App Store Server Library https://developer.apple.com/jp/documentation/storekit/in-app_purchase/testing_at_all_stages_of_development_with_xcode_and_sandbox/
  59. Test fl ight の課 金 動作は確認しておく ‣開発環境では起きない振る舞い変化を観測 • V 1

    で課 金 済みユーザーが V 2 形式の復元を直後取得できない • AppStore.sync() を 行 っても復元されない...... • 購読を 行 うと、復元と同等の動きになる (購読済みの動作) ‣外部課 金 イベントの遅延が特に注意 • Transaction.updates {} が流れてくると思っていたら来ない......等 • currentEntitlements 等を利 用 して、Workaroundを検討する 7 2 StoreKit 2
  60. 外部システムとの連携 ‣実際には端末で完結するケースはほとんどない • 課 金 ログの継続的な分析 • 認証 / 認可

    による有料機能の有効化 • ID システムとの連携 • 他の課 金 システムとの兼ね合いを気にするサービスも 7 4
  61. 課 金 ログ分析システム構築の選択肢 ‣App Store Server API で Polling 更新

    • transaction_id を控えて 生 成 ‣App Store Server Push Noti fi cation で通知受信時更新 • リアルタイムに更新 • クライアントの状態に依存せず更新が届く ‣外部サービスと連携※ 1 7 5 ※ 1 https://www.revenuecat.com/docs/dashboard-and-metrics/charts
  62. AppStoreConnect でも値は確認できる ‣集計後のデータ • より細かい分析を 行 う場合は 自 前で 用

    意するといい 7 6 https://developer.apple.com/jp/help/app-store-connect/view-sales-and-trends/view-subscription-data/
  63. 継続的な課 金 ステータスの確認 ‣Sanity Check • リリース後、特定の課 金 イベントが急激に減っていないか •

    App Store Connect の値との乖離がないか ‣ユーザーのリテンション • Refund の数や無料体験 ・ 再 入 会の割合 • 解約予約状態のユーザー群の把握 • プロモーショナルオファーを打つときの参考に • オファーコード ・ iOS 1 8 からは win-back o ff er も 7 7
  64. V 2 TransactionHistory は古い履歴も取得可能 ‣既にデータマートを 用 意している場合は移 行 が必要 •

    V 1 ベースの Receipts や Noti fi cation でおそらく作られている • V 2 Endpoint は昔の履歴も V 2 形式で引ける • transaction_id (orignal_*) を控えて V 2 で叩き直す ‣サブスクリプション以外のマートも作成可能 • 消費型 ・ 自 動更新しないサブスクの履歴も取得できるように!(NEW) • WWDC 2 4 のタイミングでリリースされました ✨ 7 8
  65. 認証 ・ 認可 ‣ 自 社IDとは連携せず有料機能の認可だけ付ける場合 • 認可をつける UserID 相当をどうやって

    用 意するか • 他の端末や解約後再課 金 した際に同じクライアントと判断したい 7 9 https://developer.apple.com/documentation/appstoreserverapi/originaltransactionid Transactionの種類 初回購 入 更新 解約後再購 入 original_transaction_id " 10 000 0 00 " 同じ値 同じ値 transaction_id " 10 000 0 00 " 異なる値 異なる値 (参考 GooglePlay) purchase_token " 10 000 0 00 " 同じ値 異なる値
  66. ただし original_transaction_id の扱いに注意 ‣original_transaction_id は変化しうる • App Store のストア地域単位で別 ID

    に設定される • アメリカで購 入 後、 日 本にストアを変更する • 同じ課 金 プロダクトでも original_transaction_id は変化 • 無料体験は引き継がれない • 複数プランがある場合同じ値を引き継ぐか確認 8 0 https://forums.developer.apple.com/forums/thread/ 6 4959 2 他のケースも実際のデータも 見 てよく確認する (20240826 追記: 正確な表現じゃないため) どちらか 一方 のみに適 用 されて、 二 重に適 用 されない
  67. 暗黙的な制約を 見 落とさないように ‣注意すること • ユーザーの original_transaction_id が 一 意前提の設計をしない

    • 1種類のサブスクのみ提供していると 見 落としやすい ‣どうするか • Transaction History API は transaction_id から 一 連のつながりが辿れる • original かどうか問わず叩ける、便利 • ユーザーが持つ original_transaction_ids を 見 るように作る 8 1
  68. データが取得できなくなる original_transaction_id ‣正常に取得できていた transaction_id が突然エラーに変化 • TransactionIdNotFoundError • アカウントが消えたりするなどの条件で起きうるらしい? •

    基本はこの場合は有料機能をアンロックしない対応が正解 (Doc 参照) 8 2 https://developer.apple.com/documentation/appstoreserverapi/transactionidnotfounderror データを 見 てからわかる!
  69. StoreKit 2 は他課 金 サービスとの 二 重購読は防げる? ‣ 自 社クレジット課

    金・ Google Play課 金 との重複を避けたい • StoreKit 2 で課 金 の 自 動復元もできるようになった! • アプリ内で導線を防げば問題は回避できるか? 8 3
  70. 実際にはアプリ外からのアプリ内課 金 はそこそこ起きる ‣オファーコード (URLリンク形式) • App Store アプリから購読開始可能 ‣アプリ内課

    金 してから解約した場合 • 「設定 → サブスクリプション」から再購読可能 (1ヶ 月 以内) ‣ファミリー共有による権利の有効化 • 権利の取得は防げない ‣他端末からの課 金・ 単純にログインしてない間に課 金 ...... • 「別課 金 ID でログイン」 + 「アプリ内課 金 も 自 動復元されて有効状態」が発 生 する • 実際にはこれが 一 番多い 8 4
  71. 二 重購読は起きる、解決 方 法を提 示 する ‣最適なUX • 検知して解消を働きかける ‣Apple

    課 金 の場合、まずサブスクリプショングループを適切に設定する • グループ内で 二 重購読は起きない ‣外部サービス課 金 の場合、検知して通知する • ログイン時 → アプリ内課 金 かつログイン先も既に課 金 していたらエラーにする • 既にログインしていたら → 起動時にチェックして通知 • Transactions.currentEntitlements() からアプリ内課 金 有効か確認 • ログイン中の課 金手 段情報をバックエンドから確認 • "Apple" じゃなければおかしいので通知! 8 5
  72. まとめ ‣StoreKit 2 + App Store Server API を学ぶ •

    既存の V 1 で組んだシステムへの影響を確認 • 特に App Store Server Noti fi cation V 2 は便利になって(個 人 的に)おすすめ ‣App Store Server Library が課 金 開発を 大 幅にサポート • 後 方 互換もできて決定版!継続的なメンテナンスも安 心 ‣既存システムと連携するにあたって • 自 社システムと連携する際 original_transaction_id 等の扱いには注意 • アプリ内課 金 以外と連携する場合要件に合わせて、 二 重購読ケア等を考える 8 7