Project

General

Profile

CR #236 » CR-035_POC8剩餘四項之設計與裁定.md

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

 

CR-035 POC-8 剩餘四項之設計與裁定——8-2 權限參數化、8-3/8-5 授權端扣抵、8-6/8-7 臨時額度串動、scope_ref

項目 內容
文件編號 CR-035
版本 1.1(✅ Phase 1/2/3 全數完成——8-3/8-5、8-2、8-6/8-7 皆已實作並真機驗證。★ 更正 §2.4 之「繳款截止日」來源:原載讀 CC_STMT 為跨 schema、不可行,已改為推算並標記 DERIVED。★ §2.3 之折衷已處理:角色改由已驗證之權杖決定(折衷移除);客層保留但附四項證據。前版 1.0)
狀態 ✅ 已裁定並全數執行完畢(Phase 1/2/3 皆已實作並真機驗證)
提出日 2026-10-02
提出者 WorkBuddy AI(開發團隊)
緣起 BEAM PMO:「請接續完成 8-2 角色/客層上限、8-3/8-5 授權端扣抵與超額拒絕(屬 authorization-service,在此之前專用額度尚不能實際動用)及 8-6/8-7 臨時額度串動、scope_ref(MCC)」
前案 CR-014 v1.1(#A-153)——專用額度資料模型已落地;本 CR 為其 §8 已知限制之收斂
關聯 BRD-004 POC-8、BRD-001 §5.4 BF-04(BR-04-01~BR-04-04)、SDD-001 §3.4、SDD-005、SDD-006、SCS-001、SCS-002、GOV-006 2.2.3b
嚴重度 🟡 中——在此之前專用額度「可設定但不能動用」(POC-8 之核心價值未兌現)

1. 動工前事實查證(真機;cardcore-preflight-fact-check)

查證項 結果
CC_LIM.SPECIAL_LIMITS 欄位 12 欄(含 currency);無 scope_ref → 需新增
CC_LIM.LIMIT_HOLDS 欄位 11 欄;無 special_limit_id → 需新增。hold_type IN ('GENERAL','SPECIAL','INSTALLMENT')——SPECIAL 值早已在值域內(既有設計已預留,惟從未使用)
「客層」來源 ★ 全庫無 SEGMENT 欄位。最接近者為 CC_PRODUCT.PRODUCTS.CARD_GRADE(卡等)與 CC_RW.POINTS_TIERS.TIER_CODE(忠誠層級)
「角色」來源 ★ 不存在——LimitAdjustmentCommand 無角色欄位;平台亦無 RBAC 資料源
posting.transaction.posted.v1 payload postingId/txNo/accountId/txType/amount/currency/postingDate/subAccountEntries[]——★ 不含 authorizationId(8-6 需要它才能識別「哪一筆用了專用額度」)。惟 CC_POSTING.TRANSACTIONS 有 AUTHORIZATION_ID 欄位 → 可作為可選欄位補入(SAD-004 §5.3 不升版)
繳款截止日來源 CC_STMT.STATEMENTS.payment_due_date(NOT NULL)、CC_POSTING.BALANCES.payment_due_date(可空);StatementGenerationService 以 帳單日 + 25 天 產生
授權服務之額度讀模型 account_limit_caches(total_amount/used_amount)+ limit_holds——無任何專用額度欄位
ACL limit-service 接受 cc-authorization-service ✓;product-service 接受 cc-limit-service ✓;★ card-service 只接受 cc-authorization-service → limit-service 不得呼叫 card-service(影響 8-2 之客層自動取得)

2. 逐項設計與裁定

2.1 scope_ref(用途範圍,MCC)——POC-8 之 8-3 前置

裁定:SPECIAL_LIMITS 新增 scope_ref VARCHAR2(64 CHAR)(V7)。

值 語意
NULL/空白 不限定——該用途之任何 MCC 皆可動用
非空 逗號分隔之 MCC 白名單(如 9311,9399);交易之 MCC 須恰在其中

理由:BRD-001 §5.4 BF-04 之措辭為「依用途設定(繳稅/保費/特定 MCC)」——
用途為主軸、MCC 為細化。故「不限定」是合法設定,非缺漏。

多筆命中時之選擇順序(決定性,避免不確定行為):

  1. scope_ref 非空且命中 者優先(較特定);
  2. 其次為 scope_ref 為 NULL(不限定)者;
  3. 同級則依 purpose 升冪、id 升冪(保證決定性)。

⚠️ 繳稅之 MCC 慣例:9311(Tax Payments)/9399(Government Services, NEC)。
種子資料將以 scope_ref='9311,9399' 設定 TAX 專用額度。


2.2 8-3/8-5:授權端扣抵與超額拒絕(本 CR 之核心)

現況:AuthorizeUseCase 只查 limitPort.availableLimit(accountId, currency)(卡人額度),
專用額度完全不參與——故 8-3 之「不佔卡人額度」與 8-5 之「超額拒絕」皆無法成立。

裁定:三段式池選擇(Pool Selection),於 AuthorizeUseCase 內實作:

① 查「適用之專用額度」(account, currency, mcc)→ 得 (specialLimitId, 可用專用額度)
② checkLimit:
     若 ① 存在 且 可用專用額度 ≥ 交易金額  → PASS(pool = SPECIAL)
     否則若 卡人可用額度 ≥ 交易金額        → PASS(pool = CARDHOLDER,8-4)
     否則                                  → FAIL(CC-LIM-1001,8-5)
③ hold:pool = SPECIAL → holdType='SPECIAL' + specialLimitId
         pool = CARDHOLDER → holdType='GENERAL'

★ 關鍵裁定(避免「雙重佔用」):使用專用額度時,LIMIT_HOLDS 只記一筆(hold_type='SPECIAL'),
且
不動** account_limits 之任何欄位**。故「不佔卡人額度」(BR-04-01)是結構性成立,
不是靠事後沖銷——下游之 available = total − used − holds 天然不含該筆(見 SCS-001 §7.15 之既有設計)。

專用額度池之佔用:special_limits.used_amount 於 hold 時 +=、release 時 -=(下限 0,同一交易)。
→ 與卡人額度之 used_amount+limit_holds 雙層語意刻意不同:
專用額度之「已用」=佔用中(授權已佔、尚未釋放),
因專用額度無「請款比對」之獨立訊號可依(見 §3 已知限制)。

Stand-in 降級:limit-service 不可用時,StandInLimitPort 之本地讀模型沒有專用額度資料
→ 查無適用之專用額度 → 自然退回卡人額度(8-4)。此為安全降級(不會超額放行專用池)。
⚠️ 如實揭露:Stand-in 期間專用額度不可用(非「假裝有」)。

新增端點(limit-service):
GET /api/v1/accounts/{accountId}/special-limits/applicable?currency=&mcc=&amount=
→ 回 SpecialLimitApplicabilityView { applicable, specialLimitId, purpose, scopeRef, availableAmountMinor, reason }
(applicable=false 時亦回 200——「無適用」是正常結果,非錯誤;reason 說明未命中原因。)


2.3 8-2:角色/客層上限(參數化權限)

裁定:新增 CC_LIM.SPECIAL_LIMIT_POLICIES(V8)。

欄位 說明
id PK
role_code 角色(如 cardcore-csr/cc-payment-service/UNVERIFIED)
segment_code 客層;* = 不限
currency 幣別
max_amount 該組合之可設定上限(分)
status ACTIVE/INACTIVE
created_at/updated_at/version SQL-05 通用欄位

UNIQUE (role_code, segment_code, currency);索引 (role_code, status)。

2.3.1 ★★ 折衷之處理(本 CR 之重點之一)

初版設計把「角色」與「客層」兩者都推給「請求帶入」,並承認那是折衷。
動工前查證後,兩者之處置不同:

① 角色:折衷移除——改由「已驗證之權杖」決定(伺服器端)

證據:common-spring 已有 JwtRoleConverter(SCS-022 §7.2)——
將 Keycloak 之 realm_access.roles 抽為 ROLE_<name> authorities,
且 SecurityAutoConfiguration 已將其裝入 JwtAuthenticationConverter。

裁定:role_code 一律由伺服器自 Authentication 決定,不接受請求帶入(防提權)。
來源優先序(皆為已驗證權杖之事實,或明確之未驗證狀態):

序 條件 role_code verified
1 有 ROLE_* authorities(使用者權杖之 realm roles) 去 ROLE_ 前綴;多筆取字典序最小(決定性) true
2 無 realm roles 但為服務權杖(JwtAuthenticationToken 之 azp 存在) azp 值(如 cc-payment-service) true
3 以上皆無(認證停用/匿名) UNVERIFIED false(併記 WARN)

★ 為何「取字典序最小」而非「取最寬鬆」:多角色使用者之語意本就不明;
取最小者決定性且不會因角色組合而放寬,並於軌跡記錄「實際使用之角色」。
★ 序 3 為 fail-closed 之樞紐:認證停用時角色為 UNVERIFIED,
必須顯式設定 role_code='UNVERIFIED' 之政策才會通過——不會「因為沒認證所以放行」。

② 客層:折衷保留,但附證據、防護與後續

為何無法自動取得(真機查證):權威來源為 CC_PRODUCT.PRODUCTS.CARD_GRADE,
惟取得路徑為「歸戶 → 卡片 → 產品 → 卡等」,而——

# 障礙 證據
1 CC_LIM 無任何產品/卡等欄位 all_tab_columns 實查
2 card-service 之 ACL 只接受 cc-authorization-service cardcore-service-acl.yml
3 party-service 之 ACL 不含 cc-limit-service 同上
4 CC_PARTY.ACCOUNTS 無客層欄位 實查(且全庫無 SEGMENT/LEVEL)

→ 自動取得需同時開放兩個 ACL + 補讀模型,與 8-2 之價值不成比例。

裁定:segment_code 由請求帶入(可選,預設 *),並——

  1. 定位為「政策查詢鍵」而非「授權判斷」(真正的權限在角色,角色已不可偽造);
  2. 程式與文件明確標註其未經權威來源驗證;
  3. 列 WBS 後續:若客戶要求自動取得,需三項變更(見 §3)。

2.3.2 政策查詢與檢核

檢核時點:LimitAdjustmentService.apply(),adjustType='SPECIAL' 時——
afterAmount ≤ 適用政策之 max_amount。

查詢優先序(決定性):(role, segment) → (role, '*') → 皆無 → 拒絕
(fail-closed,CC-LIM-1002「超過可設定上限」)。

⚠️ 不設 ('*','*') 萬用列:那會讓「忘了設定政策」與「刻意全域放行」無法區分。
需要全域上限者,明確列出各角色即可。

2.3.3 政策之維護(8-1 之「依人員權限控管」)

新增 2 個端點:GET /api/v1/special-limit-policies(查詢)與
PUT /api/v1/special-limit-policies/{roleCode}/{segmentCode}(upsert;冪等)。
★ 停用 = PUT 帶 status=INACTIVE(不做 DELETE——政策為稽核相關設定,宜留痕)。
⚠️ 端點本身之權限:目前僅受 limit-service 之一般認證保護(任何已驗證呼叫者皆可設定)——
正式應限定特定角色;平台無 RBAC 資料源可資判斷,故如實揭露並列後續(見 §3)。


2.4 8-6/8-7:臨時額度串動

裁定:以事件驅動,於 limit-service 新增兩個消費端。

8-6(綜所稅請款 → 調高卡人臨時額度)

  • 訂閱:cc.posting.transaction.posted(SAD-002 §3.2 早已載明 limit 為消費者,惟從未實作)。
  • 前置(契約缺口):現行 payload 不含 authorizationId → 無法識別「哪一筆用了專用額度」。
    → 補為可選欄位(SAD-004 §5.3:新增可選欄位不升版)。來源已存在
    (CC_POSTING.TRANSACTIONS.AUTHORIZATION_ID),不需臆造。
  • 處理:依 authorizationId 查本服務之 LIMIT_HOLDS → 若 hold_type='SPECIAL':
    1. 將該筆佔用釋放(請款比對,BR-03-02)→ 專用池 used_amount -= 金額;
    2. 調高臨時額度:temporary_amount += 金額,temp_start = 入帳日,
      temp_end = 繳款截止日 + 7 日。
    3. 寫入串動紀錄(見下)以供 8-7 對應。
  • 「繳款截止日」之來源:★ 本節原設計有誤,已於實作時更正——
    原載「以 CC_STMT.STATEMENTS.payment_due_date 之最近一期為準」,
    惟 limit-service 之 schema 為 CC_LIM,讀 CC_STMT 屬跨 schema
    (SAD-003/SQL-07 禁止)→ 不可行。
    → 改以「入帳日 + 帳單週期(預設 25)+ 7 日」推算,
    並於紀錄標記 due_date_source = DERIVED——不臆造為「帳單之繳款截止日」。
    可配置:cardcore.limit.special-temp-raise.statement-cycle-days/.grace-days。
    精確值需 statement 整合(或於 posted.v1 補 paymentDueDate)→ 列後續。

8-7(繳款 → 調降卡人臨時額度)

  • 訂閱:cc.payment.payment.posted(本服務已訂閱,見 SCS-002 §7.7)→ 併入既有消費者。
  • 處理:依「該歸戶尚未回收之 8-6 串動紀錄」,依先進先出回收
    min(繳款金額, 未回收總額) → temporary_amount -= 回收額(下限 0),
    並將對應紀錄標記 recovered_at。
  • ★ 不得直接將 temporary_amount 歸零——該欄可能同時承載人工設定之臨時額度(§7.2 之申請)。
    故必須以串動紀錄精確扣回,否則會誤砍客戶經核准之臨時額度。

新增表 CC_LIM.SPECIAL_LIMIT_TEMP_ADJUSTMENTS(8-6/8-7 之串動紀錄)

欄位 說明
id PK
account_id/currency 歸戶 × 幣別
special_limit_id 來源專用額度
authorization_id/posting_id 來源(可追溯)
raised_amount 8-6 調高之金額
recovered_amount 8-7 已回收金額(累計)
raised_on/raise_until 調高日/迄日(=繳款截止日 + 7)
due_date_source STATEMENT/DERIVED
status OPEN/CLOSED
created_at/updated_at/version SQL-05 通用欄位

3. 分階段執行(每階段皆為「完整可驗證之交付單元」)

階段 範圍 內容 驗收
Phase 1 scope_ref + 8-3/8-5 ✅ 已完成(2026-10-02,#A-154):V7__special_limit_pool.sql(special_limits.scope_ref+limit_adjustments.scope_ref+limit_holds.special_limit_id);SpecialLimitRepository#findApplicable/increaseUsed/decreaseUsed;LimitApplicationService 之 SPECIAL 池路徑與釋放回沖;新端點 special-limits/applicable;authorization-service 之 SpecialLimitPort+三段式池選擇;契約;單測;真機 11 項 ✅ 專用額度交易不佔卡人額度(account_limits 全程未變、availableLimit 不因專用佔用而減);池不足 → 回退卡人額度;兩者皆不足 → DECLINED 51
Phase 2 8-2 V8(SPECIAL_LIMIT_POLICIES);CallerIdentityPort(★ 角色自權杖);apply() 政策檢核;政策 GET/PUT 端點;契約;單測;真機 不同角色之可設定上限確實生效;查無政策 → 拒絕;★ 請求帶入之角色不得提權
Phase 3 8-6/8-7 ✅ 已完成(2026-10-02,#A-156):posting.transaction.posted.v1 補可選 authorizationId(不升版);V9(串動紀錄表);PostingTransactionPostedListener(8-6)+ 繳款消費端併入 8-7(窄埠);raiseTemporary/lowerTemporary(★ 相對增量);契約;單測;真機 6 項(真實 Kafka + Oracle) ✅ 請款 → 臨時額度 0 → 15000、迄日 2026-11-03(入帳日+32);繳款 10,000 → 5000、再繳 5,000 → 0 且紀錄 CLOSED;★ 專用池 used_amount 對稱回沖 15000 → 0

共用:一階段一遷移(V7/V8/V9)——
★ 不預先建立「有表無程式」的空表(避免重蹈 CR-014 §0「表已存在、程式全無」之覆轍)。
各階段之實作與驗證獨立,可分別交付。


4. 契約變更(破壞性與非破壞性)

契約 變更 性質
limit-service.yaml HoldRequest 新增可選 specialLimitId 非破壞(新增可選)
limit-service.yaml 新增 GET /accounts/{accountId}/special-limits/applicable + SpecialLimitApplicabilityView 非破壞(新增端點)
limit-service.yaml SpecialLimitView 新增 scopeRef 非破壞(新增可選)
limit-service.yaml LimitAdjustmentRequest 新增可選 applicantRole/applicantSegment 非破壞(新增可選)
posting-service.yaml 事件 payload posted.v1 補可選 authorizationId 非破壞(SAD-004 §5.3)

依 CR-030 P4:全部為非破壞性(新增可選欄位/新增端點),不升版。


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

  1. role_code/segment_code 未經身分系統驗證 → 部分解除(2026-10-02):
    ★ role_code 已改由「已驗證之權杖」決定(JwtRoleConverter 之 realm roles/服務權杖之 azp),
    不接受請求帶入 → 折衷移除。⚠️ segment_code 仍由請求帶入(附證據,見 §2.3.1 ②)。
  2. 客層未自動取得(limit-service 不得呼叫 card-service/party-service;CC_LIM 無產品欄位)
    ——須由呼叫端帶入。後續:需三項變更(SAD-002 §5 開放 cc-limit-service → card/party;
    party 提供歸戶→卡片;card/product 提供卡等)。
  3. ★ 政策維護端點之權限未收斂(§2.3.3):目前任何已驗證呼叫者皆可設定政策上限——
    正式應限定特定角色,惟平台無 RBAC 資料源可資判斷 → 如實揭露,列後續。
  4. 專用額度之「已用」語意為「佔用中」,非「已入帳未清償」:
    因專用池之請款比對訊號即為「釋放」(Phase 3 實作後,請款會釋放佔用並改以臨時額度表達)。
    Phase 1~2 期間,請款不會釋放專用佔用(尚無消費者)→ 專用池之佔用只增不減,
    須以人工或後續 Phase 3 收斂。
  5. scope_ref 為 MCC 白名單,不支援萬用字元/範圍(如 93**)。
  6. Stand-in 期間專用額度不可用(本地讀模型無此資料)→ 一律退回卡人額度。

6. 修訂記錄

版本 日期 修訂內容
1.1 2026-10-02 ✅ Phase 1/2/3 全數完成。Phase 1(#A-154):scope_ref + 8-3/8-5(三段式池選擇;★ BR-04-01 結構性成立)。Phase 2(#A-155):8-2(政策表 + ★ 角色自權杖——折衷移除;客層保留但附四項證據)。Phase 3(#A-156):8-6/8-7(串動紀錄 + posted.v1 補可選 authorizationId + FIFO 回收)。★ 更正 §2.4:「繳款截止日」原設計讀 CC_STMT 為跨 schema、不可行 → 改為推算並標記 DERIVED。★ 併載真機揭露之兩項陷阱:all_tab_columns 跨 schema 掃描靜默回 0 列;Keycloak 權杖之雜訊角色(default-roles-*/offline_access)會使角色解析荒謬化
1.0 2026-10-02 初版:動工前真機查證(8 項)、四項設計與裁定(scope_ref 值域與選擇順序、8-3/8-5 之三段式池選擇與「結構性不佔卡人額度」、8-2 之政策表與 fail-closed、8-6/8-7 之事件驅動與串動紀錄表)、契約變更(全為非破壞)、三階段執行計畫、五項已知限制
1.0 2026-10-02 初版:動工前真機查證(8 項)、四項設計與裁定(scope_ref 值域與選擇順序、8-3/8-5 之三段式池選擇與「結構性不佔卡人額度」、8-2 之政策表與 fail-closed、8-6/8-7 之事件驅動與串動紀錄表)、契約變更(全為非破壞)、三階段執行計畫、五項已知限制
    (1-1/1)