WWDC Quick Look 💓 By SwiftGGTeam
What's new in StoreKit and In-App Purchase

What's new in StoreKit and In-App Purchase

元の動画を見る

ハイライト

StoreKit の取引履歴 API がついに完了した消耗品の取引をカバーします。TransactionRenewalInfo に新しい currencyprice フィールドが追加されました。従来の In-App Purchase API は iOS 18 で正式に非推奨とマークされました。


主要内容

アプリ内課金機能を開発する際、消耗品の購入記録の追跡は常に頭の痛い問題でした。ユーザーが 100 コインを購入し、消費して、取引が finish されると、その記録は取引履歴から消えてしまいます。「購入履歴」を表示したり、消耗品の重複購入防止を実装したりする必要がある場合、独自のデータベースを構築して追跡する必要がありました。iOS 18 はついにこの欠落を補いました:取引履歴 API が完了した消耗品の取引を含めるようになりました。Info.plist で SKIncludeConsumableInAppPurchaseHistorytrue に設定するだけで、フレームワークは finish 済みかどうかに関わらず、すべての消耗品の取引をクエリ結果に含めます。

もう一つの長年の痛点は価格表示です。App Store Connect で製品の価格を設定できますが、アプリ内では具体的な金額や通貨情報を直接取得できず、サーバー側の中継やハードコーディングに頼るしかありませんでした。iOS 18 は Transactioncurrencyprice を、RenewalInfocurrencyrenewalPrice を追加しました。これらのフィールドは Swift の @backDeployed 属性を通じて iOS 15 まで下位互換があり、古いシステムでも使用できます。

UI 面では、WWDC23 で導入された StoreKit Views が今年大幅に拡張されました。サービスレベル別にサブスクリプションオプションをグループ化する SubscriptionOptionGroup が追加され、compactPicker / pagedPicker / pagedProminentPicker の 3 つの新しいコントロールスタイルが追加されました。購入ボタンの位置を制御する placement API が追加され、SubscriptionStoreControlStyle プロトコルがオープンになり、完全にカスタムなサブスクリプションストアコントロールを作成できるようになりました。Win-back offers は今年新しく導入されたサブスクリプションオファータイプで、特に離脱したユーザーを対象としており、Apple の編集チームによって App Store の Today / Games / Apps タブでフィーチャーされる可能性があります。


詳細

完了した消耗品の取引が履歴 API に含まれる(00:40

以前は、Transaction.currentEntitlementsTransaction.all はサブスクリプション、非消耗品、および未完了の消耗品の取引のみを返していました。iOS 18 から、Info.plist で以下を設定します:

<key>SKIncludeConsumableInAppPurchaseHistory</key>
<true/>

その後、通常の Transaction.updates リスナーと Transaction.all クエリに完了した消耗品の取引が含まれるようになり、追加のコードは必要ありません。

Transaction と RenewalInfo の新しいフィールド(01:32

  • Transaction.currency:購入時に使用された通貨コード(例: "USD"
  • Transaction.price:App Store Connect で設定された価格
  • RenewalInfo.currency:更新時の通貨コード
  • RenewalInfo.renewalPrice:次回更新で請求される金額

これらのフィールドは、Swift の @backDeployed 属性を通じて Xcode 16 でコンパイル時に使用可能になり、実行時の最低サポートは iOS 15 です。

SubscriptionOptionGroup と compactPicker スタイル(04:26

公式が提供する Destination Video サブスクリプションストアのサンプルコード:

import StoreKit
import SwiftUI

struct DestinationVideoShop: View {

  var body: some View {
    SubscriptionStoreView(groupID: Self.subscriptionGroupID) {
      SubscriptionOptionGroupSet { product in
        StreamingPassLevel(product)
      } label: { streamingPassLevel in
        Text(streamingPassLevel.localizedTitle)
      } marketingContent: { streamingPassLevel in
        StreamingPassMarketingContent(level: streamingPassLevel)
        StreamingPassFeatures(level: streamingPassLevel)
      }
    }
    .subscriptionStoreControlStyle(.compactPicker, placement: .bottomBar)
  }

}

キーポイント:

  • SubscriptionOptionGroupSet はクロージャが返す列挙値(ここでは StreamingPassLevel)に基づいて自動的にグループ化します。各一意の値がグループを生成します
  • label クロージャはグループタイトル(例:「Premium」/「Basic」)を提供し、フレームワークは自動的にタブ切り替えとしてレンダリングします
  • marketingContent クロージャは、現在アクティブなグループに基づいて異なるマーケティングビューを返します
  • .compactPicker スタイルはサブスクリプションオプションを 1 行の横方向セレクターに圧縮します。placement: .bottomBar はコントロールをボトムバーに固定し、マーケティングコンテンツをスクロールしても常に表示されます

グループスタイルの切り替え(09:06

デフォルトの .tabs スタイルに加えて、.links スタイルも使用できます:

.subscriptionStoreOptionGroupStyle(.tabs)   // タブ切り替え、異なるサービスレベルに適している
.subscriptionStoreOptionGroupStyle(.links)   // ナビゲーションリンク、より多くのオプションをシートで展開する場合に適している

カスタムコントロールスタイル(13:41

iOS 18 は SubscriptionStoreControlStyle プロトコルをオープンにしました。SubscriptionPickerSubscribeButton プリミティブと組み合わせることで、ゼロから独自のコントロールを構築できます:

import StoreKit
import SwiftUI

struct BadgedPickerControlStyle: SubscriptionStoreControlStyle {
  func makeBody(configuration: Configuration) -> some View {
    SubscriptionPicker(configuration) { pickerOption in
      HStack(alignment: .top) {
        VStack(alignment: .leading) {
          Text(pickerOption.displayName)
            .font(title2.bold())
          Text(priceDisplay(for: pickerOption))
          if pickerOption.isFamilyShareable {
            FamilyShareableBadge()
          }
          Text(pickerOption.description)
        }
        Spacer()
        SelectionIndicator(pickerOption.isSelected)
      }
    } confirmation: { option in
      SubscribeButton(option)
    }
  }
}

struct DestinationVideoShop: View {

  var body: some View {
    SubscriptionStoreView(groupID: Self.subscriptionGroupID) {
      SubscriptionPeriodGroupSet { _ in
        StreamingPassMarketingContent()
      }
    }
    .subscriptionStoreControlStyle(BadgedPickerControlStyle())
  }

}

キーポイント:

  • SubscriptionStoreControlStyle プロトコルに準拠するには、makeBody メソッドだけを実装すればよい
  • configuration にはコントロールを構築するために必要なすべてのデータが含まれている
  • SubscriptionPicker はフレームワークが提供するプリミティブで、選択状態を管理する
  • pickerOption.displayName / .isFamilyShareable / .description などのプロパティは、製品メタデータから直接読み取られる
  • SubscribeButton(option) はフレームワークが提供する購入ボタンのプリミティブで、タップすると購入フローがトリガーされる
  • SubscriptionPeriodGroupSet はサブスクリプション期間でグループ化する便利な API

Win-back offers(02:34

Win-back offers は、サブスクリプションをキャンセルした離脱ユーザー向けです。App Store Connect で資格ルールを設定でき、アプリ内にハードコードされたチェックを書く必要はありません。表示方法は 2 つあります:StoreKit Message API を介してアプリ内にポップアップ表示する(追加のコードは不要)、または Apple の編集チームによって App Store の Today / Games / Apps タブでフィーチャー表示される。

Xcode テストの強化(15:38

  • StoreKit 設定に App Policies セクションが追加され、プライバシーポリシーと利用規約のローカルテストが可能
  • サブスクリプショングループの表示名のローカライズテストをサポート
  • アプリ内課金画像のテストが可能(ProductViewprefersPromotionalIcon を介して)
  • Dialogs 設定でシステムアラートを無効にでき、UI 自動化テストを容易にする
  • Transaction Manager が Purchase Intent の送信をサポートし、App Store プロモーション購入をテストできる
  • 課金問題メッセージのテストをサポートし、サブスクリプション更新失敗シナリオをシミュレートできる

従来の In-App Purchase API の非推奨化(21:06

iOS 18 から、SKPaymentQueue などの従来の API は非推奨とマークされ、ユニファイドレシートも同様に非推奨となります。既存のアプリは影響を受けずに引き続き実行できますが、これらの API は新しい機能を受け取らなくなります。StoreKit 2 が唯一、継続的に更新されるパスです。


重要ポイント

  • 何をするかcurrencyprice フィールドを使って、サーバー側の価格クエリを代替する。なぜ価値があるか:ネットワークリクエストを 1 回減らし、価格表示をよりリアルタイムにし、ユーザーが地域を切り替えてもサーバーの同期を待つ必要がない。どう始めるか:価格を表示する SwiftUI ビューで、Transaction から currencyprice を読み取り、NumberFormatter で表示をフォーマットする。更新価格も同様に RenewalInfo.renewalPrice から取得する。

  • 何をするかSubscriptionOptionGroupSet を使ってサブスクリプションストア UI をリファクタリングする。なぜ価値があるか:手書きのグループ化ロジックを宣言的なコードで代替し、タブ切り替えとマーケティングコンテンツの連動はフレームワークが処理するため、コード量が大幅に減少する。どう始めるか:サービスレベルを表す列挙型(例:premium / basic)を定義し、SubscriptionOptionGroupSet のクロージャでその列挙値を返す。フレームワークは自動的に値でグループ化し、タブまたはリンクスタイルをレンダリングする。

  • 何をするかSKIncludeConsumableInAppPurchaseHistory を有効にし、独自の消耗品購入追跡ロジックを削除する。なぜ価値があるか:ローカルデータベースのメンテナンスコストを省き、Transaction.all が直接完全な消耗品履歴を返すため、返金と重複防止のロジックがよりシンプルになる。どう始めるか:Info.plist にこのキーを追加して true に設定し、Transaction.updates をリッスンして完了した消耗品の取引を受信する。

  • 何をするか:離脱ユーザー向けに win-back offers を統合する。なぜ価値があるか:Apple が App Store の編集フィーチャリング枠であなたのオファーを表示し、無料のトラフィックを獲得できる。資格ルールは App Store Connect で設定され、アプリの更新は不要。どう始めるか:App Store Connect で win-back offer を作成して資格条件を設定し、StoreKit Message API を介してアプリ内で表示するか、サーバー側で App Store Server API を介して配布する。


関連セッション

コメント

GitHub Issues · utterances