すべての App Store 購入に appAccountToken を付与せよ。さもなければ、その返金には反論できない
顧客が返金を申請すると、Apple はあなたのサーバーに CONSUMPTION_REQUEST を送ります。しかしそのトランザクションには、相手が誰なのかがまったく記されていません。appAccountToken は、購入をあなたのユーザーへとひも付ける UUID です。これを設定すれば、Apple に実データで答えられます。省けば、あとは推測するしかありません。

要点
- appAccountToken は、App Store の購入に付与する UUID です。生成されたトランザクションが、あなた自身のシステム内の正確なユーザーを指し示すようにします。Apple はそれをトランザクションに保存し、そのトランザクションが現れるあらゆる場所で返します。
- Apple が強制する唯一の形式ルールは、その値が有効な UUID であることです。それ以外のもの、id、メールアドレス、連結した文字列などを渡すと、StoreKit は黙ってそれを破棄し、appAccountToken を nil として返します。
- StoreKit 2 では、購入オプション Product.PurchaseOption.appAccountToken(_:) を一つ指定して設定します。そのアカウント用に生成し保存した、安定した UUID を使います。
- 最初の購入時に一度設定すれば、Apple はサブスクリプションチェーン内のすべての更新、請求リトライ、アップグレードにわたって同じトークンを引き継ぎます。
- 2025 年以降、Set App Account Token エンドポイントにより、アプリ内フローでは決して到達できなかった、アプリ外で行われた購入(オファーコードの引き換えやプロモーション購入など)にも、サーバーからトークンを付与できるようになりました。
- appAccountToken こそが、Apple の CONSUMPTION_REQUEST に答えられるようにするものです。これがなければ、12 時間の枠内で、あなたが利用状況を説明すべき顧客へと返金をひも付けることができません。
- 識別できない返金は、反論できない返金です。留められるはずの証拠があった購入について返金してしまい、さらにそれらを提供するためにすでに費やした計算リソース、API 呼び出し、支払いまで失います。
顧客が Apple に返金を申請し、Apple はあなたのサーバーに CONSUMPTION_REQUEST を送ります。あなたには、その人物が製品をどう使ったかについて実データで答えるための 12 時間があります。ところが通知を開いてみると、相手が誰なのかまったく分からないと気づきます。トランザクションには originalTransactionId と製品 id は載っていますが、あなた自身のデータベース内のアカウントを指し示すものは何もありません。その隙間こそ appAccountToken が埋めるものであり、購入時に設定しなかったのなら、その販売については後から埋めることはできません。
appAccountToken は、購入に付与する UUID です。生成された App Store のトランザクションが、あなたのシステム内の正確なユーザーを指すポインターを持つようにします。設定すれば、その顧客について Apple がこの先投げかけるすべての返金の問いに、相手の身元が添えられて届きます。省けば、あとは推測するしかありません。以下では、このフィールドが何であるか、どう設定するか、アプリ外の購入を救う新しいエンドポイント、そして返金が来たときにこの欠けたひも付けが実際にいくらの代償になるかを説明します。
appAccountToken とは実際に何か
appAccountToken は、あなたが生成し、購入の瞬間に StoreKit へ渡す不透明な UUID です。Apple はそれをトランザクションに保存し、その購入のトランザクション情報の中で返し、そこに残り続けます。Apple の言葉では、それは「トランザクションを、あなた自身のサービス上のユーザーのアカウントと関連付ける UUID」です。唯一の形式ルールは、それが UUID でなければならないということです。Apple はそれを読み取らず、何にマッピングされるかを検証せず、あなたの側でそれが何を意味するかを気にしません。それは、あなたが制御するひも付けです。
それはトランザクション上に存在するため、トランザクションが現れるあらゆる場所で戻ってきます。サーバー通知の署名付きトランザクション、App Store Server API の Get Transaction Info レスポンス、そしてサブスクリプションチェーン内のすべての更新は、最初の購入で設定していれば、いずれも同じトークンを持ちます。一つの UUID を一度付与すれば、その関係が続く限り、顧客の請求に付き従います。
本物の UUID でなければ、静かに消える
Apple が強制する唯一のルールは形式です。StoreKit 2 は RFC 4122 UUID を要求します。連結した文字列、整数の id、メールアドレスを渡しても、StoreKit はエラーを投げません。その値を破棄し、トランザクションは appAccountToken が nil に設定された状態で戻ってきます。開発者は絶えずこれに突き当たり、症状は常に同じで、Apple 自身のフォーラムで「appAccountToken is missing in the transaction payload」のような表現として現れ、ほぼ必ず、渡された値が有効な UUID でなかったことが原因です。サーバー側で本物の UUID を生成し、それをアカウントにひも付けて保存し、StoreKit には決してそれ以外のものを渡さないでください。
購入時にどう設定するか
StoreKit 2 では、単一の購入オプションです。ユーザーがサインアップするとき、または最初にチェックアウトに到達したときに、サーバーで UUID を生成し、そのアカウントのレコードに保存し、同じ値を購入呼び出しに渡します。
そのシグネチャは Product.PurchaseOption.appAccountToken(_ token: UUID) であり、購入は try await product.purchase(options: [.appAccountToken(token)]) のようになります。トランザクションが、App Store Server API を通じて検証されて、あるいはサーバー通知で届いて戻ってくると、それはその UUID を持っており、あなたのサーバーは一度のクエリで顧客を照会できます。
アカウントごとに一つの安定したトークンを使う
同じユーザーの購入ごとに新しいトークンを生成してはいけません。Apple は同じチェーン内の更新、請求リトライ、アップグレードでトークンを返すため、アカウントごとに安定した UUID があれば、最初の購入から将来のあらゆるイベントまで、きれいに一本の糸が通ります。購入ごとに変わるトークンはその糸を断ち切り、目的そのものを台無しにします。一つのアカウントに一つのトークン。そのアカウントが購入するたびに再利用します。
アプリ外の購入を救うエンドポイント
2025 年まで、一つの穴がありました。顧客がオファーコードを引き換えたり、App Store から直接プロモーションされたアプリ内購入を買ったりすると、あなたのアプリは購入フローを一度も実行しないため、appAccountToken を設定する場所がどこにもありませんでした。それらのトランザクションは匿名で届き、そのままの状態でした。
WWDC 2025 は、Set App Account Token エンドポイントでこの隙間をふさぎました。あなたのサーバーが App Store Server API で PUT /inApps/v1/transactions/{originalTransactionId}/appAccountToken を、本体に UUID を入れて呼び出すと、Apple はそのトランザクションにトークンを設定します。すべての製品タイプで機能し、オファーコードの引き換えとプロモーション購入を対象とし、送った値はトランザクション上にすでにあるトークンを上書きします。これで、アプリを一度も通さずに、サーバーから後づけで購入を関連付けられます。

| 購入チャネル | appAccountToken を設定する場所 | 備考 |
|---|---|---|
| アプリ内購入 | 購入時の StoreKit 購入オプション | Product.PurchaseOption.appAccountToken(UUID) |
| オファーコードの引き換え | Set App Account Token エンドポイント、サーバー側 | フックするアプリ内フローがないため、後から設定する |
| App Store からのプロモーションアプリ内購入 | Set App Account Token エンドポイント、サーバー側 | 購入はあなたのアプリの外で発生する |
| サブスクリプションの更新 | 何もする必要はない | 最初の購入から自動的に引き継がれる |
欠けたひも付けが、どこであなたのお金を失わせるか
トークンの目的は、整った記録ではありません。Apple が開発者に投げかける唯一の返金の問い、すなわち CONSUMPTION_REQUEST は、それが関わる顧客を見つけられて初めて答えられる、ということです。
買い手が消耗型アイテムまたは非更新サブスクリプションについて返金を申請すると、Apple はあなたのサーバーに CONSUMPTION_REQUEST 通知を送り、Send Consumption Information で返信するための 12 時間を与えます。あなたの答えは、その特定の顧客に関するデータです。彼らが製品をどれだけ消費したか、アカウントの継続期間、生涯支出、提供状況。appAccountToken 自体がそのリクエスト内のフィールドの一つであり、さらに重要なのは、それこそが、通知のトランザクションを、あなたがこれから利用状況を説明しようとしているアカウントへとマッピングする手立てだということです。トークンがなければ、照会もなく、正確な答えもありません。
空白の答えが実際にいくらの代償になるか
識別できない返金は、悪い選択を強います。消費リクエストに何もなしで答えることもできますが、それは低消費と読み取られ、製品を大量に使った顧客も含めて、Apple を返金の承認へと傾かせます。あるいは推測することもできます。どちらにせよ、あなたは反論する証拠があった購入について返金しており、それらを提供する実際のコストはすでに支払っています。
そのコストは販売価格ではありません。一連のモデル API 呼び出しを実行し、画像を生成し、動画を書き出し、あるいはクリエイターへの支払いをトリガーした消耗型アイテムは、提供された瞬間に実際のお金を費やしました。返金は顧客の支払いを返します。しかしプロバイダーの請求書は返しません。識別できない一人の顧客に、その人が申請するすべての返金を掛け、さらに、あなたが相手を知らないことを当てにするすべての連続返金者を掛け合わせれば、あなたが省いたトークンは、あなたが決して書かなかった最も高価な一行のコードになります。
同じ考え方は Android にも、別の名前で存在する
Google Play は setObfuscatedAccountId で同一の問題を解決します。これは購入にアカウント識別子を付与し、orders.reviewrefund を通じた Google のチャージバック審査を、実在のユーザーに対して答えられるようにします。ストアが違い、仕組みが違っても、教訓は同じです。購入時に身元を付与するか、さもなければ後で争議に反論できないか、です。App Store では、そのツールが appAccountToken であり、UUID でなければなりません。
トークンを所定の場所に保つ三つの習慣
- アカウントごとに UUID を生成して保存する。顧客ごとに一つの安定したトークンを、サインアップ時または最初のチェックアウト時に作成し、そのレコードに保存して、すべての購入で再利用する。
- 渡す前に検証する。購入コードでその値が本物の UUID であることを確認し、不正な id が決してトランザクション上で静かに nil トークンにならないようにする。
- アプリ外の購入を補完する。トークンのないオファーコードの引き換えやプロモーション購入についてサーバー通知が届いたら、Set App Account Token エンドポイントを呼び出して正しいものを付与する。
この三つを行えば、Apple がこの先あなたに送るすべてのトランザクションは、返金リクエストも含めて、それが属する顧客にすでにひも付いた状態で届きます。
これが RefundHalt が依存する土台です。私たちはすべてのトランザクションとサーバー通知から appAccountToken を読み取り、そのアカウントについて私たちがすでに追跡している利用状況とひも付け、12 時間の枠内で顧客の実際の数字を使って Apple の消費リクエストに答えます。トークンはその糸です。一度設定すれば、あなたの返金防御はつかまるものを手にします。
よくある質問
- App Store における appAccountToken とは何ですか?
- appAccountToken は、あなたが生成し、StoreKit を通じて購入に付与する UUID です。生成された App Store のトランザクションが、あなた自身のシステム内の特定のユーザーアカウントを指し示すようにします。Apple はそれをトランザクションに保存し、トランザクション情報、サーバー通知、同じチェーン内のすべての更新で返すため、返金リクエストを含むあらゆる将来のイベントを、正しい顧客に結び付けられます。
- なぜ私の appAccountToken は nil または欠けているのですか?
- ほぼ必ず、渡した値が有効な UUID でなかったからです。StoreKit 2 は RFC 4122 UUID を要求し、それ以外は黙って破棄するため、連結した文字列、整数の id、メールアドレスは nil の appAccountToken として返り、それでも購入は成功します。もう一つのよくある原因は、オファーコードの引き換えのような、あなたのアプリの外で行われた購入で、トークンを設定するアプリ内フローが実行されなかった場合です。
- オファーコードのために、購入後に appAccountToken を設定できますか?
- はい、2025 年以降は可能です。App Store Server API の Set App Account Token エンドポイントにより、あなたのサーバーは PUT /inApps/v1/transactions/{originalTransactionId}/appAccountToken を本体に UUID を入れて呼び出すことで、既存のトランザクションにトークンを付与または上書きできます。すべての製品タイプで機能し、オファーコードの引き換えやプロモーションされたアプリ内購入など、あなたのアプリの外で行われた購入のために作られています。
- appAccountToken は UUID でなければなりませんか?
- はい。Apple が強制する唯一の形式ルールは、その値が有効な UUID であることです。それ以外は不透明なので、あなたの側では好きなアカウントキーにマッピングできますが、UUID でなければ StoreKit はそれを保存せず、トランザクションは appAccountToken が nil に設定された状態で戻ってきます。
- appAccountToken は返金にどう役立ちますか?
- 顧客が返金を申請すると、Apple は CONSUMPTION_REQUEST を送り、その特定の買い手に関するデータで応答するための 12 時間を与えます。appAccountToken は、通知のトランザクションを、あなたが利用状況を報告すべきアカウントに一致させる手立てであり、消費リクエスト自体のフィールドの一つでもあります。これがなければ実際の利用状況で答えられず、その結果 Apple は、あなたに争う証拠があった返金を承認する方へ傾きます。
- appAccountToken は購入ごとに異なるべきですか?
- いいえ。アカウントごとに一つの安定した UUID を使い、そのユーザーが行うすべての購入で再利用してください。Apple はサブスクリプションチェーン内の更新やアップグレードでトークンを引き継ぐため、安定したトークンは時間をまたいだきれいなひも付けを与えます。購入ごとに変わるトークンはそのひも付けを断ち切り、トランザクションを同じ顧客に結び付けるのを難しくします。
出典と参考資料
- Apple Developer: appAccountToken (StoreKit Transaction)
- Apple Developer: Product.PurchaseOption.appAccountToken(_:)
- Apple Developer: Set App Account Token (App Store Server API)
- Apple Developer: appAccountToken (App Store Server API)
- Apple Developer: ConsumptionRequest (Send Consumption Information)
- Apple Developer: Send Consumption Information
- WWDC25: Dive into App Store server APIs for In-App Purchase
RefundHalt
App Store と Google Play の返金を自動処理
続きを読む
Google Play の購入を三日以内に確認しないと Google が返金します。それがあなたにいくらの損失をもたらすか
サーバーが三日以内に確認しなかった購入を、Google Play は自動的に返金し、取り消します。これは顧客の判断ではなく統合の不備であり、完全に防げます。正確なルール、なぜそれが発動するのか、失われた売上一件が本当はいくらかかるのかを解説します。
連続的な返金の悪用はあなたに二重の損失を与える。ストアが用意した反撃の方法を紹介します
何度も繰り返し返金を求める顧客は偶然ではありません。返金の悪用は金額を返させるだけでなく、すでに費やした計算リソースまで奪います。そして両ストアは、そのパターンを結びつけるための本人性シグナル、Apple の appAccountToken と Google の難読化アカウント ID を用意しています。