顧客の App Store 返金履歴をまるごと返すエンドポイントが一つあり、それが何を返すのかを解説する
Apple の Get Refund History エンドポイントは、顧客の完全な App Store 返金履歴を署名付きトランザクションとして返します。すべてのフィールド、revision トークンによるページングの仕組み、なぜアプリ単位ではなく顧客単位なのか、そして見逃した返金がいくらの損失になるのかを解説します。

要点
- Get Refund History は App Store Server API のエンドポイントで、顧客があなたのアプリで返金されたアプリ内課金を署名付きトランザクションの一覧として返します。これにより、通知が届かなかった場合でも返金を照合し、アクセスを取り消せます。
- その顧客の任意のトランザクション id を使って GET /inApps/v2/refund/lookup/{transactionId} を呼び出すと、Apple はあなたのアプリ内のすべての購入タイプにわたるその顧客の返金を返します。問い合わせた一件だけではありません。
- レスポンスには 3 つのフィールドがあります。signedTransactions は 1 ページあたり最大 20 件の JWS トランザクションで、返金の古い順に並びます。加えてページング用の revision トークンと hasMore ブール値です。
- 最後の revision トークンを保存しておきます。次回それを渡し返すと、Apple はその時点より新しい返金だけを返すので、履歴全体のダンプが毎回のわずかな新規行に変わります。
- デコードした各トランザクションには revocationDate と revocationReason が含まれます。revocationReason が 1 なら、顧客はあなたのアプリの実際のまたは知覚された問題が理由で返金したことを意味し、0 は誤購入などの別の理由を意味します。
- このエンドポイントはアプリ単位ではなく顧客単位です。アプリ全体のすべての返金を一覧化する呼び出しは存在しないので、トランザクション id を起点にアカウント単位で照合するか、アプリ全体の視点には REFUND 通知フィードを読みます。
- 組み込む理由はお金です。決して捕捉できなかった返金はアカウントを生かし続け、App Store がすでに補償済みの顧客のために、あなたは計算資源、モデル API 呼び出し、ストレージ、支払いを払い続けます。
Apple は、あなたのアプリについて顧客のアカウントで承認したすべての返金の照会可能な記録を保持しており、一度の呼び出しでそれを返します。そのエンドポイントが Get Refund History で、App Store Server API の一部です。それは顧客の完全な App Store 返金履歴を署名付きトランザクションの一覧として返します。トランザクション id を渡すと、Apple が何を返金したかが返り、あなたはそれをまだ有効にしているものと照合します。
なぜわざわざそうするのか。見えていない返金は、払い続けている返金です。お金はすでに失われていますが、アカウントは生きたままで、生きている限り毎時、計算資源、モデル API 呼び出し、ストレージ、そしてその顧客に紐づくあらゆる支払いに支出し続けます。返金通知は、それが起きた瞬間に捕捉するためのものです。Get Refund History は通知が機能しなかったときの最後の砦です。障害の後、webhook を落としたデプロイの後、あるいは一度の呼び出しで全体像が必要なサポート案件で使います。
App Store 返金履歴エンドポイントが返すもの
App Store Server API に対して GET /inApps/v2/refund/lookup/{transactionId} を呼び出します。他のすべての呼び出しと同じ JWT で署名します。パス内のトランザクション id はその顧客の任意のトランザクションで構いません。Apple はそれをフィルタではなく識別子として読み取り、あなたのアプリ全体でその顧客の返金された購入を返します。消耗型、非消耗型、自動更新および非更新のサブスクリプションを問わずです。このエンドポイントの古い V1 は 1 回のレスポンスで最大 50 件の返金を返しましたが、非推奨です。現行版はページングするので、履歴の長い顧客も巨大なペイロードなしに扱えます。
レスポンスは 3 つのフィールド
| Field | 保持する内容 |
|---|---|
| signedTransactions | この顧客の最大 20 件の返金済みトランザクション。各件は検証しデコードする署名付き JWS。revocationDate により返金の古い順に並ぶ。空配列はその顧客にあなたのアプリでの返金がないことを意味する |
| revision | ページングトークン。渡し返して次ページを取得し、最後の一つを保持して次回は新規返金だけを取得する |
| hasMore | Apple がこのページで返したよりも多くの返金済みトランザクションを保持しているとき True。その場合は revision を付けて再度呼び出す |
返金済みトランザクション 1 件が教えてくれること
signedTransactions の各項目は JWS です。Apple の証明書チェーンで検証し、デコードすると、返金フィールドが埋まった通常のトランザクションペイロードが得られます。ここで重要なのは次のものです。
| Field | 教えてくれること |
|---|---|
| transactionId | 返金済みトランザクションの id。あなたが記録した購入へ結び付ける結合キー |
| originalTransactionId | チェーン内で最初の購入の id。サブスクリプションの更新をひとまとめにする手掛かり |
| productId | 返金された製品。正しい権利だけを取り消し、他は一切触らないため |
| revocationDate | Apple がそのトランザクションを返金した UNIX 時刻(ミリ秒単位) |
| revocationReason | Apple が返金した理由。1 はあなたのアプリの実際のまたは知覚された問題、0 は誤購入などの別の理由 |
| price, currency | 金額(milliunits 単位)とその ISO 4217 通貨コード。返された金額を合計できる |
| appAccountToken | 購入時に付与した UUID。返金を自分のユーザーへ対応付ける最もきれいな方法 |
revision トークンこそ、一覧全体を繰り返し読み直さずに済む方法
このエンドポイントの素朴な使い方は、顧客を検索して毎回すべてのページをたどることです。それでも動きますが、返金が 50 件ある顧客では、すでに知っている 50 行に新しい 1 行が加わるだけです。revision トークンはその無駄をなくすために存在します。各レスポンスは revision を持ちます。hasMore が true のとき、それを渡し返して次ページを取得します。末尾に達したら、見た最後の revision を保持します。
このエンドポイントがしないこと
これを土台にする前に、捨てておくべき期待が一つあります。Get Refund History はアプリ単位ではなく顧客単位です。先週あなたのアプリが受けたすべての返金を尋ねることはできません。それが答えるのは一つの問い、つまりこのアカウントにどの返金があるか、だけであり、尋ねるにはそのアカウントのトランザクション id を持って来る必要があります。開発者は絶えずこの壁にぶつかり、存在しないアプリ全体の返金エンドポイントを探しに行きます。
アプリ全体の視点は別のところにあります。あなたの App Store Server Notifications フィードは、Apple が各返金を承認した瞬間に REFUND 通知を送り、Get Notification History は日付範囲で返金タイプに絞ってそのフィードを再生できます。だから区分けは明快です。通知とその履歴はアプリ全体のストリームを与えます。Get Refund History は必要に応じて一つのアカウントの権威ある一覧を与えます。それこそサポートデスクや障害の後に欲しいものです。

見逃した返金がお金でいくらの損になるか
エンドポイントは配管です。請求こそ、その管を敷く理由です。その一覧にあるすべての返金はすでに返された金であり、あなたの手に残る唯一の変数は、もう払わないアカウントにどれだけ長く支出し続けるか、です。
返金済みアカウントの提供に払い続けている
Apple が返金を承認した瞬間、購入代金は失われます。動き続けるのは提供コストです。ユーザーごとに実作業をこなすアプリなら、それは計算資源、モデル API 呼び出し、ストレージ、そして利用に紐づくクリエイターやパートナーへの支払いです。アクセスを一度も切らない返金済み顧客は、あなたが自腹で賄うサブスクリプションです。Get Refund History に対して照合し、見つけたものに応じて取り消すことが、通知がすり抜けたときにそのメーターを止める方法です。
返金理由 1 は、変装した欠陥報告
revocationReason は無視すると二重の代償になります。最初の代償は返金そのもの。二つ目は同じ原因からの将来のすべての返金です。ある製品が revocationReason 1、すなわちあなたのアプリの実際のまたは知覚された問題を伴って戻り続けるなら、Apple は顧客が返金を求める原因のラベル付きサンプルをあなたに手渡しています。製品ごとに傾向を追えば、返金を一件ずつ払い出す代わりに、その漏れを塞げます。
遅れて捕まえても、捕まえないよりはずっと良い
チャージバックは銀行との間で最終的であり、もう一方のストアでは今や開発者が負担する手数料も伴います。App Store の返金はそれとは違います。決済済みですが、権利は知った瞬間に取り消せます。だからこのエンドポイントを通じて数日遅れて見つけた返金でも、見つける価値があります。お金は取り戻せませんが、その裏でまだ走っていた支出は止められます。
これが通知と、そして Google とどう噛み合うか
各部品を一つのシステムとして考えてください。REFUND 通知は生の信号で、Apple の判断に応じてサーバーへプッシュされます。Get Refund History は単一顧客のためのプル型の信頼できる情報源で、プッシュが失敗したとき、あるいは人がアカウント全体を目の前に必要とするときに行う呼び出しです。Google Play 側では、形は同じ考え方で名前が違います。VoidedPurchaseNotification がリアルタイムでプッシュされ、Voided Purchases API があなたがプルする一覧です。どちらのストアもストリームと台帳を与えます。過ちは、ストリームだけを信じることです。ストリームは落ちるからです。
RefundHalt 流のつなぎ方
すべての部品が揃えば、そのループは小さくなります。REFUND 通知をトリガーとして受け取ります。Get Refund History に対して照合し、落ちた webhook が返金済みアカウントを生かしたままにしないようにします。各トランザクションをデコードし、appAccountToken または transactionId でユーザーへ結び付け、revocationReason を読んで欠陥返金が単に片付けられるのでなくフラグ立てされるようにし、アカウント全体ではなく正確な権利を取り消します。revision トークンでページングし、古いものではなく新しい返金を読みます。
この部分こそ RefundHalt があなたの代わりに実行するものです。返金通知を待ち受け、権威ある一覧が必要なときは Get Refund History に切り替え、すべての署名付きトランザクションを検証し、正確な購入を取り消し、revision を保持して各回が変わった分だけを読むようにします。ポーリングと JWS 検証を自分で立ち上げることなく、数秒でアクセスが切られ、誰が何を、なぜ返金されたのかのきれいな記録が手に入ります。
よくある質問
- 一人の顧客だけでなく、アプリ全体のすべての返金を見るにはどうすればよいですか。
- Get Refund History ではできません。顧客単位であり、尋ねたいアカウントのトランザクション id が必要だからです。アプリ全体の視点には、App Store Server Notifications フィードを使ってください。Apple が各返金を承認するたびに REFUND 通知を送ります。そして Get Notification History で、日付範囲にわたり返金タイプに絞ってそのフィードを再生します。
- Get Refund History エンドポイントは何件の返金を返しますか。
- 現行版は 1 ページあたり最大 20 件の返金済みトランザクションを、最も古い返金を先頭にして返し、hasMore が true のときは revision トークンで残りをページングします。非推奨の V1 エンドポイントは 1 回のレスポンスで最大 50 件を返しました。総数に上限はないので、履歴の長い顧客は単により多くのページにまたがるだけです。
- revision トークンは何のためのものですか。
- ページングの方法であり、毎回顧客の履歴全体を読み直さずに済む方法です。各レスポンスには revision が含まれます。それを渡し返して次ページを取得し、最後の一つを保存して次回の検索がその時点より新しい返金だけを返すようにします。これにより、定期的な照合がわずかな新規行の一覧に収まります。
- 返金済みトランザクションの revocationReason は何を意味しますか。
- Apple がそのトランザクションを返金した理由です。値 1 は、顧客があなたのアプリ内の実際のまたは知覚された問題が原因で返金したことを意味し、0 は誤購入などの別の理由を意味します。revocationDate は返金がいつ起きたかを UNIX ミリ秒で教えます。revocationReason を読めば、製品欠陥と一度きりの後悔による返金を切り分けられます。
- すでに REFUND 通知を処理しているなら、これはまだ必要ですか。
- はい、最後の砦としてです。通知は生の信号ですが、プッシュは障害、まずいデプロイ、webhook の変更の最中に届かないことがあり、見逃した返金は返金済みアカウントを生かしたまま、あなたにお金を払わせ続けます。Get Refund History は、あなたが照合するプル型の信頼できる情報源であり、Apple がすでに返金したものが有効なまま残らないようにします。
出典と参考資料
- Apple Developer: Get Refund History (App Store Server API)
- Apple Developer: RefundHistoryResponse
- Apple Developer: JWSTransactionDecodedPayload (revocationDate, revocationReason)
- Apple Developer: Get Refund History V1 (deprecated)
- Apple Developer: App Store Server Notifications V2 notificationType (REFUND)
- Apple Developer: Support customers and handle refunds (WWDC21)
RefundHalt
App Store と Google Play の返金を自動処理
続きを読む
アプリはアプリ内で返金リクエストシートを表示できます。顧客が送信をタップした後にAppleが何をするかを解説します
Appleのアプリ内返金リクエストは、顧客がアプリを離れることなく、Appleが構築して審査するシート上で返金を申請できる仕組みです。beginRefundRequestが何を返すか、サーバー上で開始するCONSUMPTION_REQUESTと48時間のタイマー、そしてこのボタンを出荷する価値があるかを解説します。
Google Play の購入が返金またはチャージバックされたとき、それを知る手段が Voided Purchases API です
Google Play は購入が返金またはチャージバックされると、静かにその購入を無効化します。Voided Purchases API はそうした注文の一覧であり、これを使ってアクセスを取り消せます。ここではすべてのフィールド、30日間のウィンドウ、注文を隠してしまう revoke オプション、そしてそのコストを解説します。