Project

General

Profile

CR #201 » SCS-013_清算結算服務程式設計規格.md

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

 

SCS-013 清算結算服務(clearing-service)程式設計規格

項目 內容
文件編號 SCS-013
版本 1.4(CR-033:更正 §7.1 之消費者與 §10 #5 之性質——原載「消費者為 posting——尚未實作」不成立:現行 SAD-004 §5.2 已標「不做消費端」(CR-030 §4.1:清算為批次對帳);「入帳串接未實作」屬 SDD-003 §14 之同步呼叫缺口(非事件流),其真正阻塞為會計科目(應收/應付)設計,屬外部輸入;併記「clearing → posting 不在 posting-service 之 ACL」。前版 1.3(GOV-006 4.8 W-2 推廣・CR-027 批次 A-4(2026-10-01,#A-131):新增 §7.1 清算比對事件輸出(clearing.batch.matched.v1)——SAD-002 §3.9~3.17 明列、SDD-006 §4.2 已定義 payload,惟從未有人發射。★ 分區鍵=runId;★ 零筆數亦發射。⚠️ 實作完成、單測通過、V3 遷移已套用,惟端到端真機驗證受阻(POST /clearing/runs 受服務間 ACL 保護,僅允許 cc-dispute-service;未取得 M2M 權杖)——不宣稱已驗證(見 §7.1)。前版 1.2(新增 §6.1 清算執行清單端點)
狀態 已核定(規格先行)
對應需求 ISS-2.13(信用卡發卡清算)、ACQ-4.6(收單清算)、DEB-3.6(DEBIT 卡發卡清算)
服務埠號 待骨架產生時依 CSP-006 §4 分配(自 8081 起編)
套件根 com.beam.cardcore.clr
權威 schema CC_CLR
關聯文件 SDD-001 §5.x、SDD-002 §3.7、BRD-002、GOV-005

1. 對應需求

FR 編號 需求名稱 來源
ISS-2.13 信用卡發卡清算 BRD-002
ACQ-4.6 收單清算 BRD-002
DEB-3.6 DEBIT 卡發卡清算 BRD-002

1.1 上層設計依據(本規格不重複這些圖,僅引用)

圖種 文件 數量(實測) 本服務相關
ER-Diagram SDD-001_領域模型與ER-Diagram 16 個 erDiagram CLEARING_BATCHES/CLEARING_ITEMS/SETTLEMENTS 實體
DFD SDD-002_DFD與流程分解 10 個 flowchart 清算結算流(SDD-002 §3.7)
Sequence SDD-003_Sequence-Diagrams 15 個 sequenceDiagram 清算檔解析/對帳/結算時序
狀態機 SDD-005_狀態機設計 10 個 stateDiagram ⚠️ CLEARING_BATCHES.status(RECEIVED→PARSED→MATCHED→SETTLED/REJECTED)以 CHECK 控管,未定義正式狀態機
API Contract SDD-006 + contracts/openapi/clearing-service.yaml 17 份 本服務 3 支端點

2. 服務職責與邊界

承作:

  • 清算批次(CLEARING_BATCHES:收單/發卡清算檔,含組織、方向、檔型、總額、手續費、Checksum)
  • 清算明細(CLEARING_ITEMS:逐筆交易,對帳狀態 MATCHED/UNMATCHED/EXCEPTION)
  • 結算(SETTLEMENTS:毛額、 interchange/network 費、淨額)
  • 對帳(reconciliations 端點)

不做(屬他服務):

  • 實際入帳 → posting-service(對帳相符後發請款)
  • 商戶管理 → merchant-service

3. 領域模型

  • 聚合:ClearingBatch(清算批次)、ClearingRecord(清算明細)、SettlementDifference(結算差異,依 services.yaml aggregates)
  • 值物件:OrgCode、Direction、FileType、BatchStatus、Money、Currency
  • 狀態:CLEARING_BATCHES.status ∈ {RECEIVED, PARSED, MATCHED, SETTLED, REJECTED}(CHECK,預設 RECEIVED);CLEARING_ITEMS.status ∈ {MATCHED, UNMATCHED, EXCEPTION}(預設 UNMATCHED);SETTLEMENTS.status ∈ {PENDING, SETTLED}

4. 程式結構

套件/類別 職責 對應需求
domain/model/ClearingBatch ClearingItem Settlement 聚合與值物件 ISS-2.13/ACQ-4.6
domain/port/ClearingRepository 持久化埠 —
application/ClearingService 檔解析/對帳/結算 ISS-2.13
infrastructure/jdbc/ClearingRepositoryImpl JDBC 實作 —
interfaces/rest/ClearingController REST 端點 ISS-2.13

5. 資料模型(約束為 Oracle 實測值)

表 關鍵欄位 約束(實測)
CLEARING_BATCHES BATCH_NO VARCHAR2(32) UNIQUE;ORG;DIRECTION;FILE_TYPE;FILE_NAME;FILE_DATE/SETTLEMENT_DATE DATE;TX_COUNT/TOTAL_AMOUNT/FEE_AMOUNT NUMBER(19);CURRENCY CHAR(3) DEFAULT 'TWD';CHECKSUM;STATUS DEFAULT 'RECEIVED' CK_CLEARING_BATCHES_ORG:org IN ('VISA','MASTERCARD','NCCC','AMEX','DFS','JCB','DISCOVER','DINERS')(V2 擴充);direction IN ('INBOUND','OUTBOUND');file_type IN ('VCF','EMS','SETTLEMENT','CHARGEBACK','FEES');status IN ('RECEIVED','PARSED','MATCHED','SETTLED','REJECTED')
CLEARING_ITEMS BATCH_ID(FK→BATCHES.ID);TX_REF VARCHAR2(32);CARD_NO_MASKED;AMOUNT/FEE NUMBER(19);MCC;MERCHANT_ID;STATUS DEFAULT 'UNMATCHED';MATCHED_TX_ID status IN ('MATCHED','UNMATCHED','EXCEPTION');FK(BATCH_ID) REFERENCES BATCHES(ID)
CLEARING_RUNS(V2 新增) RUN_NO VARCHAR2(32) UNIQUE;BUSINESS_DATE DATE;STATUS DEFAULT 'RUNNING';ORGS VARCHAR2(200)(逗號分隔);TX_COUNT/TOTAL_AMOUNT/DIFFERENCE_COUNT/DIFFERENCE_AMOUNT NUMBER(19) status IN ('RUNNING','MATCHED','DIFFERENCE','FAILED')
CLEARING_RECONCILIATIONS(V2 新增) RUN_ID(FK→CLEARING_RUNS.ID);ORG;DIFFERENCE_TYPE;DIFFERENCE_COUNT/DIFFERENCE_AMOUNT NUMBER(19);RESOLUTION VARCHAR2(120) org IN (8 值);difference_type IN ('TIMING','FX_RATE','SUBSTANTIVE');FK(RUN_ID)
SETTLEMENTS ORG;SETTLEMENT_DATE;GROSS_AMOUNT/INTERCHANGE_FEE/NETWORK_FEE/NET_AMOUNT NUMBER(19);CURRENCY;STATUS DEFAULT 'PENDING';DIRECTION(V2 新增) status IN ('PENDING','SETTLED');direction IN ('ACQUIRER_PAYOUT','ISSUER_RECEIVABLE')

含 CREATED_AT/UPDATED_AT TIMESTAMP(6) WITH TIME ZONE、VERSION 樂觀鎖。

⚠️ 為何需要 CLEARING_RUNS(不可挪用 CLEARING_BATCHES):一次「清算執行」涵蓋
多個批次檔(多組織、多檔型)——與批次為一對多;且批次狀態語意為檔案處理、
執行狀態語意為對帳結果,兩者正交(CR-016 裁定 A)。

V1 基線已凍結(SAD-003 §9);V2 變更見 V2__clearing_runs_and_reconciliations.sql。


6. API 設計(來自 contracts/openapi/clearing-service.yaml)

端點 方法 說明 對應 FR
/api/v1/clearing/runs POST 清算批次執行 ISS-2.13/ACQ-4.6
/api/v1/clearing/reconciliations/{runId} GET 對帳結果查詢 ISS-2.13
/api/v1/clearing/settlements POST 結算 ISS-2.13
/api/v1/clearing/runs GET 清算執行清單(最近 N 筆;2026-09-30 補建,見 §6.1) ISS-2.13/ACQ-4.6

錯誤碼:400 檔案格式/Checksum 錯誤、404 找不到批次、409 重複批次。

6.1 清算執行清單端點(2026-09-30 補建,HANDOVER #A-104)

緣由:SDD-007 §5.4.26 之桶 C「批次作業監控」(/system/batch)——
bff-web 依 SCS-020 §9.9 聚合四服務之批次執行紀錄,需各服務提供「列出批次」端點。
⚠️ 實測揭露:/api/v1/clearing/runs 原僅有 POST(觸發)——GET 實測回 404。

GET /api/v1/clearing/runs?limit=N(回應 ClearingRunSummary[],200)

參數 型別 說明
limit integer(可選) 回傳筆數上限。預設 20,上限 100

回應欄位(ClearingRunSummary):

欄位 說明
runId 執行識別碼(UUID)
runNo 執行編號(CLR + 業務日 + 連號,可稽核)
businessDate 業務日
status RUNNING/MATCHED/DIFFERENCE/FAILED
txCount/totalAmount 彙總筆數/金額(分,整數)
differenceCount/differenceAmount 差異筆數/金額(分)
createdAt/updatedAt 建立/最後更新時刻

⚠️ 本服務無 startedAt/finishedAt 欄位——CLEARING_RUNS 之 DDL 僅有
CREATED_AT/UPDATED_AT
。故如實回傳 createdAt/updatedAt,不臆造 startedAt
(清算為同步執行,CREATED_AT 即受理時刻,語意上近似開始時間)。
bff-web 之統一時間錨點以其 createdAt 為準(SCS-020 §9.9.1 載明)。

行為要求:

  1. 唯讀——僅查詢,不觸發清算。
  2. 依 createdAt 遞減(最近執行者在前)。
  3. 無資料回 200 + 空陣列——非 404。
  4. 不快取(SCS-020 §9.9.4 #4)。
  5. ⚠️ 與 POST /clearing/runs 同路徑、不同方法——GET 為清單、POST 為觸發;
    實作時勿以 @RequestMapping 通吃,須明確 @GetMapping/@PostMapping。

錯誤碼:400 參數錯誤(limit 超出 1..100)。無 404 情境。

未竟:

  • 不支援分頁游標(僅取最近 N 筆);不含差異明細(下鑽仍走
    GET /clearing/reconciliations/{runId})。

7. 關鍵邏輯

  • 檔解析:收單/發卡清算檔(VCF/EMS/SETTLEMENT/CHARGEBACK/FEES)解析後寫 CLEARING_BATCHES+CLEARING_ITEMS,驗 CHECKSUM。
  • 對帳:逐筆 CLEARING_ITEMS 與 posting-service 交易(MATCHED_TX_ID)比對;不符標 EXCEPTION 轉人工。
  • 結算:彙總毛額扣 interchange/network 費得淨額,寫 SETTLEMENTS,觸發 posting-service 請款入帳。

CR-016(BEAM PMO 2026-09-22 裁定)後定案之實作要點

  1. 清算執行(POST /clearing/runs,成功碼 202):逐組織彙總該業務日之明細
    (CLEARING_ITEMS 經 batch_id 關聯批次取得 org,以 FILE_DATE 對應業務日),
    得筆數/金額合計與異常筆數/金額;狀態由結果推導——
    差異筆數為 0 → MATCHED,否則 DIFFERENCE(不由呼叫方指定,使狀態與差異不可能矛盾)。
  2. 對帳結果:僅有異常之組織落列(無異常者不需人工覆核);
    分類本期一律 SUBSTANTIVE(時序差需帳務側未入帳交易、匯差需匯率表,皆不具備)。
  3. 結算(POST /clearing/settlements,成功碼 202):依執行之業務日與組織逐組織產生結算單;
    GROSS = Σ 明細金額、INTERCHANGE_FEE = Σ 明細費用、NETWORK_FEE = 0、NET = GROSS − INTERCHANGE − NETWORK。
  4. 冪等:以冪等鍵派生主鍵(UUID v3,前綴 clr:run:/clr:settlement:{org}:)——
    同鍵重送不重複產生(執行與結算單皆然)。
  5. 零筆數:允許完成為 MATCHED 但 txCount=0,必記 WARN,並如實回報 txCount
    使呼叫方得以分辨「無資料」與「全數相符」(不新增狀態以免虛構語意)。

7.1 清算比對事件輸出(clearing.batch.matched.v1)——2026-10-01 新增(GOV-006 4.8 W-2 推廣・CR-027 批次 A-4)

緣由:SAD-002 §3.9~3.17 明列本服務發布 clearing.batch.matched.v1(用途:清算比對完成),
SDD-006 §4.2 亦已定義 payload,惟從未有人發射(同 SCS-002 §7.5 之型)。

主題:cc.clearing.batch.matched(命名規則 SAD-004 §5.4)。

項目 值
eventType clearing.batch.matched.v1
Outbox aggregate_type clearing
Outbox event_type clearing.batch.matched(不含版本)
Kafka 分區鍵 runId(清算批次為聚合)

payload(v1;欄位名依 SDD-006 §4.2):

欄位 來源(clearing_runs) 說明
runId id 批次識別碼(亦為信封 aggregateId)
businessDate business_date 業務日(YYYY-MM-DD)
txCount tx_count 明細筆數
totalAmount total_amount 金額合計(最小單位)
differenceCount difference_count 差異筆數(0 表示全數相符)

處理:ClearingRunService.execute 於 run.complete(...) + runRepository.update(run)
之後、同一交易寫 outbox(該方法已標 @Transactional)。

設計要點:

  • ★ 分區鍵為 runId:同一批次之事件落同一分區(保序)。
  • ⚠️ 零筆數亦發射(txCount=0、differenceCount=0)——如實反映「該業務日無明細」之事實,
    由消費端自行依 txCount 判讀(與 §7 #5 之既有立場一致:不虛構語意)。
  • ⚠️ 本事件「刻意不做消費端」(SAD-004 §5.2 消費者欄為「—」;CR-030 §4.1:
    清算為批次對帳作業,以批次讀取為正解,事件驅動反而引入順序與完整性風險)。

★ 更正(CR-033,2026-10-02):本節原載「消費者為 posting——尚未實作」——
不成立:現行權威清單 SAD-004 §5.2 已標「不做消費端」;
SDD-003 §14 之 Clearing → Posting(產生會計分錄)為同步呼叫且在寫事件之前。
→ posting 非本事件之消費者;「入帳串接未實作」屬同步整合缺口(見 §10 #5),非事件流。

真機驗證(2026-10-01;真實 Oracle + Kafka):

驗證 結果
遷移 ✅ CC_CLR.outbox(V3)建立成功(查得 0 列=BEFORE 無餵養者)
服務啟動 ✅ clearing-service(8094)以新程式啟動、正常服務 HTTP
端到端(觸發清算) ⛔ 受阻——未完成:POST /api/v1/clearing/runs 回 401 CC-PLT-4010(未提供有效之認證憑證)。根因:該端點受服務對服務 ACL(CR-011)保護——cardcore-service-acl.yml 之 clearing-service 僅允許 cc-dispute-service;本機認證雖已停用(日誌證實 cardcore.security.enabled=false),ACL 過濾器仍要求有效權杖。
嘗試取得 M2M 權杖 ⛔ 失敗:以 ~/.config/cardcore/m2m.env 之 CARDCORE_M2M_CLIENT_SECRET_DIS 向 Keycloak 取 client_credentials 權杖 → 回傳長度 0(未取得 access_token)。未進一步追查(避免耗費過多;且該權杖之用途與本事件無關)。

⚠️ 誠實揭露(重要):本節之實作已完成並通過單元測試與編譯,
惟端到端真機驗證尚未完成——不宣稱「已驗證」。
待辦:以合法 M2M 權杖(cc-dispute-service)或經閘道/BFF 之使用者權杖重跑;
或於本地以暫時停用 ACL 之方式驗證(須先確認該開關存在且可還原)。


8. 外部依賴與降級

依賴 失敗處理(Stand-in)
posting-service(交易比對/請款入帳) 對帳失敗標 EXCEPTION 不阻塞整批;請款經冪等重試

9. 測試案例(對應 TRP-001)

  • UT-CLR-001:發卡清算(ISS-2.13)——檔解析、對帳、結算
  • UT-ACQ-006:收單清算(ACQ-4.6)
  • UT-DEB-006:DEBIT 卡發卡清算(DEB-3.6)、Checksum 驗證、EXCEPTION 處理

10. 已知限制(誠實揭露)

  • CLEARING_BATCHES/ITEMS 狀態轉換未建模為 SDD-005 正式狀態機。
  • 清算檔格式(VCF/EMS 等)之完整欄位規格須引用組織(VISA/MC/NCCC)檔案規範,待與 migration-service/收單資料源確認。
  • clearing-service 骨架與埠號尚未產生(規格先行) → 已產生,埠號 8094,已啟動實測。

CR-016 執行後新增之限制(2026-09-22)

  1. 清算檔解析未實作(GOV-006 C-5 明載「檔格式除外」):
    CLEARING_BATCHES/CLEARING_ITEMS 之寫入者(檔解析器)不在本期範圍;
    本期之對帳以既有明細為輸入。
  2. 差異分類僅能判定 SUBSTANTIVE:時序差(TIMING)需「帳務側未入帳交易」、
    匯差(FX_RATE)需匯率表,本服務皆不具備——不靜默假造分類。
  3. NETWORK_FEE 固定為 0:CC_CLR 無費率設定表,網路費率無從取得;
    INTERCHANGE_FEE 取明細之逐筆費用(檔案帶入之事實)。
  4. 結算單識別碼未回傳:契約之本端點回 ClearingRunView(202),
    已產生之 SETTLEMENTS 識別碼無法由回應取得(無結算查詢端點)。
  5. 結算不觸發 posting-service 入帳:SCS-013 §7 原載「觸發請款入帳」,
    本期僅落 SETTLEMENTS 紀錄——入帳串接未實作(需 posting 側之應收/應付科目設計)。
    ★ 性質澄清(CR-033 §3.3,2026-10-02):此為 SDD-003 §14 所載「同步呼叫」之實作缺口,
    不是事件消費缺口;其真正阻塞為會計科目(應收/應付)設計——屬業務/會計輸入
    (CR-030 §4.2),不得由開發臆造。另:clearing → posting 不在 posting-service 之 ACL
    (cardcore-service-acl.yml),實作時須一併更新 SAD-002 §5。
  6. TIMING/FX_RATE 之自動判別、RESOLUTION 之自動結案未實作:
    本期 RESOLUTION 為固定文字(待人工覆核)。

11. 修訂記錄

版本 日期 修訂者 說明
1.2 2026-09-30 WorkBuddy AI 新增 §6.1「清算執行清單端點」(GET /api/v1/clearing/runs?limit=N)——解鎖 SDD-007 §5.4.26 桶 C「批次作業監控」(SCS-020 §9.9)。⚠️ 併載實測揭露:該路徑原僅有 POST、GET 回 404。新增 ClearingRunSummary(含 runNo/businessDate/createdAt/updatedAt);⚠️ 本服務無 startedAt/finishedAt——如實回 createdAt/updatedAt,不臆造。limit 預設 20/上限 100;無資料回空陣列非 404
1.3 2026-10-01 WorkBuddy AI GOV-006 4.8 W-2 推廣・CR-027 批次 A-4:新增 §7.1 清算比對事件輸出(clearing.batch.matched.v1)——V3__outbox.sql + OutboxPort/ClearingEventFactory/JdbcOutboxRepository/{KafkaEventPublisher,OutboxRelayConfig,OutboxRelayScheduler};ClearingRunService.execute 同交易寫 outbox;@EnableScheduling;Kafka 值序列化改 StringSerializer。★ 分區鍵=runId;★ 零筆數亦發射(不虛構語意)。單測 ClearingEventFactoryTest 4 例通過;V3 遷移真機套用成功。⚠️ 端到端真機驗證受阻:POST /api/v1/clearing/runs 受服務間 ACL(CR-011)保護(僅允許 cc-dispute-service)→ 401 CC-PLT-4010;嘗試以 M2M 憑證取權杖失敗 → 未驗證,如實揭露。
1.0 — 開發團隊 初版(規格先行)
1.1 2026-09-22 WorkBuddy AI CR-016 執行完成(BEAM PMO 裁定:A/B/D/E 一次核准;org 由 DDL 增列)——①**§5 資料模型**:新增 CLEARING_RUNS/CLEARING_RECONCILIATIONS 兩表、SETTLEMENTS.DIRECTION、org 值域擴充至 8 值,並載明「為何需要執行表(不可挪用批次表)」;②**§6** 註明 POST /clearing/runs 之成功碼為 202;③**§7** 新增實作要點(狀態推導、僅異常落列、費用來源、冪等派生主鍵、零筆數警示);④**§10** 移除「骨架未產生」、新增 6 項限制;⑤補建本修訂記錄區(原文件欠缺)。單測 24 例 0 失敗;真機 e2e 七項(含遷移前後對照與 DB 複核)
1.4 2026-10-02 CR-033:更正事件消費者與缺口性質——①**§7.1** 原載「消費者為 posting——尚未實作」不成立:現行 SAD-004 §5.2 已標「不做消費端」(CR-030 §4.1:清算為批次對帳);②**§10 #5** 載明「入帳串接未實作」屬 SDD-003 §14 之同步呼叫缺口(非事件流),其真正阻塞為會計科目(應收/應付)設計(外部輸入,CR-030 §4.2),併記「clearing → posting 不在 posting-service 之 ACL,實作時須一併更新 SAD-002 §5」。★ 零程式變更(純文件更正)
(52-52/67)