所有文章
Playbook閱讀時間 7 分鐘

蘋果做出裁定後會發出三種 App Store 退款通知,而 REFUND_REVERSED 會把這筆銷售還給你

蘋果透過 App Store Server Notifications V2 發送四種退款訊息,而大多數應用程式只處理其中兩種。REFUND 告訴你要撤銷權益,REFUND_DECLINED 意味著保留這筆銷售,REFUND_REVERSED 則把銷售還給你,並要求你恢復先前撤銷的內容。以下說明每一種各需要什麼處理。

一支智慧型手機顯示著付款收據,旁邊放著一個退回的信封和一枚硬幣,寓意蘋果在裁定退款後發出的 App Store 退款通知

重點摘要

  • 蘋果透過 App Store Server Notifications V2 發送四種與退款相關的訊息。CONSUMPTION_REQUEST 索取你的證據,而 REFUND、REFUND_DECLINED 和 REFUND_REVERSED 會在蘋果已經做出裁定後回報結果。
  • REFUND 通知意味著 App Store 已對該筆交易退款。它攜帶 revocationDate 和 revocationReason,是你撤銷與該單筆交易綁定權益的訊號,而不是撤銷該產品的每一次購買。
  • revocationReason 有兩個取值。1 表示因你的產品存在問題而核准退款,0 表示因其他原因核准退款。取值為 1 的情況是值得記錄並追蹤趨勢的品質訊號。
  • REFUND_DECLINED 表示蘋果駁回了顧客的退款。你保留這筆銷售,無需改動任何東西,但前提是你在裁定成為最終結果之前沒有撤銷過存取權限,這樣才安全。
  • REFUND_REVERSED 表示蘋果撤銷了它先前已核准的退款,通常發生在顧客對該退款提出異議之後。交易上的撤銷欄位會消失,蘋果自身的指引是,如果你撤銷過內容,就需要恢復它。
  • 對全部四種通知都要以 HTTP 200 回應。如果你的伺服器曾當機而漏掉了某一條,Get Refund History 端點可以讓你按交易 id 查詢已退款的交易並進行對帳。
  • 針對過去某個訂閱週期的退款並不總是意味著應當結束存取。如果一個更新的付費週期仍然有效,對舊交易執行撤銷會切斷一位仍在付費的顧客的服務。

蘋果對你的退款做出裁定後,它並不會就此沉默。一旦結果確定,App Store 會向你的伺服器發送三種 App Store 退款通知之一,每一種都要求不同的動作。REFUND 表示錢已經退掉,你應當收回存取權限。REFUND_DECLINED 表示顧客的請求失敗,你保留這筆銷售。REFUND_REVERSED 表示蘋果撤銷了它先前已經核准的退款,於是這筆銷售重新歸你所有,你需要歸還先前收回的一切。大多數應用程式只接好了第一種,而悄悄忽略了另外兩種。一位付費顧客就是這樣被鎖在了他們已經付過錢的東西之外。

這三種與 CONSUMPTION_REQUEST 不同,後者是唯一一種要求你回覆的退款訊息。裁定後的通知不想要爭辯。它們要的是一個 HTTP 200 以及對顧客存取權限的正確改動。以下說明每一種的含義、承載事實的確切欄位,以及處理不當時錢會從哪裡漏掉。

四種退款通知,以及哪一種需要回覆

App Store Server Notifications V2 是一條單一的資訊流。你只把它指向一個 URL,蘋果就會把每一種通知類型都發到那裡,所以無論你是否處理,你其實都已經收到了全部四種退款訊息。其中有四種類型涉及退款,而只有一種是提問。

通知蘋果在告訴你什麼你的動作是否需要回覆
CONSUMPTION_REQUEST顧客申請了退款,蘋果需要你的資料在 12 小時內 Send Consumption Information需要,真實資料
REFUNDApp Store 已對該筆交易退款撤銷該筆交易的權益不需要,HTTP 200
REFUND_DECLINEDApp Store 駁回了退款保留存取,不改動任何東西不需要,HTTP 200
REFUND_REVERSED蘋果撤銷了它已核准的退款恢復你先前撤銷的內容不需要,HTTP 200

REFUND 通知究竟在告訴你什麼

當 App Store 成功地向顧客退回了一筆交易時,REFUND 就會觸發。它適用於每一種購買類型:消耗型項目、非消耗型項目、自動續訂訂閱以及非續訂訂閱。通知內部經過簽署的交易,現在攜帶了退款之前沒有的兩個欄位,而這兩個欄位就是全部故事。

revocationDate 和 revocationReason 承載事實

revocationDate 是 App Store 對該筆交易退款或撤銷它的 UNIX 時間,以毫秒為單位。revocationReason 告訴你退款的類別,它恰好有兩個取值。

revocationReason蘋果的含義你應從中讀出什麼
1因產品存在問題而核准退款一個品質或交付訊號。記錄它,追蹤趨勢,並在某個產品或某個組建版本中尋找規律
0因其他原因核准退款一次普通退款。撤銷權益,然後繼續

一筆交易上出現 revocationDate 本身就是標誌。如果你之後取回一筆交易,發現它帶有 revocationDate,那麼無論有沒有通知,這次購買都已被退款。請同時讀取它的原因,這樣某個版本上出現的一波取值為 1 的退款潮就不會作為雜訊從你眼前溜過。

按交易撤銷,而不是按產品撤銷

這裡的陷阱是撤銷得太多。一條 REFUND 只指名一筆交易。它並沒有讓你停用顧客對該產品 id 曾經進行過的每一次購買。蘋果自身的指引是,在你切斷任何東西之前,先檢查顧客仍然持有哪些存取權限,因為權益之間會重疊。經典的情形是訂閱:退款落在上個月的續訂上,而這個月的續訂正處於有效且已全額付款的狀態。若你按產品撤銷,你就為一筆針對已經結束的週期的退款,切斷了一位當前仍在付費的顧客。

REFUND_DECLINED 意味著你已經贏了,所以不要把它撤回

當 App Store 駁回了顧客的退款請求時,REFUND_DECLINED 就會到來。顧客提出了申請,蘋果說不行,你保留這筆銷售。表面上沒有什麼要做的,而這正是關鍵。這條通知暴露的錯誤是另一個:過早撤銷存取權限。

如果你的程式碼在蘋果做出裁定之前就對 CONSUMPTION_REQUEST 做出反應、收回了顧客的存取權限,那麼 REFUND_DECLINED 就是那個決定爆雷的時刻。蘋果留下了你的錢,而你卻把一位退款被駁回的顧客鎖在了門外。這位顧客現在為一個他無法使用的產品付費,提交一張支援工單,並把這件事記在心裡。修復辦法是一條規則,而不是一個功能:在 REFUND 時撤銷,絕不在請求時撤銷。REFUND_DECLINED 只不過是蘋果在確認,過早撤銷本來就是錯誤的做法。

REFUND_REVERSED 是那條把錢還給你的通知

REFUND_REVERSED 是幾乎沒人處理的那一條,也是把錢還給你的那一條。當蘋果撤銷它先前已核准的退款時,它就會發送這條通知,通常發生在顧客對該退款提出異議之後。REFUND 先前給交易添加的撤銷欄位又被移除了,於是該筆購買再次顯示為已付款。蘋果用一句話說明了開發者的職責:如果你的應用程式因相關退款而撤銷了內容或服務,就需要恢復它們。它適用於任何購買類型,從消耗型項目到自動續訂訂閱。

數週之後才到來的問題

開發者在蘋果自己的論壇上提出的真正問題是時機。一條 REFUND_REVERSED 可能在最初的 REFUND 之後數週才到來,那時距訂閱週期到期早已過去很久。那時你還要不要恢復存取?恢復該筆交易實際授予的東西,範圍限定在該筆交易所涵蓋的內容之內。對於消耗型或非消耗型項目,把解鎖重新打開。對於已經過去的訂閱週期,你並不是在發放新的時長,而是在更正記錄,讓顧客的歷史準確無誤,並讓任何仍然有效的權益重新生效。恢復那一筆具體的交易,然後由你的重疊邏輯來判定當前哪些是有效的。

一枚硬幣被放回智慧型手機旁邊,寓意一次被撤銷的 App Store 退款把銷售還給了開發者

把這件事做對的價值在哪裡

這些通知中的每一條都對應一個真實的數字,而處理不當的代價並不只是銷售價格本身。

REFUND:別再為一位已退款的顧客付出服務成本

REFUND 一到,銷售價格就已經沒了。你仍然能控制的,是繼續交付所帶來的成本。一筆已退款的權益每多存活一小時,你就在為顧客不再付費的東西繼續花錢:運算、模型 API 呼叫、儲存,以及與其使用相關的任何創作者或合作方分潤。在 REFUND 時及時撤銷可以讓這個計費表停下來。忽略這條通知就意味著你在為一個商店已經補償過的人資助產品。

REFUND_DECLINED:別把一場勝利變成一次善意退款

當你過早撤銷、而退款之後又被駁回時,你在帳面上保住了這筆銷售,卻在實際中失去了它。付了錢的顧客無法使用產品,於是你接手了一場支援對話,並且往往還要給一筆酌情退款來把事情擺平。這是在為一筆從未有過風險的銷售付兩次錢。正確處理 REFUND_DECLINED 不需要任何成本,這正是為什麼在 REFUND 之前保持存取權限不動,是你能採納的最省錢的規則。

REFUND_REVERSED:最糟的組合是錢和存取權限都沒了

忽略 REFUND_REVERSED,你就會走向全盤最糟的結局。你已經收了錢,而顧客卻一無所有。他們已經聯繫過一次銀行來撤銷退款,而一個被鎖在如今仍要為其付費的產品之外的人,很可能會第二次聯繫銀行。下一次爭議可能演變為信用卡拒付,那是由銀行終裁的,代價比這筆銷售本身高得多。REFUND_REVERSED 一到就恢復存取權限,是整個退款流程中最便宜的保險。

需要接好哪些環節

一旦模型對了,處理起來其實很小。把權益以交易 id 為鍵,讓每一條通知都指向唯一一筆購買。收到 CONSUMPTION_REQUEST 時,在 12 小時內發送你的資料。收到 REFUND 時,撤銷那筆交易。收到 REFUND_DECLINED 時,什麼都不做。收到 REFUND_REVERSED 時,恢復。對所有通知都迅速返回 HTTP 200,然後在你自己的時間裡做存取權限的改動。

對於通知留下的缺口,使用 Get Refund History 端點。如果你的伺服器在一次故障中當機、漏掉了一條 REFUND,就針對某個交易 id 呼叫 App Store Server API 的退款查詢,路徑為 /inApps/v2/refund/lookup/{transactionId},並讀回帶有 revocationDate 和 revocationReason 的已簽署交易。它一次對一筆交易進行對帳,並翻頁瀏覽顧客的已退款購買,這樣一個漏掉的 webhook 就不會變成一個被永久設錯的權益。

這正是 RefundHalt 為你運行的部分。它監聽全部四種類型,在 REFUND 時撤銷,在 REFUND_DECLINED 時保持存取不動,並在 REFUND_REVERSED 時自動恢復,每一步都以確切的交易為鍵。一筆被撤銷的退款不會在佇列裡乾等而讓付費顧客一直被鎖在外面,而一筆被駁回的退款也絕不會觸發一次你還得回退的撤銷。

常見問題解答

REFUND 和 REFUND_REVERSED 有什麼區別?
REFUND 表示 App Store 對一筆交易進行了退款,你應當撤銷該權益;而 REFUND_REVERSED 表示蘋果撤銷了它已核准的退款,你應當恢復先前撤銷的內容。兩者成對出現:一筆購買可以先走 REFUND,之後如果顧客的異議被推翻,再走 REFUND_REVERSED。把你的存取權限改動以交易 id 為鍵,這樣每條通知都作用於正確的那筆購買。
收到 REFUND 通知需要回傳什麼嗎?
不需要。你對 REFUND、REFUND_DECLINED 和 REFUND_REVERSED 都以一個不帶內文的 HTTP 200 回應。只有 CONSUMPTION_REQUEST 要求你發送資料,且需在 12 小時內透過 Send Consumption Information 端點發送。另外三種是蘋果在回報一個裁定,而不是在提問。
收到 REFUND_DECLINED 通知時我該做什麼?
什麼都不改,因為顧客的退款被駁回了,你保留這筆銷售。REFUND_DECLINED 唯一會給你帶來工作量的情形,是你在蘋果裁定之前就過早撤銷了存取權限。在 REFUND 時撤銷,而不是在 CONSUMPTION_REQUEST 時撤銷,那麼 REFUND_DECLINED 就成了一個確認,說明存取權限被正確地保持了原狀。
當 REFUND_REVERSED 在退款數週後才到來時,我該恢復存取權限嗎?
是的,恢復該筆具體交易所授予的權益。蘋果說明,如果你的應用程式因相關退款而撤銷了內容,就需要恢復它。對於消耗型或非消耗型項目,把解鎖重新打開。對於已經過期的訂閱週期,你是在更正記錄,而不是發放新的時長,所以由你的重疊邏輯來判定當前哪些是有效的。
我該如何補上伺服器漏掉的退款通知?
使用 App Store Server API 的 Get Refund History 端點,它在 /inApps/v2/refund/lookup/{transactionId} 按交易 id 查詢某位顧客的已退款交易。它返回帶有 revocationDate 和 revocationReason 的已簽署交易,這樣在一次故障之後,你無需等待一條已經觸發過的通知就能對存取權限進行對帳。它每次呼叫處理一個交易 id,並翻頁瀏覽該顧客的已退款購買。

資料來源與延伸閱讀

RefundHalt

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

繼續閱讀

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

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