CR #208 » CR-007_帳單交易未依期間過濾致重複計收.md
CR-007 帳單交易未依期間過濾 → 每期重複計收(含循環息)
| 項目 | 內容 |
|---|---|
| 編號 | CR-007 |
| 版本 | 1.2(2026-09-20 核定並執行完成) |
| 狀態 | ✅ 已核定/已執行並實測通過 |
| 提出日期 | 2026-09-20 |
| 核定日期 | 2026-09-20(BEAM PMO:採 A——posting 交易查詢加 from/to,statement 以本期區間查詢;逾時改可組態+重試) |
| 提出者 | WorkBuddy AI |
| 影響文件 |
SCS-011 §7/§8.1/§10、SCS-007(新增交易查詢之期間參數)、contracts/openapi/posting-service.yaml(由 tools/gen_openapi.py 產生)、PostingQueryPort/PostingServiceClient
|
| 關聯 |
CR-006(已核定並執行——本次缺陷由其「每月計息」而放大為利息重複計收)、HANDOVER §3.C #C-38
|
| 嚴重度 | 🔴 P0(帳務金額錯誤)——客戶端多收款,且循環息每期累加 |
1. 背景
CR-006 核定後,statement-service 已於結帳前觸發計息(SCS-011 §8.1),
實機執行 10 月帳單週期(M01/businessDate=2026-10-15)成功:
帳單產生、finance_charges=2064。
驗證金額時發現該數字不對——10 月帳單把兩個月的利息都算進去了。
追查後確認根因不在計息,而在帳單彙總交易時沒有任何期間過濾。
2. 實測證據(真實 Oracle,2026-09-20)
2.1 帳單金額(同一帳戶 018f2a3b-…-0010)
| 帳單月份 | 前期餘額 | 繳款 | 消費 | 循環息 | 費用 | 新餘額 |
|---|---|---|---|---|---|---|
| 2026-09 | 0 | 12,345 | 55,500 | 0 | 300 | 43,455 |
| 2026-10 | 43,455 | 12,345 | 67,845 | 2,064 | 300 | 101,319 |
- 繳款 12,345 與費用 300 兩期完全相同 —— 同一筆交易被兩期帳單重複計收。
- 帳單明細筆數:9 月 3 行 → 10 月 6 行(舊的 3 行原封不動再列一次)。
2.2 循環息
CC_POSTING.TRANSACTIONS 之 INTEREST 交易:
TX 2026-09-15 amt=1032 (CR-006 實測時手動觸發)
TX 2026-10-15 amt=1032 (本次 10 月週期自動觸發)
10 月帳單 finance_charges = 1032 + 1032 = 2064
——9 月已發生之利息又在 10 月帳單計收一次。
依此機制,第 N 期帳單會計收第 1~N 期全部利息,呈等差累加(第 3 期 = 3096、第 4 期 = 4128…)。
3. 根因
SCS-011 §7 明載:「由 posting-service 取當期交易與餘額」。
但實作並未做到:
// StatementGenerationService.generateOne
var transactions = postingClient.getTransactions(accountId); // 無任何日期參數
// PostingQueryPort
List<TxnSnapshot> getTransactions(UUID accountId); // 簽章本身就沒有期間
// PostingServiceClient
get("/api/v1/accounts/" + accountId + "/transactions?page=1&size=200"); // 僅分頁
contracts/openapi/posting-service.yaml 之
/api/v1/accounts/{accountId}/transactions 僅有 page/size 兩個查詢參數——
契約層就不支援期間過濾,故這不是「忘了傳參數」,而是端到端都沒有這個能力。
結論:文件(§7「當期」)與實物(抓全部)不一致,屬實作缺陷,非設計取捨。
4. 為何現在才爆(與 CR-006 之關係)
- 9 月帳單產生時,
posting尚無任何INTEREST交易 →finance_charges=0,
缺陷存在但被掩蓋(消費/繳款重複計收其實早已發生,見 §2.1)。 -
CR-006讓利息每期自動產生後,缺陷轉為利息逐期累加,金額放大且持續惡化。
5. 選項與建議
| # | 方案 | 做法 | 優點 | 代價/風險 |
|---|---|---|---|---|
| A ⭐ | 查詢端加期間參數 |
posting 交易查詢端點新增 from/to(postingDate 區間);statement 以本期區間查詢 |
語意最正確(與 §7「當期」一致);無跨服務寫入 | 須改契約(gen_openapi.py)、posting 查詢邏輯、PostingQueryPort 簽章、statement 客戶端;分頁上限仍須處理 |
| B | 已出帳標記 |
posting 交易表加 statement_id,產生後回寫標記 |
不會遺漏 | 跨服務寫入權威表(posting 擁有資料所有權),一致性複雜,須 Saga |
| C | 帳單端水位 |
statement 自存 LAST_BILLED_AT,依 postingDate > 水位 過濾 |
不動 posting 契約 |
水位與分頁上限(200 筆)疊加,仍可能截斷;新增狀態須與 Checkpoint 續跑一致 |
建議採 A:
① 與 SCS-011 §7 既有文字一致(改的是實物,不是文件);
② 不讓 statement 寫入 posting 的權威資料——符合「權威來源單一」原則;
③ 期間條件下推至 SQL,可避開分頁截斷(C 辦不到)。
A 之配套(須同時定奪):分頁 size=200 之上限——期間過濾後單期交易仍可能 >200 筆,
建議改為「依期間查完為止」(分頁迴圈)或將 size 上限提高,並於 SCS-011 §10 明列。
6. 影響範圍(若採 A)
| 產出 | 變更 |
|---|---|
tools/gen_openapi.py |
posting-service 交易查詢端點新增 from/to 參數(不可手改 YAML) |
contracts/openapi/posting-service.yaml |
由產生器重產 |
posting-service |
查詢邏輯支援 postingDate 區間(SCS-007 須補對應章節) |
statement-service |
PostingQueryPort.getTransactions 簽章加期間;PostingServiceClient 帶參數 |
SCS-011 §7/§8.1/§10
|
明列期間條件與「計息日 periodEnd 須落在本期區間內」 |
| 測試 | 新增「跨期不重複計收」之整合與單元測試(TRP-001 須補編號) |
7. 風險揭露(未核定前之現況)
⚠️ 在核定前,
statement-service之帳單週期批次會產出金額不正確之帳單
(重複計收歷史交易與歷史利息)。
目前僅於本機開發環境執行過,未於任何客戶環境啟用。
建議核定前不要再對同一帳戶重複執行週期批次(每次執行都會再累加一層重複金額)。
8. 核定結果
| # | 核定 | 日期 |
|---|---|---|
| 5(方案選擇) | ✅ 採 A——posting 交易查詢加 from/to,statement 以本期區間查詢 |
2026-09-20 |
| A 配套(分頁上限) | ✅ 逐頁取至「不足一頁」為止(size=200),頁數上限 20(4,000 筆/期),超限丟錯(CC-STM-5001)。不靜默截斷、亦不調高共用 SizeParam 上限以免牽動全平台 |
2026-09-20 |
| 相關:#C-40 逾時 | ✅ 改為可組態+重試(批次語意下放寬預設值) | 2026-09-20 |
9. 實作結果與實測證據
9.1 區間語意(半開 (from, to])
| 邊界 | 語意 | 理由 |
|---|---|---|
to |
POSTING_DATE <= to(含) |
結帳日當日計收之循環息 postingDate 即為結帳日,不含將漏收當期利息
|
from |
POSTING_DATE > from(不含) |
上期結帳日(含)前之交易已於上期計收,含入將重複計收 |
9.2 實測(真實 Oracle,2026-09-20 07:40)
① 交易查詢端點之區間語意
帳戶 018f2a3b-…-0010,帳上共 7 筆交易(含 09-15 與 10-15 兩筆 INTEREST):
| 查詢 | 筆數 | 09-15 利息 | 10-15 利息 |
|---|---|---|---|
無 from/to
|
7 | ✅ 在 | ✅ 在 |
from=2026-09-15&to=2026-10-15 |
6 | ❌ 已排除(from 不含) |
✅ 保留(to 含) |
→ 半開 (from, to] 之語意經實機證實,且兩個邊界各自發揮作用。
② 帳單週期(關鍵:不再累加)
執行 11 月週期(businessDate=2026-11-15):
| 帳單月 | 前期餘額 | 繳款 | 消費 | 循環息 | 費用 | 明細 |
|---|---|---|---|---|---|---|
| 2026-09 | 0 | 12,345 | 55,500 | 0 | 300 | 3 |
| 2026-10 | 43,455 | 12,345 | 67,845 | 2,064(累加) | 300 | 6 |
| 2026-11 | 101,319 | 0 | 0 | 1,066 | 0 | 1 |
- 11 月帳單只含本期之 1 筆利息,歷史繳款/消費/費用全數不再重複計收。
(若缺陷仍在,finance_charges應為1032+1032+1066 = 3,130、明細 8 筆。) - 利息為 1,066 而非 1,032 係正確:10/15→11/15 為 31 天
(67,845 × 18.5% × 31/365 = 1,066.4 → 1,066)。
③ 逾時(#C-40 一併驗證)
11 月週期為重啟後之首次呼叫(冷路徑),過去會於 800ms 逾時而致
「帳戶被跳過、statementCount=0」;本次首次即成功(statementCount=1)。
⚠️ 歷史髒資料仍在
10 月帳單之 2,064 與 11 月帳單之 prev_balance=101,319 皆為修復前產生,
金額仍是錯的。修復僅保證往後之週期不再重複計收;
驗證成效一律以新月份為準。
9. 修訂記錄
| 版本 | 日期 | 說明 |
|---|---|---|
| 1.0 | 2026-09-20 | 初版。實測發現帳單未依期間過濾交易,致繳款/費用/循環息重複計收;提列三方案並建議採 A |
| 1.2 | 2026-09-20 | BEAM PMO 核定採 A 後執行完成並實機實測:①交易查詢新增 from/to,半開 (from, to] 經實機證實(09-15 排除、10-15 保留);②11 月帳單僅含本期 1 筆利息 1,066、明細 1 筆,不再累加(缺陷仍在時應為 3,130/8 筆);③逾時改可組態後冷路徑首次呼叫即成功。並揭露歷史髒資料仍在 |