CR #207 » CR-006_計息串接之資料面缺口.md
CR-006 statement/posting 串接 pricing-service 之資料面缺口
| 項目 | 內容 |
|---|---|
| 編號 | CR-006 |
| 版本 | 1.1(2026-09-20 核定) |
| 狀態 | ▶ 已核定/執行中 |
| 提出日期 | 2026-09-19 |
| 核定日期 | 2026-09-20(BEAM PMO:③ 採 B「posting 新增計息服務」,其餘照我方建議推進) |
| 提出者 | WorkBuddy AI |
| 影響文件 |
SCS-007(補計息服務章節)、SCS-006(cardNo 選填)、contracts/openapi/pricing-service.yaml(由 tools/gen_openapi.py 產生)、SCS-011 §10
|
| 關聯 |
HANDOVER §3.C #C-3(★ 關鍵路徑最後一哩)、CR-005(已核定並執行)、SCS-019
|
1. 背景
pricing-service 之 3 個 REST 端點已於 CR-005 核定後完成並實機實測
(利息 796、費用 5 筆、違約金夾擠 3 期=90000)。
HANDOVER §3.C #C-3 隨之列為關鍵路徑最後一哩:
「statement 逾期加成、posting recalculateInterest 尚未呼叫它」。
動工前查核發現實況與該描述不符,且缺口在資料面而非接線面:
| 查核項 | 實況(2026-09-19 實測/讀碼) |
|---|---|
posting-service 之 recalculateInterest
|
不存在——應用程式層僅 PostingService/DuplicateCheckService/PostingQueryService,無任何利息計算
|
statement-service 是否計算利息 |
否——StatementGenerationService.generateOne 僅彙總 posting 既有之 INTEREST 交易(case "INTEREST" -> interest += amount),本身不產出利息 |
| 利息從何而來 |
無人產生 TxType.INTEREST 之交易——posting 有該型別可入帳,但沒有任何程式呼叫它 |
換言之,C-3 不是「把既有佔位接上」,而是「新增一個尚不存在的能力」:
誰去呼叫pricing、算出來的利息如何變成INTEREST交易入帳。
2. 缺口明細
#1 每日餘額(ADB)資料來源 —— 🔴 阻塞
pricing 之 ADB 計息法(calcMethod=ADB,CR-005 #3 定案之四種計息方法之一)
需輸入 dailyBalances(日終餘額序列)。
查 posting-service 之 DDL,表僅有:
balance_history、balances、sub_account_entries、transactions
——沒有每日餘額表,亦無任何 daily_balance/ADB 相關欄位。
→ ADB 計息目前無資料可供輸入。
#2 卡號來源 —— 🔴 阻塞
InterestCalcRequest 之 必填欄位為 accountId、cardNo、periodStart、periodEnd。
但 posting-service 之交易表不含 card_no 欄位(查 DDL 無此欄)。
→ 即使接線完成,必填欄位仍無法滿足。
#3 計息之觸發點與責任歸屬 —— 🔴 阻塞(設計決策)
利息究竟由誰、在何時計算並入帳?三個候選:
| 選項 | 說明 | 優點 | 疑慮 |
|---|---|---|---|
| A |
結帳時由 statement-service 呼叫 pricing,再把利息交 posting 入帳 |
貼近「帳單週期」語意 | statement 須同時依賴 pricing 與 posting;SCS-011 §8 原僅定義對 posting 之依賴 |
| B |
入帳域由 posting-service 新增計息應用服務(即 C-3 所稱 recalculateInterest),由批次/結帳流程觸發 |
責任單純(利息=一種入帳),statement 仍只讀 posting | 需在 SCS-007 新增能力;且 posting 仍缺 #1、#2 之資料 |
| C |
獨立批次作業(SCS-019):週期性掃描未結清帳戶 → 呼叫 pricing → 入帳 |
與結帳解耦、可重試 | 需批次框架支援;時序(先計息或先結帳)須定義 |
#4 違約金(lateFeeAmount)之入帳科目 —— 🟡
InterestCalcView 回傳 lateFeeAmount/lateFeeCount。
posting 之 TxType 目前有 FEE、INTEREST、ADJUSTMENT 等,
違約金應歸 FEE 還是新增 LATE_FEE? 影響分戶與報表歸類。
#5 四捨五入與幣別一致性 —— 🟡(原 HANDOVER #C-32)
pricing 計息結果傳遞至 posting 入帳、再由 statement 彙總,
三段之取整時點與幣別尚未驗證一致(SCS-006 §10 已列為限制)。
串接後此風險由「潛在」變為「實際」。
#6 statement 最低應繳之「逾期加成」 —— 🟡
Statement.computeMinimumDue 目前為簡化版
(Statement.java 註明:實務尚涉及循環息、逾期加成與法規上限)。
是否於本期納入逾期加成?若納入,需求哪些輸入?
3. 建議方案(待 BEAM PMO 裁定)
| # | 建議(我方意見) | 須 BEAM PMO 定奪之點 |
|---|---|---|
| 1 | 若本期不走 ADB,可用 CONSTANT_PRINCIPAL+principalAmount 先行串接(pricing 已支援,實測 796 即此法);ADB 待新增日終餘額表後再啟 |
本期是否接受先以非 ADB 計息串接? 或直接新增日終餘額表(新增 V*__daily_balances.sql,須定義留存與回填) |
| 2 | 由 posting 呼叫 card-service 取得卡號,或改由呼叫端(statement/批次)帶入 |
卡號由誰提供? 是否放寬 pricing 之 cardNo 為選填(須改契約,另提 CR) |
| 3 |
建議 B:posting-service 新增計息應用服務(即 recalculateInterest),責任歸入帳域,statement 維持只讀 posting(不改 SCS-011 §8 之依賴邊界) |
A/B/C 擇一;並定義觸發時序(計息是否必須早於結帳) |
| 4 | 建議歸 FEE 並於 SUB_ACCOUNT 以子型區分,避免 TxType 擴張 |
是否接受?或新增 LATE_FEE
|
| 5 | 建議定為「一律於 pricing 端取整(HALF_UP、最小單位分),下游不得再取整」,並於串接時補一致性整合測試(即 HANDOVER #C-32) |
取整責任歸屬 |
| 6 | 建議本期維持簡化,但於 SCS-011 §10 明列「未含逾期加成」 |
是否本期納入 |
4. 不受缺口影響、可立即施作之範圍
-
pricing-service端點本身(已完成並實測,不須改動)。 -
posting-service之pricing客戶端骨架:可依既有
PostingServiceClient(標準HttpClient、逾時 800ms、失敗拋領域例外)模式先行建立,
待 #1/#2 定案後填入欄位對應。 -
HANDOVER §3.C #C-32(Round-off 一致性整合測試)之測試設計,
可先於pricing側準備。
5. 誠實揭露(提出時)
-
HANDOVER §3.C #C-3之描述與實況不符:該列稱兩服務「仍為待定價就緒之佔位」,
實測讀碼後確認posting-service並無recalculateInterest方法,
statement-service亦未計算利息(僅彙總既有INTEREST交易)。
→ 已於本 CR 更正描述;HANDOVER將同步修訂。 -
本 CR 未做任何程式改動——動工前查核階段發現阻塞即提出,
依「不邊寫邊猜」原則,未逕自假設計息方法或新增資料表。 - 上列缺口任一未裁定即動工,將導致「串接完成但資料湊不出來」之假象——
此為本次刻意避免之結果。
6. 核定結果與定奪(2026-09-20 BEAM PMO)
BEAM PMO 批示:「③ 觸發點歸屬 —— posting 新增計息服務,其餘也由您的建議推進」。
| # | 核定結果 |
|---|---|
| 1 |
照建議:本期以 CONSTANT_PRINCIPAL + principalAmount 串接(ADB 已實測可行,值 796);ADB 須待新增日終餘額表後再啟,列為後續 |
| 2 | ⚠️ 依新事實調整建議(見下):改為放寬 pricing 之 cardNo 為選填,不再由 posting 向 card-service 取卡號 |
| 3 |
B:posting-service 新增計息應用服務(recalculateInterest),責任歸入帳域;由結帳/批次流程於結帳前觸發 |
| 4 |
照建議:違約金歸 TxType.FEE(SubAccount.FEE),不新增 LATE_FEE 型別 |
| 5 |
照建議(用詞已依實作更正):取整責任歸 pricing——四捨五入 HALF_UP 取整至最小單位(分)(SCS-006 §7、CSP-007 §5.1);posting/statement 下游不再取整 |
| 6 |
照建議:statement 最低應繳維持簡化,於 SCS-011 §10 明列「未含逾期加成」 |
#2 為何調整——動工後查得之新事實(誠實揭露)
原建議「由 posting 呼叫 card-service 取得卡號」。進一步查證後發現此路不通且不宜:
-
查無其端點:
card-service契約僅POST /api/v1/cards(發卡)與
GET /api/v1/cards/{cardId},沒有依accountId查詢之端點。 -
更關鍵——明文卡號在本系統內不存在:
Card領域模型僅有cardNoMasked,資料表亦只存卡號雜湊;
GET /api/v1/cards/{cardId}回傳之為**** **** **** 1111。
這是正確的 PCI 設計,不應為串接而開明文出口,
更不應讓明文卡號在服務間傳遞。
故改為:InterestCalcRequest.cardNo 放寬為選填,計息以 accountId 為識別主鍵,
cardNo 若帶入僅供對帳顯示、應為遮罩值。回傳 InterestCalcView.cardNo 同步改為選填。
此調整同時省去新增 card-service 查詢端點之工作量,且更符合資安要求。
7. 實作結果與實測證據(2026-09-20)
交付
| 項目 | 內容 |
|---|---|
posting-service |
新增 InterestCalculationPort(domain/port)、PricingServiceClient(infrastructure/client)、InterestPostingService(application)、InterestRecalcRequest/InterestRecalcView、InterestPostingController
|
| 端點 | POST /api/v1/posting-interest/recalculate |
pricing-service |
InterestCalcRequest.cardNo 移除 @NotBlank(配合 #2 放寬為選填) |
| 契約 |
tools/gen_openapi.py 調整後重產(驗證 0 錯誤) |
| 錯誤碼 | 新增 CC-PLT-5005(503 定價服務呼叫失敗),錯誤碼 114 → 115
|
| 測試 |
posting-service 新增 6 測試(UT-POST-021/022),全服務 17 測試 0 失敗
|
實機實測(真實 Oracle + 真實 pricing-service)
帳戶 018f2a3b-…0010,本金 67,845 分,區間 2026-08-16~09-15,利率 18.5%(ACT_365)。
| # | 情境 | 結果 |
|---|---|---|
| ① |
dryRun=true 試算 |
200,interestAmount=1032、calcBasis=CONSTANT_PRINCIPAL,postingIds 為空 ✅ |
| ② | 實際入帳 | 200,產生 postingId;Oracle 查得 tx_type=INTEREST、amount=1032 ✅ |
| ③ | 同期重送 | 回傳同一個 postingId;Oracle COUNT=1(未重複計息) ✅ |
| ④ | pricing 不可用 |
503 CC-PLT-5005;Oracle INTEREST_TX_COUNT 仍為 1——未 fail-open ✅ |
金額自證:67,845 × 18.5% × 30/365 = 1,031.6 → 1,032 分(HALF_UP),與定價端一致。
分戶餘額同步為 interest_amount=1032、total_amount=69,177(67,845+1,032)✅
動工後修正之兩處(誠實揭露)
-
#2 建議方向調整(理由見 §6 之 #2 說明):卡片無明文卡號,改為放寬
cardNo選填。 -
#5 用詞更正:原建議寫「取整至元」,實作查得
SCS-006 §7/
InterestCalcView明定金額為最小單位(分)、HALF_UP取整至分——
已更正 CR 與SCS-007 §8.1之措辭(數字不變,但單位用詞若沿用將導致帳務誤解)。 -
客戶端漏
X-Idempotency-Key:pricing之POST interest-calculations
要求該標頭為必填,初次呼叫得 HTTP 400(實測後補上,鍵值由帳戶+區間衍生)。