CR #501 » CR-055_共享池(共用額度)與預借現金企業子額度之語意凍結與計算式.md
CR-055 共享池(共用額度)/預借現金/企業子額度——語意凍結與計算式
| 項目 | 內容 |
|---|---|
| 文件編號 | CR-055 |
| 版本 | 1.3 |
| 狀態 |
口徑已凍結;P4-1/P4-2/P4-3 全數落地並真機驗證(§8/§10/§11);§6 維度已由 BEAM PMO 確認(自然人帳戶級/公司戶歸戶級/不混算);身分證 360 見 CR-056
|
| 觸發 | BEAM PMO 2026-10-17 回覆 CR-054 §9 Q-A~Q-E |
| 需求依據 | RFP FR.md §2.4(1)「歸戶、持卡人、卡片調額/查詢」;CR-052 C-1(ii)/C-4;CR-053 維度模型 |
| 影響模組 |
limit-service(檢核與查詢)、authorization-service(授權呼叫)、商品參數(product-service) |
| 關聯 |
CR-052/CR-053/CR-054
|
1. 口徑原文(BEAM PMO 2026-10-17,逐字保留要點)
「共享池的觀念是『總額度的另一面說法-共用額度』。如果某自然人/公司戶在某銀行的
總額度是 5 萬,這個人在這家銀行有三張卡,一張額度 5 萬、一張 2 萬、另一張 3 萬,
他的共用額度仍然是 5 萬,而不是 5+2+3=10 萬!對卡片級而言,若當初的額度 2 萬,超刷的部分就走共享池,但共享池的上限就是 5 萬,
就算其他兩張都沒刷,這一張兩萬額度的最多也只能刷到 5 萬!還有,共享池是個參數,2 萬那張卡若不能走共享池,那他的上限就是當初核定的 2 萬!」
「A-3:會!(一般消費走卡片額度、預借現金走共享池)——因為共享池的觀念不在消費類型,
而是該卡能否享有共享池的權利!」「B:共享池是個參數,只有能夠適用共享池的商品,才會在他可以消費的類別裡面『消費』。
其卡片級的『可用』仍是他原本的額度。」「C:預借現金是總額度的某個百分比所產生的可預借現金額度。不是獨立額度——
總額度 5 萬、評等 80% → 4 萬是預借現金上限,總額度仍為 5 萬,不是 5+4=9 萬!」「D:企業子額度是集團型總公司的總額度,各分公司、部門、分公司總經理、高階主管、
採購單位可以有個別的子額度。D-2:當然要(子額度佔用扣減上層)。」「E:同意(60 秒容忍、300 秒告警)。」
2. 精確語意模型(本文件為權威)
2.1 三個不同的「額度」概念(★ 不可混用)
| 概念 | 定義 | 現行承載 |
|---|---|---|
| 總額度(shared pool 之上限) | 該客戶在本行之共用上限;不是各卡額度之和 |
account_limits 之 ACCOUNT 維度(permanent + temporary + shadow) |
| 卡片名目額度 | 核卡時核給該卡的金額;僅為該卡自身可用之上限(不享共享池時) |
account_limits 之 CARD 維度(V11) |
| 共享池權利 | 該卡是否可動用總額度(參數,商品級) | ★ 本 CR 新增:account_limits.shared_pool_eligible(卡片維度快照) |
★★ 關鍵:共享池不是額外池,而是「允許該卡用到總額度」的權利。
因此總額度 ≠ Σ卡片額度,且「享共享池」的卡其上限不會變成「卡額度 + 總額度」。
2.2 可刷上限(授權檢核用,風控底線)
若 該卡享共享池(shared_pool_eligible = true):
可刷上限 = 總額度 − 帳戶已用 − 帳戶全部未釋放佔用 ← 即「帳戶可用」
否則:
可刷上限 = 卡片名目額度 − 該卡未釋放佔用 ← 卡額度硬上限
★ 兩者皆下限 0。授權時檢核「本次金額 ≤ 可刷上限」。
2.3 卡片級「可用」的呈現(查詢用)
BEAM PMO 明示:「其卡片級的『可用』仍是他原本的額度」——故查詢須分開呈現:
| 欄位 | 語意 |
|---|---|
availableAmount |
卡片自身可用=卡片名目額度 − 該卡佔用(不扣帳戶已用)← 這是「原本的額度」 |
sharedPoolEligible |
是否享共享池(參數快照) |
sharedPoolAvailableAmount |
可動用總額度之剩餘=帳戶可用(僅 eligible=true 時有值;否則 null) |
★ 一個數字不足以表達:
availableAmount是「這張卡自己的錢」,
sharedPoolAvailableAmount是「這張卡還能向共用池借多少」。兩者不可相加。
2.4 BEAM PMO 之 5 萬例子——逐情境驗算
設定:總額度 5 萬;三張卡名目 5/2/3 萬;帳戶已用 0、佔用 0。
| # | 情境 | 可刷上限(正解) | 現行系統 | 判定 |
|---|---|---|---|---|
| ① | 2 萬卡(享共享池)首次刷卡 | 5 萬(總額度,不是 2+5=7) | 5 萬(看帳戶可用) | ✅ 一致 |
| ② | 同上,其他兩卡已刷 4 萬 | 1 萬(5−4) | 1 萬 | ✅ 一致 |
| ③ | 2 萬卡(不享共享池)刷卡 | 2 萬(卡額度硬上限) | 5 萬 | ❌ 超放 3 萬 |
| ④ | 同上,該卡已佔用 1.5 萬 | 0.5 萬 | 5 萬 | ❌ 超放 |
★★ 情境 ③/④ 即本 CR 要修的真實缺陷:現行
hold()只檢核帳戶可用、
完全不看卡片名目額度(CARD維度僅供查詢)。→ 不享共享池的卡會用到總額度。
2.5 預借現金(P4-2)
預借現金額度上限 = 總額度 × ratio (ratio 由商品/信用評等參數決定,例 80%)
預借現金可用 = min(預借上限 − 已預借金額, 帳戶可用)
★ 不是獨立額度:總額度 5 萬、ratio 80% → 預借上限 4 萬,總額度仍 5 萬(非 9 萬)。
★ 與一般消費共用同一總額度(故 已預借 亦計入帳戶已用/佔用)。
2.6 企業子額度(P4-3)
- 上層:集團總公司總額度(
CORP_SUB之上層,對應subject_type=CORP_SUB之根)。 - 下層:分公司/部門/分公司總經理/高階主管/採購單位——各為一個
CORP_SUB子主體。 - 子額度佔用扣減上層(BEAM PMO 明示「當然要」)→ 上層可用須扣除所有下層佔用之和。
- ⚠️ 需要組織樹(誰是誰的上層)——現行無此資料結構 → P4-3 需另立資料模型。
3. 與現行模型之落差(實查)
| # | 落差 | 現況 | 影響 |
|---|---|---|---|
| G-1 | 授權不看卡片額度 |
hold() 僅檢核帳戶可用;CARD 維度只供查詢(#A-285) |
★★ 不享共享池的卡會超放(§2.4 情境③/④) |
| G-2 | 無「共享池權利」欄位 | 無 | 無法區分「享/不享」 |
| G-3 | 查詢未分開呈現兩種可用 |
availableAmount 為「卡額度 − 帳戶已用 − 卡佔用」(保守下界) |
與 BEAM PMO 語意不符(他要「卡額度 − 卡佔用」) |
| G-4 | 無預借現金額度 | 無資料模型與參數 | P4-2 |
| G-5 | 無企業組織樹 |
CORP_SUB 僅值域預留 |
P4-3 |
| G-6 | 「總額度」維度待確認 | 現行 ACCOUNT 維度;BEAM PMO 稱「自然人在本行總額度」 |
見 §6 待確認 |
4. 參數設計(BEAM PMO:商品(產品級)參數)
參數鍵(namespace=product) |
值域 | 說明 |
|---|---|---|
limit.shared-pool-eligible |
true/false
|
該商品之卡是否可動用總額度;缺值 fail-closed(不臆造) |
limit.cash-advance-ratio |
0–100(%) | 預借現金額度佔總額度之比例(P4-2) |
★ 參數 → 卡片快照(留痕原則):設定卡片額度時,由呼叫端依商品參數帶入
sharedPoolEligible 並寫入 CARD 維度列;事後改參數不改變已設定之卡片語意。
⚠️ limit-service 無 card→product 對映(CR-052 C-5)→ 無法自行查商品參數,
故由呼叫端(BFF/card-service,知道 product_code)帶入。
5. 分批
| 批次 | 內容 | 狀態 |
|---|---|---|
| P4-1 | 共享池權利欄位+授權卡片上限檢核+查詢分開呈現 | ✅ 本批落地(V13) |
| P4-2 | 預借現金:cash-advance-ratio 參數+額度計算+查詢輸出(C-4) |
✅ 已落地(V15/V16;§10)。★ 授權端尚未送 CASH_ADVANCE(§10.5) |
| P4-3 | 企業子額度:組織樹資料模型+上層扣減 | ✅ 已落地(V17;§11)。★ 覆核流程與卡片綁定未接(§11.5) |
6. 維度確認(BEAM PMO 2026-10-17 已裁定,本節為權威)
| 客戶型態 | 總額度維度 | 本批實作 |
|---|---|---|
| 自然人 | 帳戶級(同一人在本行該帳戶之總額度;不論幾張主卡/附卡) | ✅ 已相符(ACCOUNT 維度) |
| 公司戶 | 歸戶級(集團總公司之總額度;分公司/部門/主管/採購單位為子額度) | ⏳ P4-3(需組織樹) |
6.1 ★★ 個人與公司額度不混算(BEAM PMO 2026-10-17 例子)
張三在 A 銀行個人帳戶總額度 5 萬;Z 公司在 A 銀行公司戶總額度 100 萬,
張三持 Z 的差旅卡 10 萬、採購卡 30 萬。
張三在 A 銀行的帳戶總額度仍是 5 萬——個人與公司各自獨立。
落地含義:
- 個人卡之共享池上限=個人帳戶可用(不因持有公司卡而提高)。
- 公司卡之共享池上限=公司戶(歸戶)可用。
- 同一自然人跨二者時,兩邊額度不得相加、不得互相扣減。
6.2 但「以人為視角」要查得到(CR-056)
BEAM PMO 同時要求:由身分證字號查詢時,要能帶出張三在 Z 公司的兩張卡。
★ 這與「額度不混算」不衝突:額度分開算,但查詢視角要能跨帳戶彙整。
→ 已開 CR-056。盤點揭露硬缺口:parties 綁 account_id(同一人跨帳戶為不同 party 記錄)、
身分證僅有加密欄與遮罩欄(遮罩不可作比較鍵)→ 無 id_no_hash 則跨帳戶 360 查詢不可行。
第一步(GET /api/v1/cards?accountId|partyId)已落地;id_no_hash 屬資安決策,待 BEAM PMO 裁定(CR-056 §4)。
6.3 採購卡之「限資產類消費」
BEAM PMO 例中採購卡「只能限制在資產類的消費」=用途限制(MCC 白名單)。
現行可由 special_limits.scope_ref 承載(CR-035 §2.1),但該表為帳戶層;
若客戶要求卡片層用途限制,需另立資料模型(本 CR 不動,記錄待辦)。
8. P4-1 執行結果(2026-10-17,#A-289)
| 項目 | 落地 | 真機證據 |
|---|---|---|
| 共享池權利欄位 |
V13:account_limits.shared_pool_eligible(DEFAULT 1 向後相容) |
真 Oracle 實查欄位存在 |
| 調額單留痕 |
V14:limit_adjustments.shared_pool_eligible(可空;僅 CARD 維度) |
同上 |
| 授權卡片上限檢核 |
hold():不享共享池 → 檢查「卡額度 − 卡佔用」;並與帳戶可用取較嚴者
|
★ 卡額度 200,000、帳戶 10,000,000:刷 150,000 通過;再刷 100,000 拒絕(CC-LIM-1001,訊息含「不享共享池…卡可用=50000」) |
| 查詢分開呈現 |
availableAmount=卡額度−卡佔用;sharedPoolEligible;sharedPoolAvailableAmount(僅 eligible 有值) |
不享共享池:available=50,000、pool=null;享共享池:available=100,000、pool=9,650,000(=10,000,000−200,000−150,000) |
| 參數 → 快照 | 調額請求帶 sharedPoolEligible,核准/生效時落地 CARD 列 |
設定 false 後查詢回 false
|
★ 修正之真實缺陷(本批最大價值):原 hold() 完全不看卡片名目額度——不享共享池的卡
可一路刷到總額度(例:總額度 5 萬、卡額度 2 萬 → 可刷 5 萬=超放 3 萬)。現已擋下。
★ 實作期發現之第二個缺陷(已修,勿重犯):初版於申請時即建立 CARD 列(為寫入權利),
但若該申請待覆核,卡片會出現「名目額度 0」之列 → hold() 以「卡可用 0」拒絕該卡所有交易。
→ 已改為「權利記在調額單、核准/直接生效時才建列」;並以既有測試
(cardAdjustmentOverThresholdRequiresApprovalAndKeepsDimension 斷言「待覆核期間不得先建列」)鎖住。
★ 測試:limit-service 202 測 0 失敗(新增 4 例:不享共享池被拒/享共享池可動用總額度/
無 CARD 列向後相容/卡額度與帳戶可用取較嚴);契約 112 端點/0 錯誤、verify_contract_dto 不符 0、
validate_sql 違規 0、三掃描器 0、Mermaid 0。
★ 仍未做(誠實揭露):
-
P4-2 預借現金:需「已預借」來源與
cash-advance-ratio參數(比率基數為總額度)。 - P4-3 企業子額度:需組織樹資料模型(現行無)。
- §6 之待確認:「總額度」維度為帳戶級或歸戶/當事人級——本批採帳戶級(保守:不會超放,只會少給共享額度)。
10. P4-2 預借現金(2026-10-17,#A-296)
BEAM PMO 指示「P4-2 預借現金…請執行」。語意完全依 §2.5 凍結值實作,未另行解釋:
預借現金額度上限 = 總額度 × ratio / 100 (向下取整)
預借現金可用 = min(預借上限 − 已預借金額, 有效可用)
10.1 落地
| 項目 | 內容 |
|---|---|
V15 |
account_limits.cash_advance_ratio NUMBER(3)(可空、CHECK 0–100)+limit_adjustments.cash_advance_ratio(同,供調額單留痕) |
V16 |
擴充 limit_holds.hold_type 值域加入 CASH_ADVANCE(見 §10.3 之真實缺口) |
| 領域 |
AccountLimit.cashAdvanceLimit(today)=totalLimit × ratio / 100;cashAdvanceConfigured();比例 0–100 於記憶體建構亦檢核(不靠 DDL 唯一防線) |
| 查詢 |
GET /instruments/{id}/limits 增 cashAdvanceRatio/cashAdvanceLimitAmount/cashAdvanceUsedAmount/cashAdvanceAvailableAmount
|
| 授權 |
hold(holdType='CASH_ADVANCE') 先檢預借子上限,再走既有「帳戶可用」檢核(兩者皆須通過) |
| 參數 → 快照 | 調額請求帶 cashAdvanceRatio,於核准/直接生效時落地 CARD 列(比照 V14,申請時不建列) |
10.2 三個刻意的語意決定
| 決定 | 理由 |
|---|---|
null ≠ 0 |
null=未設定 → 預借一律拒絕(fail-closed,不臆造 80% 這種「舉例值」);0=明確禁止(上限 0,訊息走「額度不足」)。兩者必須可區分,否則「還沒設」與「不准用」會混為一談 |
| 「有效可用」取較嚴者 | 享共享池 → 帳戶可用;不享共享池 → 卡片自身可用。若一律用帳戶可用,不享共享池的卡就能用預借繞過卡片硬上限(=§3 G-1 的缺陷以另一種形式復活) |
預借佔用進同一張 limit_holds |
CR-052 Q-C C-3 建議「共用,以 hold_type 區分」——避免兩套佔用表導致總額度被重複使用 |
10.3 ★★ 實作期攔下的真實缺口(值域未涵蓋)
V1 基線之 ck_holds_hold_type 值域為 ('GENERAL','SPECIAL','INSTALLMENT'),
沒有 CASH_ADVANCE → 真機測試第一次執行即 ORA-02290(違反檢查約束)。
這不是測試寫錯,是語意與 DDL 不一致:§2.5 要求「已預借」可被識別,而唯一可識別的位置就是 hold_type。
修法:V16 動態定位該系統命名約束(SYS_C… 名稱隨環境不同,不可寫死)後卸除,
再以具名 ck_holds_hold_type 重建。不改成「無 CHECK」——值域即行為契約,
放寬為任意字串會讓打錯字靜默變成新型別。
10.4 真機驗證(Testcontainers 真 Oracle +真 Flyway)
| 情境 | 結果 |
|---|---|
| 未設定比例 → 預借 |
拒絕,訊息「預借現金未開放(該卡未設定預借現金比例,fail-closed)」;查詢四欄皆 null
|
| 卡額度 200,000、ratio 80% | 上限 160,000(不是 360,000);預借 100,000 後:已預借 100,000、可用 60,000 |
| ★ 預借佔用是否計入帳戶佔用 |
是:帳戶 holdAmount=100,000、availableAmount=900,000(與一般消費共用總額度) |
| 帳戶可用 1,000,000 但預借上限 160,000 → 預借 170,000 | 拒絕(錯誤訊息帶上限 160000,供客服判讀) |
ratio 0
|
上限 0、可用 0;預借 1,000 → 拒絕(走「額度不足」而非「未開放」) |
| 不享共享池(卡 200,000/ratio 20%) | 上限 40,000;卡可用 100,000 → 可用取 40,000;預借 50,000 → 拒絕(卡額度充足仍被預借上限擋下) |
| 不享共享池且卡可用 30,000(< 上限 40,000) | 可用取 30,000(較嚴者) |
調額單帶 cashAdvanceRatio=60
|
生效後 CARD 列 ratio=60、上限 120,000(200,000×60%) |
★ 測試:CashAdvanceLimitIntegrationTest 7 例全通過(真 Oracle、零替身)。
10.5 誠實揭露
| 項 | 說明 |
|---|---|
CASH_ADVANCE 之判定靠呼叫端 |
本服務不判斷「這筆是不是預借現金」——holdType 由授權端(authorization-service)依交易類型帶入。若上游帶錯,檢核即失效。目前授權端尚未帶此值(見下) |
授權端尚未送 holdType=CASH_ADVANCE |
authorization-service 之授權流程目前一律送 GENERAL(交易類型 → 預借現金之對映屬其職責)。→ 本批完成的是限額能力,尚未在授權路徑生效。列為後續(需 CR 補 authorization-service 之交易類型對映) |
| 預借比率來源 | 由呼叫端依商品參數 limit.cash-advance-ratio 帶入(本服務無 card→product 對映,CR-052 C-5);商品參數本身尚未建立(product-service 之參數清單未含此鍵) |
11. P4-3 企業子額度(2026-10-17,#A-296)
BEAM PMO 指示「P4-3 企業子額度(需組織樹)…請執行」。語意依 §2.6 凍結值:
上層可用 = 上層總額度 − 上層已用 − 上層自身佔用 − **所有下層佔用之和**(下限 0)
11.1 資料模型(V17)
| 表/欄 | 內容 |
|---|---|
corp_units |
id/unit_no(唯一)/unit_name/parent_unit_id(根為 NULL)/root_unit_id(反正規化)/depth/settlement_account_id/status
|
limit_holds.corp_unit_id |
佔用歸屬企業單位(可空);與 instrument_id 並存不互斥
|
| 額度列 |
account_limits 之 subject_type='CORP_SUB'、subject_id = corp_units.id
|
★ 為何要獨立一張表,不在 account_limits 上硬加 parent_id:
① 組織樹是主體結構、額度是掛在結構上的數字,生命週期不同(單位可先建、額度後核);
② 同一單位未來可能有多幣別額度列(account_limits 唯一鍵含 currency),
樹只能有一份,否則多幣別會出現「多棵不一致的樹」。
★ settlement_account_id(額度歸屬帳戶)為何必要:account_limits.account_id 為 NOT NULL,
CORP_SUB 列亦須有歸屬帳戶。企業額度在實務上歸屬企業戶結算帳戶 → 由呼叫端於建立根單位時提供,
子單位繼承;不由系統臆造(CR-053 D-4 同類原則)。
★ 單位不得實體刪除:有歷史額度與佔用 → 僅能以 status='INACTIVE' 停用(本版尚未提供停用端點)。
11.2 端點
| 端點 | 說明 |
|---|---|
POST /api/v1/corp-units |
建根/子單位(unitNo 冪等;已存在回既有、不覆寫) |
GET /api/v1/corp-units/{unitId}/limit |
查額度:同時回「自身佔用」與「下層佔用」——只回一個總數會讓客服無法解釋「上層為何可用變少」 |
PUT /api/v1/corp-units/{unitId}/limit |
設定總額度(風控核定端點) |
POST /api/v1/corp-units/{unitId}/holds |
建立佔用(檢核走上層鏈) |
11.3 三個容易做錯的點(本批刻意處理)
| 陷阱 | 後果 | 本批做法 |
|---|---|---|
| 只檢核自身可用 | N 個子單位各自把子額度花光 → 上層被超放 N 倍 | 檢核走上層鏈(自身→父→…→根),任一層不足即拒絕 |
| 把「自身佔用」算進「下層之和」 | 同一筆佔用被扣兩次 → 可用被低估一倍 | 兩個查詢分開(corpUnitHolds vs descendantCorpHolds),下層不含自身
|
| 遞迴方向寫反 |
CONNECT BY 回空集合且不報錯 → 靜默低估下層佔用 |
以真機整合測試鎖住(三層樹,根=750,000) |
11.4 真機驗證(Testcontainers 真 Oracle +真遞迴 SQL)
三層樹:根 1,000,000 → 分公司 300,000 → 部門 100,000
| 動作 | 驗證 |
|---|---|
| 分公司佔用 200,000、部門佔用 50,000 | 分公司:自身 200,000、下層 50,000、可用 50,000 |
| 根查詢 | 自身 0、下層 250,000、可用 750,000(遞迴兩層都算到) |
| 葉節點(部門) | 下層 0、可用 50,000 |
| 根 100,000/分公司 80,000/部門 50,000;分公司花光 80,000 後部門再借 30,000 | 拒絕,訊息指出不足之層=B-003(分公司)——否則維運不知往上要調誰 |
| 子單位未帶額度 | 列存在但 0 元 → 佔用拒絕(不當 0 元放行、也不當無限大) |
重複 unitNo 建立 |
回既有、名稱與額度皆不覆寫 |
★ 測試:CorpLimitIntegrationTest 6 例全通過。
11.5 誠實揭露
| 項 | 說明 |
|---|---|
| 企業額度之「申請→覆核」未接 |
PUT /corp-units/{id}/limit 是核定端點(風控用),非客戶申請路徑。LimitAdjustmentService 目前僅由 instrumentId 判斷 CARD 維度,尚未支援 CORP_SUB 之覆核流程 |
不發射 limit.hold.created.v1 |
該事件消費端(授權投影)語意為卡片授權佔用,payload 無企業單位維度;照發會讓投影出現「無載體的佔用」→ 污染卡片級可用額度。故企業額度佔用不進事件流,以本服務即時查詢為權威 |
| 停用端點未做 |
status='INACTIVE' 之值域已在 DDL,但無 API;停用後既有佔用如何處置(釋放?保留?)需客戶口徑 |
| 未與卡片綁定 | 企業卡消費目前不會自動產生 corp_unit_id 之佔用(需 authorization-service 帶企業單位識別);本批完成的是額度模型與檢核能力
|
12. 授權路徑生效(2026-10-18,#A-297)
BEAM PMO 指示「授權路徑還沒生效 ← 請完成」。§10.5/§11.5 揭露的兩個缺口,本批補上。
12.1 缺口一:授權端一律送 GENERAL(P4-2 的限額能力從未觸發)
| 項目 | 內容 |
|---|---|
| 問題 |
LimitPort.hold(...) 不帶型態,RemoteLimitPort 固定用 HoldRequest.general → hold_type 永遠 GENERAL → CASH_ADVANCE 的子上限檢核一次都沒跑過
|
| 修法 |
LimitPort.hold 增 holdType(與 instrumentId);AuthorizeUseCase 以 txType 對映:CASH_ADVANCE→CASH_ADVANCE、INSTALLMENT→INSTALLMENT、其餘(含未知)→GENERAL
|
| ★ 未知一律 GENERAL 之意義 | 不是放行:金額檢核(卡片上限/帳戶可用/預借上限)與型態無關,一律執行;型態只決定「要不要多一道子上限」 |
| 測試 |
AuthorizeUseCaseTest 新增 2 例(CASH_ADVANCE→CASH_ADVANCE;PURCHASE→GENERAL),並斷言 instrumentId 確實傳遞 |
12.2 ★ 順帶修掉一個更嚴重的缺口:instrumentId 從未傳給額度服務
LimitPort.hold(...) 原本連 instrumentId 都沒有 → limit_holds.instrument_id 永遠為空。
這正是 CR-054 §3.3 量到「34/34 筆佔用無載體」的真正原因之一(不只是「歷史資料」)——
新交易也不會帶。後果:卡片級已用/可用只能取下界,holdAttribution 永遠 PARTIAL。
本批一併補上:授權帶 card.instrumentId() → 真機實查佔用列 INSTRUMENT_ID 有值。
12.3 缺口二:企業卡消費不帶企業單位(P4-3 在授權上等於沒生效)
| 項目 | 內容 |
|---|---|
| 設計選擇 |
由 limit-service 自行解析(V19 corp_unit_instruments),不要求授權服務帶 corpUnitId
|
| 理由 | 授權服務沒有(也不該有)企業組織資料;若要求它帶,就得在授權服務複製組織樹或每次多打一次同步查詢。放在額度域:授權只要照常帶 instrumentId → 授權服務零改動、零額外往返
|
| 端點 |
POST /api/v1/corp-units/{unitId}/instruments(一卡一單位;已綁其他單位回 CC-LIM-1003,不靜默改綁) |
| 佔用行為 |
hold() 解析所屬單位 → 先跑上層鏈檢核(任一層不足即拒絕)→ 佔用列同時寫 instrument_id 與 corp_unit_id
|
12.4 真機驗證(#A-297;真 Oracle + 真 Kafka + 真 Keycloak)
| 步驟 | 結果 |
|---|---|
| 發卡(8082) |
cardId=0dbe10fb…、411111******0335
|
Kafka cc.card.card.issued → limit-service |
自動建立 CARD 權利列(PRODUCT_DEFAULT、金額 0、shared_pool_eligible=1) |
| 查額度 |
cardLimitConfigured=false、permanentAmount=0、sharedPoolAvailableAmount=1,000,000
|
| 佔用 30,000(未綁企業單位) |
成功,DB corp_unit_id=NULL、instrument_id 有值
|
| 建企業單位(額度 200,000)+綁定該卡 | 綁定成功 |
佔用 100,000(授權只帶 instrumentId) |
成功,DB corp_unit_id=0A838A7E…(★ 自動歸屬) |
| 企業額度查詢 | 自身佔用 100,000、可用 100,000(200,000 − 100,000) |
★ 測試資料已清理(corp_units 0、limit_holds 回 34、account_limits 回 18)。
12.5 仍未做(誠實揭露)
| 項 | 說明 |
|---|---|
Stand-in 上送仍不帶 instrumentId |
本地讀模型(CC_AUTH.LIMIT_HOLDS)無該欄;自行回填等於臆造 → 該類佔用在權威端仍無載體歸屬(如實揭露) |
商品參數 limit.cash-advance-ratio 未建 |
故真機之預借檢核仍需呼叫端明確帶比例;limit.shared-pool-eligible 亦未建(走系統預設,見 CR-057 §9.3) |
| 企業卡「改綁」 | 目前禁止(回 CC-LIM-1003);改綁流程(含既有佔用如何處置)需客戶口徑 |
| 企業額度「申請→覆核」 | 仍未接 LimitAdjustmentService(PUT /corp-units/{id}/limit 為核定端點) |
13. 商品參數落地與消費端接線(2026-10-18,#A-298)
BEAM PMO「依照您的建議推進」→ 把 §4 設計的參數真的建起來並接上消費端。
13.1 建了什麼、刻意不建什麼
| 參數 | 處置 | 理由 |
|---|---|---|
product/limit.shared-pool-eligible |
建(product-service V3,值 true,效期 2026-01-01 起) |
與 V13 之 shared_pool_eligible DEFAULT 1 完全一致 → 建了不改變任何行為,只是把「系統隱含預設」升級為顯式、可稽核、可覆寫的參數(原本只寫在 DDL 的 DEFAULT 裡,沒人看得到) |
product/limit.cash-advance-ratio |
刻意不建 | 這是授信政策(預借現金額度佔總額度幾成)。§2.5 的 80% 只是舉例 → 填任何數字都是臆造政策。留空=未設定=預借不可用(fail-closed),待客戶核定 |
product/limit.approval-threshold-minor |
不種 | 未設時 limit-service 以組態預設 500,000 fail-closed 降級(CR-045 §7.14 A4),已有明確預設 → 再種一份會產生「兩個真相」 |
13.2 消費端接線(★ 沒有這一步,參數建了也不會生效)
| 參數 | 消費端 | 行為 |
|---|---|---|
shared-pool-eligible |
CardIssuedProjectionService(#A-297 已接) |
發卡事件 → CARD 權利列之 shared_pool_eligible
|
cash-advance-ratio |
LimitAdjustmentService.resolveCashAdvanceRatio(本批新增) |
卡片級調額未帶比例時自動取參;優先序:呼叫端明確帶入 → 商品參數 → 未設定(null,預借不可用)。值域外(非數字/超出 0–100)視同未設定並記 WARN,不得靜默截斷
|
13.3 ★★ 真機攔下的真實缺陷(本批最大價值)
真機驗證時發現:調額核准後 cardLimitConfigured 仍是 false(金額已寫入 200,000)。
| 項目 | 內容 |
|---|---|
| 根因 |
applyAdjustedAmount 只更新金額,沒把 limit_source 由 PRODUCT_DEFAULT 翻成 MANUAL
|
| 後果 |
limitConfigured() 恆為 false → hold() 永遠不套卡片上限 → 已核定額度的卡仍可刷到帳戶總額度 |
| 嚴重度 | 等於 #A-286 修掉的「不享共享池之卡超放」缺陷,在「先發卡、後核定」的卡片上靜默復活 |
| 修法 | 三個 UPDATE(永久/臨時/彈性)一律加 limit_source = 'MANUAL'——套用調額即代表人工核定
|
| 回歸測試 |
CashAdvanceLimitIntegrationTest.核定後須翻為人工核定:權利列 → 核准 → 斷言 cardLimitConfigured=true
|
★ 教訓:「金額寫進去了」≠「語意對了」。同一張表上並存「金額」與「金額的來源」時,
只更新金額會留下自相矛盾的列(有 200,000 卻說「未核定」),而這種矛盾不會報錯。
13.4 真機驗證(真 Oracle+真 Kafka+真 product-service)
| 步驟 | 結果 |
|---|---|
參數改 limit.shared-pool-eligible=false
|
權威值已變 |
| 投遞發卡事件(真 Kafka) | CARD 權利列:PRODUCT_DEFAULT|金額 0|shared_pool_eligible=**0**(★ 參數被實際消費) |
參數 limit.cash-advance-ratio=80
|
已建立 |
| 卡片級調額(不帶比例) | CARD 列 cash_advance_ratio=80、上限 160,000(200,000×80%)→ ★ 參數被實際消費 |
腳本:ops/local/e2e/limit_param_loop_e2e.sh。
13.5 ★ 待裁定:業務日之語意(本批未擅自改)
真機驗證時撞到一個跨服務的真實問題:
limit-service之LocalDate.now(clock)取的是 UTC 日期。台北時間 00:00–08:00 之間,
UTC 日期仍是「昨天」→ 取參時會拿到昨天的版本(本次驗證因此一開始取到舊值true)。
| 選項 | 說明 |
|---|---|
| A(建議) | 額度域之業務日統一改為 Asia/Taipei(Clock.system(ZoneId.of("Asia/Taipei")))——銀行業務日不應隨 UTC 跳動 |
| B | 維持 UTC,並要求參數生效日一律提前一天設定(治標,且易忘) |
★ 未擅自改:Clock 影響本服務所有日期語意(臨時額度起訖、覆核門檻版本、預借比例版本),
屬跨功能影響 → 需 BEAM PMO 裁定後另批處理。
13.6 工具教訓(省下後人半小時)
| 坑 | 事實 |
|---|---|
| Flyway 的歷史表在 Oracle 是引號小寫識別名 |
cc_product."flyway_schema_history",欄名亦為 "installed_rank"/"success"——用大寫會 ORA-00904
|
COMMENT ON TABLE 不可用相鄰字串字面值 |
Oracle 不自動串接 → ORA-03046(本批首版即踩,V3 因此失敗一次;已改為說明寫在註解) |
| Kafka CLI 不在 PATH | 容器內為 /opt/kafka/bin/kafka-console-producer.sh
|
| bash 變數後接中文全角字元 |
$VAR( 會被吃進變數名 → unbound variable;一律 ${VAR}(第二次踩) |
9. 修訂紀錄
| 版本 | 日期 | 內容 |
|---|---|---|
| 1.5 | 2026-10-18 |
商品參數落地+消費端接線(#A-298):product-service V3 建 limit.shared-pool-eligible=true(與 V13 預設一致,行為中性);刻意不建 cash-advance-ratio(授信政策待客戶核定)。新增 LimitAdjustmentService.resolveCashAdvanceRatio(未帶比例時自動取參)。★★ 真機攔下缺陷:調額核准未把 limit_source 翻為 MANUAL → cardLimitConfigured 恆 false → 卡片上限永不檢核(超放缺陷靜默復活);已修+回歸測試。★ 另揭露業務日走 UTC 之跨服務問題(待裁定,未擅自改) |
| 1.4 | 2026-10-18 |
授權路徑生效(#A-297):① LimitPort.hold 增 holdType+instrumentId,AuthorizeUseCase 以 txType 對映型態(CASH_ADVANCE→CASH_ADVANCE,未知→GENERAL);② ★ 順帶修掉「instrumentId 從未傳給額度服務」(limit_holds.instrument_id 永遠為空之真因);③ V19 corp_unit_instruments + POST /corp-units/{id}/instruments,hold() 自動解析企業單位並跑上層鏈檢核。真機:發卡→權利列→未核定仍可佔用→綁定後佔用自動歸屬企業單位 |
| 1.3 | 2026-10-17 |
P4-2/P4-3 落地(#A-296):預借現金(V15 比例欄+V16 值域擴充——真機攔下 ORA-02290 之真實缺口;上限=總額度×ratio、可用=min(上限−已預借, 有效可用);null≠0;7 例真機);企業子額度(V17 組織樹+limit_holds.corp_unit_id;上層可用扣減所有下層佔用;檢核走上層鏈並指出不足之層;6 例真機)。★ 揭露:授權端尚未送 CASH_ADVANCE、企業額度覆核流程未接、企業卡未綁 corp_unit_id
|
| 1.2 | 2026-10-17 |
§6 維度確認(BEAM PMO):自然人=帳戶級、公司戶=歸戶級;個人與公司額度不混算(附張三/Z 公司例);併記錄採購卡用途限制與 CR-056 身分證 360 之缺口 |
| 1.1 | 2026-10-17 |
P4-1 落地(#A-289):V13 共享池權利欄+V14 調額單留痕;授權卡片上限檢核(修正「不享共享池之卡超放」);查詢分開呈現(卡可用 vs 共享池剩餘);真機驗證四情境。★ 併揭露實作期第二缺陷(申請時建列會使待覆核卡片被鎖)與其修正 |
| 1.0 | 2026-10-17 | 開立並凍結 BEAM PMO 口徑:共享池=總額度之共用權利(非額外池);三概念分離(總額度/卡片名目額度/共享池權利);可刷上限與查詢呈現之精確公式;5 萬例子逐情境驗算並揭露現行「不享共享池之卡會超放」(G-1);預借現金為總額度百分比(非獨立額度);企業子額度扣減上層且需組織樹。P4-1 落地、P4-2/P4-3 分批 |