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

待處理購買看起來像一筆銷售,但錢還沒到帳,提前發放就等於把產品白白送出去

App Store 和 Google Play 都有待處理購買狀態,也就是商店已經受理但尚未扣款的訂單。在付款到帳之前就解鎖,每一筆最終未成立的訂單都是純粹的成本。本文說明待處理購買在兩家商店上如何運作,錯誤發放一筆會付出什麼代價,以及如何在不造成損失的情況下處理它們。

在暖光下,收銀員把一個包好的小包裹從櫃台遞過來,說明待處理購買中商品在付款到帳之前就已交付

重點摘要

  • 待處理購買是商店已經受理但尚未扣款的真實訂單。你的程式碼看到一筆新購買,但錢還沒到帳,而且可能永遠不會到帳。
  • 只有在 Google Play 上狀態為 PURCHASED,或在 Apple 上交易已完成時,才發放權益。絕不要在 PENDING 狀態或 Apple 的待處理結果上解鎖。
  • 在 Google Play 上,門市現金付款、銀行轉帳以及部分電信帳單代收是透過外部管道結算的,因此在客戶真正付款之前,購買回傳的是 PENDING 而不是 PURCHASED。
  • 在 Apple 上,待處理購買通常是 Ask to Buy,需要家庭組織者批准。批准可能需要數小時或數天,完成後的交易稍後會透過 Transaction.updates 送達。
  • 在一筆隨後被取消的待處理訂單上解鎖,你就在一筆從未到帳的收費上花掉了運算資源、API 呼叫、儲存空間,或消耗型商品的實際發放。與退款不同,這裡沒有錢可以追回,因為從來就沒有收到過錢。
  • 當一筆待處理訂單未能成立時,商店會通知你。Google 會發送 ONE_TIME_PRODUCT_CANCELED,類型 2,或 SUBSCRIPTION_PENDING_PURCHASE_CANCELED,類型 20。Apple 則乾脆永遠不送達已完成的交易。
  • 待處理訂單可能在你的應用程式關閉期間變成真實銷售,所以在回到前景時要重新檢查:在 Google Play 上於 onResume() 中呼叫 queryPurchasesAsync(),在 Apple 上持續監聽 Transaction.updates。

有人在你的應用程式裡點了購買。你的記錄檔顯示一筆新訂單,你的計費監聽器觸發,於是你交付了商品。對絕大多數購買來說,這樣做完全正確。但對待處理購買來說這是個錯誤,因為訂單存在,錢卻不存在。客戶選擇了一種稍後才結算的付款方式,商店還在等著收款,而你剛剛為一筆可能永遠不會到帳的收費交付了一項付費功能。這是退款那沉默的表親。這裡沒有任何東西被撤銷,因為從來就沒有收到過任何錢。你只是把產品白白送了出去。

待處理購買是一筆處於尚未付款狀態的真實訂單,App Store 和 Google Play 都有這種狀態。兩家商店都明白地告訴你要等待。陷阱在於,待處理訂單在你的程式碼裡看起來幾乎和已完成訂單一模一樣,因此一個把每筆新購買都當作銷售的整合,會在商店還在嘗試收款的訂單上就交付商品。處理得當,你不會有任何損失。處理不當,每一筆未能成立的慢速付款都是純粹的成本,由你自掏腰包交付。

待處理購買究竟是什麼

待處理購買是商店已經記錄但尚未扣款的訂單。買家開始了流程,商店受理了它,而結算正在你的應用程式看不到的某個地方進行。Google Play 把這稱為 PENDING 狀態。Apple 稱之為待處理交易,或延遲交易。名稱不同,事實相同:商店在等待收款的同時讓一筆訂單保持開放,並且它已經告訴你不要把那筆訂單當作錢。

Google Play:付款正在別處進行

有些付款方式是透過外部管道結算的。實體門市現金、銀行轉帳以及部分電信帳單代收,都在點擊與扣款之間需要額外的步驟。當客戶選擇其中一種時,Google 回傳的購買處於 PENDING 狀態而不是 PURCHASED。對於現金付款,客戶會透過通知和電子郵件收到一組代碼,帶著它前往參與門市,向收銀員付款。在此之前,Google 什麼都沒收到,你也一樣。Google 的規則只有一句話:使用 getPurchaseState(),只有在狀態為 PURCHASED 時才發放權益。它還告訴你不要在購買處於 PENDING 時確認它,因為確認屬於已付款的訂單,而不是一個被承諾的訂單。

Apple:這筆購買在等別人點一下

Apple 的待處理狀態意味著這筆交易在完成之前需要一個外部動作。最常見的是 Ask to Buy,孩子發起一筆購買,需要家庭組織者批准。在 StoreKit 2 中,購買呼叫會回傳 Product.PurchaseResult.pending。在較舊的 StoreKit 中,這筆交易被回報為 deferred。無論哪種方式,Apple 都還沒有向任何人扣款,而完成後的交易(如果會來的話)會透過 Transaction.updates 非同步送達。向客戶顯示等待狀態,在已完成的交易到達之前不要解鎖任何東西。

為什麼發放待處理購買會讓你付出真金白銀

這裡的損失不是退款那種,把你已經入帳的錢抽回去。它在一個特定方面更糟:根本沒有錢可抽回,因為從來就沒有收到過。

你交付了,商店卻從未收款

當你在一筆隨後被取消的待處理訂單上解鎖時,你已經為服務它花了錢。執行該功能的運算資源、你付費的第三方 API 呼叫、你配置的儲存空間,以及對消耗型商品來說你所售之物的實際發放。所有這些都隨著一筆沒有產生任何收入的訂單流了出去。退款至少始於一筆真實發生過的收費。而一筆被錯誤發放的待處理購買根本就沒有過收費,所以它甚至不會顯示為流出的錢。它什麼都不顯示,而這正是它容易被忽略、也容易反覆發生的原因。

取消訊號,以及它的含意

當一筆待處理訂單失效時,商店確實會通知你。在 Google Play 上,一款未能成立的一次性商品會發送 ONE_TIME_PRODUCT_CANCELED 通知,類型 2,而一個曾處於待處理的訂閱會發送 SUBSCRIPTION_PENDING_PURCHASE_CANCELED,類型 20。當同樣的訂單反過來成功時,你會收到 ONE_TIME_PRODUCT_PURCHASED,類型 1,或 SUBSCRIPTION_PURCHASED,類型 4。在 Apple 上沒有可以捕捉的取消事件,因為一筆被拒絕的延遲交易根本就不會變成已完成的交易。如果你提前解鎖了,那份沉默就是帳單。

問題Google PlayApple
由什麼觸發現金、銀行轉帳、部分電信帳單代收Ask to Buy 批准,或其他必需的操作
你看到的狀態PurchaseState PENDING待處理結果,或一筆延遲交易
何時發放存取權限狀態為 PURCHASED交易已完成
它成功了ONE_TIME_PRODUCT_PURCHASED (1)、SUBSCRIPTION_PURCHASED (4)透過 Transaction.updates 送達的已完成交易
它未能成立ONE_TIME_PRODUCT_CANCELED (2)、SUBSCRIPTION_PENDING_PURCHASE_CANCELED (20)永遠不會有已完成的交易送達
是否已收款否,直到 PURCHASED 才收款否,直到交易完成才收款

這段等待期,以及誰在等誰

待處理購買不是一個你在與之賽跑的計時器。從你這一側看,它是一個還沒有開始走的計時器。

Google Play 給客戶的是數天,而不是數分鐘

現金或銀行轉帳付款按客戶的節奏結算,而不是你的。訂單會停留在 PENDING,直到客戶付款,或者時限用盡、Google 取消它。你自己的三天確認時限,也就是那個在你未確認購買時會自動退款的時限,要等到購買從 PENDING 轉為 PURCHASED 之後才開始計時。所以沒有必要急著去服務一筆待處理訂單。需要的只是等待狀態改變的那份自律。

Apple 的批准在家庭組織者的手機上

一個 Ask to Buy 請求會作為提示出現在組織者的裝置上,他們會在有空的時候批准或拒絕。那可能是幾分鐘、幾小時,或一天之後,你的應用程式無法催促它。唯一正確的做法是呈現等待狀態,並讓 StoreKit 在批准到來時(如果會到來的話)把已完成的交易交給你。

一個封好的紙箱和一個沙漏並排放在桌上,說明待處理購買中商品已經備好但付款尚未到帳

如何在不造成損失的情況下處理待處理購買

整件事歸結為四個習慣。沒有一個是難事,而漏掉其中任何一個,就是錢流失的地方。

在已付款狀態上發放,絕不在待處理狀態上發放

在 Google Play 上,檢查 getPurchaseState(),只在 PURCHASED 時發放,並且不要在購買處於 PENDING 時確認它。在 Apple 上,只在已完成的交易上解鎖,絕不在待處理結果上解鎖。這一條規則就堵住了整個漏洞。其餘的一切都是為了確保你在已付款狀態到達時真的能注意到。

應用程式回到前景時重新檢查

從待處理到已付款的轉變常常發生在你的應用程式沒有執行的時候。在 Google Play 上,在你的 onResume() 處理常式中呼叫 queryPurchasesAsync(),以擷取在背景變為 PURCHASED 的訂單,並把你的 Real-time Developer Notifications 監聽器作為伺服器端的事實來源。在 Apple 上,在應用程式的整個生命週期內監聽 Transaction.updates,因為一筆獲批的交易可能在原始購買呼叫回傳很久之後才到達。

啟用待處理支援,並測試兩種結局

Google 要求你在建立 BillingClient 時呼叫 enablePendingPurchases(),而且為一次性商品支援待處理交易是強制的,不是可選的。在發布之前測試它。授權測試者會取得兩種額外的延遲付款測試工具,其中付款會在幾分鐘後自動完成或自動取消,這樣你就能端到端地觀察付款路徑和未成立路徑。

告訴客戶訂單還沒完成

一個處於待處理狀態的買家是一位正處在購買中途的真實客戶,不是一次失敗。告訴他們訂單正在等待他們的付款或一次批准,並給他們一條清晰的返回路徑去完成它。一個沉默的待處理狀態會流失那些被清晰標註就能挽回的銷售,因為這些買家中的大多數仍然想要那件東西,只是還差一步。

RefundHalt 為退款和拒付已經在讀取的那些 Real-time Developer Notifications 和 App Store Server Notifications 資料流,同樣攜帶著這些訊號。那條表示一筆待處理訂單終於到帳的已購買通知,以及那條表示它沒有到帳的已取消通知,都會落在你的儀表板裡,與其餘的收入事件並排,這樣一筆未能成立的待處理訂單就成了你看得見的東西,而不是你意外為之買了單的東西。

簡而言之

待處理購買是一筆沒有付款的訂單,兩家商店都明確表示你應該等待。Google Play 把現金、銀行轉帳以及部分電信帳單代收的訂單以 PENDING 狀態回傳,並告訴你只在 PURCHASED 時才發放存取權限。Apple 為 Ask to Buy 和其他必需操作回傳一筆待處理或延遲交易,並在稍後透過 Transaction.updates 送達已完成的交易。在已付款狀態上解鎖,在應用程式恢復時重新檢查,啟用並測試待處理支援,並為客戶標註這筆等待中的訂單。做到這些,待處理購買就不會讓你付出任何代價。跳過它,你就為一筆從未到來的收費交付了一款付費產品,而這是唯一一種拿不出收據來指認的損失。

常見問題解答

什麼是待處理購買?
待處理購買是商店已經受理但尚未扣款的訂單。在 Google Play 上,它是 PENDING 購買狀態,用於稍後才結算的付款方式,例如現金、銀行轉帳以及部分電信帳單代收。在 Apple 上,它是一筆待處理或延遲交易,最常見的是等待家庭組織者批准的 Ask to Buy 購買。這兩種情況下都還沒有收到錢,所以你不應該發放存取權限。
購買處於待處理狀態時我應該發放存取權限嗎?
不應該。只有在 Google Play 上狀態為 PURCHASED,或在 Apple 上交易已完成時,才發放權益。如果你在訂單仍處於待處理時就解鎖了功能,而付款始終沒有到帳,你就免費交付了產品,而且沒有收費可以撤銷,因為根本就沒有發生過收費。
哪些付款方式會在 Google Play 上導致待處理購買?
透過外部管道結算的付款方式。實體門市的現金付款、銀行轉帳以及部分電信帳單代收選項,在點擊與扣款之間需要額外的步驟,所以 Google 會以 PENDING 狀態而不是 PURCHASED 回傳購買。對於現金付款,客戶會透過通知和電子郵件收到一組代碼,然後在參與門市付款。
什麼是 Ask to Buy,它與待處理購買有什麼關係?
Ask to Buy 是 Apple 的 Family Sharing 功能,它讓孩子可以發起一筆需要家庭組織者批准的購買。在請求等待期間,購買處於 Apple 的待處理狀態,在 StoreKit 2 中回傳為 Product.PurchaseResult.pending,在較舊的 StoreKit 中回傳為一筆延遲交易。只有當組織者批准時,已完成的交易才會透過 Transaction.updates 到達。
如果一筆待處理購買始終沒有付款會怎樣?
訂單會被取消,沒有錢易手。在 Google Play 上,對於一次性商品你會收到 ONE_TIME_PRODUCT_CANCELED 通知,類型 2,對於訂閱則會收到 SUBSCRIPTION_PENDING_PURCHASE_CANCELED,類型 20。在 Apple 上,那筆延遲交易根本不會變成已完成的交易。如果你已經發放了存取權限,那一刻損失就變成了真實的。
待處理購買和退款是一回事嗎?
不是。退款撤銷的是一筆真正收到過的付款。一筆未能成立的待處理購買從一開始就沒有被扣款,所以沒有任何東西可撤銷,退款報告裡也不會出現任何東西。如果你提前解鎖了它,代價就是你為服務一筆沒有產生任何收入的訂單而花掉的運算資源、API 呼叫、儲存空間或消耗型商品。

資料來源與延伸閱讀

RefundHalt

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

繼續閱讀

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

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