待處理購買看起來像一筆銷售,但錢還沒到帳,提前發放就等於把產品白白送出去
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 Play | Apple |
|---|---|---|
| 由什麼觸發 | 現金、銀行轉帳、部分電信帳單代收 | 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 呼叫、儲存空間或消耗型商品。
資料來源與延伸閱讀
- Android Developers: Integrate the Google Play Billing Library (PENDING purchase state, grant entitlement only on PURCHASED, enablePendingPurchases, queryPurchasesAsync, cash payment code flow, three-day acknowledgement window begins on transition to PURCHASED)
- Android Developers: Real-time developer notifications reference (OneTimeProductNotification ONE_TIME_PRODUCT_PURCHASED=1 and ONE_TIME_PRODUCT_CANCELED=2; SubscriptionNotification SUBSCRIPTION_PURCHASED=4 and SUBSCRIPTION_PENDING_PURCHASE_CANCELED=20)
- Android Developers: Test Google Play Billing (license testers get delayed-payment test instruments that auto-complete or auto-cancel for testing pending transactions)
- Apple Developer: Product.PurchaseResult (the pending case, returned when a purchase needs action such as Ask to Buy approval before it completes)
- Apple Developer: SKPaymentTransactionState.deferred (a transaction whose final status is pending an external action such as Ask to Buy)
- Apple Developer: Transaction.updates (StoreKit delivers transaction updates, including ones approved later, asynchronously)
RefundHalt
App Store 與 Google Play 的退款自動駕駛
繼續閱讀
過多的退款申請,代價可能遠超退款本身,因為兩大 App 商店都把它視為品質訊號
Apple 審查準則 5.6.4 把過多的退款申請列為 App 未達品質標準的跡象,Google 的開發人員發布協議也允許它依退款率下架新開發人員的 App。本文整理兩家商店的原文、下架的實際成本,以及如何避開風險。
在處理退款之前先驗證 App Store 伺服器通知,因為任何人都能向你的 webhook URL 發送請求
在檢查簽章之前,一則退款通知只不過是一個 HTTPS 請求。本文說明如何驗證 App Store 伺服器通知和 Google Play RTDN 訊息、各商店簽署的內容,以及偽造的退款或遭錯誤拒絕的退款會讓你付出什麼代價。