CR-015 merchant-service 契約值域與 DDL 脫鉤、且「商店型態 vs 業別」為兩個正交維度
| 項目 |
內容 |
| 文件編號 |
CR-015 |
| 版本 |
1.1(BEAM PMO 2026-09-22 裁定:A 採雙欄位、B~G 一次核准;已執行並真機實測通過,見 §4/§5) |
| 提出日 |
2026-09-22 |
| 提出者 |
WorkBuddy AI(開發團隊) |
| 緣起 |
GOV-006 2.3.2(C-4 merchant-service)動工前之查證 |
| 影響文件 |
contracts/openapi/merchant-service.yaml(產生器 tools/gen_openapi.py)、SCS-014、SDD-001 §5.9、SDD-006 §3.4(錯誤碼) |
| 關聯前例 |
CR-002(分期值域與 DDL 不一致)、CR-008(分期雙軸拆分)、CR-012(兌換列舉與 DDL 完全脫鉤 → 雙欄位) |
1. 緣起
merchant-service 目前僅有骨架(MerchantServiceApplication + LayeringTest),
DB(CC_MCH:MERCHANTS/MERCHANT_SETTLEMENTS)與契約(3 端點)均已就緒。
依「先查證 → 規格 → 實作」紀律,動工前逐項比對契約列舉與DDL CHECK(實測),
發現 3 處值域脫鉤 + 3 項契約欄位無處可存——若直接實作,將立即撞
ORA-02290(寫入 DDL 不接受之值)或靜默丟棄契約欄位(客戶欄位消失)。
2. 缺口明細(DDL 為 2026-09-22 實測值,all_constraints.search_condition_vc)
2.1 ⚠️ 核心發現:merchantType 是兩個正交維度,非單純值域不符
| 來源 |
欄位 |
值域 |
語意 |
| DDL(實測) |
MERCHANTS.MERCHANT_TYPE |
RETAIL/EC/MOTO/RESTAURANT/TRAVEL/SUPERMARKET/OTHER
|
業別(做什麼生意) |
| 契約 |
CreateMerchantRequest.merchantType |
SINGLE/FRANCHISE/CHAIN/GROUP
|
商店型態(單店/加盟/連鎖/集團) |
兩者無一交集,且語意分屬不同軸——同一欄位不可能同時表達:
一間「超市」(業別 SUPERMARKET)可以是「連鎖」(型態 CHAIN)。
此與 CR-008(分期:通路 × 業務型態)、CR-012(兌換:標的 × 折抵)同一類問題,
解法應為雙欄位,而非二選一。
2.2 status 值域不一致(3 vs 4)
| 來源 |
值域 |
差異 |
| DDL(實測) |
ACTIVE/SUSPENDED/TERMINATED
|
— |
| 契約 |
APPLIED/ACTIVE/SUSPENDED/CLOSED
|
契約多 APPLIED(申請中);CLOSED ↔ TERMINATED 名稱不同 |
2.3 MERCHANT_SETTLEMENTS.status 值域完全脫鉤(2 vs 4)
| 來源 |
值域 |
語意粒度 |
| DDL(實測) |
PENDING/SETTLED
|
入帳面(待處理/已結算) |
| 契約 |
SUBMITTED/VERIFIED/EXCEPTION/PAID
|
請款流程面(送出/核對/異常/已付) |
契約另有 exceptionReason 欄位(nullable)——DDL 無對應欄位。
2.4 契約欄位無處可存(3 項)
| 契約欄位 |
用途 |
DDL 現況 |
CreateMerchantRequest.parentMerchantId |
集團店上層(description 明載「parentMerchantId 表集團店」) |
無此欄 |
CreateMerchantRequest.acceptedCardTypes(string[]) |
受理卡種 |
無此欄(TERMINAL_IDS 為終端機陣列,語意不同) |
MerchantSettlementRequest.batchNo |
請款批次號 |
無此欄;僅有 BATCH_COUNT NUMBER(10)(批次筆數,語意不同) |
2.5 附帶:MerchantSettlementView 未回傳 DDL 既有欄位
TAX_AMOUNT/CURRENCY/SETTLEMENT_DATE 於 DDL 存在且為 NOT NULL,
契約回應僅有 grossAmount/feeAmount/netAmount——對帳單(ACQ-4.3)
所需之稅額與幣別取不到。
3. 建議方案(供裁定)
原則依 SDD-005 SM-08:「值域以可持久化之 DDL CHECK 為權威;
值分屬不同欄位者即屬混用維度,應拆分」。以下為我方建議,待 BEAM PMO 裁定後執行。
| # |
項目 |
建議 |
理由 |
| A |
商店型態 vs 業別 |
新增欄位 MERCHANT_FORM(VARCHAR2(24),CHECK IN ('SINGLE','FRANCHISE','CHAIN','GROUP'));MERCHANT_TYPE 保留業別
|
兩軸正交,合併即重演 CR-008/CR-012 之覆轍;契約 merchantType 對映至 MERCHANT_FORM(契約語意即「型態」),業別另以 industryType 補入契約 |
| B |
merchants.status |
DDL 增列 APPLIED/CLOSED(保留 TERMINATED 或改契約用 CLOSED——建議契約統一為 CLOSED,TERMINATED 保留於 DDL 供相容) |
「申請中」為真實流程(進件→核准→ACTIVE);CLOSED 較 TERMINATED 中性且契約已用 |
| C |
settlements.status |
DDL 增列 SUBMITTED/VERIFIED/EXCEPTION/PAID,並新增 EXCEPTION_REASON VARCHAR2(240)
|
契約粒度為請款流程,DDL 之 PENDING/SETTLED 不足以表達;EXCEPTION 需有原因欄(稽核) |
| D |
parentMerchantId |
DDL 新增 PARENT_MERCHANT_ID RAW(16)(自參考 FK,NULL 表非集團店) |
集團店為 ACQ-4.1 明確需求 |
| E |
acceptedCardTypes |
DDL 新增 ACCEPTED_CARD_TYPES JSON(比照 TERMINAL_IDS 之 JSON 陣列手法) |
已有同表同手法前例 |
| F |
batchNo |
DDL 新增 BATCH_NO VARCHAR2(32)(BATCH_COUNT 保留為筆數) |
語意不同,不可挪用 |
| G |
回應補欄 |
MerchantSettlementView 補 taxAmount/currency/settlementDate(改 gen_openapi.py 後重生) |
對帳單(ACQ-4.3)必要資訊 |
| H |
錯誤碼 |
沿用 CC-MRC-1001(商戶代碼重複,契約已載);新增者先查 SDD-006 §3.4 既有清單再定 |
不硬編、不新增未登錄碼 |
交付方式(若 A~G 核准):一律以 V2__merchant_form_and_status.sql 追加
(V1 基線已凍結,SAD-003 §9);契約改 tools/gen_openapi.py 後重生;
SCS-014 升版並補 §5/§6/§7;SDD-001 §5.9 補 V2 交付說明。
4. 為何不「先實作再修」
契約與 DDL 之值域若未先對齊,實作將面臨兩難:
寫 DDL 值 → 契約列舉形同虛設(客戶端送出 SINGLE 即 400,契約說可行);
寫契約值 → 觸發 ORA-02290 或需繞過約束(假綠)。
兩者皆屬客戶硬性要求所禁止(scan_scaffold/scan_faketest 之精神)。
故本 CR 先行,裁定後一次做對——避免「一號開發團隊實作、二號開發團隊推翻」之 churn。
5. 誠實揭露
- 本 CR 之 DDL 值域為 Oracle 實測(
CC_MCH,2026-09-22),非文件抄錄。
-
CC_MCH 兩表目前 0 筆資料,故無歷史資料需回填(遷移風險低)。
-
MERCHANTS.status 未建模為 SDD-005 正式狀態機(SCS-014 §10 已載)——
建議本次一併決定是否補入(若補,須先改 DDL 再改 SDD-005,依 SM-08)。
-
SETTLEMENT_ACCOUNT_ENC(加密帳號)之金鑰整合仍待基礎設施凍結
(SCS-014 §10),不在本 CR 範圍;本期建檔不接收該欄位(避免半套加密)。
3.1 施行中追加之事項(我方依「替代方案推進」原則處理,供事後覆核)
| # |
事項 |
處理 |
理由 |
| I |
merchantId(特店代碼)於請求中可選
|
未提供 → 服務端以序號產生(MERCH + 6 位);提供 → 用之,重複則 CC-MRC-1001
|
契約描述明載「代碼重複回 CC-MRC-1001」,惟結構定義無 merchantId 欄——若純由服務端產生,該錯誤碼永不可達(形同虛設)。加為可選欄使兩者並存且規則可測 |
| J |
請款金額由呼叫方帶入(grossAmount/feeAmount/taxAmount) |
契約補此三欄為必填 |
契約原僅有 merchantId/settlementDate/batchNo,無任何金額欄位;收單/清算資料源(clearing-service,C-5)未開發,服務端無處彙總(NET = GROSS - FEE - TAX 無從計算)。待 C-5 落地後改為服務端彙總 |
| K |
不納入「商戶非 ACTIVE 不得請款」之檢核 |
異常判定僅保留「淨額為負」 |
①契約未定義「異常」清單;②本服務無狀態轉換端點(狀態機未建模),建檔後恆為 APPLIED——若加此守衛,「建檔 → 請款」之正常路徑將永不可達(與 C-19 之 ALLOCATED 同類陷阱)。是否納入待狀態機建模時一併裁定 |
| L |
契約所述「同鍵異指紋回 409」未實作
|
僅實作「同鍵同主鍵」之冪等(不重複建檔) |
MERCHANTS/MERCHANT_SETTLEMENTS 無指紋欄位可存,且不宜為此挪用他欄(避免假綠) |
冪等實作方式(無冪等鍵欄位):以冪等鍵派生主鍵——
UUID.nameUUIDFromBytes("mch:merchant:" + key)(同理 mch:settlement:)——
同鍵重送得同一主鍵,第二次即命中既有列並回傳原結果(不新增、不重抽序號)。
命名空間前綴使不同實體之同鍵不致碰撞。
4. 執行清單(2026-09-22)
| 項 |
交付 |
| 遷移 |
V2__merchant_form_and_status.sql——MERCHANT_FORM/PARENT_MERCHANT_ID/ACCEPTED_CARD_TYPES(JSON)/BATCH_NO/EXCEPTION_REASON 增欄;merchants.status 與 merchant_settlements.status 之 CHECK 以資料字典動態尋得後重建為具名約束(CK_MERCHANTS_STATUS/CK_MERCHANT_SETTLEMENTS_STATUS,比照 V4__payments_unmatched_status.sql);新增 merchant_id_seq/merchant_settlement_no_seq
|
| 契約 |
改 tools/gen_openapi.py 後重生(CreateMerchantRequest 補 industryType 與可選 merchantId;MerchantView 補 industryType、status 補 APPLIED/TERMINATED;MerchantSettlementRequest 補三金額欄;MerchantSettlementView 補 taxAmount/currency/settlementDate)。驗證 0 錯誤、全案 74 端點
|
| 程式 |
DDD 四層:Merchant(含 MerchantForm/IndustryType/MerchantStatus/RiskLevel)、MerchantSettlement(含 SettlementStatus 6 值)、2 埠+2 JDBC、2 應用服務+組裝器、1 控制器(3 端點) |
| 品質 |
單測 27 例 0 失敗(領域 12/應用 15;含冪等重放、代碼重複、上層查無、雙軸獨立、淨額為負異常);scan_scaffold/scan_faketest 0 命中;validate_sql 0 違規 |
5. 真機 e2e(8095/FREEPDB1,2026-09-22)
| # |
情境 |
結果 |
| ① |
建檔(merchantType=CHAIN、industryType=SUPERMARKET,未指定代碼) |
201:MERCH000001/CHAIN/SUPERMARKET/APPLIED
|
| ② |
同冪等鍵重送 |
回同一 MERCH000001(未新增、未重抽序號) |
| ③ |
指定已存在代碼 |
422 CC-MRC-1001「特店代碼重複:MERCH000001」 |
| ④ |
查詢 GET /merchants/MERCH000001
|
200:名稱/雙軸/LOW/APPLIED 全對 |
| ⑤ |
請款 500000 − 12500 − 0
|
201:SET202609000001、net=487500、TWD、2026-09-17、SUBMITTED
|
| ⑥ |
請款 1000 − 800 − 300
|
201:net=−100、EXCEPTION、原因「淨額為負(費用+稅大於請款總額)…」 |
| ⑦ |
DB 複核 |
MERCHANTS:MERCHANT_FORM=CHAIN、MERCHANT_TYPE=SUPERMARKET(同列兩軸獨立)、ACCEPTED_CARD_TYPES=["VISA","MASTERCARD"]、PARENT_MERCHANT_ID=NULL;MERCHANT_SETTLEMENTS:兩列金額與狀態逐欄相符;V2 具名約束存在且值域已擴充 |
6. 修訂記錄
| 版本 |
日期 |
修訂內容 |
| 1.0 |
2026-09-22 |
提出。動工前查證發現 3 處值域脫鉤+3 項契約欄位無處可存+1 處回應缺欄;核心判讀為「商店型態與業別為兩個正交維度」,建議採雙欄位(比照 CR-008/CR-012)。待 BEAM PMO 裁定 |
| 1.1 |
2026-09-22 |
BEAM PMO 裁定:A 採雙欄位、B~G 一次核准。新增 §3.1 施行中追加事項(I 代碼可選/J 金額 stand-in/K 不納入商戶狀態檢核/L 異指紋未實作,均附理由)、§4 執行清單、§5 真機 e2e 七項(含 DB 複核雙軸獨立)。單測 27 例 0 失敗;SCS-014 升 1.1、SDD-001 升 1.8 |