讀懂你的伺服器早已收到的退款原因代碼,它會告訴你該修應用程式還是該和客戶爭
Apple 和 Google 送到你伺服器的每一筆退款都帶著一個原因代碼。Google Play 在每一次作廢上蓋上九種原因之一以及一個來源,Apple 則標記這筆退款是否歸咎於你的應用程式。以下是每個代碼的含義,如何把它們分成修、爭、或接受,以及它們在金錢上值多少。

重點摘要
- Apple 或 Google 送到你伺服器的每一筆退款都帶著一個退款原因代碼,而它是錢已經轉走之後,你還能讀到的退款中唯一的一部分。它告訴你退款為什麼發生,也就告訴你接下來該做什麼。
- Google Play 的 Voided Purchases API 在每一次作廢上蓋上兩個數字:一個從 0 到 8 的 voidedReason(其他、後悔、未收到、有瑕疵、意外購買、詐騙、友善詐騙、退單、未確認購買),以及一個 voidedSource:0 使用者、1 開發者、或 2 Google。
- Apple 給你的訊號較窄但很銳利。在一筆已退款的交易上,當 App Store 因為你應用程式內部的實際或感知到的問題而退款時,revocationReason 為 1,當它因為其他原因退款(例如意外購買)時則為 0。
- 這些代碼分成三堆。有瑕疵、未收到、未確認,以及 Apple 的應用程式內問題代碼,都指向你的產品,所以你去修它們。詐騙、友善詐騙、和退單是你要爭或要預防的爭議。後悔和意外購買從來就不是你能阻止的。
- voidedReason 8,未確認購買,是一筆你自己開給自己的退款帳單。對於任何你的應用程式在三天內未確認的購買,Google 會自動退款並撤銷,而這個代碼就是你在自己的整合中找到那個錯誤的方式。
- voidedReason 7,退單,是昂貴的那一個。對於在 August 3, 2026 當天或之後下的 Google Play 訂單,一筆敗訴的退單會讓開發者損失購買價格減去 Play 的服務費,再加上銀行的退單手續費,所以清點你標記為退單的作廢,就是在清點真實的額外成本。
- Voided Purchases API 只回溯 30 天,而且它是以 Google 看到作廢的時間來篩選,不是購買發生的時間,所以一個你沒有在那個視窗內擷取到的原因代碼,就是一個你永遠失去的原因代碼。
當 Apple 或 Google 退款給你的某位客戶時,錢通常在你有機會表態之前就已經沒了。之後落到你伺服器上的東西看起來像一張收據,而大多數團隊也就把它當收據看待。它不只是這樣。每一筆退款都帶著一個退款原因代碼,而它是決定做出之後,你仍然能讀到的退款中唯一的一部分。Google Play 告訴你這筆退款是一次退單,或是一次後悔的請求,或是一筆你自己的應用程式從未確認的購買。Apple 告訴你這筆退款是否歸咎於你應用程式內部的某個東西。讀懂那個代碼,退款就不再是報表裡的一行,而變成一道指令:修這個、爭這個、或放掉這個。以下是每個代碼的含義、如何分流它們、以及每一個各自的成本。
退款原因代碼到底是什麼
退款原因代碼是商店對於一筆購買為何被撤銷所給出的自家標籤。你無法設定它,也無法和它爭論。它在事後隨退款附帶而來,而兩家商店以不同的形式、以及非常不同的解決方式來揭露它。
Google Play 在每一次作廢上蓋上一個原因和一個來源
Google Play 的 Voided Purchases API 對每一筆被撤銷的購買回傳一筆記錄,而每筆記錄都帶著兩個重要的整數。voidedReason 說明這筆購買為何被作廢。voidedSource 說明是誰發起的。兩者合起來,就把一筆光禿禿的退款變成一句話:這筆訂單因為退單而被作廢,由 Google 發起,或因為後悔而被作廢,由使用者發起。你透過輪詢 API,或訂閱在作廢落地時觸發的即時開發者通知來讀取它們。無論哪種方式,這兩個數字都是值得保留的 payload。
Apple 給你的訊號較窄,但很銳利
Apple 不會給你一個九選一的原因。在一筆已退款的交易上,Apple 把 revocationReason 設為兩個值之一。值為 1 表示 App Store 因為你應用程式內部的實際或感知到的問題而退了這筆交易。值為 0 表示它因為其他原因退款,例如意外購買。這個欄位只出現在被退款或被撤銷的交易上,和一個 revocationDate 一起,位於 REFUND App Store Server Notification 的已簽署交易資訊之內。兩個值不算多,但真正重要的那個,1,是 Apple 在告訴你這筆退款是關於你的產品,而不是客戶的三心二意。
Google Play 給你的九個原因
Google 的 voidedReason 是兩者中較豐富的,而每一個值都值得一眼認出,因為每一個都指向不同的地方。以下是完整的一組,直接取自 VoidedPurchase 資源,附上每個代碼實際上在叫你做什麼。
| voidedReason | Google 的標籤 | 這個代碼在告訴你什麼 |
|---|---|---|
| 0 | 其他 | 沒有記錄具體原因。歸類收好並關注數量,而不是單一個案。 |
| 1 | 後悔 | 客戶改變了心意。你的應用程式沒有任何問題。 |
| 2 | 未收到 | 客戶說他們從未收到付了錢的東西。一個要查的交付問題。 |
| 3 | 有瑕疵 | 這筆購買無法運作。一個產品錯誤,也是這份清單上最可著手的代碼。 |
| 4 | 意外購買 | 誤點或非本意的購買。考慮更清楚的確認步驟。 |
| 5 | 詐騙 | Google 把這筆交易標記為詐騙。不是你的客戶,也不是你該留的收入。 |
| 6 | 友善詐騙 | 買家對一筆自己下單並收到的付款提出爭議。證據仍能觸及這一個。 |
| 7 | 退單 | 銀行撤銷了付款。最昂貴的路徑,現在還附帶一筆手續費。 |
| 8 | 未確認購買 | 你的應用程式從未確認這筆購買,所以 Google 自動退了款。你程式碼裡的一個錯誤。 |
voidedSource 告訴你是誰扣下扳機
原因旁邊坐著 voidedSource,它回答一個不同的問題:是誰撤銷了這筆。0 表示使用者自己做的,透過自助或銀行。1 表示開發者做的,也就是你或你自己的工具發出退款。2 表示 Google 做的,出於它自己的判斷,包括對未確認購買的自動退款。當你看到作廢暴增時,來源是第一刀切分。一整片 source 2 是 Google 對你的帳戶採取行動,而那通常是一個指回你整合本身、而非指向你客戶的訊號。
把每一筆退款分成修、爭、或接受
代碼之所以有用,是因為它告訴你一筆退款值得三種回應中的哪一種。大多數團隊對所有退款一視同仁,把力氣耗在那些永遠贏不了的上面。這些代碼分得很乾淨。
修:你的產品造成的退款
有些代碼是穿著退款外衣的錯誤回報。Google 上的有瑕疵(3)和未收到(2),以及 Apple 上為 1 的 revocationReason,全都說著同一件事:客戶付了錢,而你的應用程式沒有交付。未確認購買(8)是這些之中最銳利的,因為過錯完全在你的計費程式碼裡。這些是最便宜就能消除的退款,因為你消除它們靠的是修正你自己擁有的東西,而不是說服任何人。這一堆裡不斷上升的計數,是一個帶著金額標價的產品缺陷。
爭:有人正在操弄的退款
詐騙(5)、友善詐騙(6)、和退單(7)是爭議。純粹的詐騙(5)不是你的客戶,也不是你原本就留得住的收入。友善詐騙(6),也就是買家拿到了他們付錢換來的東西然後又提出爭議,是你的證據仍能撼動的唯一一種爭議,而退單(7)就是那份證據被提交的地方。當其中一個落地時,若你還沒撤銷存取就撤銷它,而在有審查視窗開著的地方,你用你對那個帳戶所知道的來回應它。
接受:從來就不是你能阻止的退款
後悔(1)和意外購買(4)是客戶自己的回心轉意。Apple 為 0 的 revocationReason 也坐在這裡。沒有功能失效,也沒有詐騙發生。你可以用更清楚的購買確認來緩和意外購買那一堆,但你無法把一筆後悔退款爭回來,而花在嘗試上的時間,是從真正有錢的修那一堆裡被奪走的時間。
| 分堆 | Google 代碼 | Apple 訊號 | 你的行動 |
|---|---|---|---|
| 修 | 2 未收到、3 有瑕疵、8 未確認 | revocationReason 1 | 追查背後的產品或計費錯誤的根本原因 |
| 爭 | 5 詐騙、6 友善詐騙、7 退單 | (透過 REFUND 浮現,不是透過原因) | 撤銷存取,用證據回應審查視窗 |
| 接受 | 1 後悔、4 意外購買 | revocationReason 0 | 記錄下來,調整購買流程,繼續前進 |

讓原因代碼容易丟失的 30 天陷阱
在 Google 那一邊有一個硬性限制,把這件事從一個報表功能變成一個截止期限。如果你沒有持續擷取這些代碼,你就在丟失它們。
Voided Purchases API 只回溯 30 天
Google 明確表示這個 API 只能顯示過去 30 天內被作廢的購買。更舊的作廢無論你傳入什麼 startTime 都不會回傳,而 startTime 這個值本身也不能設得比 30 天前更早。對一個天真的整合來說更糟的是,這個 30 天視窗是以 Google 的系統看到一筆購買被作廢的時間來衡量,不是以購買發生的時間,甚至不是以記錄裡的 voidedTimeMillis。所以一個你沒有在那個視窗內拉取的退款原因代碼就沒了,而任何有空檔的每月匯出工作,都會默默地丟掉它來不及擷取的作廢。
這些原因代碼在金錢上值多少
有兩個代碼帶著具體的價格,而讀懂它們,就是你為那些否則會藏在一個彙總退款率裡的問題標上一個數字的方式。
有一個代碼是你自己開出的帳單
voidedReason 8,未確認購買,是你造成的退款最乾淨的例子。Google Play 要求你的應用程式在授予權益後的三天內確認一筆購買,如果你沒做到,Google 會自動退這筆訂單的款並撤銷該項目。每一個蓋上 8 的作廢都是一筆真實的銷售,來自一個想要這產品的客戶,卻因為一個確認購買的呼叫從未觸發而被交還回去。損失的金額是完整的銷售價格,加上你為了交付已經花掉的運算、API 呼叫、和儲存。這不是一筆你去談判的退款。它是一個你去關掉的錯誤,而代碼就是你找到它的方式。
退單代碼現在附帶一筆手續費
voidedReason 7,退單,在 August 3, 2026 改變了成本。對於在那一天當天或之後下的 Google Play 訂單,一筆敗訴的退單會讓開發者損失購買價格減去 Play 的服務費,再加上銀行的退單手續費,而 Google 只承擔它自己的服務費。因為退單手續費是固定的,而產品價格不是,在一筆便宜的應用程式內購買上,光是手續費就可能超過客戶付的金額。清點你的代碼 7 作廢現在是在清點一個成本項目,不只是一筆損失的銷售,這正是為什麼退單這一堆在你建立的任何退款報表裡都值得擁有自己的一列。
| 原因代碼 | 它讓你損失什麼 | 為什麼這個代碼重要 |
|---|---|---|
| 8 未確認購買 | 完整的銷售價格加上交付成本,發生在一筆客戶想要的銷售上 | 它是自己造成的,所以這個代碼是一個錯誤追蹤器 |
| 7 退單(Aug 3, 2026 當天或之後的訂單) | 銷售價格減去 Play 的服務費,再加上銀行的退單手續費 | 唯一一個在損失的銷售之上再加一筆手續費的代碼 |
| 3 有瑕疵 | 銷售價格加上交付成本,對每一個踩到這個錯誤的客戶重複一次 | 這個代碼裡的數量把一個產品缺陷以金額標出大小 |
| 1 後悔 | 銷售價格,以及你已經花掉的交付成本 | 真實的成本,但不是換個代碼就能拿回來的那種 |
Apple 和 Google 如何對齊
兩家商店以不同的解析度回答同一個問題,所以一份跨商店的退款報表必須把它們正規化,而不是期待它們相符。
| 問題 | App Store | Google Play |
|---|---|---|
| 代碼住在哪裡 | REFUND 通知的已簽署交易裡的 revocationReason | Voided Purchases API 及其通知裡的 voidedReason |
| 有多少個原因 | 兩個:1 你應用程式內的問題,0 其他 | 九個,從 0 其他到 8 未確認購買 |
| 是誰做的 | 未分項列出 | voidedSource:0 使用者、1 開發者、2 Google |
| 能往回讀多遠 | 只要你查詢就能在交易上取得 | 只有過去 30 天的作廢 |
| 最銳利的訊號 | 值為 1 表示這筆退款是關於你的產品 | 代碼 3、8、和 7 各自指向一個獨立、可修的成本 |
兩家商店永遠不會為同一筆退款給你同一個代碼,這沒關係。重要的是兩者都遞給你一個機器可讀的原因,也都獎勵一個會去讀它的團隊。Apple 的單一位元告訴你什麼時候一筆退款是你產品的錯。Google 的九個原因和它的來源旗標告訴你你正看著的是哪一個產品錯誤、哪一場爭議、和哪一個自己造成的計費缺口。沒有任何代碼會阻止一筆退款。兩者都告訴你該做什麼,好讓下一筆不再發生。
RefundHalt 在每一筆退款到達的那一刻就擷取它的原因代碼,兩家商店都是,並把它舒適地保留在 Google 的 30 天視窗以內,讓任何東西都不會溜走。它把每一次作廢分成修、爭、或接受,所以代碼 3 有瑕疵的暴增會以一則產品警示的形式到達你,代碼 8 未確認的暴增會以一個整合錯誤的形式到達你,而不是一次模糊的營收下滑。它在 12 小時內回應 Apple 的 CONSUMPTION_REQUEST,在 24 小時內回應 Google Play 的退單審查,並在退款或退單一落地的那一刻就撤銷存取。你無法改變一家商店蓋在一筆退款上的代碼。你可以確保你讀了每一個,並針對那些真正屬於你該修的採取行動。
常見問題解答
- App Store 和 Google Play 上的退款原因代碼是什麼?
- 它是商店對於一筆購買為何被撤銷所給出的自家標籤,隨退款一起送到你的伺服器。Google Play 的 Voided Purchases API 回傳一個從 0 到 8 的 voidedReason,以及一個 voidedSource:0 使用者、1 開發者、或 2 Google。Apple 在退款是因為你應用程式內部的問題時把 revocationReason 設為 1,因為其他原因(例如意外購買)時設為 0。你無法設定這個代碼也無法改變它,但讀懂它會告訴你這筆退款是指向你的產品、一場爭議、還是客戶的回心轉意。
- Google Play 的 voidedReason 有哪些值?
- 共有九個:0 其他、1 後悔、2 未收到、3 有瑕疵、4 意外購買、5 詐騙、6 友善詐騙、7 退單、以及 8 未確認購買。每一個都由 Voided Purchases API 針對被作廢的購買回傳,並附上一個說明是誰發起作廢的 voidedSource。代碼 2、3、和 8 指向你自己應用程式裡的問題,代碼 5、6、和 7 是爭議,而代碼 1 和 4 是客戶自己的決定。
- Apple 的 revocationReason 為 1 是什麼意思?
- 它表示 App Store 因為你應用程式內部的實際或感知到的問題而退了這筆交易,相對於值為 0,那表示退款是因為其他原因發生的,例如意外購買。這個欄位只出現在被退款或被撤銷的交易上,和一個 revocationDate 一起,位於 REFUND App Store Server Notification 的已簽署交易資訊之內。1 是 Apple 在告訴你這筆退款是關於你的產品。
- Google 為什麼用未確認這個原因代碼退了一筆購買的款?
- 因為你的應用程式沒有及時確認這筆購買。Google Play 要求你在授予權益後的三天內確認一筆購買,如果你沒做到,Google 會自動退這筆訂單的款並撤銷該項目,在那次作廢上蓋上 voidedReason 8。這是一筆你用計費錯誤造成的退款,不是客戶的請求,所以修正之處在你的購買處理程式碼裡,而不在任何談判裡。
- 我能往回讀退款原因代碼多遠?
- 在 Google Play 上,只有 30 天。Voided Purchases API 回傳過去 30 天的作廢,並忽略任何比那更早的 startTime,而且它是以 Google 看到作廢的時間來衡量這個視窗,不是以購買發生的時間。一個你沒有在 30 天內擷取到的代碼就丟了,所以你應該訂閱即時的作廢購買通知,或按一個舒舒服服落在視窗以內的排程輪詢。Apple 的 revocationReason 則是只要你查詢就一直留在交易上。
資料來源與延伸閱讀
- Google Play Developer API: REST Resource purchases.voidedpurchases (voidedReason and voidedSource values)
- Google Play Developer API: Method purchases.voidedpurchases.list (30-day lookback window)
- Google Play: Voided Purchases API overview
- Apple Developer: revocationReason (App Store Server Notifications)
- Apple Developer: Transaction.RevocationReason (StoreKit)
- Apple Developer: Handling refund notifications (revocationDate and revocationReason on REFUND)
- Google Play Console Help: Updates to refund protection and chargeback cost responsibility (August 3, 2026)
- Google Play Billing: Integrate the Google Play Billing Library (acknowledge within three days or auto-refund)
RefundHalt
App Store 與 Google Play 的退款自動駕駛
繼續閱讀
過多的退款申請,代價可能遠超退款本身,因為兩大 App 商店都把它視為品質訊號
Apple 審查準則 5.6.4 把過多的退款申請列為 App 未達品質標準的跡象,Google 的開發人員發布協議也允許它依退款率下架新開發人員的 App。本文整理兩家商店的原文、下架的實際成本,以及如何避開風險。
在處理退款之前先驗證 App Store 伺服器通知,因為任何人都能向你的 webhook URL 發送請求
在檢查簽章之前,一則退款通知只不過是一個 HTTPS 請求。本文說明如何驗證 App Store 伺服器通知和 Google Play RTDN 訊息、各商店簽署的內容,以及偽造的退款或遭錯誤拒絕的退款會讓你付出什麼代價。