Google Playでの返金は静かに発生します。決済は取り消され、顧客はアプリを使い続け、意図的に確認しない限り、あなたの側では何も変わりません。その確認のために存在するのが、まさにVoided Purchases APIです。これはキャンセル、返金、またはチャージバックされた注文を返すため、顧客が支払いを止めた対象へのアクセスを取り消すことができます。定期実行するジョブをこのAPIに接続し、返ってきた結果を読み取り、権利(エンタイトルメント)をrevokeする。これが仕組みの全体です。
ほぼすべてのチームが引っかかる落とし穴が一つあり、それはコードではなく運用上のポリシーに起因します。このAPIは実際にrevokeされた注文しか表示しません。Play Consoleで購入を返金する際にrevokeオプションにチェックを入れなければ、その注文はここには一切表示されません。つまり、返金済みの顧客が販売したものすべてを持ち去っている間も、あなたのジョブは完全に正常に動作し続けます。以下では、このAPIをフィールド単位で解説し、その制限事項と、仕組みを組んでいない場合にお金が静かに流出していく場所を見ていきます。
重要なポイント
Voided Purchases APIはpurchases.voidedpurchases.listメソッドを通じて呼び出し、Google Playがキャンセル、返金、またはチャージバックした注文を一覧表示します。これにより、顧客が保有していない購入へのアクセスを剥奪するシステムを構築できます。
表示されるのはrevokeされた注文のみです。revokeせずに発行された開発者による返金はここには表示されないため、アクセスを取り消すにはrevokeを有効にした状態で返金する必要があります。
対象期間は直近30日間のローリングウィンドウです。startTimeは30日より前には設定できないため、サーバーが1か月以上オフラインになると、それらのvoidは永久に失われます。そのため、ポーリングは定期的なスケジュールで実行する必要があります。
voidedSourceはvoidを発生させた主体を示し、0はユーザー、1は開発者、2はGoogleです。voidedReasonはその理由を示し、0のOtherから7のChargeback、8のUnacknowledged_purchaseまであります。
リアルタイムデベロッパー通知は、購入がvoidされた瞬間にVoidedPurchaseNotificationを発火させますが、これはあくまで確認を促すものとして扱ってください。何かをrevokeする前に、必ずVoided Purchases APIで確認してください。
定期購入の更新を区別する際は、purchaseTokenではなく必ずorderIdを使用してください。1つのpurchaseTokenは定期購入のすべての更新にわたって共通であるため、トークン単体ではある期間と次の期間を区別できません。
上限は1日あたり6,000リクエスト、および30秒間ごとに30リクエストです。そのため、各リクエストは時間ウィンドウで制限し、continuation tokenを使ってページングを行うべきで、注文ごとに1リクエストを送るような方法は避けてください。
APIが返す内容
このエンドポイントが解決するのは、たった一つの問いです。このアプリのどの注文が最近voidされたか、ということです。voidには、いずれも顧客にお金が返還される3つの結果、すなわちキャンセル、返金、チャージバックが含まれます。対象は1回限りのアプリ内商品と定期購入の両方で、どちらを取得するかは1つのパラメータで決まります。typeをデフォルトの0のままにしておくと、voidされたアプリ内商品の購入のみが返されます。typeを1に設定すると、voidされたアプリ内購入とvoidされた定期購入の両方を取得できます。
レスポンス内の各項目は、短く価値の高いフィールドのセットを持つvoided purchaseオブジェクトです。
orderIdは、1回限りの購入、定期購入、またはその中の個々の更新を一意に示します。これを結合キーとして扱ってください。
purchaseTokenは、1回限りの購入または定期購入を識別しますが、更新を区別することはできないため、その場合はorderIdを利用してください。
purchaseTimeMillisは、購入が発生した時刻をエポックからのミリ秒で記録します。
voidedTimeMillisは、キャンセル、返金、またはチャージバックが発生した時刻を、同様にエポックからのミリ秒で記録します。
voidedSourceは、voidを開始した主体を示し、0はユーザー、1は開発者、2はGoogleです。
voidedReasonは、原因を0から8までの整数で示します。
voidedQuantityは、数量ベースの部分返金によってvoidされた数量を示し、includeQuantityBasedPartialRefundがtrueの場合にのみ表示されます。
1つのpurchaseTokenが定期購入全体をカバーする一方で、更新ごとに新しいorderIdが発行されるため、エンタイトルメントをトークンでキー付けすると、誤った期間をrevokeしてしまうことになります。代わりにorderIdでキー付けしてください。
何かに手を付ける前にvoidedReasonを確認する
voidedReasonこそが、単純なリストを実際の意思決定に変えるものです。気持ちの変化による返金と銀行のチャージバックは、同じフィードに含まれていても全く異なるものだからです。全体のセットは以下の通りです。
1. Otherは、割り当てられたカテゴリーがありません。revokeして先に進んでください。
2. Remorseは、気持ちが変わった顧客によるもので、ごくありふれた返金です。
3. Not_receivedは、商品が届かなかったという申告で、配信状況を確認する価値があります。
4. Defectiveは、動作しなかったことを意味し、記録しておくべき品質上のシグナルです。
5. Accidental_purchaseは、意図しない購入で、共有デバイスでよく発生します。
6. Fraudは、Googleが不正と判断したトランザクションです。
7. Friendly_fraudは、本来のカード所有者が、自分自身が行った決済に対して異議を申し立てるチャージバックです。
8. Chargebackは、顧客の銀行が決済を取り消すもので、銀行側では最終決定であり、現在はその費用があなたに課されます。
9. Unacknowledged_purchaseは、アプリがacknowledge(確認応答)しなかった購入に対して、Googleが自動的に返金するものです。
理由9は、自ら招いた返金です。アプリがacknowledgeしなかった購入は、Googleによってその代金が返金されるため、このフィードに現れるunacknowledged_purchaseは、購入者の選択によるものではなく、たった一つのAPI呼び出しの漏れによって失われた収益です。
リストを静かに縮小させる30日間のウィンドウ
このAPIは過去30日より前には遡れません。startTimeはデフォルトで現在時刻から30日前に設定され、それより前の値を指定することはできません。endTimeはデフォルトで現在時刻になるため、得られるのは永続的なアーカイブではなく、常に直近1か月分のローリングウィンドウです。
この影響は深刻です。ポーリングジョブが停止し、5週間気づかれないままだとすると、最初の週に発生したvoidはすでにAPIから失われており、どのような呼び出しでも取得することはできません。それらの注文はrevokeされないままとなり、別の場所で記録していない限り、それが起きたことすら知る術がありません。網の目には、最長の停止期間とちょうど同じ幅の穴が開くことになります。少なくとも毎日ポーリングを行い、リアルタイム通知と突き合わせ、30日間を実際にそうである通り、容赦のない削除タイマーとして扱ってください。
注文がそもそも表示されるかどうかはrevokeで決まる
これは、チームがこのAPIを「壊れている」と評する最大の理由です。このAPIはrevokeされた注文しか返しません。ユーザーが開始した返金、キャンセル、チャージバック、そしてGoogle主導の返金は、すべて自動的にrevokeされるため、常にフィードに現れます。例外は開発者主導の返金です。Play Consoleから行う場合でもOrders APIを通じて行う場合でも、自分で返金を発行する際には、revokeするかどうかは別途あなた自身が決定しなければならない選択として残ります。これを選択しなければ、注文は顧客に対しては決済が完了するものの、このフィードには一切表示されません。
結論は簡潔です。アクセスを遮断することが目的であれば、revokeを有効にした状態で返金してください。これを省略すると、お金は返還したものの扉は開けたままになり、どれほど丁寧に構築したrevocationジョブであっても、処理する対象が何もないことになります。
クォータに達しないようにポーリングする
このエンドポイントにはレート制限があり、その上限は不注意なループがすぐに引っかかってしまうほど低く設定されています。1日あたり6,000リクエストまで(太平洋時間でカウント)、そして30秒間につき30リクエストを超えることはできません。この予算はウィンドウ単位のポーリングには適していますが、注文ごとに1リクエストを送る設計には不利に働きます。
時間ウィンドウとcontinuation token
maxResultsはデフォルトで1,000であり、これが設定できる最大値でもあります。あるウィンドウに1ページで保持できる以上のvoidが含まれる場合、レスポンスにはnextPageTokenを保持するtokenPaginationオブジェクトが含まれます。次の呼び出しでこのトークンを渡すことで、ページを進めることができます。startTimeとendTimeでウィンドウの端を固定し、トークンがなくなるまでページングを続け、その後にのみウィンドウをずらしてください。このペースであれば、30秒間のバーストの上限と1日あたりの許容量の両方を超えずに済みます。
リアルタイム通知が1日単位のギャップを埋める
1日単位のポーリングであっても、最大24時間は何も見えない状態になりますが、まさにこうした長いギャップこそが30日間のウィンドウによって痛手となります。リアルタイムデベロッパー通知は、このラグを埋めてくれます。購入がvoidされた瞬間に、VoidedPurchaseNotificationがあなたの管理下にあるCloud Pub/Subトピックへ発行され、バックエンドが数秒以内にこれを受け取ります。そのペイロードはコンパクトです。
purchaseTokenは、元の購入時のトークンです。
orderIdは、voidされたトランザクションのIDで、定期購入の更新ごとに新しく発行されます。
productTypeは、定期購入の場合は1、1回限りの購入の場合は2です。
refundTypeは、全額返金(1)と数量ベースの部分返金(2)を区別します。
通知は絶対的な真実としてではなく、あくまでアラートとして扱ってください。VoidedPurchaseNotificationが届いたら、Voided Purchases APIに問い合わせて注文の実際の状態を確認し、その上でのみrevokeを行ってください。アラートは「確認すべき」ことを伝え、APIは「真実」を伝えます。
金銭的にどれだけのコストがかかるか
このAPIは配管にすぎませんが、その配管を敷く理由は結局「お金の請求書」であり、その中の2つの数字は上昇を続けています。
2026年8月3日以降、チャージバックの請求はあなたに課される
2026年8月3日以降、Googleはチャージバックのコストを開発者に移転させます。購入価格を失うだけでなく、銀行のチャージバック手数料も上乗せで負担することになります。voidedReasonが7の場合、それは単なる売上の損失ではなく、手数料が付随したコスト項目となります。チャージバック自体は銀行側で最終決定されているため取り消すことはできませんが、voidを素早く検知できれば、権利をrevokeし、まだ提供中のものについては、返金されたうえで取り消しとなった顧客への支出を止めることができます。
すでに返金済みの顧客に費用を払い続けることになる
voidが発生した瞬間に、販売金額は失われます。それでもあなたがまだ制御できるのは、提供を継続するためのコストです。返金済みの権利が有効なままである1時間ごとに、顧客がもう負担しなくなった費用——コンピュート、モデルプロバイダーへの呼び出し、ストレージ、そしてその利用によって発生するクリエイターやパートナーへの支払い——はあなたの側に積み重なっていきます。このAPIを基盤にrevocationパイプラインを構築することが、その支出を止める方法です。これを行わなければ、ストアがすでに返金したユーザーのためにプロダクトを無償提供し続けることになります。
フレンドリーフラウドは単発の出来事ではなく傾向である
voidedReasonが5または6の場合、それが単独で発生することはほとんどありません。不正とフレンドリーフラウドは、アカウント、デバイス、そして時には特定のプロモーションを中心にまとまって発生する傾向があります。このAPIはすべてのvoidにvoidedSourceとvoidedReasonを付与するため、それぞれの取り消しを単発の損失として受け入れるのではなく、アカウント単位で不正利用の傾向を追跡することができます。2回目のチャージバックを起こしたアカウントは、1回目の返金では分からなかったことを教えてくれています。
まとめ
必要な要素がすべて揃えば、全体のモデルは小さくまとまります。VoidedPurchaseNotificationをリアルタイムで監視し、何も1日丸ごと待たされることのないようにしてください。Voided Purchases APIを信頼できる情報源として扱い、orderIdでキー付けして更新が混ざらないようにしてください。voidedSourceとvoidedReasonを読み取り、チャージバックを気持ちの変化による返金とは別に処理してください。30日間のウィンドウの影響を受けないほど密なペースでポーリングを行い、アクセスを遮断する意図があるときは必ずrevokeを有効にして返金してください。
これが、Refund Sensorがあなたに代わって実行しているレイヤーです。リアルタイム通知を取り込み、すべてのvoidをAPIと突き合わせて確認し、プロダクト全体ではなく正確な注文単位でrevokeを行い、銀行のチャージバックを通常の返金とは区別して扱うことで、コストの高いものが隠れることなく表面化します。数秒以内にアクセスが取り消され、誰が何をいつvoidし、その理由が何だったかの完全な記録を得られます。Pub/Subパイプラインやポーリングジョブを自分で構築する必要はありません。
これらのルールが記載されているドキュメント
よくある質問
このフィードにはrevoke(取り消し)された注文しか含まれないためです。ユーザーが開始した返金、キャンセル、チャージバック、そしてGoogle主導の返金は、いずれも自動的にrevokeされるため必ず表示されます。一方、開発者自身が発行した返金は、revokeも選択した場合にのみ表示されます。revokeせずに返金すると、注文は決済上は完了していてもここには表示されません。アクセスを取り消すことが目的であれば、必ずrevokeを有効にしてください。
30日が限界で、それより前には遡れません。startTimeはデフォルトで現在時刻から30日前に設定され、それより古い値は受け付けられません。そのため、このエンドポイントはアーカイブではなく、常に直近1か月分を保持するローリングウィンドウとして動作します。voidが30日を超えて古くなると取得する手段はなくなるため、定期的にポーリングを行い、リアルタイム通知で補完することが重要です。
どちらにも役割があります。VoidedPurchaseNotificationは数秒以内に届き、確認を促しますが、Googleのガイダンスではこれを真実そのものではなく、あくまで「確認すべき」というシグナルとして扱うべきとされています。現在の状態はVoided Purchases APIで確認し、その上でrevokeを行ってください。通知は遅延をなくす役割を担い、APIは実際の判断材料となる正式なvoidedSourceとvoidedReasonを提供します。
voidedReasonを確認してください。7は顧客の銀行が決済を取り消したチャージバックを示し、6はフレンドリーフラウド(friendly fraud)です。1は単純な気持ちの変化による通常の返金です。この区別が重要なのは、2026年8月3日以降、Googleがチャージバックの金額と銀行手数料を開発者に課すようになるためで、7は通常の返金よりもコストがかかります。
含まれます。typeパラメータを1に設定すると、voidされたアプリ内購入とvoidされた定期購入の両方が返されます。デフォルト値の0では、アプリ内商品のみが返されます。定期購入については、1つのpurchaseTokenがすべての更新にわたって共通で使われる一方、各更新トランザクションには個別のorderIdが付与されるため、voidされた正確な期間はorderIdで特定してください。



