# SCS-006　費率利息服務（pricing-service）程式設計規格

| 項目 | 內容 |
|---|---|
| 文件編號 | SCS-006 |
| 版本 | **1.3（2026-09-22：`C-32` 一致性驗證完成——四捨五入鏈結案、幣別限制明確化）** |
| 狀態 | **已核定（規格先行）；§6／§7 已依 `CR-005` 修訂** |
| 對應需求 | `ISS-2.5`（信用卡定價、費用管理）、`DEB-3.3`（DEBIT 卡定價、費用管理） |
| 服務埠號 | **8086**（已啟動實測） |
| 套件根 | `com.beam.cardcore.prc` |
| 權威 schema | `CC_PRC` |
| 關聯文件 | `SDD-001 §5.6`、`SDD-002 §3.5`、`BRD-002`、`GOV-005`、**`CR-005`（契約缺口之核定與定奪）** |

---

## 1. 對應需求

| FR 編號 | 需求名稱 | 來源 |
|---|---|---|
| `ISS-2.5` | 信用卡定價、費用管理 | `BRD-002` |
| `DEB-3.3` | DEBIT 卡定價、費用管理 | `BRD-002` |

### 1.1 上層設計依據（本規格**不重複**這些圖，僅引用）

| 圖種 | 文件 | 數量（實測） | 本服務相關 |
|---|---|---|---|
| **ER-Diagram** | `SDD-001_領域模型與ER-Diagram` | 16 個 `erDiagram` | 費率規則實體（`SDD-001 §5.6`） |
| **DFD** | `SDD-002_DFD與流程分解` | 10 個 flowchart | 定價／費用處理流（`SDD-002 §3.5`） |
| **Sequence** | `SDD-003_Sequence-Diagrams` | 15 個 `sequenceDiagram` | 利息試算、費用計算時序 |
| **狀態機** | `SDD-005_狀態機設計` | 10 個 `stateDiagram` | ⚠️ 費率規則啟用/停用以 CHECK 控管，未定義正式狀態機 |
| **API Contract** | `SDD-006` + `contracts/openapi/pricing-service.yaml` | 17 份 | 本服務 3 支端點 |

---

## 2. 服務職責與邊界

**承作**：
- 利率規則（`PRICING_RULES`：`INTEREST` 類：循環利息、預借現金利率）
- 各類費用（`ANNUAL_FEE`/`CASH_ADV_FEE`/`LATE_FEE`/`FOREIGN_TX_FEE`/`OVERLIMIT_FEE`/`FEE_WAIVER`）
- 利息試算與費用計算（對內提供試算 API）

**不做（屬他服務）**：
- 實際入帳 → `posting-service`
- 產品參數來源 → `product-service`
- 繳款 → `payment-service`

---

## 3. 領域模型

- 聚合：`PricingRule`（費率規則）、`InterestRate`（利率，屬 `PricingRule` 之子型）、`FeeSchedule`（費用排程）
- 狀態：`PRICING_RULES.status ∈ {ACTIVE, INACTIVE}`（CHECK，預設 ACTIVE）；`RULE_TYPE` 決定規則語意
- 交易型別：`PRICING_RULES.tx_type ∈ {GENERAL, CASH_ADVANCE}`（CHECK，**可為 null**——僅 `INTEREST` 規則具值；`V2__pricing_rules_tx_type.sql`）
- 計算方法：`CALC_METHOD ∈ {ADB, PREVIOUS_BALANCE, TWO_CYCLE, FLAT}`（預設 `ADB` 平均日餘額法）

---

## 4. 程式結構

| 套件／類別 | 職責 | 對應需求 |
|---|---|---|
| `domain/model/PricingRule` `InterestRate` `FeeSchedule` | 聚合與值物件 | `ISS-2.5` |
| `domain/port/PricingRuleRepository` | 持久化埠 | — |
| `application/InterestCalculationService` | 利息試算（ADB 等） | `ISS-2.5` |
| `application/FeeService` | 費用計算 | `ISS-2.5` |
| `infrastructure/jdbc/PricingRuleRepositoryImpl` | JDBC 實作 | — |
| `interfaces/rest/PricingController` | REST 端點 | `ISS-2.5` |

---

## 5. 資料模型（約束為 Oracle 實測值）

| 表 | 關鍵欄位 | 約束（實測） |
|---|---|---|
| `PRICING_RULES` | `ID` RAW(16) PK；`RULE_CODE` VARCHAR2(40) **UNIQUE**；`RULE_TYPE`；`PRODUCT_CODE`；`ORG`；`TIER`；**`TX_TYPE` VARCHAR2(16)（`V2` 新增，`IN ('GENERAL','CASH_ADVANCE')`，可為 null）**；`EFFECTIVE_FROM`/`TO` DATE；`TIERS` JSON；`RATE` NUMBER(9,6) DEFAULT 0；`ANNUAL_RATE` NUMBER(9,6)；`GRACE_PERIOD_DAYS` NUMBER(5)；`FEE_AMOUNT`/`MIN_FEE`/`CAP_AMOUNT` NUMBER(19)；`CALC_METHOD` VARCHAR2(16) DEFAULT `'ADB'`；`STATUS` | `rule_type IN ('INTEREST','ANNUAL_FEE','CASH_ADV_FEE','LATE_FEE','FOREIGN_TX_FEE','OVERLIMIT_FEE','FEE_WAIVER')`；`calc_method IN ('ADB','PREVIOUS_BALANCE','TWO_CYCLE','FLAT')`；`status IN ('ACTIVE','INACTIVE')`；`tx_type IN ('GENERAL','CASH_ADVANCE')` |

> 含 `CREATED_AT`/`UPDATED_AT` TIMESTAMP(6) WITH TIME ZONE、`VERSION` 樂觀鎖。

---

## 6. API 設計（來自 `contracts/openapi/pricing-service.yaml`）

| 端點 | 方法 | 說明 | 對應 FR |
|---|---|---|---|
| `/api/v1/pricing/interest-calculations` | POST | 利息試算（指定計算方法／區間） | `ISS-2.5` |
| `/api/v1/pricing/interest-inquiries` | GET | 利率查詢（**唯讀**試算） | `ISS-2.5` |
| `/api/v1/pricing/fees` | **GET** | **查詢費用設定**（原記 `POST` 費用計算，已依 `CR-005 #1` 更正為契約之 `GET`） | `ISS-2.5` |

> **⚠️ 本表 §6 原將 `fees` 記為 `POST` 費用計算，與契約衝突。**
> BEAM PMO 2026-09-19 核定「**以契約為主**」，且經查無其他衝突，
> 故本服務**不做費用計算**，僅回傳設定（金額取自 `FEE_AMOUNT`）。
> 詳細之參數與語意見 **`CR-005 §6`**。

> 錯誤碼：`400` 請求參數錯誤（`CC-PLT-4001`／`4002`／`4003`）、
> `404` 路徑不存在（`CC-PLT-4040`）、`422` 無適用規則（`CC-PRC-1001`）、
> `409` 規則狀態錯誤（`CC-PRC-3001`／`3002`）。

---

## 7. 關鍵邏輯

> **以下為 `CR-005`（2026-09-19 BEAM PMO 核定、我方定奪）之定案，契約原未定義。**

### 7.1 利率權威與日利率

- 以 **`ANNUAL_RATE`（年利率）** 為權威；**日利率 ＝ `ANNUAL_RATE ÷ 基礎天數`**。
- 基礎天數：`ACT_365`（預設）／`ACT_360`，由請求之 `dayCountBasis` 指定。
- 理由：`RATE`（日利率）與 `ANNUAL_RATE` 係兩個獨立欄位；若以 `RATE` 為準，
  `dayCountBasis` 參數將完全失去作用，且年利率方為對帳單／合約上可被人類核對之數值。
- 金額一律為**最小單位（分）之整數**，四捨五入（`HALF_UP`），禁止浮點（`CSP-007 §5.1`）。

### 7.2 計息天數

- 採**半開區間 `[periodStart, periodEnd)`**：
  有 `dailyBalances` 時天數＝序列筆數（每日一筆），否則＝ `DAYS.between(start, end)`。
- 此定義使契約範例成立：`2026-08-16 → 2026-09-15` ＝ **30 天**。

### 7.3 四種計息方法

| 方法 | 公式 | 備註 |
|---|---|---|
| `ADB` | Σ(日終餘額) × 日利率 | 預設 |
| `PREVIOUS_BALANCE` | 期初餘額 × 日利率 × 天數 | |
| `TWO_CYCLE` | (本期 ADB ＋ 上期 ADB) ÷ 2 × 日利率 × 天數 | **缺 `priorCycleAdb` → `CC-PLT-4003`** |
| `FLAT` | ADB × 年利率 ÷ 12 | 固定月息，不依天數攤提 |

> **不靜默替代**：`TWO_CYCLE` 缺上期 ADB 時**回錯**而非改用他法——
> 本服務不保存帳戶餘額序列，代以他值將產生**看起來合理但錯誤**之金額。

### 7.4 違約金

- 每期金額 ＝ `max(FEE_AMOUNT, MIN_FEE)`；期數 ＝ `min(overduePeriods, 3)`（**夾擠，非報錯**）。
- 總額 ＝ 每期金額 × 期數；`CAP_AMOUNT > 0` 時總額受其上限。
- 採「夾擠」之理由：逾期 5 期仍須能算出「至多收三期」之金額，回 400 反使呼叫方無從計算。

### 7.5 一般消費／預借現金之區分（`TX_TYPE`，2026-09-22 定案）

- 兩者循環利率不同（種子：18.5% vs 20%），由 **`PRICING_RULES.TX_TYPE`**（交易型別，
  `GENERAL`／`CASH_ADVANCE`）區分——欄位由 `V2__pricing_rules_tx_type.sql` 增設，
  **回填**：既有 `INTEREST` 規則之 `TX_TYPE` 取自 `TIER`。
- **`TX_TYPE` 可為 null**：僅 `INTEREST` 規則具值；費用類規則無交易型別語意，不硬塞值。
- **`TIER` 回歸本義（客層級距）**——原「以 `TIER` 暫代計息子型」之本地慣例**已解除**
  （`CR-005 §8 #1` 結案）。規則選取以 `TX_TYPE` 篩選，**不再讀取 `TIER` 之挪用值**。
- **查無對應規則時不靜默套用他種利率**，一律回 `CC-PRC-1001`
  （真機實測：清空 `TX_TYPE` 後預借現金試算即回 `CC-PRC-1001`，還原後恢復 20%——
  證明規則選取讀取權威來源且非快取）。

### 7.6 生效區間與規則選取

- `EFFECTIVE_FROM`/`TO` 管理費率版本；`AS_OF` 取 **`periodEnd`**（費用查詢則取系統日）。
- `EFFECTIVE_TO` 為 `null` 視為開放至今；**`INACTIVE` 一律不生效**。
- 同條件有多筆生效規則時，取 `RULE_CODE` 最小者（**決定性**，避免非決定性選取）。

### 7.7 費用

- 依 `RULE_TYPE` 套用 `FEE_AMOUNT`／`MIN_FEE`／`CAP_AMOUNT`；`FEE_WAIVER` 可豁免。
- ⚠️ **本期僅提供設定查詢，不做費用計算**（`CR-005 #1`）。

---

## 8. 外部依賴與降級

| 依賴 | 失敗處理（Stand-in） |
|---|---|
| 產品／組織維度可參考 `product-service`（非強依賴）；計算為純函式 | 無外部即時強依賴；計算服務失效時，授權／入帳側可回退至產品預設費率（須與 `authorization-service`/`posting-service` 協調，見各 SCS §8） |

---

## 9. 測試案例（對應 `TRP-001`）

- `UT-PRC-001`：定價／費用管理（`ISS-2.5`）——利息試算（ADB）、各類費用計算、生效區間
- 邊界：免息期（`GRACE_PERIOD_DAYS`）、最低／上限費用、幣別（參 `common-domain` `Currency`）

---

## 10. 已知限制（誠實揭露）

### 10.1 已解除（原 1.0 記載，現已完成）

- ~~`pricing-service` 骨架與埠號尚未產生~~ → **已產生，埠號 8086，已啟動實測**。

### 10.2 仍存在之限制

- **✅ 已驗證（C-32，2026-09-22）：與 `posting`／`statement` 之四捨五入一致性**——
  取整**僅發生於本服務**（`InterestCalculationService.round()`，`HALF_UP` 至分）；
  `posting` 以原值入帳（不二次取整），`statement` 以入帳交易加總（不重算）。
  真機實測（67845 分 × 18.5%）：`pricing` 試算 **1066** ＝ 入帳交易 `TXN20270115000015` **1066**
  ＝ 帳單 `STMT2027010011` `finance_charges` **1066**，且帳單明細直接引用該入帳交易編號。
- **⚠️ 幣別維度仍為限制**：本服務計息**無幣別概念**——`PRICING_RULES` 無幣別欄位、
  `InterestCalcView` 無幣別欄位、`FeeView.currency` 恆 `TWD`。`posting` 之入帳幣別
  取自帳戶餘額（非本服務），故「多幣別計價」（如 JPY 0 位小數）**尚未支援**；
  `common-domain` 之 `Money` 已具幣別小數位機制，惟本服務未使用。
  一致性已實測：帳戶幣別 `USD` → 入帳與帳單同步為 `USD`（`C-32`）。
- `PRICING_RULES` 啟用／停用未建模為 `SDD-005` 正式狀態機
  （故 `PricingRule` 亦刻意不提供狀態轉換方法）。
- **冪等鍵無從實作**：`POST interest-calculations` 為純函式、不寫入，
  契約要求之 `X-Idempotency-Key` 僅接收而無副作用可重放。
- **`CC-PRC-1001`（422「無適用利率」）被延伸用於「查無違約金規則」**——
  語意略有擴張；未新增錯誤碼以避免更動 `SDD-006 §3.4`。
- **未套用 `GRACE_PERIOD_DAYS`**：免息期判定須有繳款狀態，本服務無該資料。
- **`crossPeriodInterest` 恆為 0、`accumulatedInterest` ＝ 本期新增利息**——
  本服務不保存計息狀態。
- **`FeeView.currency` 恆為 `TWD`、`feeName` 由 `RULE_TYPE` 對照產生**——
  `PRICING_RULES` 無幣別／名稱欄位。
- **`statement` 逾期加成與 `posting` `recalculateInterest` 尚未接上本服務**——
  本服務已可提供試算，但兩服務之呼叫端**尚未串接**（後續工作）。

---

## 11. 修訂記錄

| 版本 | 日期 | 說明 |
|---|---|---|
| 1.0 | — | 初版（規格先行） |
| **1.1** | 2026-09-19 | 依 `CR-005` 核定結果修訂：**§6 `fees` 由 `POST` 更正為 `GET`**（以契約為準）；**§7 全面改寫**為定案之計算規則（利率權威、天數、四種計息方法、違約金、子型區分、規則選取）；**§10 重整**為「已解除」與「仍存在」兩類；新增 §11 修訂記錄 |
| **1.2** | 2026-09-22 | **`TX_TYPE` 欄位增設（C-31）**：`V2__pricing_rules_tx_type.sql` 增欄＋依 `TIER` 回填；§3／§5 補記欄位與值域；**§7.5 改寫**——規則選取改以 `TX_TYPE` 篩選，`TIER` 回歸本義；**§10.2 移除「`TIER` 挪用」限制項**（已解除）。單測 53 例 0 失敗（含 2 防回歸：TIER 挪用值不得被選用、同 TIER 不同 TX_TYPE 不得誤取）；真機 e2e 796／960 分列正確，清空 `TX_TYPE` → `CC-PRC-1001`、還原 → 20% 恢復 |
| **1.3** | 2026-09-22 | **`C-32` 一致性驗證完成**：§10.2 之「四捨五入／幣別一致性尚未驗證」結案——**四捨五入鏈**（本服務為唯一取整點 → `posting` 原值入帳 → `statement` 加總）真機實測三方逐分相同（1066＝1066＝1066，明細回引入帳交易編號）；**幣別**明確化為限制（本服務無幣別維度、`FeeView.currency` 恆 TWD），並實測帳戶幣別變更時入帳與帳單同步跟隨（`USD` 實證） |
