Slide 1

Slide 1 text

アプリ内課 金 と 自 社の認証認可基盤を 連携する複雑な課 金 ネットワークを 構築する Go Takagi 20 24 / 08 / 23 iOSDC JAPAN 2 024 Day 1 #Track D 1 3 : 0 0 〜

Slide 2

Slide 2 text

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 ~

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

過去の知 見 も絶対 見 よう! 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

Slide 6

Slide 6 text

うっかりを避けたい課 金 は集合知の結晶 ‣「 自 分達はこうした」の積み重ね • 自 社のコンテキストに依存する決定も多い • 同じような話は除きつつ、新しい話をできるようにします。 • 様々な知 見 の共有本当にありがとうございます🙏 6

Slide 7

Slide 7 text

バックエンドの最新 ‣2024年に伝えたいアップデート! • App Store Server Library (OSS) の良さを伝える 7 https://developer.apple.com/jp/videos/play/wwdc 2 02 4 / 1 0 0 62 /

Slide 8

Slide 8 text

ところが

Slide 9

Slide 9 text

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 /

Slide 10

Slide 10 text

Slide 11

Slide 11 text

(といいつつ) 実際には移 行 は 大 変 ‣ 自 社のシステムでアプリ内課 金 がどう組み込まれているか • 状態を把握し、どういう移 行 計画を 立 てるか • 場合によって全体のアーキテクチャを刷新すべき可能性も ‣そのために、StoreKit 2 の世界観を確認する • V 1 → V 2 で結局何が変わったのか、どう改善したのか • バックエンド側への影響も加味 1 1

Slide 12

Slide 12 text

自 社基盤との連携時の課題 ‣IDシステムと認可連携 • 思ったよりも複雑 original_transaction_id! ‣外部経由も含めた課 金 イベントを把握! • 人 はいつどこから課 金 をする? ‣(別課 金 システムとの) 二 重課 金 問題と対策 • 最適なUXを提供するために、起きうる課 金 フローを整理 1 2

Slide 13

Slide 13 text

登場 人 物

Slide 14

Slide 14 text

アプリ内課 金 ‣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 /

Slide 15

Slide 15 text

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 /

Slide 16

Slide 16 text

ユースケース ‣StoreKit • ユーザーの操作に応じて課 金 処理を実 行 • クライアントが依存し、購 入 タイミングに依存する ‣App Store Server API (旧 Receipts API) • レシートやトランザクションから課 金 情報を取得 ・ 検証 • クライアントから送られてきたタイミングに依存する (ポーリングを除く) ‣App Store Server Noti fi cations • 課 金 に関する更新通知をリアルタイムに受信 • クライアントの操作に依存せずに App Store から最新の更新情報が届く 1 6

Slide 17

Slide 17 text

スライド上の対応関係 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

Slide 18

Slide 18 text

ユースケース 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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

API の変化 ‣StoreKit 1 • Observer パターン • Transaction の更新通知を購読してハンドリング • Transaction の情報と前後のコンテキストで状況を判断 2 0 extension TransactionObserver: SKPaymentTransactionObserver { func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { // ౉ͬͯ͘Δ transactions ͷঢ়ଶΛݟ֤ͯछϋϯυϦϯάΛߦ͏ } }

Slide 21

Slide 21 text

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 { ... } } }

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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, ...... }

Slide 24

Slide 24 text

(再掲) 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 { ... } } }

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

StoreKit 2 の検証 2 6 Client Your Server App Store Receipts Verify signedTransaction (JWS) API ・ ネットワークエラーの考慮が減らせる 検証するためのサーバーを 用 意しなくていい JWS の改ざん検証を端末で 行 える

Slide 27

Slide 27 text

バックエンドと連携する場合でも検証が楽に ⭕ 2 7 Client Your Server App Store Receipts Verify signedTransaction (JWS) Send signedTransaction API へのリクエストが減る! Transactionは直近購 入 情報のみ、ペイロードが増加しない

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

どんな通知が 飛 ぶか 3 0 ユーザー サブスクリプション 有効中 SUBSCRIBE: INITIAL_BUY ① 解約予約中 DID_CHANGE_RENEWAL_STATUS: AUTO_RENEW_DISABLED ③ 解約 EXPIRED: VOLUNRARY ④ DID_RENEW ②

Slide 31

Slide 31 text

https://developer.apple.com/wwdc 22 / 100 40 3 1

Slide 32

Slide 32 text

‣通知タイプの洗練化 ⭕ • 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 対応!

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

幅広いバックエンド向け 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

Slide 38

Slide 38 text

他にも課 金 の様々な Utility をサポート ‣Receipt から transaction_id の抽出 • verifyReceipt() を代替する後 方 互換 方 法の 用 意 • Get Transaction History API 等と連携 ‣Promotion O ff er のサポート • O ff er に必要な署名 生 成もライブラリで可能に 3 8

Slide 39

Slide 39 text

これまでの 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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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 の処理と共通化できる

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

(それはそれとして) 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..........." } } }

Slide 45

Slide 45 text

github.com/shimastripe/InAppPurchaseViewer 4 5 https://github.com/shimastripe/InAppPurchaseViewer/releases

Slide 46

Slide 46 text

App Store Server API Viewer (OSS) ‣API Call + JWS 検証を 行 って 自 動展開 • ユーザーの課 金行 動ログをシュッと 一 望 😉 4 6 ↑Server Noti fi cation の History を表 示 https://github.com/shimastripe/InAppPurchaseViewer/releases

Slide 47

Slide 47 text

V 2 Migration

Slide 48

Slide 48 text

Migration にあたって ‣リスクが低い ・ 独 立 性が 高 い箇所から着 手 したい • 個別リリースができるならそれに越したことはない • 初回は JWS への移 行 も 行 わなければいけない ‣定点監視できるものがあるとなおいい • 信頼できるデータソースにあたるものはあるか 4 8

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

トリッキーな共存は危険 ‣StoreKit 1 と 2 の購 入 処理を混ぜてはいけない • StoreKit 2 でpurchase()を実 行 しつつ、 あくまで Receipt を使って決済処理を進める 5 3 https://speakerdeck.com/yuheiito/storekit 2 woshi-tutake-jin-sisutemunohururiniyuaru?slide= 5 0

Slide 54

Slide 54 text

つまり横向きに共存する購 入 フローは避ける 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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

まずはバックエンドから 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

Slide 59

Slide 59 text

App Store Server Notification V 2 V 2 Migration

Slide 60

Slide 60 text

通知先の設定 ‣Sandbox / Production でそれぞれ送出先を設定 • V 2 が出たタイミングで分けられるように ‣各々でバージョンを指定可能 • 開発環境でまず適 用 が可能 6 0 App Store Server Noti fi cation V 2

Slide 61

Slide 61 text

再送ポリシーの改善 ‣通知は返却ステータスコードに応じて再送してくれる • 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

Slide 62

Slide 62 text

移 行 直後の遅延に注意 ‣40分ほど切り替えラグがある (あった) • V 1 と V 2 それぞれ受信できる状態が存在する • 安定後、V 1 実装の削除ができる • 1 EndPoint で両バージョン捌けるようにしておくか • 切替時に EndPoint ごと変えるか 6 2 App Store Server Noti fi cation V 2

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

App Store Server Library の注意点 V 2 Migration

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

改ざん検証に使うRoot 証明書は有効期限がある ‣Apple Root Certi fi cates • G 3 が最新 • 更新があることを忘れずに • 有効期限をチェック 6 7 https://www.apple.com/certi fi cateauthority App Store Server Library

Slide 68

Slide 68 text

証明書の 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

Slide 69

Slide 69 text

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/

Slide 70

Slide 70 text

StoreKit 2 V 2 Migration

Slide 71

Slide 71 text

それでも不安なクライアント実装箇所は ‣フラグで切り替えできる仕組みを検討 • 緊急時に • 疑わしい実装を切り替えできるようにしておく • 可能であれば、V 1 実装に緊急退避 7 1 StoreKit 2

Slide 72

Slide 72 text

Test fl ight の課 金 動作は確認しておく ‣開発環境では起きない振る舞い変化を観測 • V 1 で課 金 済みユーザーが V 2 形式の復元を直後取得できない • AppStore.sync() を 行 っても復元されない...... • 購読を 行 うと、復元と同等の動きになる (購読済みの動作) ‣外部課 金 イベントの遅延が特に注意 • Transaction.updates {} が流れてくると思っていたら来ない......等 • currentEntitlements 等を利 用 して、Workaroundを検討する 7 2 StoreKit 2

Slide 73

Slide 73 text

外部システム連携

Slide 74

Slide 74 text

外部システムとの連携 ‣実際には端末で完結するケースはほとんどない • 課 金 ログの継続的な分析 • 認証 / 認可 による有料機能の有効化 • ID システムとの連携 • 他の課 金 システムとの兼ね合いを気にするサービスも 7 4

Slide 75

Slide 75 text

課 金 ログ分析システム構築の選択肢 ‣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

Slide 76

Slide 76 text

AppStoreConnect でも値は確認できる ‣集計後のデータ • より細かい分析を 行 う場合は 自 前で 用 意するといい 7 6 https://developer.apple.com/jp/help/app-store-connect/view-sales-and-trends/view-subscription-data/

Slide 77

Slide 77 text

継続的な課 金 ステータスの確認 ‣Sanity Check • リリース後、特定の課 金 イベントが急激に減っていないか • App Store Connect の値との乖離がないか ‣ユーザーのリテンション • Refund の数や無料体験 ・ 再 入 会の割合 • 解約予約状態のユーザー群の把握 • プロモーショナルオファーを打つときの参考に • オファーコード ・ iOS 1 8 からは win-back o ff er も 7 7

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

認証 ・ 認可 ‣ 自 社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 " 同じ値 異なる値

Slide 80

Slide 80 text

ただし 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 追記: 正確な表現じゃないため) どちらか 一方 のみに適 用 されて、 二 重に適 用 されない

Slide 81

Slide 81 text

暗黙的な制約を 見 落とさないように ‣注意すること • ユーザーの original_transaction_id が 一 意前提の設計をしない • 1種類のサブスクのみ提供していると 見 落としやすい ‣どうするか • Transaction History API は transaction_id から 一 連のつながりが辿れる • original かどうか問わず叩ける、便利 • ユーザーが持つ original_transaction_ids を 見 るように作る 8 1

Slide 82

Slide 82 text

データが取得できなくなる original_transaction_id ‣正常に取得できていた transaction_id が突然エラーに変化 • TransactionIdNotFoundError • アカウントが消えたりするなどの条件で起きうるらしい? • 基本はこの場合は有料機能をアンロックしない対応が正解 (Doc 参照) 8 2 https://developer.apple.com/documentation/appstoreserverapi/transactionidnotfounderror データを 見 てからわかる!

Slide 83

Slide 83 text

StoreKit 2 は他課 金 サービスとの 二 重購読は防げる? ‣ 自 社クレジット課 金・ Google Play課 金 との重複を避けたい • StoreKit 2 で課 金 の 自 動復元もできるようになった! • アプリ内で導線を防げば問題は回避できるか? 8 3

Slide 84

Slide 84 text

実際にはアプリ外からのアプリ内課 金 はそこそこ起きる ‣オファーコード (URLリンク形式) • App Store アプリから購読開始可能 ‣アプリ内課 金 してから解約した場合 • 「設定 → サブスクリプション」から再購読可能 (1ヶ 月 以内) ‣ファミリー共有による権利の有効化 • 権利の取得は防げない ‣他端末からの課 金・ 単純にログインしてない間に課 金 ...... • 「別課 金 ID でログイン」 + 「アプリ内課 金 も 自 動復元されて有効状態」が発 生 する • 実際にはこれが 一 番多い 8 4

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

まとめ

Slide 87

Slide 87 text

まとめ ‣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