ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
StoreKitテストの最新情報
App内課金やサブスクリプションのテストに役立つ最新ツールをご確認ください。ご利用のプロダクトをApp Store ConnectからXcodeのStoreKitテストに取り込む方法、トランザクションマネージャの改善点、XcodeプレビューのApp内課金フローについて解説します。また、サンドボックス環境用のApple IDを設定する際のベストプラクティスをはじめ、返金リクエスト、値上げへの同意、請求のリトライなどのテストを作成する方法も紹介します。
リソース
- Auto-renewable subscriptions overview
- Handling Subscriptions Billing
- Implementing offer codes in your app
- Learn more about setting up offer codes
- Reducing Involuntary Subscriber Churn
- Setting up StoreKit Testing in Xcode
- Testing In-App Purchases with sandbox
関連ビデオ
WWDC23
WWDC22
WWDC21
WWDC20
-
ダウンロード
♪ ♪ こんにちは Gregです 「StoreKitテストの最新情報」 へようこそ この回では Peterと StoreKitのApp内課金を テストすることに関連する 新機能を紹介します まずはXcode 14を使って App内課金のテストを 効率化する方法を いくつか紹介します 次に App内課金の実装で コーナーケースを含み 利用できる 全く新しい 機能を紹介します 最後に Peterはサンドボックスの テスト環境に関する 新しい拡張を紹介します Food Truckに取り組んでいます ドーナツ販売トラック事業者 に機能を提供するAppです すでにStoreKitと統合し Appの販売履歴機能の 完全版と ソーシャル フィードサービスの拡張機能のための サブスクリプションを 提供しています このセッションを通して Xcodeの StoreKit Testingを使い App内課金機能を テストしていきます WWDC 2020では Xcodeで StoreKit Testingを導入し 直接App内課金の テスト開始を可能にしました 今年はXcode 14で StoreKit Appの テストライフサイクル関連の アップデートを紹介します 変わらず XcodeでStoreKit 設定ファイルを作成し App Store Connectで Appを設定することなく App内課金の実装テストを 開始することが可能です App Store Connectで App設定の準備ができたら Xcode 14で 全く新しい機能を導入し App Store Connectで入力したのと 同じApp内課金プロダクトを XcodeのStoreKit Testing で使用できるようにします すでにストア上に Appがある場合は StoreKit設定ファイルを 一から設定せずに Xcode上ですぐに 使い始めることができます この便利な機能により App内課金を一度設定し Xcode ユニットテスト内 サンドボックス環境 リリースの準備ができた ときには App Storeで 同じ設定をローカルに 使用することができます XcodeでApp Store Connect のプロダクトを簡単に同期できます まず このSocial Feed+の サブスクリプションのように App Store Connectで プロダクトを設定します そして Xcodeにプロダクト データを読み込むための 同期設定ファイルを 作成します もし 英語タイトルの更新など 変更を加えたい場合は App Store Connectで 変更を行い 再度Xcodeで 設定を 同期させることができます 同期した設定をローカルで 編集可能な ファイルに変換し その場で 変更することも可能です 同期した設定のローカルへの 変換は一方的な操作で 再度同期するには 新しいファイルを 作成する必要があります すでに Food Truck Appの Social Feedの拡張版 Social Feed+の登録 グループの設定を始めています 早速 Xcodeの StoreKit Testingで これらの使用方法を 見てみましょう MacでFood Truckの プロジェクトを開いています 始めに StoreKitの 新規設定ファイルを作ります ファイルメニューから 新規ファイルを作成 StoreKitで フィルタをかけ 次へ をクリックします
Xcode 14では 新規設定ファイルを作ると チェックボックスが表示され App Store Connectで Appと 同期させることができます ローカルファイルの場合は チェックを外します 同期を設定するには チェックを入れ 正しいチームとAppの 選択を確認するだけです ピッカーメニューで別のチームや Appの選択も可能です 次へ をクリックし ファイルを保存する場所を 選択します ファイルを保存すると同時に App内購入のメタデータがApp Store Connectから同期を始めます データのダウンロード中は Appの作業を続け アクティビティバーで 進捗を確認できます 同期が終わると 通常の StoreKit設定ファイルとは 違う見た目に 気づくでしょう 同期されたファイルが 読み取り専用状態だからです Xcodeで全てのデータを 見ることができますが 変更にはApp Store Connect を開く必要があります
SafariにSocial Feed+の 月額プロダクトがあります このプロダクトの英語タイトルを 更新し 年間プランと 区別しやすくするために 接尾辞を追加してみます
これで更新されたので 保存して Xcodeに戻ります
この変更を設定ファイルに 反映させるためには 左下にあるこの 同期ボタンを押すだけです
同期が完了すると Xcodeで 変更の反映を確認できます
同期されたファイルは 読み取り専用ですが Xcodeの中で 素早く変更するため ローカルファイルに データコピーができます
設定ファイルから 項目をコピーするだけでなく 同期ファイル全体をローカル で編集可能に変換もできます あとは 同期したファイルを開き エディタメニューから 「ローカルのStoreKit設定に変換」 をクリックするだけです
変換後に取り消すことは できないので注意しましょう 再度同期するには StoreKit 設定ファイルを新しく作成する必要です これはApp Store Connectと 同期しておきたいので このアラートは キャンセルしましょう ファイルの同期が 完了したので テスト環境を 設定してみましょう まず始めに スキーマ エディタを開きます
ランアクションを選択し オプションを選択します
オプションではStoreKitの 環境をピッカーメニューから 切り替えることができます 「なし」を選ぶとSandboxに 「Food Truck」を選ぶと Xcode環境に 接続されます 現在のテストニーズに応じて 簡単に環境を 切り替えることができ どの環境でも同じプロダクトと サブスクリプションの メタデータを使用できます 同期した設定ファイルを 選んでみましょう
XcodeにStoreKitを設定完了し テストします SwiftUI Appを 使用しているため Xcodeでサブスクリプションストアを 直接プレビューできます
Xcode 14から設定 ファイルのプロダクトがそのまま SwiftUIプレビューに 読み込まれます 実際のApp内課金データで ユーザインターフェースを 簡単に構築し テストができます プロダクトオプションに サブタイトルを入れて プロダクトの詳細を 追加してみましょう ローカライズされた説明文の テキストビューを追加します
App Store Connectで設定 した説明文を使ってすぐに プレビューのアップデートを 見ることができます だいぶ良くなったと思います
UIが良くなったので iPhoneで Appを起動し 機能テストを 始めてみましょう
Xcode14では StoreKit Transaction Managerに 強力な新しい ツールがあります Appを起動し デバッグバー にある購入アイコンを押すと トランザクションマネージャを 開くことができます
右側には新しい トランザクションインスペクタがあり トランザクションの裏側の詳細を すべて視覚化 することができます このツールは App内トランザクションの 状態を把握するのに有効です 例えば Social Feed+の サブスクリプション期間が終了した日や 今後の更新情報を 確認できます また プロダクト サブスクリプショングループ サブスクリプションオファー 設定ファイルに飛ぶことができます サブスクリプショングループ横の ジャンプボタンをクリックするのです
設定ファイルの Social Feed+に行きます
このインスペクタは この回の後半の より高度な テストケースで役立ちます
また トランザクションを フィルタリングできるようになり Social Feed+の更新を 含むトランザクションのリストを ナビゲートするのに 非常に便利です このAppでは 年間販売履歴 にもアクセスできます
サブスクリプション更新がたくさんあるため どのトランザクションで機能が 利用可能かわかりにくくなります そのプロダクトのIDを入力すると 簡単にそのプロダクトの トランザクションを見つけることが できます
自動補完メニューから プロダクトIDフィルタを選択します
購入日での 絞り込みも可能なので 今購入しているものだけに 絞ることもできます
Social Feed+のサブスクリプション 期限が切れているので Appで再度 サブスクライブしてみましょう
これでサブスクリプションの 確認ができたので 新しいトランザクションだけが 表示されるようになりました
XcodeでApp内購入テスト の強化方法を見てきました App Store Connectから プロダクトと サブスクリプションを同期させ SwiftUIプレビューで StoreKit 設定を使用し トランザクションマネージャの 新ツールを活用するのです では Xcodeの 新しい機能を使って 高度なサブスクリプション ケースをカバーするために Food TruckのApp内課金の 機能テストを続けます まず Food Truckでの 購入プロダクトの返金を依頼する 返金リクエストの テストについてです 次に Social Feed+ の加入者に プロモーションを提供する オファーコードのテスト Food Truckの ユーザインターフェース における値上げの処理 最後に 課金のリトライと 猶予期間のサポートによる 不本意な解約の 減少を検討します 返金リクエストの テストを始めるため Appの サポートビューに移動し 返金する最近の トランザクションを選択します このためのコードは 簡単です refundRequestSheet モディファイアを追加し 返金ボタンを押したら isPresented Bindingを trueに切り替えます では 実際に 見てみましょう
Bindingがtrueになると 返金 請求シートが表示されます Xcode環境でテストすると 選択したissueが StoreKit APIの取消理由 と1対1で対応しました 「デベロッパの問題」から 「返金請求」を押してみましょう
App Storeでは 返金処理に 時間がかかりますが Xcodeや Sandboxのテストでは 返金リクエスト後すぐに トランザクションが返金されます トランザクションマネージャで 更新されたトランザクションの インスペクタを見ると 先ほど選択した 取消理由と取消日を 確認することができます
トランザクションマネージャで 返金をクリックするだけで 返金をテスト することができます 返金リクエストAPIは Food Truckの利用者に素晴らしい カスタマーサポートを 提供します Xcodeで返金リクエストの テストをしたので StoreKitで返金されたトランザクションの 処理方法を見ましょう
返金後 更新された トランザクションの値が Transaction.update シーケンスから発行されます revocationReasonと revocationDateプロパティを使い 返金されたトランザクションを 検出します Xcodeの返金リクエストシートで オプションを選択することで 2つの取消理由を簡単に テストすることができます それが Xcodeで返金リクエスト シートをテストする方法です iOSとmacOSで Xcode環境 またはSandboxを 使用している場合に有効です Xcodeでのテストは iOSかiPadOS 15.2以降の 動作するiPhone またはiPadが必要です XcodeでMac上のテストは OS 12.1以降が必要です では サブスクリプション オファーコードをテストしてみましょう ローカルのStoreKit 設定ファイルを使用します コード用の新しいオファーを 作るには サブスクリプションを選択し オファーコードテーブルの下の「+」 を押してください そこで オファーを設定できます これを「無料月間」と名付け 1ヶ月間の 無料キャンペーンにします
App Store Connect と同様に どのような お客様が対象となるのか お試しオファーが受けられるのか などを選定します とりあえずデフォルトの 設定にしましょう コードの設定が完了したので 「終了」を押します。 App Store Connectと 同期している場合は 設定されたオファーが 自動的に表示されます オファーが設定されたので Appのストアビューに 移動してみましょう サブスクリプションオファーを 利用するために ビューの下部近くに このボタンを追加しました Xcodeでストアビューの 実装を開くと オファーコードの実装は offerCodeRedemption モディファイアを追加し 誰かが ボタンをタップしたときに isPresented Bindingを trueに切り替えます どうなるか見てみましょう
ボタンを押すと Appの上に 引き換えシートが表示されます App Storeでは App Store Connectで生成した オファーコードを 入力することができますが Xcodeでは より合理的に テストができます 設定ファイルの コードの全オファーリストを 解除されるサブスクリプションによって グループ化されています 利用するため 先ほど 作成したオファーをタップして 利用するボタンを 押してみましょう 支払いシートが表示され お試しオファーの支払い直後に コードのオファーが 開始されます
登録後 確認画面が 表示されるので シートを閉じて Appが Social Feed+の アクセス解除を確認します
この新規トランザクションの インスペクタを見てみると お試しオファーが適用されている ことがわかります 都度払いなので 更新の欄を見ると お試しオファーの更新が あと2回あります そして 先ほど引き換えた 1ヶ月無料コード その後 通常サブスクリプションが 無期限で更新されます インスペクタは 複雑なシナリオでもも 複数のサブスクリプションの状態に 何が起こっているのか 非常に明確に示してくれます StoreKitのローカル設定に オファーコードを設定する方法と iPhoneでのテスト方法 について見てきました オファーコードは 将来および 既存のサブスクリプション登録者に 柔軟なプロモーションを 提供する素晴らしい方法です そして今 Food Truckで オファーコードを使うことが これまで以上に 簡単になりました では StoreKitでオファーを どう扱うか見てみましょう コードを引き換えた後 Transaction.updateと Status.updateの両方が 新しい値を発行します トランザクション値の offerTypeプロパティを確認し 現在のトランザクションに適用される オファーがあるかを確認できます 今見たケースでは 登録者がコードのオファーで お試しオファーを利用したため offerTypeの値は introductory になります renewalInfoの値で offerTypeプロパティを チェックすることで 次の更新でどのオファーが 存在するかを確認できます 先ほどのケースでは 都度払いを 採用しているため 初期値はお試しであることが 予想されます 2つのサブスクリプション期間の後 コードのオファーを重ねるように 値がコードに切り替わるのを 見ることができます offerTypeがcodeの場合 offerIDプロパティを使って 適用されるオファーの参照名を 取得することができます これが Xcodeでオファーコードを テストする方法です Xcode 13.3から コードのオファーを設定し iOS15.4以降を搭載した iPhoneやiPadでテストします これでFood Truckでの コード動作が確認できました Social Feed+の値上げ時の Appの対応を検証します 値上げのテストは Xcodeでは実に簡単です まず初めに ソーシャルフィード の月額利用料を値上げします
このステップは任意です 価格はそのままでも 値上げのシミュレーションは 可能です トランザクションマネージャに戻ると あとはサブスクリプションの 最新のトランザクションを選択し ツールバーの「値上げリクエストの 同意」を押すだけです
トランザクションマネージャでは トランザクションが「値上げペンディング」 状態になり デバイスではApp上に 値上げ同意を求めるシートが 表示されていることが 確認できます このシートはコードなしでも 表示されますが 新しいMessage APIを使って その動作をカスタマイズできます
Messages APIをどの様に コードに統合したのでしょうか
メッセージのシーケンスを forループイテレートし 値上げのような メッセージを受け取った場合 ドーナツエディタ のようなセンシティブなビューが 表示されないことを 確認します それ以外の場合は DisplayMessageActionで メッセージを表示します ドーナツエディタが 表示された場合は Messageの値を保持し ドーナツ編集終了後に 表示するようにします
テストに戻りましょう
App Storeでは登録者が 解約や値上げに同意するまで 異なるタイミングで 複数の値上げメッセージが 表示される場合があります Xcodeでは このメッセージをいつ表示 完全にコントロールできます トランザクションマネージャの ボタンを押す度 すでに値上げされた 状態のトランザクションでも 再度メッセージが 表示されます この延期ロジックが 機能するか試してみましょう ドーナツエディタを開いて
再びシートを開くための メッセージを送信します
まだシートは 表示されていませんが ドーナツエディタから 離れると 期待通りに シートが表示されます 値上げを承認するか シートで解約もできますが 現実には ユーザーが メールなどの外部ソースで 値上げに応じることも あるでしょう これをシミュレートするため トランザクションマネージャの 承認/辞退ボタンを 使用できます ドーナツ編集体験が気に入り トランザクションマネージャで 承認を押して 新しい価格に同意します XcodeでStoreKitを使うと 値上げなど複雑なケースの テストがとても スムーズにできます 値上げをシミュレーション する方法を見たので 次は StoreKitを使って 値上げを処理する方法です
値上げの状態を テストする場合 状態の更新シーケンスは 状態変更毎に値を発行します RenewalInfoの priceIncreaseStatus プロパティを確認することで Appで更新を検出できます 値上げで顧客が サブスクリプションを解除した場合 expirationReason プロパティに didNotConsentToPriceIncrease をチェックすることで 検出することができます 値上げテストを中心に ユニットテストも書けます まず ダイアログを 無効にすることで App上に値上げのUIを 表示せずにテストができます サブスクリプション後 requestPriceIncrease ConsentForTransaction APIを使用してサブスクリプションに 最新トランザクションの IDを渡して処理を 開始できます テストトランザクションが値上げ待ち であることを確認するため isPendingPriceIncreaseConsent プロパティをチェックします 最後に テスト内容に応じて consentToPriceIncrease ForTransaction 又はdeclinePriceIncrease ForTransactionで 価格上昇時のAppの反応を 確認できます 値上げのテストは以上です 値上げは Xcode13.3にて 全プラットフォームでテスト可能です 値上げのメッセージは iOS15.4以降でテスト可能です 最後に サブスクリプション支払いの 再試行と猶予期間を見てみます 支払いの再試行とは カードの有効期限切れなど エラーが発生した場合に サブスクリプション更新を しようとする状態です App Storeでは 支払いの再試行の際に 問題の修正と サブスクリプションの回復が 試みられます 支払いの再試行状態の開始時に 期間限定で利用を継続させる 猶予期間をオプションで 有効にすることができます Xcodeでテスト時に これを シミュレートする方法を説明します サブスクリプション更新の課金問題を シミュレートするため テスト中のStoreKit設定の 「エディタ」メニューを開き 「更新時の支払いの再試行」を 有効にします
Food Truckは課金猶予期間 に対応させたいので メニューの「課金猶予期間」 も有効にしましょう
購読率も速くして 状態がどう変化するか 見守ります
まずはSocial Feed+に 登録しましょう
リニューアルの時期に なるのを待ちましょう
トランザクションが終了すると 課金猶予期間状態になります トランザクションインスペクタで 各状態の終了時刻が見れます
課金猶予期間が 切れたばかりで 今は標準的な 支払いの再試行の状態です いつでも「トランザクションの問題解決」 ボタンで課金エラーを解決する シミュレーションを 行うことができます 問題を解決する テストをしましょう
問題が解決されたので 新しいトランザクションを取得します
「更新時の支払いの再試行」 を有効にしている限り 新しいトランザクションはそれぞれ 支払いの再試行に入り続けるので このテストを何度でも 繰り返すことができます 支払いの再試行と猶予期間を 適切に処理することは 不本意な解約を減らし 契約者 を維持する重要ポイントです Xcodeでこの状態を簡単に シミュレートできると分かったので StoreKitでこれらの状態を 処理する方法を説明します 支払いの再試行と猶予期間の 状態が変化すると 状態更新シーケンスが 新しい値を発行します Food Truckでは 課金猶予期間を設けるため 猶予期間中は加入者が Social Feed+に アクセスできるように する必要があります 契約者の猶予期間は 更新情報の gracePeriodExpirationDate プロパティで確認できます 支払いの再試行を確認するには isInBillingRetryで確認します
Statusのstateプロパティで 一方の状態を検出できます お客様が一方の状態に あることを確認したら 課金問題を解決するため ディープリンクでApp Storeに誘導できます 現在のエンタイトルメント APIを使用している場合 猶予期間の 期限切れのサブスクリプションの トランザクションを受信します StoreKitのテストセッションで billingGracePeriodIsEnabledと shouldEnterBillingRetryOnRenewal を設定することで ユニットテストで支払いの再試行と 猶予期間を制御できます Appが支払いの再試行に 入ったことを認識すると トランザクションのhasPurchaseIssue はtrueになります さまざまな ステータス更新を待ち Appが予想通りに 更新されたことを確認したら トランザクションの問題解決 メソッドを使用して App Storeのサブスクリプション回復を シミュレートできます 支払いの再試行と猶予期間は すべてのプラットフォームの Xcode 13.3以降で テスト可能です 後半ではPeterが iOSと iPadOS16のSandboxで この状態をテストする方法を より詳しく説明します
返金要求から支払いの再試行や 猶予期間の処理まで 高度なテストケースを カバーしました これらのケースを サポートする新しい StoreKit APIの 使用方法の詳細は 「App内課金の最新情報」 をご覧ください
以上 今年のXcodeでの StoreKit Testing新機能を ざっと紹介しましたが 網羅した訳ではありません 新しいサブスクリプションの 更新率があり XcodeでStoreKit 2の App内サブスクリプションテスト StoreKitTestを使って SKAdNetwork実装の ユニットテストが 書けるようになりました 詳しくは「SKAdNetworkの 最新情報」をご覧ください それでは Peterが 今年のSandbox テスト環境の 新機能を説明します ありがとう Greg App Storeのサーバ エンジニアのPeterです 新機能StoreKit Testingが 複雑なApp内課金の実装を テストするのに 役立つことを確認しました 私たちは常にお客様の声に 耳を傾けており 多くの皆様がApp Store Sandbox環境を利用して App内課金やサーバ実装の テストを行っています 今回は オンライン環境で より簡単にAppや サーバテストが出来る様 新しい機能拡張を紹介します Sandboxの Apple ID作成 App Store Connect APIの強化 支払い障害想定 について紹介します サンドボックス環境を 利用するためには App Store Connectで Apple IDを設定します サンドボックスの テスターリストは 「ユーザとアクセス」の ナビゲーションバーに移り プラスボタンで 新しいテスターを作成します 新しいテスターウィンドから 幾つかフィールドを削除し 作成プロセスを 効率化しました 必要最低限の情報しか 求めないため 不要な情報を入力せずに アカウント作成に 進むことができます アドレスには 「プラス記号」が使え テスター毎に新しいアドレス を作る必要はありません 強力パスワードは面倒ですが これも簡単にできます 安全性を高めるための提案も インラインで追加しています
Apple IDの 作成フォームを合理化し パスワードの複雑さの ヒントを改善することで アカウント設定時間を削減 Appの開発に 時間を割ればと思います App Store Connectは Apple IDの作成と管理 およびAppコンテンツと 組織の管理を行う 中心的な場所です ここ数年 アカウント地域の変更や 購入履歴のクリアなど みなさんからご要望を いただいていた機能を 追加しています 多くはApp Store Connect やデバイス上の サブスクリプション管理ページで アクセス可能です 今年後半には Apple IDのリスト照会 購入履歴を消去したり 購入状態を中断させるなど 機能を幾つかApp Store Connect APIに取り入れる予定です アカウントでの 高速テストが可能になり 一般的なツールの 自動化顧客設定に役立ちます 最後に 支払い障害想定への 対応をお知らせします 2018年には 不本意な 解約を減らすために 自動更新のサブスクリプションに対する 支払いの再試行と猶予期間を発表しました 2019年の発表以来 課金猶予期間は 3億日分の有料サービスを お客様に回収しました 結果 みなさんのビジネスには 収益の増加がもたらされ お客様にはサービスの中断が されることはありません 多くはすでに本番環境で 課金障害を処理していますが サンドボックスでより多くの テストシナリオを提供し App Storeで公開前に 課金障害をテストして 処理できるように したいと考えています 新しいアカウント 設定ページを使用して アカウントの 課金障害想定を有効にし Appのコンテキストで フォアやバックグラウンドの サブスクリプション障害をテスト サンドボックスの verifyReceipt App Store Server API App Storeサーバ通知 V2 でサブスクリプション状態を確認できます 支払いの再試行や不本意な解約を 減らすための詳細は 2018年のWWDC「サブスクリプション のデザイン」がお勧めです 今年はアカウント設定に App内課金失敗を 想定するスイッチを 新たに導入します Sandboxサブスクリプションページも 新たに設置されました 課金失敗想定を 有効にすると フォアグラウンドでの App内課金が失敗されます この動作は お客様の支払い方法が 拒否された場合の 動作と一致します また 課金障害想定により 自動更新の状態が 本番の課金障害時の状態と 一致することを確認します つまり 課金に 問題があるお客様に対しての App内メッセージを テストできます これらのサブスクリプション状態は V2通知で 確認したApp内課金の レシートに反映されます サブスクリプションの ライフサイクルを確認しましょう Sandboxで自動更新の サブスクリプションを購入すると SUBSCRIBEDや DID_RENEWといった V2通知を 受け取っています アクティブなサブスクリプションを 持つアカウントで App内課金の 失敗をテストすると 次の更新が 支払いの再試行状態になります DID_FAIL_TO_RENEW のような支払いの再試行通知を 受け取れるようになりました 課金障害想定を 無効にした後 サブスクリプションの更新回復の 試行を停止すると 次の更新の試行は成功し サブタイプ BILLING_RECOVERYの DID_RENEW 通知が届きます リトライの制限に達し 課金失敗想定が有効の場合 サブスクリプションは失効し サブタイプ BILLING_RETRYで EXPIRED を受け取ります すでに本番環境で Grace Periodを使用し SandboxでV2通知を 使用している場合 GRACE_PERIOD DID_FAIL_TO_RENEW 通知を受け取ることが 期待できます 以下は 支払いの再試行状態 猶予期間のあるサブスクリプション例です 猶予期間終了時に 課金障害想定が有効な場合 GRACE_PERIODとGRACE_PERIOD_EXPIRED と同様に サブタイプの DID_FAIL_TO_RENEW の通知を受け取ります App StoreサーバAPIで サブスクリプション情報を検証する場合 signedRenewalInfoの ペイロードをデコードし サブスクリプション状態を検証できます expirationIntentと 支払いの再試行のフィールドが入力されます 支払いの再試行ステートのレシートで /verifyReceiptを呼ぶと is_in_billing_retry_period フラグが 1 に設定されます 猶予期間を使用する場合 有効期限のフィールドが 入力される期待ができます Sandboxでの課金障害の テストが完了したら アカウント設定で スイッチを無効にできます 新しいテストが お客様にとって 最高のエクスペリエンスを 構築するのに 役立つことを願っています 本日は App内課金機能の テストを効率化するための いくつかの新しいテスト機能 について説明しました App Store Connectの設定を Xcodeと同期させることで ローカルやSandbox 環境のテストでも 同じApp内課金の 設定を利用できます Xcodeのオファーコードや 返金テストの新機能により 複雑なStoreKitの 実装を検証できます サブスクリプション管理のテストの 容易性でサービスが中断されても 優れた顧客体験を 保証するために Appを進化させることが 可能になります 課金失敗が サブスクリプションにどう影響するか SandboxでのApp Store サーバ通知 V2については WWDC 21の セッションをお勧めします 「サーバにおける App内課金の管理」です App Store Server APIと V2通知の新機能は 「App内課金の最新情報」 をご覧ください これらの新機能について 皆様の意見をお待ちしてます ご参加 ありがとうございました
-
-
6:58 - Subscription option view
VStack(alignment: .leading) { Text(subscription.displayName) .font(.headline.weight(.semibold)) Text(subscription.description) }
-
11:18 - Refund view
struct RefundView: View { @State private var selectedTransactionID: UInt64? @State private var refundSheetIsPresented = false @Environment(\.dismiss) private var dismiss var body: some View { Button { refundSheetIsPresented = true } label: { Text("Request a refund") .bold() .padding(.vertical, 5) .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .padding([.horizontal, .bottom]) .disabled(selectedTransactionID == nil) .refundRequestSheet( for: selectedTransactionID ?? 0, isPresented: $refundSheetIsPresented ) { result in if case .success(.success) = result { dismiss() } } } }
-
12:33 - Refunds emit an updated value from the transaction updates sequence
for await update in Transaction.updates { let transaction = try update.payloadValue if let revocationDate = transaction.revocationDate, let revocationReason = transaction.revocationReason { print("\(transaction.productID) revoked on \(revocationDate)") switch revocationReason { case .developerIssue: <#Handle developer issue#> case .other: <#Handle other issue#> default: <#Handle unknown reason#> } <#Revoke access to the product#> } <#...#> }
-
14:21 - Offer code view
struct SubscriptionPurchaseView: View { @State private var redeemSheetIsPresented = false var body: some View { Button("Redeem an offer") { redeemSheetIsPresented = true } .buttonStyle(.borderless) .frame(maxWidth: .infinity) .padding(.vertical) .offerCodeRedeemSheet(isPresented: $redeemSheetIsPresented) } }
-
for await verificationResult in Transaction.updates { guard case .verified(let transaction) = verificationResult else { <#Handle failed verification#> } <#Handle updated transaction#> } for await updatedStatus in Product.SubscriptionInfo.Status.updates { guard case .verified(let renewalInfo) = updatedStatus.renewalInfo else { <#Handle failed verification#> } <#Handle updated status#> }
-
16:31 - Check the active offer on the transaction value
for await status in Product.SubscriptionInfo.Status.updates { let transaction = try status.transaction.payloadValue let renewalInfo = try status.renewalInfo.payloadValue if let currentOfferType = transaction.offerType { switch currentType { case .introductory: <#Handle introductory offer#> case .promotional: <#Handle promotional offer#> case .code: <#Handle offer for codes#> default: <#Handle unknown offer type#> } self.hasCurrentOffer = true } <#...#> }
-
16:49 - Check the next pending offer on the renewal info value
for await status in Product.SubscriptionInfo.Status.updates { let transaction = try status.transaction.payloadValue let renewalInfo = try status.renewalInfo.payloadValue <#Check active current offer#> if let nextOfferType = renewalInfo.offerType { switch currentType { case .introductory: <#Handle introductory offer#> case .promotional: <#Handle promotional offer#> case .code: print("Customer has \(renewalInfo.offerID) queued") <#Handle offer for codes#> default: <#Handle unknown offer type#> } self.hasQueuedOffer = true } <#...#> }
-
18:45 - Messages updates loop
private var pendingMessages: [Message] = [] private func updatesLoop() { for await message in Message.messages { if <#Check if sensitive view is presented#>, let display: DisplayMessageAction = <#Get display message action#> { try? display(message) } else { pendingMessages.append(message) } } }
-
20:53 - Price increase changes emit an updated value from the status updates sequence
for await status in Product.SubscriptionInfo.Status.updates { let renewalInfo = try status.renewalInfo.payloadValue if renewalInfo.priceIncreaseStatus == .agreed { print("Customer consented to price increase") <#Handle consented to price increase#> } if renewalInfo.expirationReason == .didNotConsentToPriceIncrease { print("Customer did not consent to price increase") <#Handle expired due to not consenting to price increase#> } <#...#> }
-
21:19 - Unit testing price increases
let session: SKTestSession = try SKTestSession(configurationFileNamed: "<#Configuration name#>") session.disableDialogs = true <#Purchase a subscription#> var transaction: SKTestTransaction! = session.allTransactions().first session.requestPriceIncreaseConsentForTransaction(identifier: transaction.identifier) transaction = session.allTransactions().first XCTAssertTrue(transaction.isPendingPriceIncreaseConsent) <#Assert app updates for pending price increase#> // Write a test case for consenting and cancelling due to price increase: session.consentToPriceIncreaseForTransaction(identifier: transaction.identifier) // OR session.declinePriceIncreaseForTransaction(identifier: transaction.identifier) session.expireSubscription(productIdentifier: "<#Product ID#>") <#Assert app updates for finished price increase#>
-
24:57 - Billing retry and grace period status changes emit an updated value from the status updates sequence
for await status in Product.SubscriptionInfo.Status.updates { let renewalInfo = try status.renewalInfo.payloadValue if let gracePeriodExpirationDate = renewalInfo.gracePeriodExpirationDate, gracePeriodExpirationDate < .now { print("In grace period until \(gracePeriodExpirationDate)”) <#Allow access to subscription#> } else if renewalInfo.isInBillingRetry { <#Handle billing retry#> } <#...#> }
-
25:27 - Using the state property of a status value to check for billing retry states
struct SubscriptionStatusView: View { let currentSubscription: Product let status: Product.SubscriptionInfo.Status @Environment(\.openURL) var openURL var body: some View { Section("Your Subscription") { <#...#> if status.state == .inBillingRetryPeriod || status.state == .inGracePeriod { VStack { Text(""" There was a problem renewing your subscription. Open the App Store to update your payment information. """) Button("Open the App Store") { openURL(URL(string: "https://apps.apple.com/account/billing")!) } } } } } }
-
25:41 - Current entitlement APIs will account for grace period
for await entitlement in Transaction.currentEntitlements { <#Grant access to product#> }
-
25:50 - Unit testing billing retry and grace period
let session: SKTestSession = try SKTestSession(configurationFileNamed: "<#Configuration name#>") session.billingGracePeriodIsEnabled = true session.shouldEnterBillingRetryOnRenewal = true <#Purchase a subscription#> wait(for: [<#XCTExpectation#>], timeout: 60) let transaction: SKTestTransaction! = session.allTransactions().first XCTAssertTrue(transaction.hasPurchaseIssue) <#Assert app still allows access to subscription due to grace period#> wait(for: [<#XCTExpectation#>], timeout: 60) <#Assert app detects billing retry and no longer allows access to subscription#> session.resolveIssueForTransaction(identifier: transaction.identifier) <#Assert app allows access to subscription#>
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。