CR #201 » SCS-013_清算結算服務程式設計規格.md
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.yamlaggregates) - 值物件:
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_ATTIMESTAMP(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載明)。
行為要求:
- 唯讀——僅查詢,不觸發清算。
-
依
createdAt遞減(最近執行者在前)。 -
無資料回
200+ 空陣列——非404。 -
不快取(
SCS-020 §9.9.4 #4)。 - ⚠️ 與
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 裁定)後定案之實作要點
-
清算執行(
POST /clearing/runs,成功碼 202):逐組織彙總該業務日之明細
(CLEARING_ITEMS經batch_id關聯批次取得org,以FILE_DATE對應業務日),
得筆數/金額合計與異常筆數/金額;狀態由結果推導——
差異筆數為 0 →MATCHED,否則DIFFERENCE(不由呼叫方指定,使狀態與差異不可能矛盾)。 -
對帳結果:僅有異常之組織落列(無異常者不需人工覆核);
分類本期一律SUBSTANTIVE(時序差需帳務側未入帳交易、匯差需匯率表,皆不具備)。 -
結算(
POST /clearing/settlements,成功碼 202):依執行之業務日與組織逐組織產生結算單;
GROSS = Σ 明細金額、INTERCHANGE_FEE = Σ 明細費用、NETWORK_FEE = 0、NET = GROSS − INTERCHANGE − NETWORK。 -
冪等:以冪等鍵派生主鍵(
UUID v3,前綴clr:run:/clr:settlement:{org}:)——
同鍵重送不重複產生(執行與結算單皆然)。 -
零筆數:允許完成為
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/收單資料源確認。 -
→ 已產生,埠號 8094,已啟動實測。clearing-service骨架與埠號尚未產生(規格先行)
CR-016 執行後新增之限制(2026-09-22)
-
清算檔解析未實作(
GOV-006C-5 明載「檔格式除外」):
CLEARING_BATCHES/CLEARING_ITEMS之寫入者(檔解析器)不在本期範圍;
本期之對帳以既有明細為輸入。 -
差異分類僅能判定
SUBSTANTIVE:時序差(TIMING)需「帳務側未入帳交易」、
匯差(FX_RATE)需匯率表,本服務皆不具備——不靜默假造分類。 -
NETWORK_FEE固定為 0:CC_CLR無費率設定表,網路費率無從取得;
INTERCHANGE_FEE取明細之逐筆費用(檔案帶入之事實)。 -
結算單識別碼未回傳:契約之本端點回
ClearingRunView(202),
已產生之SETTLEMENTS識別碼無法由回應取得(無結算查詢端點)。 -
結算不觸發
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。 -
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」。★ 零程式變更(純文件更正) |