Highlight
本セッションは、App Store サーバ API における 3 つの中核的な役割、すなわち In-App Purchase の管理、リクエストへの署名、そして返金判断への関与を軸に展開されます。スピーカーの Riyaz が、今年の実質的なアップデートを順を追って紹介していきます。
核心となる内容
サブスクリプション系アプリのサーバサイドエンジニアであれば、誰もが一度はこの問題に直面したことがあるはずです。たとえば、ユーザーが同じアプリ内で月額のニュース購読と年額のスポーツ観戦サブスクリプションを同時に契約した場合、それぞれのサブスクリプションは独自の transactionId と originalTransactionId を持ちます。これら 2 つのサブスクリプションが「同一の顧客アカウントに紐づくものである」と自社システム側で判定するには、別途マッピングテーブルを保持しなければなりませんでした。さらに Family Sharing 経由のトランザクションでは appAccountToken を取得することができません。アプリの外側で購入が完了した場合、たとえば offer code の引き換えや App Store からの promoted purchase などでは、そもそも StoreKit 側で appAccountToken を設定する手段すらありませんでした。
今回 Apple が用意したのが、ワンストップで使える識別子 appTransactionId です。これは Apple Account ごと、アプリごとにグローバルで一意な値となっており、AppTransaction、JWSTransaction、JWSRenewalInfo の 3 つのオブジェクトに含まれます。redownload、refund、repurchase、storefront の切り替えをまたいでも値が安定して保持されます(08:30)。Family Sharing の場合、家族メンバーごとにそれぞれ独立した appTransactionId が割り当てられ、ファミリー共有のトランザクションについても同 ID で関連付けが可能です。これは appAccountToken では実現できなかった点です。あわせて新設された Set App Account Token エンドポイントを利用すれば、すでに発生したトランザクションのうち、当時はトークンを設定できなかったものに対しても、後からトークンを設定したり更新したりすることができるようになります。
詳細
3 つのトランザクション識別子の役割分担(06:52)
transactionId: 1 件の具体的な購入イベントを識別する ID(購入、復元、更新それぞれに別の値が割り当てられます)。originalTransactionId: 自動更新サブスクリプションのライフサイクル全体に紐づくキー。更新をまたいでも変わりません。appAccountToken: 開発者側で生成する UUID。App Store のトランザクションと自社の顧客アカウントを紐づけるために使用します。appTransactionId: 今年新たに追加されたフィールド。Apple Account ごと、アプリごとに一意で、すべてのトランザクションオブジェクトを横断して安定した値となります。
Set App Account Token エンドポイント(05:11)。path に originalTransactionId を、body に新しい UUID を渡すことで、対象のトランザクション(サブスクリプションの場合は最新のトランザクション)に紐づく appAccountToken を上書きします。自動更新サブスクリプションでは、新たに設定したトークンが以降の更新、アップグレード、ダウングレードにも引き継がれます。すべてのプロダクトタイプに対応しており、アカウントの所有者変更などによる紐づけのずれを修正する用途にも利用できます。
Get App Transaction Info エンドポイント(10:25)。サーバサイドから直接 AppTransaction を取得することが、初めて可能になりました。デバイスを経由する必要はありません。path には originalTransactionId、transactionId、appTransactionId のいずれかを渡すことができ、レスポンスは JWS 形式の signedAppTransactionInfo です。注意点として、このエンドポイントは device verification 関連のフィールドを返さないため、それらは引き続きデバイス側で取得する必要があります。
統一された JWS 署名フォーマット(12:17)。promotional offer、introductory offer、そして Advanced Commerce API を含む、すべての署名シーンが JWS に統一されました。以下は App Store Server Library(Java)を使って promotional offer の署名を生成するコード例です(13:48)。
PromotionalOfferV2SignatureCreator signatureCreator =
new PromotionalOfferV2SignatureCreator(privateKey, keyId, issuerId, bundleId);
String transactionId = "..."; // 任意。指定すると、この offer はその顧客専用になる
String productId = "com.example.subscription";
String offerId = "PROMO_OFFER_2025";
String signedJws = signatureCreator.createSignature(productId, offerId, transactionId);
ポイントは以下のとおりです。
PromotionalOfferV2SignatureCreatorは、App Store Connect からダウンロードした秘密鍵、keyId、issuerId、bundleId を渡してインスタンス化します。これら 4 つの値は、開発者以外の第三者が promo offer を発行することを防ぐために用いられます。transactionIdはオプションです。当該顧客に紐づく任意のトランザクション識別子(appTransactionIdを含む)を渡すと、その offer をその顧客限定にロックできます。省略した場合は顧客を限定しません。- 新しい署名は、旧バージョンに比べて入力が大幅に少なくなっています。旧バージョンは promotional offer signature という独自形式でしたが、今年の V2 では JWS の採用によりフィールド数がさらに絞り込まれています。
createSignatureの戻り値が、そのまま StoreKit に渡せる JWS 文字列となります。
Send Consumption Information V2(15:55)。返金判断のエンドポイントは、フィールド数が 12 から 5(必須 3 項目 + 任意 2 項目)まで削減されました。
customerConsented(必須): 消費データを App Store に送信することにユーザーが同意しているか。falseを設定するとリクエストは拒否されます。同意が取れていない場合は、そもそもこのエンドポイントを呼び出さないでください。sampleContentProvided(必須): 購入前にサンプルコンテンツを提供したかどうか。deliveryStatus(必須): コンテンツが正しく配信されたか。DELIVEREDまたは対応するUNDELIVERED系のステータスを指定します。refundPreference(任意): 新たにGRANT_PRORATEDが追加され、full / no refund と合わせた 3 択になりました。consumptionPercentage(任意。ただしGRANT_PRORATEDを選択し、かつプロダクトが consumable / non-consumable / non-renewing subscription の場合は必須): ミリ百分率で表現します。たとえば25000は 25% を意味します。
REFUND 通知に追加されたフィールド(20:00)
refundPercentage: 実際の返金比率。たとえば75は 75% 返金されたことを表します。revocationType:REFUND_FULL、REFUND_PRORATED、FAMILY_REVOKEのいずれか。REFUND_PRORATEDの場合は対応する比率分のみコンテンツを回収します(仮想通貨であれば比率に応じて減算するなど)。REFUND_FULLとFAMILY_REVOKEの場合は即座に全量を回収します。auto-renewable subscription における比率返金は、全額返金と同じロジックで処理して問題ありません。現在のサブスクリプションのステータスを取得し、それに応じたアクションを実行してください。
V1 エンドポイントは deprecated となりましたが、引き続きリクエストは受け付けます。V2 と Get App Transaction Info エンドポイントは、いずれも本年の後半に提供開始予定です(18:48)。
学びのポイント
1. appTransactionId を顧客アカウント紐づけの主インデックスとして利用する
なぜ取り組む価値があるか: プロダクトをまたいで(複数のサブスクリプション + In-App Purchase)、Family Sharing をまたいで、storefront をまたいでも、同一の Apple Account ごと、アプリごとに安定して紐づけることができます。originalTransactionId は単一のサブスクリプショングループ内しかカバーできず、appAccountToken は family shared なトランザクションでは取得できません。
進め方: アプリ起動時に AppTransaction.appTransactionId を取得し、サーバ側に送信して顧客アカウントへ紐づけます。以降、JWSTransaction や JWSRenewalInfo を受け取った際には、同フィールドをキーにテーブルを引きます。Get App Transaction Info エンドポイントが利用可能になれば、アプリからの報告に依存せず、サーバ側から直接問い合わせる構成も取れます。
2. Set App Account Token エンドポイントでアプリ外購入の帰属を補完する
なぜ取り組む価値があるか: ユーザーが offer code を引き換えるケースや、App Store 上での promoted purchase では StoreKit が介在せず、これまではこれらのトランザクションに appAccountToken を付与する手段がありませんでした。アカウントの統合や所有者変更といった事象も日常的に発生します。
進め方: originalTransactionId 単位で新しいトランザクションを検知した際、appAccountToken が付いていない、もしくは現在のアカウントと一致しない場合に Set App Account Token を呼び出して修正します。自動更新サブスクリプションについては一度設定すれば十分で、トークンは以降の更新、アップグレード、ダウングレードにも引き継がれます。
3. V2 の返金エンドポイントを採用し、prorated 処理を実装する
なぜ取り組む価値があるか: V2 ではフィールドが 12 から 5 へと大幅に削減され、組み込みコストが大きく下がります。GRANT_PRORATED と refundPercentage を組み合わせることで、消費済みコンテンツ(仮想通貨を比率に応じて減算するなど)を精度よく回収でき、ユーザー体験を保ちつつ会計上の損失も抑えられます。
進め方: App Store Server Library から Send Consumption Information V2 を呼び出し、必須 3 項目を確実に埋めたうえで、シーンに応じた prorated の preference を返します。REFUND 通知側では revocationType と refundPercentage を読み取り、REFUND_PRORATED のケースには比率回収のロジックを個別に用意します。
4. すべてのプロダクトタイプで返金通知を受け取る
なぜ取り組む価値があるか: V2 では non-consumable と non-renewing subscription もサポート対象に加わりました。これらのプロダクトタイプを取りこぼすと、該当する返金が発生してもサーバ側で何もアクションが取られず、帳簿と実際の権利状態が乖離してしまいます。
進め方: CONSUMPTION_REQUEST と REFUND の処理分岐を改めて見直し、すべてのプロダクトタイプに対応する回収ロジックを用意します。REFUND_DECLINED に対しては特段のアクションは不要ですが、後追い調査のために記録だけは残しておきましょう。
5. 統一された JWS 署名で署名サービスをシンプルに保つ
なぜ取り組む価値があるか: promotional offer、introductory offer、Advanced Commerce API の署名フォーマットが統一されたことで、サーバ側はひとつの署名生成ロジックを保守するだけで済むようになりました。
進め方: App Store Server Library が提供する PromotionalOfferV2SignatureCreator などのクラスを直接利用して JWS を生成し、自前で署名を組み立てるのは避けてください。秘密鍵、keyId、issuerId は App Store Connect から取得し、bundleId はプロジェクト設定から取得します。
関連セッション
- What’s new in StoreKit and In-App Purchase — 本年の StoreKit および In-App Purchase 全体のアップデート。本セッションで触れられたクライアント側の変更点はこちらで詳しく解説されています。
- Explore App Store server APIs for In-App Purchase — WWDC24 のサーバ API 入門編。本セッションの前提知識として併読すると効果的です。
- Discover Apple-Hosted Background Assets — サーバ側で運用するアセット配信のもうひとつの軸。In-App Purchase のデータと組み合わせれば、差別化された体験を設計できます。
- Enhance child safety with PermissionKit — 未成年者の購入シナリオに関わるコンプライアンス周りの整備。返金処理と連動して扱う場面が多くあります。
コメント
GitHub Issues · utterances