Project

General

Profile

CR #201 » SCS-025_徵審介面契約規格.md

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

 

SCS-025 徵審(信用審查)介面契約規格

項目 內容
文件編號 SCS-025
版本 1.1
狀態 我方建議契約(proposed)——待客戶確認;adapter 已依本契約實作完成並測試通過(v1.1)
撰寫者 WorkBuddy AI
審查者 BEAM PMO
服務 card-service(介接方)|外部徵審系統(被介接方)
對應需求 BRD-001 D-01(徵審系統,僅列名無規格)、BRD-001 BF-01(受理→徵信→核決)
對應設計 SDD-005 §12(申請案件狀態機)、SDD-006(介面契約)、SAD-002(bounded context)、SAD-005(個資保護)
關聯規格 SCS-024(進件處理,本期呼叫方)、SCS-022 §7.4(fail-closed 設計思路)
建立日期 2026-09-20

⚠️ 本契約之性質,務必先講清楚:
原 RFP(BEAM_核心現代化_信用卡核心_RFP_FR_20210312、…RFP_NFR_…)未含徵審介面規格;
BRD-001 D-01 僅將徵審列為外部系統,未給介面規格。
故本文件為我方自行設計之建議契約——目的是讓 POC 能完整演示、並於客戶質疑
「徵審在哪」時可立即提出具體介面與程式,不是客戶既有規格。
客戶若提供其自身規格,僅需替換 ExternalCreditReviewAdapter(§7),
領域層與狀態機不受影響(SAD-002 bounded context)。


1. 目的與範圍

1.1 為何需要本文件(POC 風險)

# 風險 說明
1 追溯矩陣缺口 RFP 無徵審介面需求 → GOV-004 追溯矩陣無處可掛,客戶稽核時易被指「漏做」
2 POC 中途質疑 客戶於 POC 演示進件流程時,極可能質疑「徵審在哪?」——若答「外部系統、未定義」,POC 完整性受挑戰
3 整合責任模糊 未定義介面 → 後續爭議「是誰沒提供」將耗费往來成本

對策:我方自行設計標準徵審介面契約並實作 adapter。POC 時可具體展示:

「徵審介面在這裡(SCS-025)、程式已寫好(ExternalCreditReviewAdapter)、
只差把貴行徵審系統的 endpoint 填進組態。
在填上前,系統會轉人工核決,不會自動核准。」

1.2 範圍內/外

範圍內

  • 徵審介面之建議契約(端點、請求/回應 schema、狀態碼)
  • 安全與可靠度要求(逾時、斷路器、fail-closed)
  • 與 SDD-005 §12 申請案件狀態機之對應
  • ExternalCreditReviewAdapter 之設計(含未設定時之降級)
  • POC 演示方案

範圍外

  • 徵審系統本身之建置(客戶既有系統)
  • 徵審決策邏輯(評分模型、規則)— 屬徵審系統內部
  • 聯徵(JCIC)等外部查詢之實際介接 — 由徵審系統負責

2. 名詞

名詞 說明
徵審 信用審查:徵信+核決。本文件專指「我方呼叫外部徵審系統」之介面
徵信 向徵審系統查詢信用與風險,取得決策
決策 APPROVE/DECLINE/REFER/SUPPLEMENT 四值(見 §4.3)
REFER 轉人工核決;亦為徵信不可用時之 fail-closed 結果
fail-closed 外部呼叫失敗時,採「不安全」之一側(轉人工),而非靜默放行

3. 通訊模式

項目 設計 理由
模式 同步請求/回應(REST over HTTPS) 進件流程需即時取得決策才能推進狀態機(SDD-005 §12 轉換 #2→#5)
協定 HTTPS/JSON 與 SDD-006 全系統一致
呼叫時機 SUBMIT_REVIEW 事件(SCS-024 §5.2) 受理後由系統送徵信;非建立時立即呼叫(見 SCS-024 §8.3)
冪等 請求帶 applicationId 供徵審系統去重 避免重複查詢產生重複成本(見 §6.2)

為何不用非同步(webhook/訊息):徵審決策為進件流程之必要前置,
非同步會使案件長時間懸於 REVIEWING 且需額外補償機制。同步較單純,
且逾時有明確 fail-closed 路徑(§6)。若客戶既有系統僅支援非同步,
則以 REFER 為常態、由人工核決推進——仍不需改狀態機。


4. 介面契約

4.1 端點

POST {credit-review.base-url}/api/v1/credit-reviews
項目 值
方法/路徑 POST /api/v1/credit-reviews
Content-Type application/json; charset=utf-8
認證 mTLS 或 Authorization: Bearer {token}(見 §5)
X-Idempotency-Key 必帶;值為 applicationId 之字串(供徵審系統去重)

⚠️ 路徑與埠號由組態 cardcore.credit-review.base-url 提供(見 §7.2),
不硬編。這是「換客戶系統只改組態」之前提。

4.2 請求(我方 → 徵審系統)

{
  "applicationId": "018f2a3b-0000-0000-0000-000000000001",
  "applyNo":       "APP2026092000012345",
  "accountId":     "018f2a3b-0000-0000-0000-000000000011",
  "partyId":       "018f2a3b-0000-0000-0000-0000000000b1",
  "productCode":   "PLATINUM",
  "channel":       "WEB",
  "requestedAt":   "2026-09-20T10:30:00+08:00"
}
欄位 型別 必填 說明
applicationId UUID ✅ 申請案件識別碼;供徵審系統去重
applyNo string ✅ 申請編號(人讀)
accountId UUID ✅ 帳戶識別碼
partyId UUID ➖ 當事人識別碼(選填):本期 CardApplication 未持有,故不傳;待 party-service 串接後補(SCS-025 §10 #6)
productCode string ✅ 商品代碼(徵審規則可能依商品而異)
channel string ✅ 通路:WEB/MOBILE/API/BRANCH/MERCHANT
requestedAt ISO-8601 ✅ 請求時間(含時區)

🔒 個資最小化(SAD-005):本請求不含身分證字號、姓名、生日等明文個資——
僅傳識別碼,由徵審系統自行依 accountId(未來為 partyId)查其既有資料。
如此我方不必在跨系統傳輸中攜帶個資明文,亦降低外洩面。
若客戶徵審系統要求個資明文,須另經 SAD-005 個資影響評估後方可調整。

4.3 回應(徵審系統 → 我方)

{
  "decision":    "APPROVE",
  "referenceNo": "CR-20260920-000123",
  "score":       720,
  "reasons":     ["INCOME_STABLE"],
  "decidedAt":   "2026-09-20T10:30:05+08:00"
}
欄位 型別 必填 說明
decision enum ✅ 決策四值(見下表),唯一影響狀態機之欄位
referenceNo string ✅ 徵審系統之案件編號(供稽核追溯)
score integer ➖ 信用評分(僅供參考/留痕,不影響狀態機)
reasons string[] ➖ 決策理由代碼(留痕用)
decidedAt ISO-8601 ✅ 決策時間

decision 值域與狀態機對應(SDD-005 §12)

decision 意義 案件狀態轉換 觸發者
APPROVE 自動核准 REVIEWING → APPROVED(產生核准序號) 系統
DECLINE 自動婉拒 REVIEWING → REJECTED 系統
REFER 轉人工核決 維持 REVIEWING 徵審人員
SUPPLEMENT 要求補件 REVIEWING → SUPPLEMENT 系統

⚠️ decision 出現四值以外之內容(含 null、未知字串):
一律視同 REFER(fail-closed),不得視為 APPROVE。
理由:無法解讀之決策若放行,即為靜默放行——與認證 fail-open 同類
(見 SCS-022 §7.4)。

4.4 HTTP 狀態碼處理

狀態碼 我方處置 是否重試
200 + 合法 decision 依 §4.3 轉態 —
200 + 無法解析/未知 decision 視同 REFER,記 WARN ❌
400/422 我方請求錯誤 → 記 ERROR 告警 + 轉 REFER(刻意不拋出,見下) ❌
401/403 認證失敗 → 記 ERROR、轉 REFER(憑證問題,非案件問題) ❌
429 被限流 → 轉 REFER,記 WARN ⚠️ 見 §6.2
5xx 徵審系統故障 → 轉 REFER,記 ERROR ⚠️ 見 §6.2
連線逾時/拒絕/DNS 失敗 轉 REFER,記 ERROR ⚠️ 見 §6.2

⚠️ 400/422 為何仍轉 REFER 而不拋出(v1.1 實作決定):
那是「我方程式有 bug」(如欄位缺漏),但刻意不拋出——
若拋出,單一整合 bug 會讓全部進件失敗,屬「一顆螺絲癱整條產線」。
改以 ERROR 層級日誌+告警暴露問題(大聲,但不阻斷),
案件仍轉人工核決。此與 fail-closed 一貫:既不自動核准,也不靜默放行。

⚠️ 初版(v1.0)原寫「拋 CC-CARD-4001」,與 safeReview 之
「捕獲所有例外轉 REFER」(SCS-024 §5.2 核心 fail-closed)相衝突,
故 v1.1 改為上述做法,並同步修正 §9 #9。


5. 安全

層面 要求 理由
傳輸 HTTPS(TLS 1.2+),禁止明文 HTTP 銀行基本 requirement(SAD-005)
客戶端認證 mTLS 優先;次選 Bearer token(由 Vault/K8s Secret 注入) 徵審屬高敏感;mTLS 可防憑證外洩後被重放
憑證存放 存放於 repo 之外(如 ~/.config/cardcore/),權限 600;不進版控 與 Stripe 憑證同一慣例(見 SCS-023 §7.3)
個資 請求不含個資明文(見 §4.2);日誌只記 applicationId,不記評分/理由 個資最小化
稽核 每次呼叫留存(applicationId/referenceNo/decision/耗時) 金融稽核可追溯

6. 可靠度與 fail-closed

6.1 逾時

項目 建議值 說明
connect timeout 2s 連線建立
read timeout 5s 等待決策
總上限 7s 逾時即 fail-closed 轉 REFER

⚠️ 已於 application.yml 專屬覆寫(v1.1):專案全域預設為
resilience4j.timelimiter 800ms(CSP-003 §5)+ retry 3 次——
兩者對徵審皆不適用,故為 creditReviewClient 設 instances 覆寫:
timeout-duration: 7s、retry.max-attempts: 1(不重試,見 §6.2)。
若未覆寫,800ms 會讓大量正常徵信被誤判逾時而轉人工。

進件非授權流程(授權有 2 秒硬限制),故可容許較長等待;
但仍須有上限——無上限的等待會把 HTTP 連線池耗盡,
是「慢性癱瘓」型缺陷,比快速失敗更難察覺。

6.2 重試策略(⚠️ 與一般 HTTP 呼叫不同)

項目 設計 理由
預設不自動重試 僅 429 且帶 Retry-After 時,最多重試 1 次 徵信查詢常涉及聯徵(JCIC)等外部計費查詢,且會留下查詢紀錄
連線逾時/5xx 不重試,直接轉 REFER 同上;且重試會拉長案件處理時間

⚠️ 這是本契約最容易被忽略的一點:
一般微服務呼叫習慣「失敗就重試」,但徵信查詢有成本與副作用
(重複查詢可能產生重複費用、並在聯徵留下多次查詢紀錄,
反而影響客戶信用評分)。故採「不自動重試、轉人工」——
人工可判斷是否值得再查一次。此設計亦與 fail-closed 一貫。

6.3 斷路器(Resilience4j)

專案已有 spring-cloud-starter-circuitbreaker-resilience4j 相依(SAD-001 AP-05):

項目 建議值
滑動視窗 最近 20 次呼叫
開斷路閾值 失敗率 ≥ 50%
斷路後等待 30s(half-open 試探)
斷路期間行為 直接轉 REFER,不真的發出呼叫

斷路器目的:徵審系統整體故障時,避免每次進件都卡 7 秒逾時、
拖垮 card-service 的連線池。斷路期間案件仍可受理,只是轉人工。

6.4 fail-closed 總結

任何「無法取得可信決策」之情況,一律轉 REFER、維持 REVIEWING:

  • 連線失敗/逾時/DNS 錯誤
  • 5xx/429(超過重試上限)
  • 回應無法解析、未知 decision
  • 未設定 base-url(尚未接上徵審系統)
  • 斷路器開啟

不自動核准、亦不自動婉拒——一律交給人判斷。
與 SCS-024 §5.2、SCS-022 §7.4 同一思路:
「不知道」不等於「可以」。


7. 程式實作(adapter 設計)

7.1 結構(SAD-002 bounded context)

domain/port/CreditReviewPort            ← 領域埠(已存在,SCS-024)
domain/model/CreditReviewRequest        ← 領域請求(已存在)
domain/model/CreditReviewResult         ← 領域決策(已存在)
infrastructure/adapter/
  ├─ NoopCreditReviewAdapter            ← 本地用,一律 REFER(已存在,SCS-024)
  └─ ExternalCreditReviewAdapter        ← 本規格新增:呼叫真實徵審系統
infrastructure/client/CreditReviewClient← Feign 客戶端(本服務自有 DTO)

關鍵:外部系統之 DTO 不共用領域類別,僅於 adapter 內轉換
(照 CardServiceClient 既有慣例,見 SAD-002)。
故客戶若換徵審系統/改欄位,僅需改 adapter 與 client,領域層不動。

7.2 組態

cardcore:
  credit-review:
    # 空白=未接上徵審系統 → adapter 一律回 REFER(fail-closed)
    base-url: ${CREDIT_REVIEW_BASE_URL:}
    connect-timeout-ms: 2000
    read-timeout-ms: 5000
    # true 時用 NoopCreditReviewAdapter(本地/POC 演示用)
    stub-enabled: ${CREDIT_REVIEW_STUB:false}

⚠️ base-url 預設為空白(而非指向某個假 URL):
這是刻意的——空白時 ExternalCreditReviewAdapter 不發出呼叫,
直接回 REFER 並記 WARN,使「尚未接上」在日誌上一目了然,
且不會因預設值指向不存在的服務而產生無意義的連線錯誤噪音。

7.3 adapter 行為決策表

stub-enabled base-url 使用之 adapter 行為
true 任意 NoopCreditReviewAdapter 一律 REFER(本地/POC)
false 空白 ExternalCreditReviewAdapter 不呼叫,回 REFER + WARN
false 已設定 ExternalCreditReviewAdapter 呼叫真實徵審系統(§4)

⚠️ 未設定 stub-enabled 時(matchIfMissing=true)行為同 true——走 Noop。
即「接真實徵審必須顯式宣告 stub-enabled=false」——
與認證「停用只能顯式宣告」同一思路(SCS-022 §7.4)。

三者皆為 fail-closed(無一會自動核准)。差別只在「有沒有真的發出呼叫」。


8. POC 演示方案

客戶質疑「徵審在哪」時,POC 可依序展示:

步驟 演示內容 證明什麼
1 出示 SCS-025 本文件 介面已定義(端點/schema/決策/安全/逾時)
2 展示 ExternalCreditReviewAdapter 程式碼 已依契約實作,非紙上談兵
3 於組態填入一個模擬徵審服務之 URL 實際跑一次:APPROVE→核准、DECLINE→婉拒、REFER→轉人工
4 拔掉 base-url,重送一次 系統轉人工、不自動核准(fail-closed 成立)
5 說明替換方式 客戶提供其規格時,僅換 adapter,領域層與狀態機不動

第 4 步是最有說服力的一步:證明「徵審沒接上時,系統不會偷偷核准」——
這正是銀行最在意的風險。


9. 測試要求

# 案例 預期
1 徵審回 APPROVE → APPROVED,產生核准序號
2 徵審回 DECLINE → REJECTED
3 徵審回 REFER 維持 REVIEWING
4 徵審回 SUPPLEMENT → SUPPLEMENT
5 徵審連線逾時 轉 REFER,不核准(fail-closed)
6 徵審回 5xx 轉 REFER,不自動重試
7 回應 decision 為未知值 視同 REFER
8 base-url 空白 不發出呼叫,回 REFER + WARN
9 徵審回 400 記 ERROR 告警 + 轉 REFER(不拋出,避免阻斷全部進件;見 §4.4)
10 斷路器開啟 直接 REFER,不發出呼叫

⚠️ #5 與 #8 為本契約之核心測試。
禁用任何「回 APPROVE」之假 adapter 來通過測試——
那會讓「徵審不可用不核准」永遠是綠的,屬假驗證
(與 SCS-024 §11 #5 同一要求)。

9.1 實測證據(2026-09-20,ExternalCreditReviewAdapterTest)

以可控假 CreditReviewClient(記錄呼叫次數、可指定回應或拋出例外)執行:

# 項目 實測結果
1 測試數 14 支全過
2 #8 base-url 空白 ✅ 回 REFER 且 callCount=0——證明真的沒發出呼叫
3 連線失敗/5xx/400 ✅ 全部 REFER(fail-closed)
4 decision 四值映射 ✅ APPROVE/DECLINE/REFER/SUPPLEMENT 正確
5 decision 未知值/null/回應 null ✅ 全部視同 REFER
6 "APPROVED"(非契約四值) ✅ 視同 REFER——不得被誤判為核准
7 大小寫(approve) ✅ 容許(trim + toUpperCase)

⚠️ 實測中發現並排除之一處假綠風險:adapter 的兩個 catch 分支
(FeignException 依 status 分流、其餘例外)結果都是 REFER,
故即便測試用之 stub 例外 status() 失效、走錯分支,測試仍會全綠。
已加斷言 assertEquals(400, feign(400).status()) 確保 status 有效,
進而確保「status 分流」確實被測到。

另:初版測試以 mock(FeignException) 為之,觸發 scan_faketest 之
T3 誤判(有 thenReturn 卻無 verify)。已改以真實子類別 StubFeignException
為之——既消除誤判,亦不為過關而加無意義的 verify。


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

  1. 本契約為我方建議版,尚未經客戶確認。客戶若提供其自身規格,
    §4 之欄位與端點可能全數改動;adapter 須重寫(領域層不受影響)。
  2. 未含真實徵審系統之實作。本期 adapter 為「可接」狀態,
    實際連線對象待客戶提供 endpoint 與憑證。
  3. 聯徵(JCIC)等外部查詢由徵審系統負責,本契約不涵蓋其介面與費用歸屬。
  4. 非同步模式未實作。若客戶徵審系統僅支援非同步(webhook),
    須另立補償機制;短期替代為「常態 REFER+人工核決」(不需改狀態機)。
  5. 逾時/斷路器數值為建議值,須於 SIT 壓力測試後校準。
  6. partyId 本期不傳:CardApplication 未持有該欄位,契約中列為選填;
    待 party-service 串接後方可補入請求。

11. 修訂記錄

版本 日期 修訂者 說明
1.1 2026-09-20 WorkBuddy AI 依本契約實作 ExternalCreditReviewAdapter 完成並測試通過:
新增 CreditReviewClient(OpenFeign,照 CardServiceClient 既有慣例:自有 DTO、不共用領域類)
與 ExternalCreditReviewAdapter(@ConditionalOnProperty 與 Noop 互斥,避免兩 bean 注入歧義);
領域 CreditReviewRequest 補 applyNo/channel 兩欄(契約 §4.2 需要而原領域類別無);
application.yml 為 creditReviewClient 專屬覆寫 resilience4j(7s、retry 1 次)
——因專案全域 800ms/3 次對徵審不適用(見 §6.1)。
測試 14 支全過(§9.1),含「base-url 空白時 callCount=0」之證明。
⚠️ 三處依實作回改契約(文件不得與實物不一致):
①400/422 由「拋 CC-CARD-4001」改為「記 ERROR + 轉 REFER(不拋出)」——
原設計與 safeReview 之 fail-closed 相衝突,且拋出會讓單一 bug 阻斷全部進件;
②partyId 改選填(本期 CardApplication 未持有,故不傳);
③補「未設定 stub-enabled 時同 true(接真實徵審須顯式宣告)」。
另排除一處假綠風險(兩 catch 分支皆 REFER,故加 status 斷言確保分流被測到)
1.0 2026-09-20 WorkBuddy AI 初版。依 BEAM PMO 2026-09-20 指示「RFP 無徵審規格,POC 會被質疑,須自行設計提供」建立:§1 POC 風險與對策、§3 同步模式、§4 介面契約(請求/回應/decision 四值/狀態碼)、§5 安全與個資最小化、§6 可靠度(不自動重試——徵信查詢有成本與副作用)、§7 adapter 設計與組態(base-url 空白即 fail-closed)、§8 POC 演示方案、§9 測試(#5/#8 為核心,禁假驗證)、§10 限制
(62-62/67)