# SCS-007　入帳分戶服務（posting-service）程式設計規格

| 項目 | 內容 |
|---|---|
| 文件編號 | SCS-007 |
| 版本 | **1.8**（`CR-046` AC 11 資料鏈補強：既有授權 `mcc` 經 `PostingRequest` → `TRANSACTIONS.MCC`（V9，可空）→ `posted.v1` 可選欄位；`mcc` 之權威在授權端，posting 不對映、不推導）。前版 **1.7**（`CR-046` AC 11 前置增量：既有 `PostingRequest.merchantId` 接入 `Transaction`／`CC_POSTING.TRANSACTIONS`／`posted.v1`，作為可選活動認列輸入；不新增 `mcc`／支付路徑猜測）。前版 **1.6**（`CR-046` B4 前置增量：`posting.transaction.posted.v1` 帶出既有 `instrumentId` 可選欄位，供後續活動認列；舊事件與非載體交易可為 null）。前版 **1.4**（`CR-027` 批次 A-4 事件輸出——`posting.transaction.posted.v1`／`.reversed.v1`） |
| 狀態 | **已核定（規格先行）→ 已實作並實測通過**（2026-09-19） |
| 對應需求 | `ISS-2.3`（信用卡請款處理）、`ISS-2.12`（信用卡發卡帳務）、`ACQ-4.6`（收單帳務）、`DEB-3.2`（DEBIT 卡請款處理）、`DEB-3.5`（DEBIT 卡發卡帳務） |
| 服務埠號 | **8087**（`application.yml` 實測） |
| 套件根 | `com.beam.cardcore.posting` |
| 權威 schema | `CC_POSTING`（CQRS 寫側） |
| 關聯文件 | `SDD-001 §5.4/§5.5`、`SDD-002 §3.4/§3.5`、`SDD-003`、`BRD-002`、`GOV-005` |

---

## 1. 對應需求

| FR 編號 | 需求名稱 | 來源 |
|---|---|---|
| `ISS-2.3` | 信用卡請款處理 | `BRD-002` |
| `ISS-2.12` | 信用卡發卡帳務（餘額／交易查詢） | `BRD-002` |
| `ACQ-4.6` | 收單帳務 | `BRD-002` |
| `DEB-3.2` | DEBIT 卡請款處理 | `BRD-002` |
| `DEB-3.5` | DEBIT 卡發卡帳務 | `BRD-002` |

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

| 圖種 | 文件 | 數量（實測） | 本服務相關 |
|---|---|---|---|
| **ER-Diagram** | `SDD-001_領域模型與ER-Diagram` | 16 個 `erDiagram` | `TRANSACTIONS`/`BALANCES`/`SUB_ACCOUNT_ENTRIES` 實體（`SDD-001 §5.4/§5.5`） |
| **DFD** | `SDD-002_DFD與流程分解` | 10 個 flowchart | 請款／帳務處理流（`SDD-002 §3.4/§3.5`） |
| **Sequence** | `SDD-003_Sequence-Diagrams` | 15 個 `sequenceDiagram` | 入帳時序（授權→請款→入帳） |
| **狀態機** | `SDD-005_狀態機設計` | 10 個 `stateDiagram` | ⚠️ `TRANSACTIONS.status`（PENDING→POSTED→REVERSED/FAILED）以 CHECK 控管，未定義正式狀態機（見 §3、§10） |
| **API Contract** | `SDD-006` + `contracts/openapi/posting-service.yaml` | 17 份 | 本服務 6 支端點 |

---

## 2. 服務職責與邊界

**承作**：
- 交易入帳（`TRANSACTIONS`：採購／預借／費用／利息／繳款／退款／調整／沖銷／分期／紅利兌／拒付）
- 分戶餘額（`BALANCES`：本金／利息／費用／稅／總額／最低應繳／繳款截止／帳齡）
- 子帳分錄（`SUB_ACCOUNT_ENTRIES`：雙式分錄，前後餘額）
- 餘額歷史（`BALANCE_HISTORY`：逐日快照）
- 冪等與重複檢核（`IDEMPOTENCY_KEY`、`posting-duplicate-checks`）

**不做（屬他服務）**：
- 授權決策 → `authorization-service`
- 清算結算 → `clearing-service`
- 帳單產生 → `statement-service`

---

## 3. 領域模型

- 聚合：`Transaction`（交易入帳）、`Balance`（分戶餘額）、`BalanceHistory`（餘額歷史）
- 值物件：`TxType`（`PURCHASE`/`CASH_ADVANCE`/`FEE`/`INTEREST`/`PAYMENT`/`REFUND`/`ADJUSTMENT`/`REVERSAL`/`INSTALLMENT`/`REWARD_REDEEM`/`CHARGEBACK`）、`SourceType`、`PostingStatus`、`Money`、`Currency`
- 狀態：`TRANSACTIONS.status ∈ {PENDING, POSTED, REVERSED, FAILED}`（CHECK，預設 POSTED）；沖銷經 `REVERSAL_OF` 自參照；`SUB_ACCOUNT ∈ {PRINCIPAL, INTEREST, FEE, TAX}`

---

## 4. 程式結構

| 套件／類別 | 職責 | 對應需求 |
|---|---|---|
| `domain/model/Transaction` `Balance` `BalanceHistory` `SubAccountEntry` | 聚合與值物件、雙式分錄不變式 | `ISS-2.3`/`ISS-2.12` |
| `domain/port/TransactionRepository` `BalanceRepository` | 持久化埠 | — |
| `application/PostingService` | 交易入帳＋分戶更新（同一交易邊界） | `ISS-2.3` |
| `application/DuplicateCheckService` | 冪等／重複檢核 | `ISS-2.3` |
| `infrastructure/jdbc/*RepositoryImpl` | JDBC 實作（含 `SUB_ACCOUNT_ENTRIES` FK） | — |
| `interfaces/rest/PostingController` `BalanceQueryController` | REST 端點 | `ISS-2.12` |

---

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

| 表 | 關鍵欄位 | 約束（實測） |
|---|---|---|
| `TRANSACTIONS` | `ID` RAW(16) PK；`TX_NO` VARCHAR2(24) **UNIQUE**；`ACCOUNT_ID`；`CARD_ID`；`AUTHORIZATION_ID`；`TX_CODE`；`TX_TYPE`；`AMOUNT` NUMBER(19)；`CURRENCY` CHAR(3)；`IDEMPOTENCY_KEY` VARCHAR2(120) **UNIQUE**；`SOURCE_TYPE`；`STATUS` DEFAULT `'POSTED'`；`POSTING_DATE`/`VALUE_DATE` DATE；`POSTED_AT`；`REVERSAL_OF` RAW(16) | `tx_type IN (11 值)`；`source_type IN ('CLEARING','ONLINE','BATCH','MANUAL','SYSTEM')`；`status IN ('PENDING','POSTED','REVERSED','FAILED')`；`UQ_POSTING_IDEM` UNIQUE(`IDEMPOTENCY_KEY`)；**分區**：`PARTITION BY RANGE(POSTING_DATE) INTERVAL(NUMTOYMINTERVAL(1,'MONTH'))` 含初始 `P0` 分區 |
| `BALANCES` | `ACCOUNT_ID` **UNIQUE**；`PRINCIPAL_AMOUNT`/`INTEREST_AMOUNT`/`FEE_AMOUNT`/`TAX_AMOUNT`/`TOTAL_AMOUNT`/`MINIMUM_DUE` NUMBER(19) DEFAULT 0；`PAYMENT_DUE_DATE`；`AGING_BUCKET` NUMBER(5)；`CURRENCY` CHAR(3) DEFAULT `'TWD'` | PK `ID`；`UQ`(`ACCOUNT_ID`) |
| `BALANCE_HISTORY` | `ACCOUNT_ID`；`AS_OF_DATE`；各金額欄 | `UQ_BALANCE_HISTORY` UNIQUE(`ACCOUNT_ID`,`AS_OF_DATE`) |
| `SUB_ACCOUNT_ENTRIES` | `TRANSACTION_ID`（FK→`TRANSACTIONS.ID`）；`SUB_ACCOUNT`；`AMOUNT`；`BEFORE_AMOUNT`；`AFTER_AMOUNT`；`CURRENCY` | `sub_account IN ('PRINCIPAL','INTEREST','FEE','TAX')`；FK(`TRANSACTION_ID`) REFERENCES `TRANSACTIONS`(`ID`) |

> 含 `CREATED_AT`/`UPDATED_AT` TIMESTAMP(6) WITH TIME ZONE、`VERSION` 樂觀鎖。`TRANSACTIONS` 分區設計符合 `SDD-001 §7.1` 實測結論（INTERVAL 分區須附初始分區，且分區鍵用 `DATE` 非 `TIMESTAMP WITH TIME ZONE`）。

---

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

| 端點 | 方法 | 說明 | 對應 FR |
|---|---|---|---|
| `/api/v1/postings` | POST | 交易入帳（**發射 `posting.transaction.posted.v1`**，見 §7.2） | `ISS-2.3`/`ACQ-4.6` |
| `/api/v1/postings/{postingId}` | GET | 入帳明細 | `ISS-2.12` |
| `/api/v1/postings/{postingId}/reversals` | POST | 沖銷（**發射 `posting.transaction.reversed.v1`**，見 §7.2） | `ISS-2.3` |
| `/api/v1/accounts/{accountId}/balances` | GET | 分戶餘額查詢 | `ISS-2.12` |
| `/api/v1/accounts/{accountId}/transactions` | GET | 交易明細查詢（**可帶 `from`／`to` 期間**，見 §7.1） | `ISS-2.12` |
| `/api/v1/posting-duplicate-checks` | POST | 冪等重複檢核 | `ISS-2.3` |
| `/api/v1/posting-interest/recalculate` | POST | **循環利息／違約金計收**（`CR-006`） | `ISS-2.5` |

> 錯誤碼：`409` 重複入帳（冪等鍵衝突）、`400` 分錄不平衡（借方≠貸方）、`404` 找不到。

---

## 7. 關鍵邏輯

- **雙式分錄**：每筆 `Transaction` 必須產生 `SUB_ACCOUNT_ENTRIES`，`BEFORE_AMOUNT`/`AFTER_AMOUNT` 自證前後餘額；`BALANCES` 總額須等於各子帳加總（不變式）。
- **冪等**：`IDEMPOTENCY_KEY` 唯一，重送同一鍵回傳原結果，避免重複入帳。
- **沖銷**：新交易 `REVERSAL_OF` 指向原交易，原交易標 `REVERSED`，不物理刪除（稽核留痕）。
- **分區**：`TRANSACTIONS` 依 `POSTING_DATE` 月距分區，查詢須帶日期範圍以利分區剪枝。

### 7.1　交易查詢之期間過濾（`CR-007`，2026-09-20 核定採方案 A）

> **背景**：`statement-service` 結帳時原以「取該帳戶全部交易」彙總，
> 致**每期帳單重複計收**歷史交易與歷史循環息（P0，見 `CR-007 §2` 實測）。
> 本節為該缺陷之根治——**期間條件下推至 `posting` 端 SQL**。

**查詢參數（`GET /api/v1/accounts/{accountId}/transactions`）**

| 參數 | 型別 | 必填 | 語意 |
|---|---|---|---|
| `from` | `string(date)` | 選填 | `POSTING_DATE > from`——**不含** |
| `to` | `string(date)` | 選填 | `POSTING_DATE <= to`——**含** |

**區間語意為半開 `(from, to]`**，理由：

| 邊界 | 為何如此 |
|---|---|
| `to` **含**本期結帳日 | 結帳當日計收之**循環息**其 `postingDate` 即為結帳日（`§8.1` 之 `periodEnd`），若不含將**漏收當期利息** |
| `from` **不含**上期結帳日 | 上期結帳日（含）之前之交易已於上期帳單計收，若含入將**重複計收** |

> ⚠️ 二者**不可對調**：`[from, to)` 會同時造成「漏收當期利息」與「重複計收上期利息」。

**未帶 `from`／`to`**：回傳該帳戶全部交易（**沿用既有行為**，供客服／對帳等查詢使用）。
但**帳單結帳路徑一律必須帶**（`SCS-011 §7`）——`statement` 端之 `PostingQueryPort`
以「期間為必要參數」之簽章強制，使遺漏期間於**編譯期**即失敗，不再依賴人為記得。

**分頁（呼叫端責任）**：回傳筆數達 `size` 代表**可能仍有未取回之交易**。
呼叫端必須**逐頁取至「不足一頁」為止**——僅取第一頁等同**靜默截斷**，
將造成帳單短收，屬帳務錯誤。

> **頁數上限採 Fail-Closed**：為避免資料持續寫入導致迴圈不止，
> 頁數上限由呼叫端組態（預設 **20** 頁 × `size=200` ＝ 4,000 筆／期）；
> 超限即丟錯（`CC-STM-5001`）並記入批次，交由人工介入，
> **不靜默回傳不完整之結果**。

### 7.2　事件輸出（Transactional Outbox；`CR-027` 批次 A-4）

**掛點一**：`POST /api/v1/postings`（交易入帳）→ **同交易**寫入 `outbox`
**掛點二**：`POST /api/v1/postings/{postingId}/reversals`（錯帳沖正）→ **同交易**寫入 `outbox`

| 項目 | `posting.transaction.posted.v1` | `posting.transaction.reversed.v1` |
|---|---|---|
| Kafka 主題 | `cc.posting.transaction.posted` | `cc.posting.transaction.reversed` |
| 分區鍵 | `aggregateId` ＝ **`postingId`** | 同左 |
| `aggregate_type` | `posting` | `posting` |
| `event_type`（outbox 欄） | `posting.transaction.posted`（**不含**版本） | `posting.transaction.reversed` |
| `event_version` | `1` | `1` |
| payload | `postingId`／`txNo`／`accountId`／★ **可選** `instrumentId`／★ **可選** `merchantId`／★ **可選** `mcc`／`txType`／`amount`／`currency`／`postingDate`／`subAccountEntries[]` ＋ ★ **可選** `authorizationId`（`CR-035 §2.4`；**不升版**） | `postingId`／`reversalOf`／`amount`／`reason` |

> ★ **`instrumentId`（可選；`CR-046` B4 前置增量）**：直接取既有 `Transaction.instrumentId`／`CC_POSTING.TRANSACTIONS.INSTRUMENT_ID`，作為活動認列與後續同構回饋之最小識別；非載體交易可為 null，舊事件可缺欄，消費端不得臆造。
>
> ★ **`merchantId`（可選；`CR-046` AC 11 資料鏈補強）**：來源為既有 `PostingRequest.merchantId`（其業務來源可追溯至授權之 merchantId）；本次新增至 posting 交易欄位與事件，未新增 `mcc` 或支付路徑猜測。舊交易與未提供者為 null。
>
> ★ **`mcc`（可選；`CR-046` AC 11 資料鏈補強、`CR-030 §4.2`／`CR-029 C-3` 缺口收斂）**：來源為既有 `PostingRequest` 新增之 `mcc` 欄位——呼叫端（請款／入帳發起方）自**授權紀錄**（`CC_AUTH.AUTHORIZATIONS.mcc`，權威）如實帶入；`posting` 不做 MCC 對映、不從 merchantId 推導、不驗 4 碼格式（與授權側 `AuthorizeUseCase` 之 4 碼檢核分工不同——該檢核屬授權風控，非入帳職責）。非授權來源交易（利息／費用／沖正）與舊事件為 `null`；消費端（rewards 累點／campaign 活動認列）**必須容忍缺欄**，不得臆造。
>
> ★ **`authorizationId`（可選；`CR-035 §2.4`／`#A-156`）**：POC-8 之 **8-6** 需要它——
> `limit-service` 據以判定「該筆請款是否用了**專用額度**」（查 `limit_holds.hold_type='SPECIAL'`）。
> 來源 `CC_POSTING.TRANSACTIONS.AUTHORIZATION_ID` **早已存在**（**不需臆造**）；
> 依 `SAD-004 §5.3`「新增**可選**欄位**不升版**」→ 仍為 `v1`。
> ⚠️ 非授權來源之交易（如費用、利息）為 `null`——消費端**必須容忍缺欄／null**（舊事件）。

**`subAccountEntries[]` 逐元素欄位**（源自 `SubAccountEntry`，雙式分錄自證前後餘額）：

| 欄位 | 來源 | 說明 |
|---|---|---|
| `subAccount` | `entry.subAccount().code()` | `PRINCIPAL`／`INTEREST`／`FEE`／`TAX` |
| `amount` | `entry.amount()` | **含正負號**之變動量（最小單位） |
| `beforeAmount` | `entry.beforeAmount()` | 分錄前餘額 |
| `afterAmount` | `entry.afterAmount()` | 分錄後餘額 |
| `currency` | `entry.currency()` | — |

**寫入時機與冪等**

| 情境 | 行為 |
|---|---|
| 入帳成功（`effect.transaction()` 已存） | 寫 1 列 `posted.v1` |
| 重複入帳（業務冪等鍵已存在 → `CC-POST-4001`） | **拋錯於寫入之前** → **不發射** |
| 沖正成功（反向交易已存、原交易已標 `REVERSED`） | 寫 1 列 `reversed.v1` |
| 沖正冪等重播（同鍵已有沖正交易 → 回前次結果） | **早退於寫入之前** → **不發射** |

> ⚠️ **`amount` 一律最小單位（分）**（`SDD-006 §2.5`）——**禁浮點**。
> ⚠️ **`posted.v1` 之 `amount` 為交易金額（正數）**；方向由 `txType` 與 `subAccountEntries[].amount` 之**正負號**表達，
> 二者不可混用。
> ⚠️ **`reversed.v1` 之 `postingId` 為「沖正交易」之識別碼**，`reversalOf` 才是「被沖正之原交易」——
> 消費端**不得**以 `postingId` 當作被沖正者。

**消費者**：`statement`／`rewards`／`limit`／`pricing`（`SAD-004 §5.2`）——⚠️ **尚未實作**（批次 C）。

> ⚠️ **`posting.exception.raised.v1` 本期不發射**：`SDD-006 §4.2` 已定義 payload，惟本服務
> **無「入帳例外」之領域概念**（`SCS-007 §10` 已載「授權比對採非同步」）→ **不臆造語意**；
> 待有明確例外來源後再議。

---

## 8. 外部依賴與降級

| 依賴 | 失敗處理（Stand-in） |
|---|---|
| 授權參考 `authorization-service`（`AUTHORIZATION_ID`，非同步事件，非即時強依賴） | 入帳為本地交易；若下游讀模型（`proj`/`rpt`）延遲，查詢側可能短暫不一致，由 CQRS 最終一致補齊 |
| 請款來源 `clearing-service`（分批） | 批次失敗可重跑（冪等），不影響已入帳交易 |
| **定價 `pricing-service`（8086，同步強依賴）** | **不可用即失敗**（見 §8.1）——拋 `CC-PLT-5005`，**不入帳、不回傳假資料** |

### 8.1　循環利息／違約金計收（`CR-006`，2026-09-20 核定）

> **為何放在 `posting-service`**（BEAM PMO 2026-09-20 定奪 #3 之 B）：
> 利息本質是**一種入帳**，責任歸入帳域；`statement-service` 維持只讀 `posting`
> （不含利息之「計算」），故不新增 `statement → pricing` 之依賴。

**端點**：`POST /api/v1/posting-interest/recalculate`

| 項目 | 內容 |
|---|---|
| 輸入 | `accountId`（必填）、`periodStart`／`periodEnd`（必填）、`currency`（選填，預設 `TWD`）、`calcMethod`（選填）、`dryRun`（選填，預設 `false`） |
| 輸出 | `interestAmount`、`lateFeeAmount`、`lateFeeCount`、`appliedRate`、`calcBasis`、`calcMethod`、`currency`、`postingIds[]`、`dryRun` |
| 觸發者 | 結帳／批次流程，**必須早於結帳**（否則帳單彙總不到本期利息） |

**處理流程**

1. 由 `BalanceRepository` 讀取帳戶餘額，作為 `principalAmount`（本期不帶 `dailyBalances`，見限制）。
2. 呼叫 `pricing-service` `POST /api/v1/pricing/interest-calculations`
   （逾時與重試由 `ServiceClientHttpClient` 依 `cardcore.service-client.*` 組態處理，
   `CSP-003 §5`；**不硬編**——原硬編 800ms 於批次冷路徑不足，曾致帳戶被跳過，見 `#C-40`）。
3. **取整責任**：`pricing` 端已四捨五入（`HALF_UP`）**取整至最小單位（分）**
   （`CR-006 #5`、`SCS-006 §7`、`CSP-007 §5.1`）；`posting` **不得再次取整**，原值入帳。
   註：金額一律為**分之整數**，非元。
   ✅ **已驗證（`C-32`，2026-09-22）**：真機實測入帳交易金額與 `pricing` 試算**逐分相同**
   （`pricing` 1066 ＝ 交易 `TXN20270115000015` 1066 ＝ 帳單 `finance_charges` 1066）。
4. **幣別**：入帳幣別取自**請求** `currency`（未提供時預設 `TWD`），
   亦即在 `statement` 情境下即帳戶餘額幣別。
   ⚠️ **不隨請求傳給 `pricing`**——`pricing` 之計息無幣別維度
   （`PRICING_RULES`／`InterestCalcView` 皆無幣別欄位，`SCS-006 §10.2`）。
   故「多幣別計價」尚不支援（費率規則擴充後方可）；惟幣別標記本身已一致，實測見 `C-32`。
4. 入帳（`PostingService.post`，雙式分錄）：
   - `interestAmount > 0` → `TxType.INTEREST`（`SubAccount.INTEREST`）
   - `lateFeeAmount > 0` → `TxType.FEE`（`SubAccount.FEE`）——**不新增 `LATE_FEE` 型別**（`CR-006 #4`）
   - 金額為 0 者**不產生交易**
5. **冪等鍵**：`INT:{accountId}:{periodStart}:{periodEnd}`——
   同一期重送不會重複計息（沿用 `IDEMPOTENCY_KEY` 唯一約束）。
6. `dryRun=true` 時**只回傳不入帳**（供結帳前試算）。

**失敗處理**

| 情境 | 處理 |
|---|---|
| `pricing` 逾時／不可用／回應無法解析 | 拋 `CC-PLT-5005`（503），**不入帳**；呼叫方可安全重送（冪等） |
| 找不到帳戶餘額 | `CC-PLT-4040`（404） |
| 入帳分錄不平衡 | 由 `PostingService` 既有不變式攔下（`400`） |

> ⚠️ **不提供「定價不可用時以預設利率計算」之降級**——
> 利息金額錯誤將直接造成帳務與法規風險，寧可失敗也不可用可疑數字入帳。

---

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

- `UT-POST-001`：請款處理（`ISS-2.3`）——正常入帳、雙式分錄平衡、沖銷
- `UT-POST-020`：發卡帳務查詢（`ISS-2.12`）——餘額／交易查詢、分區剪枝
- `UT-POST-021`：循環利息計收（`CR-006 §8.1`）——呼叫 `pricing` 後入 `INTEREST`／`FEE`；
  金額 0 不入帳；`dryRun=true` 不入帳；冪等鍵重送不重複計息
  （`C-32` 增補 2 例：**持久化金額＝定價端原值**（逐分相同，防二次取整）、
  **請求幣別傳入定價查詢**）
- `UT-POST-022`：`pricing` 呼叫失敗 → `CC-PLT-5005` 且**不入帳**（無假資料兜底）
- 冪等重複檢核、跨幣別（`Currency`）四捨五入

---

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

- `TRANSACTIONS.status` 轉換未建模為 `SDD-005` 正式狀態機（以 CHECK 約束控管）。
- CQRS 讀模型（`proj` Postgres）與寫側（`CC_POSTING`）為最終一致，跨庫查詢一致性須在整合測試驗證。

**實作後補記（2026-09-19，誠實揭露）**：

1. **授權請款比對（`CC-POST-1001`／`CC-POST-1002`）尚未實作**。
   契約描述提到入帳時比對授權，惟本規格 §8 明定授權參考為「非同步事件、非即時強依賴」，
   兩者存在設計衝突。本期依 §8 採**不即時呼叫**授權服務，`authorizationId` 僅記錄不比對。
   待事件驅動（授權事件落地讀模型）就緒後，再補比對邏輯與 `CC-POST-1001/1002` 之丟出。
2. **重複請款偵測之回應語意由本規格界定**（契約未明定）：
   疑似重複 → `409 CC-POST-4002`（轉人工判定）；未發現重複 → `200` 且 `data` 為 `null`
   （契約 `Envelope.data` 允許 null）。
3. **HTTP `X-Idempotency-Key` 未另行持久化**。
   防重機制以**業務冪等鍵**（`businessIdempotencyKey`，對應 `UQ_POSTING_IDEM`）為準，
   此亦為契約明定之防重依據；HTTP 層冪等鍵重播之獨立儲存待需求確認後補
   （可沿用 party-service 之 `PTY_IDEMPOTENCY` 模式建立 `POST_IDEMPOTENCY`）。
4. ~~`recalculateInterest`（沖正重算利息）**未實作**——參數已收，邏輯待利息引擎就緒後補。~~
   → **已於 `CR-006`（2026-09-20 核定）定義並實作**，見 **§8.1**（端點
   `POST /api/v1/posting-interest/recalculate`）。
   註：本項原為 `HANDOVER §3.C #C-3` 描述失真之來源——該列稱其為「佔位待串接」，
   實則程式碼中**並無此方法**（僅本規格提及）。已同步更正 `HANDOVER`。

**`CR-006` 實作後新增之限制（2026-09-20）**：

5. **本期不啟用 ADB 計息**——`posting` 無日終餘額表（DDL 僅
   `balance_history`／`balances`／`sub_account_entries`／`transactions`），
   故不傳 `dailyBalances`，`pricing` 退回 `CONSTANT_PRINCIPAL`
   （以 `principalAmount` 為常數餘額，`calcBasis` 會標示之）。
   **ADB／`TWO_CYCLE` 須待新增日終餘額表後再啟。**
6. **不傳 `cardNo`**——`Card` 領域模型僅有 `cardNoMasked`、資料表只存雜湊，
   明文卡號在本系統內**不存在且不得存在**（PCI）。
   `CR-006 #2` 已將 `pricing` 之 `cardNo` 放寬為**選填**，計息以 `accountId` 為識別主鍵。
7. ✅ **取整責任已定於 `pricing`**（`HALF_UP` 取整至最小單位**分**），`posting` 不再取整；
   一致性**已驗證（`C-32`，2026-09-22）**：真機實測 `pricing` 試算 **1066**
   ＝ 入帳交易 `TXN20270115000015` **1066** ＝ 帳單 `finance_charges` **1066**（逐分相同）。
8. **入帳幣別不隨請求傳給 `pricing`**——`pricing` 之計息**無幣別維度**
   （`PRICING_RULES`／`InterestCalcView` 皆無幣別欄位，`SCS-006 §10.2`），
   故 `InterestCalculationQuery.currency` 僅決定**本服務之入帳幣別**。
   多幣別計價（如 JPY 0 位小數）須待費率規則擴充幣別欄位；惟**幣別標記本身已一致**
   （`statement` 以帳戶餘額幣別觸發計息，`C-32` 已以 `USD` 實證）。

**`CR-027` 批次 A-4 執行後新增之限制（2026-10-01）**：

9. **事件消費者未實作**：`SAD-004 §5.2` 載 `posting.transaction.posted.v1` 之消費者為
   `statement`／`rewards`／`limit`／`pricing`，惟四者**尚未訂閱** → 現階段為「**有人發、沒人收**」
   （屬批次 C 範圍）。
10. **`posting.exception.raised.v1` 未發射**：`SDD-006 §4.2` 已定義 payload，惟本服務
    **無「入帳例外」之領域概念**（授權比對採非同步）→ **不臆造語意**（見 §7.2 末）。
11. **`sequenceNo` 為「同 JVM 內遞增之時間戳基底序號」**：**多實例**部署可能重複；
    正解為「聚合層級持久化序號」（`SAD-004 §5.4`），本期未做（與 `CR-027 §5 #12` 同型）。
12. **`reversed.v1` 之 `reason` 直接取自請求**：未經列舉或長度正規化（契約為自由文字
    `max 200`）；`recalculateInterest` **不進入事件**（該參數之語意為「是否重算利息」，
    屬本服務之副作用，非事件內容）。
13. **活動認列所需的 `mcc`／`merchantId`**：`merchantId` 已接入（`V8`）；`mcc` 已依本節 §7.2 接入（呼叫端自授權紀錄帶入，`V9`）。**仍缺**：支付路徑（`payPath`）無核定輸出契約、活動歸戶（`householdId`）無解析流程——二者屬活動認列契約（`SCS-026`／BR-13／BR-05）範圍，本服務不臆造。

---

## 11. 修訂記錄

| 版本 | 日期 | 修訂者 | 說明 |
|---|---|---|---|
| 1.0 | — | 開發團隊 | 初版（規格先行） |
| 1.1 | 2026-09-20 | WorkBuddy AI | 依 **`CR-006`** 新增 **§8.1「循環利息／違約金計收」** 與 §10 限制 5～7（不啟用 ADB／不傳 `cardNo`／取整責任）。⚠️ **本列為回填**——原文件僅有孤懸之 1.2 列，無表頭亦無 1.0／1.1 |
| 1.2 | 2026-09-20 | WorkBuddy AI | 依 **`CR-007`**（BEAM PMO 核定採**方案 A**）新增 **§7.1「交易查詢之期間過濾」**：交易查詢新增 `from`（**不含**）／`to`（**含**）參數，區間語意為半開 `(from, to]`；載明 `to` 含結帳日之理由（當期循環息 `postingDate` 即結帳日）、`from` 不含上期結帳日之理由（避免重複計收），以及分頁 **Fail-Closed**（達上限即丟錯，不靜默截斷）。§6 同步標註 |
| 1.3 | 2026-09-22 | WorkBuddy AI | **`C-32`**：①§8.1 逾時敘述更正（不硬編 800ms，改由 `ServiceClientHttpClient` 統一處理）；補**取整一致性已驗證**與**幣別不傳 `pricing`** 之揭露；②§9 `UT-POST-021` 增補 2 例（持久化金額＝定價原值、請求幣別傳入查詢）；③§10 #7 由「尚未驗證」改為**已驗證**、新增 #8（幣別維度限制）；④**補建本修訂記錄表頭與 1.0／1.1 列**（原文件結構缺漏） |
| 1.8 | 2026-10-16 | WorkBuddy AI | **`CR-046` AC 11 資料鏈補強**：`PostingRequest` 新增 `mcc`，接入 `TRANSACTIONS.MCC`（`V9`，可空）與 `posted.v1` 可選欄位；`mcc` 權威在授權端，posting 不對映／不推導／不重驗格式。收斂 `CR-030 §4.2`／`CR-029 C-3` 之 payload 缺口（`mcc`／`merchantId` 均已備妥）。 |
| 1.7 | 2026-10-16 | WorkBuddy AI | **`CR-046` AC 11 前置增量**：既有 `PostingRequest.merchantId` 接入 `Transaction`／`CC_POSTING.TRANSACTIONS`／`posted.v1` 可選欄位；不新增 `mcc`／支付路徑猜測。 |
| 1.6 | 2026-10-16 | WorkBuddy AI | **`CR-046` AC 11 契約盤點**：明列 `AuthorizeRequest`／`CC_AUTH.AUTHORIZATIONS` 已有 `mcc`／`merchantId`，但 `Transaction`／`CC_POSTING.TRANSACTIONS` 未持久化 `mcc`、`PostingRequest.merchantId` 未接入交易聚合；因此不臆造 `posted.v1` 活動欄位。 |
| 1.5 | 2026-10-16 | WorkBuddy AI | **`CR-046` B4 前置增量**：`posting.transaction.posted.v1` 帶出既有 `Transaction.instrumentId`／`CC_POSTING.TRANSACTIONS.INSTRUMENT_ID` 為可選欄位，供後續活動認列；非載體交易與舊事件可為 null，未新增猜測對映。 |
| 1.4 | 2026-10-01 | WorkBuddy AI | **`CR-027` 批次 A-4（`#A-134`）**：①**§7.2 新增事件輸出規格**——`posting.transaction.posted.v1`（入帳）與 `posting.transaction.reversed.v1`（沖正）：主題／分區鍵／payload 逐欄／`subAccountEntries[]` 逐元素欄位／四個冪等情境之發射與否；②**§10 新增 4 項限制**（消費者未實作、`exception.raised` 未發射、`sequenceNo` 多實例風險、`reason` 未正規化）；③**§6 同步標註** |
