Project

General

Profile

CR #216 » CR-015_商戶契約值域與DDL脫鉤且商店型態與業別為正交維度.md

內容更新:內部稱謂去識別化(2026-10-18) - Redmine Admin, 10/18/2026 05:32 AM

 

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
    (1-1/1)