跳至主要內容

09 / WORK / CASE STUDY

多租戶 B2B 批發電商平台

企業批發客戶的自助採購系統:分層報價、信用額度、月結對帳與金流物流同步;商家端營運中,管理後台已開發完成、因驗收暫緩啟用。

TYPE
FULL STACK
SCOPE
公司內部 · 商家端營運中
STACK
DJANGO · DRF · CELERY · MYSQL · REACT · TYPESCRIPT
LINKS
公司內部專案(不公開)

01 / PROBLEM

批發與經銷客戶的下單,很容易停在電話、通訊軟體與 Excel:沒有依採購量自動套用的階梯報價、沒有信用額度控管,對帳靠人工核對出貨明細與請款金額。這套系統把 B2B 自助下單、權威報價(單價一律後端重算、不信任前端)、月結對帳與爭議申訴、訂單與金流物流狀態同步收斂到一個服務,讓企業客戶像操作一般電商一樣完成批發採購,內部保有額度與帳務的控管權。

02 / CONSTRAINTS

  • 公司內部專案,程式碼不公開;涉及金流與訂單的修復必須在不中斷交易下完成。
  • 必須與平台既有的 5 個以上服務整合而不破壞其契約:會員、金流閘道、庫存、分析、業務歸屬與佣金——本服務不持有這些領域的資料,只做整合。
  • 商家端與管理端共用同一組後端,任何一處漏掉權限檢查,商家就可能看到別家公司的資料。
  • 裸機自架、無雲端託管預算,服務生命週期由自製腳本管理。

03 / ARCHITECTURE

Django + DRF 獨立微服務,12 個業務 app(認證、公司、商品、B2B 定價、購物車、銷售訂單、對帳、報價單、通知、促銷、帳務、聯絡),35 個資料模型、21 個資源註冊加 97 個自訂 action、30 個 Celery 任務,後端約 1.4 萬行;前端為獨立 React 19 + TypeScript SPA,約 4.1 萬行,商家端 22 頁營運中、管理端 30 頁已開發待啟用。B2B 獨立成服務而非塞進既有電商的理由:報價、信用額度與月結規則和 B2C 的定價付款邏輯差異夠大,分開讓兩邊各自演進。

JWT 認證、全域預設 IsAuthenticated,免認證端點(登入、註冊、金流回調、服務間呼叫)逐一顯式宣告;MySQL 加 Redis(快取與關鍵任務分庫),服務間以 REST 加共用內部密鑰溝通,金流走平台的全方位金流閘道。前端雙 axios instance(商家端/管理端各一),401 單飛換發 token、佇列重放併發請求。

04 / RESPONSIBILITIES

前後端獨立開發(後端 24、前端 102 個 commit 皆為我;2025/10–2026/07):12 個業務模組的資料模型與 API、金流串接與退款鏈路、權限修復、後端測試,以及商家端與管理端全部頁面、雙 client 與設計 token 系統。對接的會員、金流閘道、庫存等服務本身屬平台其他服務,不在本專案範圍。

05 / CHALLENGES → SOLUTIONS

CHALLENGE

稽核發現:取消已付款信用卡訂單的鏈路「看起來」完整——有排程、有呼叫退款服務的程式碼——但排程實際打的是一個只查詢、不執行退款的端點。訂單顯示已取消,持卡人的錢從未退回;問題橫跨三個服務,出在三方對「這支端點會不會真的退款」認知不一致。

SOLUTION

新增真正執行退刷的金流 client 並接上兩個取消入口;select_for_update 防兩台裝置同時取消造成重複退款;退款非冪等、明確標註禁止 retry;權益回沖改為交易提交後非同步派工,取消回應從 17 秒降到約 3 秒。以一筆真實訂單做端到端驗證:金流狀態確認取消、重複呼叫冪等。

CHALLENGE

結帳改走金流閘道的全方位支付後,付款頁偶發打不開,閘道回報簽章不符。乍看像演算法寫錯,但雙方演算法一致——真正原因是中轉頁把品項名稱直接塞進隱藏表單欄位,名稱含 & 或 < 時被瀏覽器截斷,實際送出的字串和簽章計算用的不一致。

SOLUTION

中轉頁對隱藏欄位做 HTML escape,確保瀏覽器解析後送出的內容與簽章計算完全一致;同批修復加上 B2B 專屬的訂單類型分流,避免與 POS 訂單共用冪等序號互相覆蓋。修復後未再復現。

CHALLENGE

安全檢查發現部分端點「內網來源免認證」,而內網判斷讀的是 X-Forwarded-For——用戶端可自由填寫的標頭,塞一個 127.0.0.1 就能跳過登入。同批稽核也發現結帳金額完全信任前端送來的單價,以及訂單層折扣未分攤、對帳單加總高於實際應收。

SOLUTION

移除可偽造的內網豁免,預設一律要登入、真正的例外逐一顯式宣告;新增權威定價模組,單價由後端依當時階梯價與促銷重算;修正折扣分攤讓對帳單與應收一致。三項修復連同對應測試一併提交,已上線運行。

06 / SYSTEM FACTS

35

資料模型(12 模組)

297

後端測試函式

~41k

前端 TS/TSX 行數

126

commits · 單一開發者

07 / LESSONS

「排程有跑」不等於「業務有發生」:退款排程長期正常執行、零報錯,但打的是查詢端點。金流這類必須產生副作用的任務,監控要對準「副作用是否發生」,而不是排程有沒有觸發。

後端位址寫死在前端原始碼,是當時圖快留下的債:現在要多一個測試環境,得改碼重建而不是換個環境變數。重來會在只有一個環境的第一天,就把 API 位址做成環境變數。

NEXT

業務外勤現場開通 PWA