CR #201 » SCS-025_徵審介面契約規格.md
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-002bounded 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. 已知限制(誠實揭露)
-
本契約為我方建議版,尚未經客戶確認。客戶若提供其自身規格,
§4之欄位與端點可能全數改動;adapter 須重寫(領域層不受影響)。 -
未含真實徵審系統之實作。本期 adapter 為「可接」狀態,
實際連線對象待客戶提供 endpoint 與憑證。 - 聯徵(JCIC)等外部查詢由徵審系統負責,本契約不涵蓋其介面與費用歸屬。
-
非同步模式未實作。若客戶徵審系統僅支援非同步(webhook),
須另立補償機制;短期替代為「常態REFER+人工核決」(不需改狀態機)。 - 逾時/斷路器數值為建議值,須於 SIT 壓力測試後校準。
-
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 限制 |