App 訂閱內購的伺服器端驗證:Apple 與 Google 的坑

訂閱制 App 有一個殘酷的真相:使用者按下「訂閱」、蘋果或 Google 跳出付款成功,不代表你可以放心開通服務,更不代表這筆錢之後都會準時進來。續訂會失敗、信用卡會過期、使用者會退款、甚至會有人拿偽造的收據來騙開通。我們在自家產品上實作過完整的訂閱內購流程——Apple 用 JWS 驗證、Google 用 Service Account 驗證——這篇把伺服器端該做的事,還有我們踩過的坑,整理成一份實作筆記。

第一原則:App 說的話不能信,一切以伺服器驗證為準

最常見的錯誤設計:App 端收到平台回傳的「購買成功」,就直接呼叫自家 API 開通會員。問題在於這個呼叫可以被偽造——越獄手機、修改過的 App、重放別人的收據,都能騙過只聽 App 片面之詞的後端。

正確流程是:App 把購買憑證傳給你的伺服器,由伺服器直接向 Apple / Google 查證這筆交易是否真實有效,查證通過才開通。訂閱狀態的唯一真相存在你的資料庫,而你的資料庫只信平台官方的回覆,不信 App。這一層沒做,等於收銀機讓客人自己按「已付款」。

Apple 這邊:JWS 驗證的幾個坑

Apple 現行的做法是把交易資訊做成 JWS(JSON Web Signature,一段帶數位簽章的資料,可以驗證確實由 Apple 簽發、未被竄改)。伺服器端要做的是驗簽章、驗憑證鏈,確認這段資料真的來自 Apple。我們實作時踩過的坑:

  • 憑證鏈要驗到底:不能只解開內容就當真,要一路驗回 Apple 的根憑證。市面上不少範例程式碼跳過這步,等於沒驗。
  • 沙盒與正式環境會打架:審核人員用的是沙盒環境的收據,你的正式伺服器如果只查正式環境,審核時會被判定「購買功能壞掉」而退件。標準做法是先查正式環境,收到特定錯誤再退回查沙盒。
  • Server Notifications 一定要接:續訂成功、續訂失敗、使用者退款、進入寬限期,Apple 都會主動通知你的伺服器。不接的話,使用者退款了你不知道,服務繼續開著,等於白送。

Google 這邊:Service Account 與那些文件沒明說的事

Google 的驗證走 Service Account(服務帳戶,一組讓伺服器以你的開發者身份呼叫 Google API 的金鑰),拿購買 token 去查訂閱的即時狀態。幾個實務重點:

  • 金鑰是最高機密:那份金鑰檔案等於你 Google Play 後台的鑰匙,絕不能進版本控制、不能包進 App。我們看過金鑰被 commit 進公開 repo 的真實災難。
  • 狀態通知走 Pub/Sub:Google 的訂閱事件通知要透過它的訊息服務接,設定路徑比 Apple 曲折,但同樣不能省——理由跟上面一樣,退款跟停扣你必須即時知道。
  • 通知只是門鈴,狀態要回查:收到事件通知後,正確做法是拿著識別碼向 Google 回查最新訂閱狀態,以回查結果為準,而不是直接信任通知內容。通知可能亂序、可能重送,把它當門鈴而不是當公文,系統會穩很多。
訂閱制的錢不是扣了就算數。開通那一刻只是故事的開頭,續訂、寬限、退款、恢復,每一段你的伺服器都要在場。

狀態機:訂閱不是「有效/無效」兩種狀態

把訂閱想成布林值(訂了/沒訂)是第二常見的錯誤。實際的狀態至少有:有效、已取消但未到期(取消後用到期滿是正常權益)、扣款失敗寬限期中(平台還在重試,先別急著斷服務)、已退款(要立刻收權限)、到期後恢復訂閱。每個狀態的進出都對應平台的某種事件,我們的建議是老老實實畫出狀態機、為每個轉換寫自動化測試——這類金流邏輯,是我們堅持交付標準必含自動化測試的原因:人工測不完所有路徑,而每條漏測的路徑都直接關係到錢或客訴。

另外提醒一個設計層的決定:訂閱狀態建議記在「帳號」而不是「裝置」上,使用者換手機、跨 iOS 和 Android,權益才能跟著走。這又回到會員架構的題目,可以搭配多產品共用會員的 SSO 架構思考一起看。

訂閱內購是那種「Demo 很快、做對很久」的功能:購買流程兩天就能動,但把退款、寬限、恢復每條路都接穩,才是真正的工程量。如果你的 App 正要導入訂閱制,或現有的內購常出現「使用者說扣款了但沒開通」的客訴,歡迎找我們聊聊,或先了解我們的客製化系統與 SaaS 服務怎麼處理這類含金量高的整合。

這類問題,我們每天都在自己的產品上解

免費 30 分鐘線上諮詢・先釐清方向,不推銷・一個工作天內回覆

我要詢案

← 更多觀點