所有文章
Deep dive閱讀時間 8 分鐘

當 Google Play 的購買被退款或被 chargeback 時,Voided Purchases API 就是你得知此事的方式

當購買被退款或被 chargeback 時,Google Play 會悄悄地作廢它。Voided Purchases API 就是這些訂單的清單,讓你可以撤銷存取權。這裡有每一個欄位、30 天的時間窗、會隱藏訂單的 revoke 選項,以及它的成本。

一支智慧型手機旁邊放著一本紙本帳簿、一把上鎖的黃銅掛鎖,以及一枚正在滑走的硬幣,說明回報退款與 chargeback 訂單的 Google Play Voided Purchases API

重點摘要

  • Voided Purchases API,也就是 purchases.voidedpurchases.list 方法,會回傳 Google Play 已取消、退款或 chargeback 的訂單,讓你可以建立一套撤銷系統,切斷客戶不再擁有的東西的存取權。
  • 只有被 revoke 的訂單才會出現。開發者在未使用 revoke 選項的情況下發出的退款,對這個 API 是看不見的,所以如果你想收回存取權,就必須在開啟 revoke 的情況下退款。
  • 時間窗是 30 天。startTime 不能早於 30 天前,所以停機超過一個月的伺服器會永久失去那些作廢的訂單。要按排程輪詢。
  • voidedSource 告訴你是誰作廢了訂單:0 是使用者,1 是開發者,2 是 Google。voidedReason 告訴你原因,從 0 Other 到 7 Chargeback 以及 8 Unacknowledged_purchase。
  • Real-time developer notifications 會在購買被作廢的那一刻推送一個 VoidedPurchaseNotification,但要把它當成一個訊號。在撤銷之前,呼叫 Voided Purchases API 取得權威的清單。
  • 用 orderId 而不是 purchaseToken 來辨識訂閱的續訂。一個 purchaseToken 涵蓋一項訂閱的每一次續訂,所以單靠 token 無法分辨兩次續訂。
  • 配額是每天 6,000 次查詢,以及任何 30 秒視窗內 30 次查詢,所以要用 continuation token 逐頁翻閱結果,並按時間窗查詢,絕不要一筆訂單一次呼叫。

Google Play 上的退款不會來敲你的門。錢款移動了,客戶還開著 App,除非你去找,否則你這邊什麼都不會改變。Voided Purchases API 就是你去找的地方。它交給你一份已取消、退款或 chargeback 的訂單清單,讓你可以撤銷客戶不再付費之物的存取權。把一個排程工作指向它,讀取清單,切斷權限。這就是整個循環。

有一個陷阱會絆倒大多數團隊,而且它不在程式碼裡。只有被 revoke 的訂單才會在這裡出現。如果你在 Play Console 退款一筆購買卻沒有勾選 revoke 選項,那筆訂單永遠不會到達這個 API,而你的工作會乾淨地執行,同時一個已退款的客戶保留了你賣給他的一切。這篇文章逐一欄位走過這個 API、界定它的數字,以及當你略過它時錢從哪裡漏出去。

Voided Purchases API 實際上回傳什麼

這個 API 回答一個問題:這個 App 的哪些訂單最近被作廢了。作廢涵蓋三種都以客戶拿回錢款告終的結果。取消、退款或 chargeback。它適用於一次性的 App 內產品和訂閱,而你用單一參數選擇範圍。把 type 設為 0,你就只會得到被作廢的 App 內產品購買,這是預設值。把它設為 1,你就會同時得到被作廢的 App 內購買和被作廢的訂閱購買。

清單中的每一筆項目都是一個被作廢的購買物件。欄位不多,而且每一個都重要。

被作廢購買上的欄位

欄位它存放什麼
orderId唯一辨識一筆一次性購買、一筆訂閱購買,或單一次訂閱續訂的訂單 id。這是你的 join 鍵
purchaseToken辨識一筆一次性購買或一項訂閱的 token。它不區分續訂,所以那種情況請用 orderId
purchaseTimeMillis購買發生的時間,以自 epoch 起的毫秒計
voidedTimeMillis購買被取消、退款或 chargeback 的時間,以自 epoch 起的毫秒計
voidedSource誰發起了作廢:0 使用者,1 開發者,2 Google
voidedReason購買被作廢的原因,一個 0 到 8 的整數
voidedQuantity來自以數量為基礎的部分退款的作廢數量,只有當 includeQuantityBasedPartialRefund 為 true 時才會回傳

在採取行動之前先讀 voidedReason

voidedReason 是把原始清單轉成決策的欄位。買家後悔的退款和銀行的 chargeback 都落在同一份清單裡,但它們不是同一種事件,而八月的定價讓其中一種變得昂貴。以下是完整的一組。

voidedReasonLabel它對你意味著什麼
0Other未指派任何類別。撤銷然後繼續
1Remorse買家改變了主意。一筆普通的退款
2Not_received客戶說他從未收到產品。值得檢查你的交付
3Defective產品無法運作。一個品質訊號,記錄下來
4Accidental_purchase一筆無意的購買,常見於共用裝置
5FraudGoogle 將這筆交易標記為詐欺
6Friendly_fraud一筆 chargeback,其中合法的持卡人爭議一筆他自己做的扣款
7Chargeback客戶的銀行撤銷了付款。與銀行是最終定案,而且現在向你收費
8Unacknowledged_purchaseGoogle 自動退款了一筆你的 App 從未確認(acknowledge)的購買

30 天的時間窗是清空你清單的陷阱

Voided Purchases API 只能顯示過去 30 天內被作廢的購買。startTime 參數預設為目前時間減 30 天,而且不能設得比那更早。endTime 預設為現在。所以這個端點是一個滾動的一個月時間窗,而不是一個封存檔案。

後果很直接。如果你的輪詢工作壞了而五週內沒有人注意到,第一週的作廢就已經因逾期而離開了 API。沒有任何呼叫能把它們帶回來。你不會撤銷那些訂單,而且除非你用其他方式擷取過它們,否則你甚至不會知道它們曾經存在。這個 API 是一張安全網,上面有一個跟你最糟糕的停機一樣大的洞。

revoke 選項決定一筆訂單是否會出現

這是團隊回報 API 壞掉的最常見原因。只有被 revoke 的訂單才會被回傳。使用者發起的退款、取消、chargeback 和 Google 發起的退款總是會被 revoke,所以它們總是會出現。開發者發起的退款則不同。當你自己退款一筆訂單,透過 Play Console 或 Orders API 時,你選擇是否也要 revoke 它。退款而不 revoke,那筆訂單就與客戶結清了,但永遠不會浮現在 Voided Purchases API 裡。

隨之而來的規則很簡單。如果你的意圖是收回存取權,就在開啟 revoke 選項的情況下退款。否則你已經退了錢卻讓門開著,而你的撤銷工作,無論寫得多好,都沒有東西可以處理。

如何在不觸及配額的情況下輪詢它

這個端點有速率限制,而限制低到一個天真的迴圈就會撞上它們。你每天有 6,000 次查詢,以太平洋時間計算,任何 30 秒期間內不超過 30 次查詢。這個預算對於視窗式輪詢綽綽有餘,卻對一筆訂單一次請求的設計充滿敵意。

查詢視窗與 continuation token

maxResults 預設為 1,000,這也是上限。當一個視窗容納超過一頁的作廢時,回應會帶著一個含有 nextPageToken 的 tokenPagination 物件。在下一次呼叫時把那個 token 傳回去,以走過各頁。設定 startTime 和 endTime 來界定你在意的視窗,翻頁直到 token 用完,然後推進視窗。這個模式讓你同時待在 30 秒的爆量限制和每日上限之內。

Real-time developer notifications 填補了空隙

每天輪詢仍會留下最多一天的盲區,而 30 天的時間窗會懲罰長間隔。Real-time developer notifications 移除了延遲。Google 會在購買被作廢的那一刻,把一個 VoidedPurchaseNotification 發佈到你擁有的 Cloud Pub/Sub topic,而你的後端會在幾秒內消費它。這則訊息很小。

RTDN 欄位它存放什麼
purchaseToken來自原始購買的 token
orderId被作廢交易的訂單 id,每一次訂閱續訂都是一個新的
productType1 代表訂閱,2 代表一次性購買
refundType1 代表全額退款,2 代表以數量為基礎的部分退款
一隻手在智慧型手機旁把一把黃銅掛鎖扣在一疊收據上,說明在 Google Play 購買被作廢後撤銷存取權

這在金錢上讓你付出什麼代價

這個 API 是水電管線,但接上它的理由是一張帳單。那份清單裡的每一筆作廢都對應到一個真實的數字,而其中兩個正變得更昂貴。

從 2026 年 8 月 3 日起 chargeback 的帳單落到你身上

從 2026 年 8 月 3 日起,Google 把 chargeback 的成本轉嫁到開發者身上。你失去購買價金,還要在上面付銀行的 chargeback 手續費。voidedReason 為 7 不再只是一筆流失的銷售,它是一個附帶手續費的支出項目。你無法逆轉一筆 chargeback,它與銀行是最終定案,但你可以在它之後止血。快速抓到作廢讓你可以撤銷權限,而且對於你仍在交付的任何東西,停止在一個先退款又被撤銷付款的客戶身上花錢。

你持續付費去服務一個已退款的客戶

作廢一出現,購買價金就在那一刻消失了。你仍能控制的是持續交付的成本。一個已退款的權限每多活一個小時,你就持續為客戶不再資助的東西付費:運算、模型 API 呼叫、儲存,以及任何綁在他使用量上的創作者或夥伴分潤。一套由這個 API 驅動的撤銷系統,就是你關掉那個計量表的方式。略過它,你就是在為商店已經補償過的人資助這個產品。

Friendly fraud 是一個值得追蹤趨勢的模式

voidedReason 為 5 或 6 不是一次性的事件。Fraud 和 friendly fraud 會按帳號、按裝置,有時按促銷活動群聚。API 在每一筆作廢上給你 voidedSource 和 voidedReason,這足以按帳號追蹤濫用的趨勢,而不是把每一次撤銷付款當成一項孤立的成本。一個做了兩次 chargeback 的客戶,正告訴你第一次退款沒有告訴你的事。

用 RefundHalt 的方式把它接起來

一旦你握住所有的零件,這個模型就很小。即時聆聽 VoidedPurchaseNotification,好讓任何東西都不必等上整整一天。呼叫 Voided Purchases API 作為事實的來源,以 orderId 為鍵,好讓訂閱續訂永遠不會被搞混。讀取 voidedSource 和 voidedReason,好讓 chargeback 的處理不同於後悔退款。按一個緊到 30 天時間窗永遠不會咬到你的排程輪詢,並在你的意圖是切斷存取權時,一律在開啟 revoke 選項的情況下退款。

這就是 RefundHalt 為你執行的部分。它消費即時通知,把每一筆作廢與 API 對帳,撤銷確切的那筆訂單而不是整個產品,並把銀行 chargeback 與一筆普通退款分開,好讓昂貴的那些被標記出來,而不是被埋沒。你在幾秒內就取得被撤銷的存取權,以及一份誰作廢了什麼、為什麼的紀錄,而不必自己架起一條 Pub/Sub 管線和一個輪詢工作。

常見問題解答

為什麼我的已退款訂單沒有出現在 Voided Purchases API 裡?
因為只有被 revoke 的訂單才會被回傳。使用者退款、取消、chargeback 和 Google 發起的退款總是會被 revoke 且總是會出現。開發者發起的退款只有在你也選了 revoke 選項時才會出現。如果你退款一筆訂單卻沒有 revoke 它,那筆訂單就結清了但對這個 API 是看不見的,所以每當你打算收回存取權時,就在開啟 revoke 的情況下退款。
Voided Purchases API 能回溯多久?
三十天。startTime 參數預設為目前時間減 30 天,而且不能設得比那更早,所以這個端點是一個滾動的一個月時間窗,而不是一個封存檔案。一筆逾 30 天而過期的作廢訂單會從 API 中消失,沒有任何辦法取回它,這就是為什麼你要按排程輪詢,並以 real-time notifications 作為後盾。
我應該用 real-time developer notifications 還是 Voided Purchases API 來撤銷存取權?
兩者都用。VoidedPurchaseNotification 在幾秒內抵達並告訴你去看,但 Google 自己的指引是把它當成一個訊號,而不是事實的來源。呼叫 Voided Purchases API 以確認目前的狀態,然後撤銷。通知移除了延遲,而 API 給你權威的 voidedSource 和 voidedReason 以供採取行動。
我要如何在 API 裡分辨 chargeback 和一筆普通退款?
讀取 voidedReason 欄位。值為 7 是 chargeback,意思是客戶的銀行撤銷了付款,而 6 是 friendly fraud。值為 1 是一筆後悔退款。這很重要,因為從 2026 年 8 月 3 日起,Google 把 chargeback 的購買價金和銀行手續費轉給開發者,所以 voidedReason 為 7 讓你付出的比一筆普通退款更多。
Voided Purchases API 涵蓋訂閱嗎?
涵蓋。把 type 參數設為 1,就能同時取得被作廢的 App 內購買和被作廢的訂閱購買。預設值 type 0 只回傳 App 內產品購買。對於訂閱,用 orderId 辨識確切的被作廢週期,因為一個 purchaseToken 涵蓋每一次續訂,而每一筆續訂交易都會產生一個新的 orderId。

資料來源與延伸閱讀

RefundHalt

App Store 與 Google Play 的退款自動駕駛

繼續閱讀

下一筆退款申請已經在路上。

讀完另一封關於未能抗辯退款的客服郵件所需的時間,就足夠您設定好 RefundHalt。