ハイライト
App Store Server API は今年、ONE_TIME_CHARGE 通知がすべてのワンタイム購入をカバーするよう追加され、CONSUMPTION_REQUEST は自動更新サブスクリプションの返金にも拡張されました。また、Get Transaction History V2 では、顧客の完全な取引履歴が返されるようになりました。
主要内容
多くの開発者は、内購の検証をクライアント側で行ったままです。StoreKit で取引を受け取り、ローカルで finish して終わり。問題は、クライアントデータは改ざん可能であり、デバイスがオフラインの可能性があり、返金はユーザーがアプリを使用していないときに発生するため、サーバーはこれらのイベントを把握できないということです。結果として、ユーザーが返金されても権利を回収できず、更新が発生しても適時に開通できず、消耗型商品の残高が合わなくなります。
App Store Server はまさにこの情報の乖離を解消するために存在します。3つの部分で構成されています。App Store Server API は、サーバーが App Store に対して能動的に問い合わせるためのものです(12のエンドポイントで、取引履歴、消費情報の送信、通知管理をカバー)。App Store Server Notifications V2 は、App Store がサーバーに能動的に通知するためのものです(サブスクリプションの更新、返金、価格変更などのイベント)。App Store Server Library(Java / Python / Node.js / Swift)は、署名検証、データデコード、プロモーションオファー署名などのロジックをすぐに使えるインターフェースとしてラップし、GitHub でオープンソースとして公開されています。
今年のアップデートは3つの方向に集中しています。第一に、購入ライフサイクル:新しい ONE_TIME_CHARGE 通知タイプが追加され、消耗型、非消耗型、および非更新サブスクリプションのワンタイム購入をカバーします。既存のサブスクリプション通知と合わせることで、サーバーはアプリ内のすべての取引の通知を受け取れるようになりました。第二に、返金への参加:CONSUMPTION_REQUEST 通知は消耗型から自動更新サブスクリプションに拡張され、ユーザーが申告した返金理由を示す consumptionRequestReason フィールドが新たに追加されました。また、Send Consumption Information を呼び出す際に refundPreference を送信して、返金に同意するかどうかを表現できます。第三に、取引履歴:Get Transaction History エンドポイントが V2 にアップグレードされ、製品タイプや完了状態に関係なく、顧客のすべての取引を返すようになりました。
詳細
消費情報の送信フロー(07:02)
ユーザーが消耗型アプリ内課金の返金を申請すると、App Store はサーバーに CONSUMPTION_REQUEST 通知を送信します。12時間以内に Send Consumption Information エンドポイントを呼び出して消費データを送信する必要があります。以下は、App Store Server Library(Java)を使用した実装例です。
// 1. 検証器と API クライアントを作成
SignedDataVerifier verifier = new SignedDataVerifier(/* 設定パラメータ */);
AppStoreServerAPIClient apiClient = new AppStoreServerAPIClient(/* 設定パラメータ */);
// 2. 受信した通知を検証・デコード
DecodedSignedData notification = verifier.verifyAndDecodeNotification(signedNotification);
// 3. 通知タイプを判定
if (notification.getNotificationType() == NotificationTypeV2.CONSUMPTION_REQUEST) {
// 4. 取引情報を抽出・検証
String signedTransactionInfo = notification.getData().getSignedTransactionInfo();
JWSVerificationHeader header = verifier.verifyAndDecodeTransaction(signedTransactionInfo);
String transactionId = header.getTransactionId();
// 5. 消費情報リクエストを構築(今年追加の refundPreference フィールド)
ConsumptionRequest consumptionRequest = new ConsumptionRequest();
consumptionRequest.setCustomerConsumedValue("SAMPLE_CONTENT_PROVIDED");
consumptionRequest.setDeliveryStatus("DELIVERED");
consumptionRequest.setRefundPreference(determineRefundPreference(
notification.getData().getConsumptionRequestReason(), header));
// 6. 消費情報を送信
apiClient.sendConsumptionInformation(transactionId, consumptionRequest);
}
キーポイント:
SignedDataVerifierは、データの出所がデバイス、API、通知のいずれであっても、すべての署名データの検証とデコードを統一的に処理します(02:59)consumptionRequestReasonは今年追加されたフィールドで、ユーザーが申告した返金理由(例:「UNINTENTIONAL_PURCHASE」)を含み、これに基づいてrefundPreferenceを決定できます(11:15)refundPreferenceを使用すると、PREFER_GRANT(返金に同意)またはPREFER_DENY(返金を拒否)の意向を表現でき、Apple はこれを最終的な返金判断に組み入れます(12:06)- 今年から、CONSUMPTION_REQUEST 通知は自動更新サブスクリプションの返金リクエストにも送信されるようになり、消耗型アプリ内課金に限定されなくなりました(11:02)
取引履歴の取得 V2(16:42)
// 任意の transactionId を使用して、顧客の完全な取引履歴を取得
String transactionId = "existing_transaction_id";
GetTransactionHistoryRequest request = new GetTransactionHistoryRequest()
.setSort(Order.ASCENDING)
.setProductTypes(/* オプションのフィルタ */);
HistoryResponse response = apiClient.getTransactionHistory(transactionId, null, request, "v2");
// バージョンパラメータ ↑
キーポイント:
- V1 バージョンは、返金済み、取り消し済み、またはデバイス上で未完了の消耗型取引のみを返していました。V2 は、製品タイプ、返金状態、完了状態に関係なく、顧客のすべての取引を返します(18:52)
- エンドポイントはページネーション対応で、
revisionパラメータを使用して前回のリクエスト位置を追跡し、後続のリクエストでは新規または更新された取引のみを取得します(17:24) - 移行は、URL パスのバージョンを V2 に変更し、完了済みの消耗型取引の処理に備えるだけです(以前は出現しませんでした)(19:50)
- V1 は非推奨としてマークされています(19:44)
取引情報と更新情報の新フィールド(25:25)
TransactionInfo に追加されたフィールド:
price:購入時の表示価格。通貨のミリ単位で表現されます(例:4990 = $4.99)currency:購入時に使用された通貨コード(例:「USD」)offerDiscountType:購入にオファーが使用された場合、その支払いモードを識別します:FREE_TRIAL、PAY_UP_FRONT、またはPAY_AS_YOU_GO
RenewalInfo に追加されたフィールド:
renewalPrice:次回更新時の予想表示価格currency:更新価格の通貨コードofferDiscountType:次回更新時に予想されるオファーの支払いモード
これらのフィールドにより、サーバー側でユーザーが現在支払っている価格と次回更新の価格を判断できるようになり、App Store Connect を参照する必要がなくなりました。例えば、PAY_UP_FRONT オファーの初期価格が $4.99 で、更新後の renewalPrice が $9.99 と表示された場合、これに基づいてそのユーザーにプロモーションオファーを送信するかどうかを決定できます。
重要ポイント
-
やること:ONE_TIME_CHARGE 通知を導入し、サーバー側ですべてのワンタイム購入を追跡する 価値ある理由:これまでは消耗型および非消耗型の購入にサーバー側の通知がなく、クライアント側の報告に依存していましたが、デバイスがオフラインまたは改ざんされると取引が漏れていました 始め方:App Store Connect で Server Notifications V2 を有効にし、通知処理ロジックに
ONE_TIME_CHARGEタイプの分岐を追加し、通知内のappAccountTokenを使用してユーザーを直接紐付け、コンテンツを付与する -
やること:CONSUMPTION_REQUEST のフルフローを実装し、サブスクリプションの返金に対する消費情報の送信と refundPreference を含める 価値ある理由:現在、サブスクリプションの返金でも CONSUMPTION_REQUEST がトリガーされるようになりました。
consumptionRequestReasonとユーザーの過去の消費データに基づいて返金に同意するかどうかを決定でき、返金判断の結果に直接影響します 始め方:既存の消費情報送信コードにサブスクリプション取引の処理分岐を追加し、refundPreferenceフィールド(PREFER_GRANT/PREFER_DENY)を設定し、determineRefundPreferenceメソッドを使用して返金理由と取引データを総合的に判断する -
やること:Get Transaction History V2 へ移行する 価値ある理由:V1 は一部の消耗型取引のみを返していましたが、V2 は完全な履歴を返すため、購入記録の表示、サーバー側の権利監査、消耗型残高の照合が可能になります 始め方:API パスのバージョンを V1 から V2 に変更し、各リクエストで返された
revisionを保存して増分同期に使用し、完了済みの消耗型取引(以前は出現しませんでした)の処理に注意する -
やること:price / currency / offerDiscountType の新フィールドを活用して、正確なプロモーションオファー施策を構築する 価値ある理由:これらのフィールドにより、ユーザーが現在および次回更新で実際に支払う価格が分かるため、どのユーザーに割引オファーを送信するのが適切か判断できます(例:低価格のままのユーザーには、上位製品へのアップグレードを促す) 始め方:transactionInfo と renewalInfo をデコードした後にこれら3つの新フィールドを抽出し、ユーザーの価格プロファイルを構築し、
DID_CHANGE_RENEWAL_STATUS/AUTO_RENEW_DISABLED通知を受信した際にオファー評価ロジックをトリガーする
関連セッション
- What’s new in StoreKit and In-App Purchase — StoreKit のクライアント側の内購新機能。本文のサーバー側の視点と補完関係にあります。
- Meet AdAttributionKit — 新しいプライバシー保護型広告アトリビューションフレームワーク。内購のコンバージョン追跡と関連します。
- What’s new in App Store Connect — App Store Connect の新機能。推薦ノミネーションやマーケティングアセットの生成を含みます。
コメント
GitHub Issues · utterances