Cover

在 SaaS 產品開發的路上,收費模式與金流串接絕對是開發者會遇到的魔王關卡。最近在重構點餐系統的店家後台升級方案流程時,為了滿足不同的商業需求,我們同時導入了「PAYUNi 統一金流」與「Creem 訂閱金流」。這篇文章會帶大家看看,我們如何優雅地將這兩種截然不同的金流模式,整合進同一個系統架構中。

為什麼需要兩家金流商?

如果你也有做過 SaaS,可能會想:為什麼不選一家搞定就好?說白了,因為不同的商業場景需要不同的武器。

  • PAYUNi 統一金流:這是深耕台灣本土市場的強大金流,支援信用卡、ATM 虛擬帳號與超商代碼繳費。在我們的系統中,它主要負責「一次性付款」模式,幣別為台幣(TWD)。當我們需要搭配本地的優惠碼活動,或是計算舊方案剩餘天數折抵時,PAYUNi 是非常靈活的選擇。它的運作模式是經典的 Form POST,由前端帶著加密表單導向付款頁面。
  • Creem 訂閱金流:相較於傳統金流,Creem 是專為 SaaS 訂閱制打造的現代化服務。它以美元(USD)計價,採用純訂閱 Checkout 模式。前端只需一個 URL 就能重新導向到付款頁面,後續的續扣、暫停或取消,全靠強大的 Webhook 機制與後端同步。

訂閱制系統幫我們處理了什麼麻煩事?

「自己寫一個 Cronjob 每天扣款不行嗎?」當然可以,但維護訂閱狀態的狀態機(State Machine)往往會讓人下足了重本卻又痛不欲生。

引入 Creem 這類專門的訂閱制服務,最大的好處是它幫我們扛下了複雜的狀態管理與生命週期。像是:

  • 付款失敗與重試機制:當信用卡扣款失敗時,狀態會變成 unpaid,系統可以設定寬限期,而不是直接把客戶停權。
  • 排程取消:客戶點擊取消訂閱,狀態會進入 scheduled_cancel,這意味著客戶在「本期結束前」依然享有付費權限,直到週期結束才會真正轉為 expiredcanceled
  • 降級與暫停:如果是我們自己刻,要處理何時把權限鎖住、何時恢復,邏輯會非常破碎。現在我們只要乖乖聽 Webhook 的指令辦事就好。

這讓我們能將心力放在核心的業務邏輯,而不是整天追查為什麼某個店家的卡片沒扣到錢卻還在使用付費功能。

點餐系統串接實戰:統一的介面,各自的表述

在點餐系統的店家方案升級架構中,最大的挑戰是如何讓前端感受不到兩家金流的差異,同時後端又能保持乾淨的領域模型。

核心設計:Provider Factory 模式

我們共用同一個升級入口:當前端呼叫 POST admin/plan/upgrade 時,後端的 PlanService 會建立一筆待付款的訂閱紀錄,接著透過 ISubscriptionBillingProviderFactory 來決定要呼叫哪一家金流。

如果這筆訂單要走 PAYUNi,系統會回傳 FormPost 的 URL 與加密後的表單資料;如果是走 Creem,則會建立一個 Checkout,直接回傳 Redirect 的 URL。前端拿到回應後,只要判斷 paymentRedirectType 就能決定要怎麼跳轉。

PAYUNi 的一次性付款流程

在 PAYUNi 的流程中,這是一場經典的表單大戰。後端建立付款請求後,前端會建立 HTML Form POST 出去。付款成功後,PAYUNi 透過 Server-to-Server 的 Callback 呼叫我們的 api/platform-payment/notify,我們解密驗證無誤後,直接把本地的訂閱狀態改為 Active

Creem 的 Webhook 同步流

Creem 的邏輯截然不同。我們在呼叫 /v1/checkouts 時,會把 storeIdsubId 等中繼資料(Metadata)塞進去。當客戶在 Creem 完成結帳後,Creem 會打 Webhook 給我們。為了避免網路抖動導致重複觸發,我們用 Provider + Event ID 實作了嚴格的冪等(Idempotency)處理。

為什麼 Creem 適合新創團隊與 AI 開發?

在這次實戰中,我們也發現了 Creem 兩個對獨立開發者或新創團隊極具吸引力的優勢:

  • AI Agent 極速串接:Creem 官方直接提供了專屬的 AI 技能檔(creem.io/SKILL.md)。在如今這個 AI 協作開發的時代,只要把這份文件餵給你的 AI Agent,它就能非常快速地幫你把串接邏輯與 Webhook 骨架搭建起來,省去了大把翻找 API 文件的時間。
  • 支援個人身分申請:很多傳統金流商光是審核公司行號與資本額就能卡上一個月。Creem 允許以個人身分申請,這對於剛起步的 MVP 產品或獨立開發者來說是一大福音,讓你能專注在產品驗證上。但還是要溫馨提醒,資金回到台灣端依然需要依法報稅,這點可不能馬虎。

資料表的分工哲學

為了解決這兩者的差異,我們並沒有為它們各自開一套資料表,而是採用了共用主表,擴充欄位的策略。

  • sto_store_subscription(店家訂閱主表):記錄了訂閱的起訖時間與狀態。PAYUNi 靠本地的邏輯推算到期日;而 Creem 則多了 ExternalSubscriptionIdCurrentPeriodEndAt 等擴充欄位,完全由外部驅動。
  • sto_platform_payment(平台收款紀錄):每筆收款都是獨立的,用 Provider 欄位區分來源。

這種設計讓我們在查詢「這家店現在有沒有付費方案」時,邏輯極度單純,不必再去 Join 各自的金流表。

寫在最後

將一次性付款與純訂閱制金流揉合在一起,初期在系統設計上確實死了不少腦細胞。尤其是要兼顧 PAYUNi 靈活的本地優惠折抵,以及 Creem 強大的生命週期狀態同步。

但成果是值得的。我們不僅保留了台灣市場最習慣的付款體驗,也為未來純訂閱制的擴張打好了基礎。保持領域模型的純粹,將外部金流的髒活交給專屬的 Provider 去封裝,是這次架構翻新最重要的體會。

如果你的 SaaS 產品剛好也遇到類似的跨國金流與訂閱制整合難題,或是對系統架構設計有任何想法,都歡迎隨時交流探討!