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

Apple 的 Send Consumption Information 現在只要求五個欄位,而非十二個,以下逐一解讀

當客戶向 Apple 申請退款時,Send Consumption Information 載荷就是你的回應。Apple 已將它從十二個欄位精簡為五個,其中三個必填、兩個選填。以下是每個欄位、各自接受的取值,以及你必須在其中傳送的 12 小時視窗。

一部智慧型手機旁放著一疊單薄的紙本表單,大部分頁面已被撕去,象徵 Apple 的 Send Consumption Information 載荷從十二個欄位縮減為五個

重點摘要

  • Apple 的 Send Consumption Information 載荷現在包含五個欄位,較先前的十二個有所減少。三個為必填,customerConsented、deliveryStatus 和 sampleContentProvided,兩個為選填,consumptionPercentage 和 refundPreference。
  • customerConsented 是一道硬性關卡。Apple 自己的指引是,如果客戶未同意共享消耗資料,你就完全不傳送該載荷。
  • deliveryStatus 承載著你最有力的訊號。DELIVERED 表示購買正常生效,而四個 UNDELIVERED 取值則告訴 Apple 客戶從未拿到可用的產品。
  • consumptionPercentage 以一個 milliunits 為單位的精確數字,取代了舊有的四階 consumptionStatus 列舉,其中 100,000 milliunits 表示商品已被完全消耗。
  • refundPreference 現在是字串,而非數字。三個取值為 DECLINE、GRANT_FULL 和 GRANT_PRORATED,它表達的是一種傾向,而非決定。最終仍由 Apple 裁定。
  • 你透過向 Send Consumption Information 端點傳送 PUT 請求來提交載荷,以交易 id 為鍵,Apple 回傳 202 Accepted 且回應主體為空。視窗是 CONSUMPTION_REQUEST 之後的 12 小時。
  • 對於自動續訂訂閱,Apple 會根據已經過的時間自行計算消耗量,因此 consumptionPercentage 適用於消耗型與非續訂購買。

當客戶想要退款時,Apple 向你索取的事實清單大幅縮短了。Send Consumption Information 載荷,也就是開發者被允許送入 App Store 退款裁決的那唯一一次回應,過去有十二個欄位,現在只有五個。Apple 在 WWDC24 前後進行了精簡,把大部分舊有的帳戶側寫式問題收攏為兩個簡單問題外加一個數字。欄位減少並不是一處小改動。它改變了 Apple 權衡哪些證據,也改變了你絕不能填錯的是哪些欄位。

為什麼這值得你留意,而不只是一條 schema 備註,原因在此。這份載荷是 Apple 退款流程中,你這一方的說法唯一能觸及裁決的時刻。一筆消耗型退款會沖銷你已經實實在在花錢交付的收入,而這五個欄位正是你用來告訴 Apple 產品已交付並被使用的方式。錯過 12 小時視窗,或者填錯某個欄位,Apple 就只憑客戶一方的主張來裁定。

What Send Consumption Information is

Send Consumption Information 是 App Store Server API 的一個端點,你在 Apple 向你的伺服器傳送 CONSUMPTION_REQUEST 通知後呼叫它。該通知意味著某位客戶就一筆應用程式內購買向 Apple 申請退款,而 Apple 想在裁定前聽取你的意見。你的回應方式,是向該端點 PUT 一小段 JSON 主體,即 ConsumptionRequest。Apple 讀取它,與客戶的歷史一併權衡,然後作出決定。退款從來不由你決定。你提供事實。

這個 body 就是全部介面。沒有另外的表單,沒有申訴,也沒有第二次能算數的提交。你在那一次載荷裡傳送的一切,就是你的全部陳述,因此每個欄位的含義,比欄位數量所暗示的更為重要。

The five fields Apple asks for now

目前的 ConsumptionRequest 有五個成員。三個必填,兩個選填。Apple 過去就客戶帳戶所問的一切,他們的帳齡、終身消費、終身退款、遊玩時長,都已從你所傳送的內容中移除。

欄位是否必填型別承載內容
customerConsentedBoolean客戶是否同意向 Apple 共享消耗資料
deliveryStatusString你的應用程式是否交付了可用的產品
sampleContentProvidedBoolean你是否在購買前提供了免費樣品或試用
consumptionPercentageInteger購買內容被消耗了多少,以 milliunits 計
refundPreferenceString你對該退款請求偏好的結果

customerConsented is the gate

customerConsented 是一個 Boolean,也是決定你是否傳送任何內容的欄位。它記錄客戶是否同意讓你向 Apple 共享消耗資料。Apple 的指引很直接:如果客戶未同意,就不要傳送消耗資訊。所以這不是一個你為了強化己方論據而翻成 true 的欄位。它反映的是一個你必須早已握有的真實是與否,這裡為 false,就意味著載荷的其餘部分都不應傳送。

deliveryStatus is the field that moves refunds

deliveryStatus 是一個字串列舉,也是你手中最有力的槓桿。它告訴 Apple 你的應用程式是否真的交付了可用的應用程式內購買。一個取值表示是。另外四個表示否,各自對應不同原因,而每一個都告訴 Apple 客戶有正當的不滿。

取值它告訴 Apple 什麼
DELIVERED應用程式交付了可用的應用程式內購買
UNDELIVERED_QUALITY_ISSUE因品質問題未能交付購買內容
UNDELIVERED_WRONG_ITEM客戶收到了錯誤的商品
UNDELIVERED_SERVER_OUTAGE伺服器中斷阻止了交付
UNDELIVERED_OTHER因其他原因未能交付購買內容

sampleContentProvided answers a fairness question

sampleContentProvided 是一個 Boolean。它記錄你是否在客戶購買前給過他們免費樣品、試用,或關於該購買作用的明確說明。這裡為 true 是一個小小的公平性訊號:客戶有機會了解自己在買什麼。它本身不決定任何事,但它是僅有的三個必填欄位之一,可見 Apple 明確希望每次回應中都有它。

consumptionPercentage is a number now, not a status

這是變化最大的欄位。舊載荷有 consumptionStatus,一個四階列舉:UNDECLARED、NOT_CONSUMED、PARTIALLY_CONSUMED、FULLY_CONSUMED。新載荷用 consumptionPercentage 取而代之,這是一個以 milliunits 計量的整數。100,000 milliunits 表示商品已被完全消耗,那麼 50,000 就是一半,0 就是分毫未動。一個精確的數字勝過四選一的分階,因為它讓你能說客戶用掉了某個額度包的百分之九十,而不是向下取整成部分消耗。

有一個常讓人栽跟頭的注意事項。對於自動續訂訂閱,Apple 會根據已經過的時間自行算出消耗量,因此 consumptionPercentage 適用於消耗型與非續訂購買。在適用之處傳送它,在不適用之處交由 Apple 推導。

一部智慧型手機顯示一個大約填滿三分之二的環形進度圈,象徵以 milliunits 計量的 consumptionPercentage,其中 100,000 表示完全消耗

refundPreference states a preference, not a verdict

refundPreference 是一個選填字串,你在這裡告訴 Apple 你偏好的結果。它的形態也變了。舊欄位是一個數字,取值類似 prefer-grant、prefer-decline 和 no-preference。新欄位是一個具名字串,帶三個取值。

取值你所請求的內容
GRANT_FULL你希望 Apple 給予全額退款
GRANT_PRORATED你希望按已使用部分給予部分退款
DECLINE你希望 Apple 拒絕退款

What Apple dropped, and why it matters

舊的 ConsumptionRequest 有十二個欄位。其中七個已從你所傳送的內容中消失。accountTenure、lifetimeDollarsPurchased、lifetimeDollarsRefunded、playTime、userStatus、platform 和 appAccountToken 構成載荷中帳戶側寫的那一半,這些欄位要求你按客戶擁有帳戶的時長、花了多少錢、被退了多少款、使用應用程式多久來給他們分階。

Apple 刪掉它們的理由值得一提。那些欄位要求開發者交出一份客戶側寫,而大多數開發者要麼把它們留作未宣告,要麼靠猜。留下的五個關乎購買與交付,是你真正能從自己系統中核實的事實。這一轉變,是從客戶是誰,轉向這筆具體購買發生了什麼。

舊載荷目前載荷
欄位總數125
必填欄位實際上未強制任何3
消耗訊號consumptionStatus,四階consumptionPercentage,精確 milliunits
退款傾向數字列舉具名字串,3 個取值
帳戶側寫帳齡、終身消費、退款、遊玩時長、狀態已移除

The endpoint and the clock

你透過 HTTP PUT 向 Send Consumption Information 端點傳送載荷,以爭議購買的交易 id 為鍵:PUT /inApps/v1/transactions/consumption/{transactionId}。成功會回傳 202 Accepted,且回應主體為空。那個空回應是意料之中,而非 bug。它確認 Apple 已將你的資料排入佇列,卻不告訴你最終結果如何,那要稍後以 REFUND 或 REFUND_DECLINED 通知的形式到來。

時限是你無法拉長的部分。Apple 給你從 CONSUMPTION_REQUEST 起算的 12 小時來回應。只有你的第一次回應會被採用,所以第一份載荷就必須是完整且正確的那一份。Apple 可能就同一筆購買多次傳送 CONSUMPTION_REQUEST,但每一次的截止時間都是固定的,而人工審核在跨時區和週末的情況下,很難塞進 12 小時之內。

What a mishandled field costs you

The money left the building before the refund did

一筆消耗型退款不是乾淨俐落的沖銷。等到客戶就一包額度或一批 AI 生成申請退錢時,你早已為交付它花了錢:每次請求上的 GPU 推論、按 token 計費的第三方模型 API 呼叫、為你所產出內容付出的儲存,以及與該使用掛鉤的任何創作者或合作方分潤。商店售價退回給客戶。你的交付成本卻不會退回給你。所以一筆本可申辯的退款,不是一次不賠不賺的事件,而是你為服務該帳戶所付出的一切淨虧損。

The five fields are how you avoid paying twice

deliveryStatus 設為 DELIVERED,加上較高的 consumptionPercentage,是告訴 Apple 客戶已收到並使用了產品的兩項事實。它們是你證明算力、API 呼叫和儲存都各盡其責的證據。若把載荷留著不發,Apple 就永遠聽不到這些。它會憑客戶的主張來裁定,退款更有可能通過,而你要同時吞下被沖銷的收入和其背後的交付成本。

Chargebacks are the worse door, and silence points to it

一位無法透過 Apple 退款流程得到滿意結果的客戶,仍可向其銀行就該筆扣款發起爭議。信用卡拒付是最終的,它帶有一筆固定的爭議費,並把決定權從 Apple 和你手中一併奪走。妥善回應 CONSUMPTION_REQUEST,能讓爭議留在 Apple 的體系之內,你在那裡還有發言權。對它置之不理,則會把處於兩可之間的案子推向你毫無發言權的那唯一管道。

How RefundHalt handles it

這五個欄位看著簡單,直到你必須在 12 小時之內、對每一次 CONSUMPTION_REQUEST、以正確的交易為鍵,把它們填對。RefundHalt 捕捉通知,讀取你自己關於那筆購買的交付與使用紀錄,並在視窗關閉前自動傳送載荷。deliveryStatus 反映你日誌真正顯示的情況,consumptionPercentage 來自真實使用而非猜測,refundPreference 遵循你一次設定好的政策。你得到的是一筆以證據受審的可申辯退款,而非一個錯過的截止時間和一份沒有你參與就作出的決定。

常見問題解答

Apple 的 Send Consumption Information 現在有多少個欄位?
五個。三個必填,customerConsented、deliveryStatus 和 sampleContentProvided,兩個選填,consumptionPercentage 和 refundPreference。該載荷的上一版本有十二個欄位,Apple 移除了其中帳戶側寫類的欄位,例如 accountTenure、lifetimeDollarsPurchased 和 userStatus。
消耗請求中的 deliveryStatus 是什麼意思?
deliveryStatus 告訴 Apple 你的應用程式是否交付了可用的應用程式內購買。DELIVERED 表示交付了。四個 UNDELIVERED 取值,UNDELIVERED_QUALITY_ISSUE、UNDELIVERED_WRONG_ITEM、UNDELIVERED_SERVER_OUTAGE 和 UNDELIVERED_OTHER,各自以某個明確原因表示未交付。它是載荷中最有力的訊號,因此必須與你自己的日誌相符。
consumptionPercentage 是百分比還是原始數字?
它是一個以 milliunits 計量的整數,而非普通百分比。100,000 milliunits 表示客戶完全消耗了該購買,那麼 50,000 是一半,0 是分毫未動。它取代了舊的 consumptionStatus 列舉,後者只有從未消耗到完全消耗的四個分階。
把 refundPreference 設為 DECLINE 會阻止退款嗎?
不會。refundPreference 表達的是你偏好的結果,它不決定任何事。DECLINE 告訴 Apple 你寧願它不退款,GRANT_FULL 或 GRANT_PRORATED 則告訴它相反的意思,但 Apple 會把你的傾向與客戶的歷史及其自身政策一併權衡,然後作出最終裁定。
如果客戶未同意共享消耗資料怎麼辦?
那你就不應傳送該載荷。customerConsented 是一個必填的 Boolean,而 Apple 的指引是,如果客戶未同意共享消耗資料,你就完全不回應 CONSUMPTION_REQUEST。同意與否是一個你必須早已握有的真實是與否,而不是一個你為幫助己方論據而設成 true 的值。
我有多長時間來傳送消耗資訊?
從 Apple 傳送 CONSUMPTION_REQUEST 通知起算的 12 小時。你以向 Send Consumption Information 端點傳送 PUT 來回應,成功會回傳 202 Accepted 且回應主體為空。只有你的第一次回應會被採用,所以第一份載荷必須完整,而人工流程很難塞進這個視窗之內。

資料來源與延伸閱讀

RefundHalt

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

繼續閱讀

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

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