# SDD-006　API Contract

| 項目 | 內容 |
|---|---|
| 文件編號 | SDD-006 |
| 版本 | **1.24**（`CR-049`：① `posting.transaction.reversed.v1` 之 payload 由 4 欄擴為「4 契約欄 ＋ 可選 `postingDate`」——沖正交易之入帳日＝**業務日錨點**，下游回沖分錄不得以 `occurredAt` 反推（`SCS-026 §7.5①`）；② `campaign.reward.commanded.v1` 之 `recognition` 補列 `applicableRate`／`rateReason`，並載明**回沖指令同走此信封**（`originalPostingId`／`txType=REFUND`／`postingDate`／`refund:true`），消費端以 `txType=REFUND` 為權威且欄位「`recognition` 優先、`payload` 次之」。前版 **1.23**（`SCS-026 §7.14`／§7.15：`CreateCampaignRequest`／`CampaignView` 加 `priority`（D9 優先序）；`campaign.reward.commanded.v1` 之回沖指令（`refund`／`originalPostingId`）——campaign 內部契約）。前版 **1.22**（`SCS-026 §7.13` R-3b：`PointsAccountView` 加 `tierRank`（層級序；權威在 rewards））。前版 **1.21**（`CR-048`：支付路徑結構化欄位跨服務透傳——`AuthorizeRequest`／`PostingRequest` 加可選 `payPath`；`posting.transaction.posted.v1` payload 加可選 `payPath`（不升版）；`PointsAccountView` 加 `tier`（R-3））。前版 **1.20**（`SCS-026 §7.11`：`AccrualRequest` 契約加可選 `applicableRate`（活動回饋率；BRD 例 1 算式））。前版 **1.19**（`CR-047`：`rewards.points.accrued.v1` payload 加 4 可選欄位（`campaignCode`／`valueMinor`／`instrumentId`／`postingDate`；不升版）；`AccrualRequest` 契約加 3 可選欄位）。前版 **1.18**（`CR-046` AC 7：campaign 新增 `GET /api/v1/campaigns/{campaignCode}/enrollments/{householdId}`（查詢錄取狀態，含 `accruedAmountMinor` 門檻累計）；`EnrollmentView` 加 `accruedAmountMinor`）。前版 **1.17**（`CR-046` AC 7：campaign 新增 `POST /api/v1/campaigns/{campaignCode}/enrollments`）。前版 **1.16**（`CR-046` B2-6：`card.card.status-changed.v1` payload 新增可選 `instrumentId`，舊事件可為 null；不升事件版本）。前版 **1.11**（`CR-028` hold 事件群四項矛盾之化解（2026-10-01）：**§4.2 刪 `authorization.hold.created.v1`／`authorization.hold.released.v1` 兩列、補登 `limit.hold.created.v1`**；**併更正 `card.card.status-changed.v1` 之 payload**（原載**差量** `fromBlockCode`／`toBlockCode`／`reason`，實作為**變更後完整狀態**——依 `SCS-003 §7.10` 更正，列 `CR-027 §5 #13` 待覆核）；併標記 `authorization.authorization.approved.v1` 與 `limit.hold.created.v1` 之**可選欄位**。前版 **1.10**（`GOV-007 §4`：§3.4 補登 **`CC-PLT-3003`（409 資源尚未就緒）**——供報表下載之「作業未就緒（`QUEUED`／`RUNNING`／`FAILED`）」情境，**與「狀態機非法轉換」語意區隔** → 錯誤碼 **123 → 124**。前版 **1.9**（`CR-021`：§3.4 補登 `CC-AUTH-4040`（查無授權）→ 錯誤碼 **123**；並修訂 `check-results` 契約之**陣列性**（單一物件 → `AuthCheckResult[]`）。前版 **1.8**（`C-9`：補登 NOT 三碼 → 122） |
| 狀態 | 已核定 |
| 撰寫者 | SD |
| 審查者 | SA / TL / SEC |
| 對應需求 | SYS-4（API 完備度）、API 設計與管理、SYS-7 |

---

## 1. 說明

本文件定義全系統之 **API 契約規範**。契約本體（OpenAPI 3.1）以檔案交付：

```
cardcore/contracts/openapi/<service>.yaml      # 17 個服務，共 65 個端點
```

契約由 `cardcore/tools/gen_openapi.py` 產生並驗證；**禁止手改 YAML**，
變更須修改產生器後重新產生（確保端點清單、$ref、錯誤碼一致）。

### 1.1 驗證結果

| 服務 | 端點數 | 驗證 |
|---|---:|---|
| authorization-service | 5 | ✅ |
| limit-service | 6 | ✅ |
| posting-service | 6 | ✅ |
| payment-service | 4 | ✅ |
| card-service | 7 | ✅ |
| party-service | 4 | ✅ |
| product-service | 5 | ✅ |
| statement-service | 3 | ✅ |
| pricing-service | 3 | ✅ |
| installment-service | 3 | ✅ |
| rewards-service | 4 | ✅ |
| clearing-service | 3 | ✅ |
| dispute-service | 3 | ✅ |
| merchant-service | 3 | ✅ |
| notification-service | 2 | ✅ |
| report-service | 2 | ✅ |
| migration-service | 2 | ✅ |
| **合計** | **65** | **0 錯誤** |

驗證項目：OpenAPI 版本、info 必填、servers 存在、路徑符合 `/api/v1/`、
operationId 唯一、2xx 與 409 回應存在、寫入操作具 requestBody 與冪等標頭、
所有 `$ref` 可解析且無孤立元件。

---

## 2. 通用規範

### 2.1 請求標頭

| 標頭 | 必填 | 說明 |
|---|:--:|---|
| `Authorization` | ✅ | `Bearer <JWT>` 或 `ApiKey <key>` |
| `X-Channel` | ✅ | `WEB` / `MOBILE` / `PARTNER` / `BATCH` / `SYSTEM` |
| `X-Idempotency-Key` | ✅（寫入） | 冪等鍵，8–120 字元 |
| `X-Trace-Id` | ○ | 未帶則由閘道產生 |
| `Accept-Language` | ○ | `zh-TW`（預設）/ `en-US` |
| `Content-Type` | ✅ | `application/json` |

### 2.2 回應信封

所有回應一律採統一信封：

```json
{
  "traceId": "01HZX8K2M4N5P6Q7R8S9T0V1W2",
  "data": { },
  "error": null
}
```

失敗時：

```json
{
  "traceId": "01HZX8K2M4N5P6Q7R8S9T0V1W2",
  "data": null,
  "error": {
    "code": "CC-LIM-1001",
    "message": "可用額度不足",
    "details": [{ "field": "amount", "reason": "exceeds available limit" }]
  }
}
```

### 2.3 HTTP 狀態碼

| 狀態碼 | 語意 | 觸發情境 |
|---|---|---|
| 200 | 成功 | 查詢、更新成功 |
| 201 | 已建立 | 資源建立成功 |
| 202 | 已接受 | 非同步作業受理（批次、報表、清算） |
| 400 | 請求錯誤 | `ValidationError` |
| 401 | 未認證 | 權杖無效或過期 |
| 403 | 無權限 | 角色不足 |
| 404 | 不存在 | `ResourceNotFoundError` |
| 409 | 衝突 | 冪等鍵衝突、樂觀鎖衝突、狀態衝突 |
| 422 | 業務規則不允許 | `BusinessRuleError` |
| 429 | 超過速率 | 限流 |
| 500 | 系統錯誤 | `InfrastructureError` |
| 503 | 服務不可用 | 斷路器開啟、維護中、`ExternalSystemError` |

### 2.4 分頁

| 參數 | 型別 | 預設 | 上限 |
|---|---|---|---|
| `page` | integer | 1 | — |
| `size` | integer | 50 | 200 |

分頁回應於 `data.meta` 帶 `page`、`size`、`total`。

### 2.5 金額與日期

| 型別 | 規範 |
|---|---|
| 金額 | **整數，最小單位（分）**；`{"amount": 12345, "currency": "TWD"}` = 123.45 元 |
| 日期 | `date`：`YYYY-MM-DD` |
| 時間 | `date-time`：ISO 8601 含時區，一律 **UTC** |
| 比率 | `number`，小數表示（`0.185` = 18.5%） |

> **禁止**：金額使用浮點數（`BRD-001 C-01`）。

> ★ **通則（`CR-030 §2 #10` 裁定）：凡事件 payload 涉及金額者，一律帶 `currency`（可選）。**
> **理由**：金額**無幣別即無語意**——多幣別帳戶下不可解讀。
> **業界依據**：ISO 20022 之 `Amount` 一律為「**Currency + Amount**」複合型別；金融訊息中**裸金額**屬建模錯誤。
> **相容性**：新增**可選**欄位依 `SAD-004 §5.3` **不升版**；消費者**須容忍缺欄**（缺則**不臆造幣別**，
> 依各消費者之規格處理——例如 `SCS-016 §7.11`／`§7.12`：**不建立通知並記 WARN**）。

### 2.6 識別碼

| 型別 | 格式 |
|---|---|
| 資源主鍵 | UUID v7（字串） |
| 業務編號 | 前綴 + 序號（如 `AUTH20260917000001`） |
| 追蹤識別碼 | ULID（26 字元） |
| 冪等鍵 | 客戶端產生，8–120 字元 |

---

## 3. 錯誤碼規範

### 3.1 編碼規則

```
CC-<SVC>-<NNNN>
```

| 段 | 說明 |
|---|---|
| `CC` | 固定前綴（Credit Card） |
| `SVC` | 4 碼服務代碼 |
| `NNNN` | 4 碼；首位表類別，後三位為序號 |

### 3.2 服務代碼

| 代碼 | 服務 | 代碼 | 服務 |
|---|---|---|---|
| `AUTH` | authorization-service | `RWD` | rewards-service |
| `LIM` | limit-service | `CLR` | clearing-service |
| `CARD` | card-service | `DSP` | dispute-service |
| `PTY` | party-service | `MRC` | merchant-service |
| `PRD` | product-service | `NOT` | notification-service |
| `POST` | posting-service | `RPT` | report-service |
| `PAY` | payment-service | `MIG` | migration-service |
| `STM` | statement-service | `PLT` | platform/common |
| `PRC` | pricing-service | `GW` | platform/gateway |
| `INS` | installment-service | | |

### 3.3 類別位

| 首位 | 類別 | HTTP | 可否重試 |
|---:|---|---|:--:|
| 1 | 業務規則拒絕 | 422 | ❌ |
| 2 | 併發／冪等衝突 | 409 | 部分可 |
| 3 | 狀態機衝突 | 409 | ❌ |
| 4 | **請求與資源** | **400 / 404 / 405 / 409** | ❌ |
| 5 | 外部與基礎設施 | 503 / 500 | ✅（冪等者） |

#### 3.3.1 類別位 4 之語意擴充（`CR-004`，2026-09-19）

類別位 4 ＝「**請求本身或其所指資源無法被受理**」，涵蓋四個子語意——
**皆屬請求層，尚未進入業務邏輯**：

| 子語意 | HTTP | 何時使用 | 代表碼 |
|---|---:|---|---|
| 請求不合法 | 400 | 必填標頭／查詢參數缺失、主體格式錯誤、參數型別或 Bean Validation 失敗 | `CC-PLT-4001`／`CC-PLT-4002`／`CC-PLT-4003` |
| 資源或路徑不存在 | 404 | 無對應路由、聚合根查無 | `CC-PLT-4040`、`CC-PTY-4040`、`CC-POST-4040` |
| 方法不支援 | 405 | HTTP 動詞與端點不符 | `CC-PLT-4050` |
| 資源重複 | 409 | 業務冪等鍵已存在 | `CC-POST-4001`、`CC-POST-4002` |

> **擴充理由（實測）**：`CR-004` 於 8089 實測發現，Spring MVC 之**請求層例外**
> （缺標頭 `MissingRequestHeaderException`、壞 JSON `HttpMessageNotReadableException`、
> 方法不支援 `HttpRequestMethodNotSupportedException`、路徑不存在 `NoResourceFoundException`）
> **皆非 `DomainException` 子類**，因而全數落 `GlobalExceptionHandler` 之兜底回 **500**——
> 既違反契約（應為 400／404／405），且**每個客戶端錯誤都觸發 ERROR 告警**（告警疲勞）。
>
> 原類別位 4 僅涵蓋「資源與重複」（404／409），**無處安放 400 與 405**；
> 若強行借用類別位 1（業務規則拒絕 422）或 5（基礎設施 5xx）皆為語意錯置。
> 故擴充本類別位之語意，使其成為**請求層錯誤之歸屬**。
>
> **設計原則**：類別位 4 之碼**一律不得重試**（客戶端須修正請求後重送）；
> 與類別位 1（業務規則拒絕）之區別在於——**類別位 4 尚未進入業務邏輯**。

### 3.4 錯誤碼清單

| 錯誤碼 | HTTP | 訊息 | 說明 | 處理建議 |
|---|---:|---|---|---|
| `CC-AUTH-1001` | 422 | 授權拒絕 | 檢核未通過 | 依回應碼判斷 |
| `CC-AUTH-1002` | 422 | 卡片狀態不允許交易 | 卡片為 LOCKED／LOST／CLOSED | 提示客戶聯繫客服 |
| `CC-AUTH-1003` | 422 | 超過單筆限額 | 交易金額超過單筆限額 | 拆單或調額 |
| `CC-AUTH-1004` | 422 | 超過日限額或日限次 | 當日累計超限 | 隔日再試 |
| `CC-AUTH-1005` | 422 | MCC 控管拒絕 | 風險參數拒絕 | 聯繫客服 |
| `CC-AUTH-1006` | 422 | EMV 驗證失敗 | ARQC 驗證不通過 | 重新插卡 |
| `CC-AUTH-1901` | 422 | 授權併發衝突（已重試） | 重試 3 次仍衝突 | 稍後重試 |
| `CC-AUTH-4040` | 404 | 資源不存在（授權／檢核明細） | 查無指定授權編號（`CR-021` 補實作之沖正／檢核明細端點所需；沿用 per-service `4040` 慣例） | 確認授權編號 |
| `CC-AUTH-5001` | 503 | 發卡主機無回應 | 主機逾時 | 依 Stand-in 規則處理 |
| `CC-LIM-1001` | 422 | 可用額度不足 | `available < amount` | 調額或降低金額 |
| `CC-LIM-1002` | 422 | 超過可設定上限 | 超出角色／客層上限 | 送主管複核 |
| `CC-LIM-1003` | 422 | 專用額度不可用 | 未設定或已過期 | 先設定專用額度 |
| `CC-LIM-2001` | 409 | 額度併發衝突 | 樂觀鎖失敗 | 重試 |
| `CC-LIM-5001` | 503 | 商品參數服務無法連線 | 取參失敗（product-service 不可達／逾時／查無參數）；依 CR-045 D4 之 fail-closed 降級至預設覆核門檻 | 檢查參數服務可用性 |
| `CC-CARD-1001` | 422 | 快發條款未勾選 | `fastIssueAgreed=false` | 先勾選條款 |
| `CC-CARD-1002` | 422 | 三要素驗證失敗 | 身分證字號／生日／核准序號不符 | 確認後重試 |
| `CC-CARD-1003` | 422 | 卡號區段已用盡 | 號碼池不足 | 聯繫系統管理 |
| `CC-CARD-3001` | 409 | 狀態轉換不允許 | 非法狀態轉換 | 依狀態機重新操作 |
| `CC-CARD-3002` | 409 | 終態不可再轉換 | 已 CLOSED | 不可操作 |
| `CC-CARD-1004` | 422 | Token 重複綁定 | 同卡同平台已有 `ACTIVE` 之 Token | 先解綁或改用其他平台 |
| `CC-CARD-4001` | 400 | 請求欄位驗證失敗 | 欄位格式或列舉值不符 | 檢視欄位 |
| `CC-CARD-4040` | 404 | 資源不存在（卡片／Token／申請案件） | 查無指定識別碼 | 確認識別碼 |
| `CC-CARD-2001` | 409 | 冪等鍵衝突 | 同鍵異指紋 | 更換冪等鍵 |

> **`CC-CARD-1004`／`CC-CARD-2001`／`CC-CARD-4001`／`CC-CARD-4040` 之來源**（`HANDOVER #C-10`，2026-09-20）：
>`card-service` 補發卡／快發快綁／Token／申請案件四項（契約原已宣告 8 端點、實作僅 3 端點）。
>既有 5 碼僅涵蓋業務規則（1001～1003）與狀態機（3001／3002），
>**無 400（欄位驗證）與 404（查無資源）**——新增端點需此二碼以維持「所有錯誤一律套用封套」（`CSP-003 §3`）；
>`1004` 則為 Token 重複綁定之業務規則；`2001` 沿用既有冪等衝突碼之慣例（`CC-PTY-2001`／`CC-POST-2001`／`CC-PLT-2001`）。三者編碼對齊既有 `1000`＝業務規則、`4001`／`4040`＝協定層之慣例。
| `CC-PTY-1001` | 422 | 管制碼不允許操作 | 帳戶管制中 | 先解除管制 |
| `CC-PTY-2001` | 409 | 冪等鍵衝突 | 同鍵異指紋 | 更換冪等鍵 |
| `CC-PTY-4001` | 400 | 請求欄位驗證失敗 | 欄位格式或必填不符 | 檢視欄位 |
| `CC-PTY-4040` | 404 | 資源不存在（歸戶／持卡人） | 查無指定識別碼 | 確認識別碼 |
| `CC-PRD-1001` | 422 | 產品代碼重複 | 已存在 | 更換代碼 |
| `CC-PRD-1002` | 422 | 參數生效日重疊 | 同期間已有參數 | 調整生效日 |
| `CC-POST-1001` | 422 | 授權請款比對不符 | 金額或幣別不符 | 人工確認 |
| `CC-POST-1002` | 422 | 授權失效或不存在 | 無對應授權 | 人工確認 |
| `CC-POST-2001` | 409 | 冪等鍵衝突 | 同鍵異指紋 | 更換冪等鍵 |
| `CC-POST-2002` | 409 | 樂觀鎖衝突 | 併發更新 | 重試 |
| `CC-POST-3001` | 409 | 交易狀態不允許沖正 | 已 REVERSED | 不可再沖正 |
| `CC-POST-4001` | 409 | 重複入帳（業務冪等鍵已存在） | 同一筆帳務 | 檢視前次結果 |
| `CC-POST-4002` | 409 | 整批重複請款（疑似） | 檔案指紋相同 | 人工判定 |
| `CC-POST-4000` | 400 | 請求驗證錯誤 | 欄位格式或必填不符 | 修正請求 |
| `CC-POST-4040` | 404 | 資源不存在（入帳交易／分戶餘額） | 查無指定識別碼 | 確認識別碼 |
| `CC-PAY-1001` | 422 | 繳款金額不符 | 與應繳金額不符 | 確認金額 |
| `CC-PAY-2001` | 409 | 繳款冪等衝突 | 重複送件 | 檢視前次結果 |
| `CC-PAY-4000` | 400 | 請求驗證錯誤（含分配不平衡） | 欄位格式、金額或分配加總不符 | 修正請求 |
| `CC-PAY-4040` | 404 | 資源不存在（繳款／自扣約定） | 查無指定識別碼 | 確認識別碼 |
| `CC-PAY-3001` | 409 | 繳款狀態不允許沖正 | 已 REVERSED | — |
| `CC-STM-1001` | 422 | 帳單週期不存在 | 週期代碼錯誤 | 確認週期 |
| `CC-STM-5001` | 503 | 批次執行中 | 同一週期已有執行中批次 | 等待完成 |
| `CC-STM-2001` | 409 | 週期重複執行（同帳戶同月份已產生） | `UQ_STMT_MONTH` 衝突 | 檢視既有帳單 |
| `CC-STM-4000` | 400 | 請求驗證錯誤 | 欄位格式或必填不符 | 修正請求 |
| `CC-STM-4040` | 404 | 資源不存在（帳單／批次執行） | 查無指定識別碼 | 確認識別碼 |
| `CC-PRC-1001` | 422 | 無適用利率 | RBP 未評核且無產品利率 | 先完成 RBP 評核 |
| `CC-PRC-1002` | 422 | 計息期間重疊 | 期間已有計息 | 調整期間 |
| `CC-INS-1001` | 422 | 分期期數不支援 | 非 3／6／12／24 | 使用支援期數 |
| `CC-INS-1002` | 422 | 總約未簽署 | `agreementSigned=false` | 先完成簽署 |
| `CC-INS-4000` | 400 | 請求驗證錯誤 | 欄位格式或期數不符 | 修正請求 |
| `CC-INS-4040` | 404 | 資源不存在（分期計畫） | 查無指定識別碼 | 確認識別碼 |
| `CC-INS-3001` | 409 | 分期狀態不允許操作 | 非 ACTIVE | 依狀態機操作 |
| `CC-RWD-1001` | 422 | 點數不足 | 餘額不足且政策為拒絕 | 部分折抵或改設定 |
| `CC-RWD-1002` | 422 | 未達最低折抵金額 | 低於門檻 | 提高消費金額 |
| `CC-RWD-1003` | 422 | 點數帳戶已凍結 | AV2 到期 | 不可兌換 |
| `CC-RWD-1005` | 422 | 回饋規則代碼重複 | 規則代碼已存在 | 更換規則代碼 |
| `CC-RWD-2001` | 409 | 扣點併發衝突 | 樂觀鎖失敗 | 重試 |
| `CC-RWD-4000` | 400 | 請求驗證錯誤 | 欄位格式或必填不符 | 修正請求 |
| `CC-RWD-4040` | 404 | 資源不存在（點數帳戶） | 查無指定歸戶 | 確認識別碼 |
| `CC-CLR-1001` | 422 | 清算差異未歸零 | 存在差異 | 差異處理後重新比對 |
| `CC-CLR-5001` | 503 | 卡組織清算檔未到 | 檔案未取得 | 稍後重試 |
| `CC-DSP-1001` | 422 | 爭議類型不支援 | — | 確認類型 |
| `CC-DSP-1002` | 422 | 超過卡組織時限 | 已逾時限 | 不可提爭議 |
| `CC-MRC-1001` | 422 | 特店代碼重複 | 已存在 | 更換代碼 |
| `CC-NOT-1001` | 422 | 通知樣板不存在 | 樣板代碼錯誤 | 確認樣板 |
| `CC-NOT-1002` | 422 | 無可用發送通道 | 樣板未啟用任何請求之通道（偏好與樣板可用通道無交集） | 確認通道或樣板設定 |
| `CC-NOT-1003` | 422 | 樣板變數不足 | `variables` 缺少樣板內文所引用之變數 | 補齊變數 |
| `CC-NOT-4040` | 404 | 資源不存在（通知） | 查無指定通知識別碼 | 確認識別碼 |
| `CC-RPT-1001` | 422 | 報表定義不存在 | 報表代碼錯誤 | 確認代碼 |
| `CC-MIG-1001` | 422 | 遷移類型不支援 | — | 確認類型 |

> **`CC-NOT-1002`／`CC-NOT-1003`／`CC-NOT-4040` 之來源**（`CR-017 §9.4`，2026-09-22）：
> `notification-service` 實作（`C-9`）時發現既有 `CC-NOT-*` 僅 3 碼（`1001` 樣板不存在、
> `3001`／`3002` 狀態機），**缺**「無可用通道」（業務規則）與「查無通知」（協定層）——
> 二者皆為契約端點所必需（`POST /api/v1/notifications` 之 422、`GET /api/v1/notifications/{id}`
> 之 404），故依 `CSP-003 §2`「不得自創錯誤碼」程序補登。
> 編碼對齊既有慣例：`1000`＝業務規則、`4040`＝協定層「查無資源」（同 `CC-CARD-4040`／`CC-PTY-4040`）。

| `CC-PLT-2001` | 409 | 冪等鍵衝突 | 同鍵異指紋 | 更換冪等鍵 |
| `CC-PLT-2002` | 409 | 狀態版本衝突 | 樂觀鎖／狀態條件更新失敗（`SDD-005 SM-04`），如 Saga 並行協調 | 重讀最新狀態後重試 |
| `CC-PLT-3001` | 409 | **非法狀態轉換** | 來源狀態未定義此事件之轉換（`SDD-005 SM-01/SM-02`） | 查詢當前狀態後重送合法事件 |
| `CC-PLT-3002` | 409 | **終態不可轉換** | 來源狀態為終態（`SDD-005 SM-05`），例如 `CLOSED` → `ACTIVE` | 不得重送，須另立新單 |
| `CC-PLT-3003` | 409 | **資源尚未就緒** | 目標資源尚未產生完成——如非同步作業仍在 `QUEUED`／`RUNNING`，或已 `FAILED`（**與「狀態機非法轉換」語意不同**，見 `GOV-007 §4`） | 待作業完成後重試；`FAILED` 者查作業狀態 |
| `CC-PLT-4001` | 400 | 缺少必要請求標頭或參數 | 必填標頭（如 `X-Channel`）或查詢參數未提供 | 補上後重送 |
| `CC-PLT-4002` | 400 | 請求主體格式錯誤 | 主體非合法 JSON，或欄位型別無法解析 | 修正請求主體 |
| `CC-PLT-4003` | 400 | 請求參數驗證失敗 | 參數型別不符，或 Bean Validation 未通過 | 依 `details` 修正欄位 |
| `CC-PLT-4010` | 401 | **未認證** | 無 `Authorization` 標頭、格式非 `Bearer`、權杖過期或簽章驗證失敗 | 重新取得權杖後重送 |
| `CC-PLT-4030` | 403 | **無權限** | 已認證但角色／權限不足（`SAD-005 §4`） | 確認帳號之角色授權 |
| `CC-PLT-4040` | 404 | 資源或路徑不存在 | 無對應路由，或聚合根查無 | 確認路徑與識別碼 |
| `CC-PLT-4050` | 405 | HTTP 方法不支援 | 動詞與端點不符 | 改用正確方法 |

> **`CC-PLT-4001`～`CC-PLT-4050` 之來源**（`CR-004`，2026-09-19）：
> 本 5 碼為**請求層錯誤**（類別位 4，見 §3.3.1），由 `common-spring` 之
> `GlobalExceptionHandler` 統一拋出，**服務不需自行實作**。
> 服務僅在**需要更精確語意**時才使用自有 4xxx 碼（如 `CC-PTY-4040`「歸戶查無」）。
> 對應之 Spring 例外：
> `MissingRequestHeaderException`／`MissingServletRequestParameterException` → `4001`；
> `HttpMessageNotReadableException` → `4002`；
> `MethodArgumentNotValidException`／`HandlerMethodValidationException`／
> `ConstraintViolationException`／`MethodArgumentTypeMismatchException` → `4003`；
> `NoResourceFoundException` → `4040`；`HttpRequestMethodNotSupportedException` → `4050`。

> **`CC-PLT-4010`／`CC-PLT-4030` 之來源**（`SCS-022`，2026-09-19）：
> `CR-004` 之 5 碼僅涵蓋 400／404／405，**無 401（未認證）與 403（無權限）**——
> 因當時 `GlobalExceptionHandler` 尚未處理認證／授權（Spring Security 攔截發生在
> **進入控制器之前**，不在 `@ControllerAdvice` 之攔截範圍）。
> W-3 SSO 接線（`SCS-022`）後，`common-spring` 之 `SecurityAutoConfiguration`
> 需此二碼以維持「**所有回應一律套用 `ApiResponse` 封套**」（`CSP-003 §3`）。
> 依 `CSP-003 §2`「不得自創錯誤碼」程序補登；
> 後兩碼對齊 HTTP 狀態碼（401／403），與既有 `4040`／`4050` 之慣例一致。

**各服務之狀態機錯誤碼**（`SDD-005 SM-01/SM-02/SM-05`；由共用狀態機引擎依服務代碼拋出）

| 代碼 | HTTP | 名稱 | 觸發條件 | 客戶端處置 |
|---|---:|---|---|---|
| `CC-AUTH-3001` | 409 | 非法狀態轉換 | 授權相關狀態機（來源狀態未定義此事件） | 查詢當前狀態後重送合法事件 |
| `CC-AUTH-3002` | 409 | 終態不可轉換 | 授權相關狀態機（來源狀態為終態） | 不得重送，須另立新單 |
| `CC-CARD-3001` | 409 | 非法狀態轉換 | 卡片狀態機（SDD-005 §2） | 同上 |
| `CC-CARD-3002` | 409 | 終態不可轉換 | 卡片狀態機，例如 `CLOSED` → `ACTIVE` | 同上 |
| `CC-LIM-3001` | 409 | 非法狀態轉換 | 額度佔用／額度調整狀態機（SDD-005 §3／§8） | 同上 |
| `CC-LIM-3002` | 409 | 終態不可轉換 | 額度調整已 `REJECTED`/`EXPIRED`/`REVOKED` 仍嘗試覆核 | 同上 |
| `CC-POST-3001` | 409 | 非法狀態轉換 | 帳務交易狀態機（SDD-005 §4） | 同上 |
| `CC-POST-3002` | 409 | 終態不可轉換 | 帳務交易已 `REVERSED` 仍嘗試入帳 | 同上 |
| `CC-PAY-3001` | 409 | 非法狀態轉換 | 繳款狀態機（SDD-005 §5） | 同上 |
| `CC-PAY-3002` | 409 | 終態不可轉換 | 繳款已沖正仍嘗試分配 | 同上 |
| `CC-INS-3001` | 409 | 非法狀態轉換 | 分期計畫狀態機（SDD-005 §6） | 同上 |
| `CC-INS-3002` | 409 | 終態不可轉換 | 分期已結清仍嘗試攤還 | 同上 |
| `CC-DSP-3001` | 409 | 非法狀態轉換 | 爭議案件狀態機（SDD-005 §7） | 同上 |
| `CC-DSP-3002` | 409 | 終態不可轉換 | 爭議已 `CLOSED` 仍嘗試調整 | 同上 |
| `CC-STM-3001` | 409 | 非法狀態轉換 | 帳單狀態機（SDD-005 §9） | 同上 |
| `CC-STM-3002` | 409 | 終態不可轉換 | 帳單已結清仍嘗試產製 | 同上 |
| `CC-RWD-3001` | 409 | 非法狀態轉換 | 點數帳戶狀態機（SDD-005 §10） | 同上 |
| `CC-RWD-3002` | 409 | 終態不可轉換 | 點數帳戶已 `CLOSED` 仍嘗試入帳 | 同上 |
| `CC-PTY-3001` | 409 | 非法狀態轉換 | 當事人／帳戶狀態機 | 同上 |
| `CC-PTY-3002` | 409 | 終態不可轉換 | 當事人／帳戶已終止 | 同上 |
| `CC-PRD-3001` | 409 | 非法狀態轉換 | 產品／參數狀態機 | 同上 |
| `CC-PRD-3002` | 409 | 終態不可轉換 | 產品已下架 | 同上 |
| `CC-PRC-3001` | 409 | 非法狀態轉換 | 定價／利率狀態機 | 同上 |
| `CC-PRC-3002` | 409 | 終態不可轉換 | 定價已失效 | 同上 |
| `CC-CLR-3001` | 409 | 非法狀態轉換 | 清算批次狀態機 | 同上 |
| `CC-CLR-3002` | 409 | 終態不可轉換 | 清算批次已結案 | 同上 |
| `CC-MRC-3001` | 409 | 非法狀態轉換 | 特店狀態機 | 同上 |
| `CC-MRC-3002` | 409 | 終態不可轉換 | 特店已終止 | 同上 |
| `CC-MIG-3001` | 409 | 非法狀態轉換 | 遷移作業狀態機 | 同上 |
| `CC-MIG-3002` | 409 | 終態不可轉換 | 遷移作業已完成 | 同上 |
| `CC-NOT-3001` | 409 | 非法狀態轉換 | 通知狀態機 | 同上 |
| `CC-NOT-3002` | 409 | 終態不可轉換 | 通知已結案 | 同上 |
| `CC-RPT-3001` | 409 | 非法狀態轉換 | 報表作業狀態機 | 同上 |
| `CC-RPT-3002` | 409 | 終態不可轉換 | 報表已歸檔 | 同上 |
| `CC-PLT-5000` | 500 | 未預期系統錯誤 | 未分類之伺服器例外（`GlobalExceptionHandler` 之兜底） | 已告警，聯繫系統管理 |
| `CC-PLT-5001` | 503 | 外部系統逾時 | 逾時 | 重試（冪等者） |
| `CC-PLT-5002` | 500 | Saga 補償失敗 | 補償步驟失敗 | 告警並人工介入 |
| `CC-PLT-5003` | 503 | 事件發布失敗 | Kafka 不可用 | 自動重試 |
| `CC-PLT-5004` | 500 | 欄位加解密失敗 | 金鑰不存在、密文損毀或格式不符 | **告警**，人工介入（不得回退為明文） |
| `CC-PLT-5005` | 503 | 定價服務呼叫失敗 | `pricing-service` 不可用、逾時或回應無法解析（`CR-006` 計息串接） | **不入帳**，重試（計息請求帶冪等鍵，可安全重送） |
| `CC-GW-1001` | 422 | 缺少冪等鍵 | 寫入操作未帶標頭 | 補上標頭 |
| `CC-GW-1002` | 401 | 權杖無效或過期 | 簽章不符／已過期／被竄改 | 重新登入 |
| `CC-GW-1003` | 403 | 權限不足 | 角色或範圍（scope）不足 | 申請權限 |
| `CC-GW-4290` | 429 | 超過速率限制 | 限流 | 退避重試 |
| `CC-GW-5001` | 503 | 服務不可用 | 斷路器開啟 | 稍後重試 |

### 3.4.19 `campaign-service`（行銷活動引擎；`CR-046`／`BRD-005`）

>★ 本組為 `campaign-service` 專用。**編碼對齊既有慣例**：`1000`＝業務規則、`2001`＝冪等衝突、
>  `3001`＝狀態機、`4001`／`4040`＝協定層。
>⚠️ `CC-CAM-1002`／`CC-CAM-1003` 之語意**直接來自客戶 BRD**（`BRD-005` D9 疊加必填、
>  例 9 利率型回沖必填）——非我方推測，故採**獨立錯誤碼**而非通用 400，以便業務端追溯。
| `CC-CAM-1001` | 422 | 活動期間不合法 | 迄日早於起日 | 調整期間 |
| `CC-CAM-1002` | 422 | 疊加模式未宣告 | 未提供 `stackingMode`（`BRD-005` D9 明訂為必要參數、無預設值） | 補上疊加模式 |
| `CC-CAM-1003` | 422 | 利率型活動須宣告回沖規則 | `rewardForm=RATE_CUT` 但未提供 `clawbackRule`（`BRD-005` 例 9） | 補上回沖規則 |
| `CC-CAM-1004` | 422 | 錄取名額已額滿 | 波次名額用罄（`BRD-005` BR-06） | 改選其他波次／活動 |
| `CC-CAM-2001` | 409 | 冪等鍵衝突 | 同鍵異指紋 | 更換冪等鍵 |
| `CC-CAM-3001` | 422 | 活動狀態不允許此操作 | 狀態機不允許（如非 DRAFT 欲核准） | 檢視活動狀態 |
| `CC-CAM-4001` | 400 | 請求欄位驗證失敗 | 欄位格式或必填不符 | 檢視欄位 |
| `CC-CAM-4040` | 404 | 資源不存在（活動／方案／任務） | 查無指定識別碼 | 確認識別碼 |

---

## 4. 事件契約

事件信封與命名規範見 `SAD-004 §5`。事件之 JSON Schema 位於
`cardcore/contracts/events/<context>.<aggregate>.<verb>.v<major>.json`。

### 4.1 事件信封（固定）

```json
{
  "eventId": "01HZX8K2M4N5P6Q7R8S9T0V1W2",
  "eventType": "posting.transaction.posted.v1",
  "eventVersion": 1,
  "occurredAt": "2026-09-17T02:15:30.123Z",
  "aggregateId": "018f2a3b-...",
  "sequenceNo": 42,
  "traceId": "01HZX8K2M4N5P6Q7R8S9T0V1W2",
  "causationId": "01HZX8K2M4N5P6Q7R8S9T0V1W1",
  "correlationId": "AUTH20260917000001",
  "schemaRef": "posting.transaction.posted.v1",
  "payload": { }
}
```

### 4.2 主要事件

> ★ **事件之唯一權威清單為 `SAD-004 §5.2`**（`CR-030 §2 #5`）。本表僅列 **payload 欄位**。

| 事件 | payload 主要欄位 |
|---|---|
| `authorization.authorization.approved.v1` | `authorizationId`, `authNo`, `cardId`, `accountId`, `amount`, `currency`, `holdId`, `holdAmount`, `standinFlag`, `cardNoMasked`＊, `merchantId`＊, **`decision`＊** |
| `authorization.authorization.declined.v1` | `authorizationId`, `respCode`, `failedChecks[]`　⛔ **刻意未發射**（見下） |
| `limit.limit.adjusted.v1` | `accountId`, `adjustType`, `beforeAmount`, `afterAmount`, `effectiveFrom` |
| `limit.hold.created.v1` | `holdId`, `accountId`, `holdAmount`, `holdType`, `instrumentId`＊, `authorizationId`＊, `currency`＊ |
| `limit.hold.released.v1` | `holdId`, `accountId`, `releasedAmount`, `instrumentId`＊ |
| `card.card.issued.v1` | `cardId`, `cardNoMasked`, `productCode`, `instantIssued` |
| `card.card.status-changed.v1` | `instrumentId`＊, `cardNoHash`, `cardId`, `accountId`, `cardNoMasked`, `status`, `blockCode`, `expiryDate` |
| `card.token.bound.v1` | `tokenId`, `cardId`, `platform` |
| `product.parameter.changed.v1` | `namespace`, `paramKey`, `effectiveFrom` |
| `posting.transaction.posted.v1` | `postingId`, `txNo`, `accountId`, `instrumentId`＊, `merchantId`＊, `mcc`＊, **`payPath`＊**（`CR-048`；支付路徑，未提供＝未判定）, `txType`, `amount`, `currency`, `postingDate`, `subAccountEntries[]` |
| **`campaign.reward.commanded.v1`** | `commandId`, `campaignCode`, `recognition{campaignCode, postingId, txNo, accountId, instrumentId＊, merchantId＊, mcc＊, txType, amount, currency, postingDate, applicableRate＊, rateReason＊}`　（★ **`CR-046` B4 首次定義**：`REWARD_CMDS(PENDING)` 經 relay 派發；rewards 以 `campcmd:<commandId>` 冪等入點。★ **`CR-049`**：**回沖指令同走此信封**，`recognition{…, originalPostingId, txType=REFUND, amount, postingDate, refund:true}`——消費端以 **`txType=REFUND` 為權威**，欄位一律「`recognition` 優先、`payload` 次之」） |
| `posting.transaction.reversed.v1` | `postingId`, `reversalOf`, `amount`, `reason`, **`postingDate`＊**（★ **`CR-049`**：沖正交易之入帳日＝**業務日錨點**；下游回沖分錄以此為 `posting_date`，**不得以 `occurredAt` 反推**——`SCS-026 §7.5①`。可選欄位，**不升版**） |
| `posting.exception.raised.v1` | `postingId`, `exceptionType`, `detail` |
| `payment.payment.posted.v1` | `paymentId`, `accountId`, `amount`, `allocations[]`, `availableLimitAfter`, `currency`＊ |
| `payment.payment.unmatched.v1` | `paymentId`, `accountId`, `amount`, `daysUnmatched`, **`currency`＊** |
| **`payment.payment.reversed.v1`** | **`paymentId`**, **`accountId`**, **`amount`**, **`reason`**, **`currency`＊**　（★ **`CR-030 §2 #9` 首次定義**） |
| `statement.statement.issued.v1` | `statementId`, `accountId`, `statementMonth`, `totalAmount`, `minimumDue`, `paymentDueDate`, `currency`＊ |
| `installment.plan.created.v1` | `planId`, `accountId`, `originalAmount`, `periodCount`, `perPeriodAmount` |
| `rewards.points.accrued.v1` | `accountId`, `pointsType`, `bucket`, `points`, `afterBalance` |
| `rewards.points.redeemed.v1` | `redemptionId`, `accountId`, `pointsUsed`, `amountPaid`, `merchantShare` |
| `clearing.batch.matched.v1` | `runId`, `businessDate`, `txCount`, `totalAmount`, `differenceCount` |
| `dispute.case.opened.v1` | `disputeId`, `accountId`, `transactionId`, `disputeType`, `amount` |
| `dispute.case.closed.v1` | `disputeId`, `resolution`, `adjustAmount` |
| `merchant.settlement.submitted.v1` | `settlementId`, `merchantId`, `grossAmount`, `feeAmount`, `netAmount` |
| `notification.sent.v1` | `notificationId`, `channels[]`, `sentAt` |

> ＊ **可選欄位**（`SAD-004 §5.3`：新增可選欄位**不升版**；通則見 **§2.5**）。
> - `cardNoMasked`／`merchantId`（`approved.v1`）——2026-10-01（`#A-125`），供通知樣板 `AUTH_APPROVED`；
>   ★ `cardNoMasked` 為**遮罩值**（不含 PAN），符合 PCI DSS。
> - `authorizationId`（`limit.hold.created.v1`）——`CR-028`，供 `authorization-service` 之本地投影對應。
> - `currency`（`limit.hold.created.v1`／`payment.posted.v1`／`statement.issued.v1`／
>   `payment.unmatched.v1`／`payment.reversed.v1`）——依 **§2.5 通則**（`CR-030 §2 #10`）。
> - **`decision`（`approved.v1`）——`CR-030 §2 #8` 新增**：值域 `APPROVED`／`STANDIN`／`REFERRAL`。
>   ★ **本事件之語意為「授權已佔用額度」**（三者皆佔用）；下游據 `decision` 分辨「核准」與「轉人工待覆核」。
> - `instrumentId`（`card.card.status-changed.v1`）——`CR-046 B2-6`：交易載體最小識別；由 `CC_CARD.INSTRUMENTS` 依 `cardId` 解析。既有尚未回填載體之卡片可為 `null`，不臆造值。
> - `instrumentId`（`posting.transaction.posted.v1`）——`CR-046` B4 前置增量：直接取既有入帳交易之 `INSTRUMENT_ID`；非載體交易（例如利息）可為 `null`，舊事件亦可缺欄；消費端不得將 `cardId` 猜回載體。
> - `merchantId`（`posting.transaction.posted.v1`）——`CR-046` AC 11 前置增量：取既有 `PostingRequest.merchantId`／`Transaction.merchantId`；歷史或未提供者為 `null`。`mcc` 與支付路徑不在本次補入，避免臆造來源。
> - `mcc`（`posting.transaction.posted.v1`）——`CR-046` AC 11 資料鏈補強：呼叫端自授權紀錄（`CC_AUTH.AUTHORIZATIONS.mcc`，權威）如實帶入；posting 不做 MCC 對映、不從 merchantId 推導；非授權來源交易與舊事件為 `null`，消費端必須容忍缺欄。
> - **`instrumentId`（`limit.hold.created.v1`／`limit.hold.released.v1`）——`CR-054` P1（2026-10-17）**：
>   交易載體最小識別，供 `authorization-service` 之投影把佔用歸到正確的卡。
>   ★ **補此欄之根因**：權威表 `CC_LIM.LIMIT_HOLDS.INSTRUMENT_ID` 早於 `V10` 即有值，
>   但**事件原本不帶** → 投影永遠拿不到（`CR-054 §3.1`「權威表有、事件沒有」）。
>   `created` 取 `hold()` 之入參；`released` 因入參不含載體，由同交易 `findHoldInstrumentId` 回查。
>   ⚠️ `V10` 前之歷史佔用無載體 → 可為 `null`（**不得回填、不得以 `cardId` 猜**）；消費端須容忍缺欄。

> ⛔ **`authorization.authorization.declined.v1` 為「刻意未發射」（`CR-030 §2 #7` 裁定）**：
> **拒絕不改變任何下游狀態**（不佔額度、不產生待請款），依「**事件＝已發生的狀態變更**」原則**不應灌入事件流**；
> 拒絕率／原因分佈改以 **metrics**（`cardcore_authorization_declined_total{reason}`）涵蓋。
> **payload 保留於本表**，供日後「拒絕率即時看板」需求時啟用（**無契約破壞**）。

> 🔧 **本表之歷史修訂**：
> **(i)** `CR-028`：**刪除** `authorization.hold.created.v1`／`.released.v1` 兩列（「同一事實兩個生產者」之矛盾）、
> **補登** `limit.hold.created.v1`；**(ii)** `CR-028`：**更正** `card.card.status-changed.v1` 之 payload——
> 原載 `fromBlockCode`／`toBlockCode`／`reason`（**差量**），惟實作（`SCS-003 §7.10`，`#A-118` **真機驗證**）
> 發射者為**變更後之完整狀態**——**本表原為該事件之唯一錯誤記載**，已依實作更正
> （**`CR-030 §2 #13` 追認以實作為準**，結案）。
> **(iii)** ★ **結構修復**：本表原被上述 `CR-028` 說明**從中間截斷**（後半表格失去表頭）——已一併修復。

### 4.3 事件相容性規則

| 變更 | 相容性 | 處置 |
|---|---|---|
| 新增可選欄位 | 相容 | 不升版 |
| 新增必填欄位 | **不相容** | 升版並雙軌 ≥ 1 release |
| 移除欄位 | **不相容** | 升版並雙軌 |
| 改型別 | **不相容** | 升版並雙軌 |
| 改語意 | **不相容** | 升版並雙軌 |
| 消費者 | 須同時容忍 `vN` 與 `vN+1` | 至少一個 release |

---

## 5. 契約測試

| 測試類型 | 說明 | 工具 |
|---|---|---|
| Provider 契約測試 | 服務實作是否符合契約 | 自動化（依 OpenAPI 驗證回應） |
| Consumer 契約測試 | 消費端對契約之期待 | 自動化 |
| 破壞性變更檢測 | 新版契約是否破壞舊版 | CI 契約 diff |
| Schema 驗證 | 回應是否符合宣告之 schema | 自動化 |
| 錯誤碼驗證 | 錯誤情境是否回傳宣告之錯誤碼 | 自動化 |

**通過標準**

| 項目 | 標準 |
|---|---|
| Provider 契約測試 | 100% 通過 |
| 破壞性變更 | 0 筆（或已升版雙軌） |
| 錯誤碼一致性 | 實作與本文件 100% 一致 |
| $ref 可解析 | 100% |

---

## 6. 端點清單（依服務）

| 服務 | 方法與路徑 | 說明 |
|---|---|---|
| authorization | `POST /api/v1/authorizations` | 取授權 |
| authorization | `GET /api/v1/authorizations/{authId}` | 查詢授權 |
| authorization | `POST /api/v1/authorizations/{authId}/reversals` | 取消／沖正 |
| authorization | `POST /api/v1/authorizations/{authId}/advices` | 人工／強制授權 |
| authorization | `GET /api/v1/authorizations/{authId}/check-results` | 檢核明細 |
| limit | `GET /api/v1/accounts/{accountId}/limits` | 歸戶額度 |
| limit | `GET /api/v1/accounts/{accountId}/limits` | 歸戶額度（★ `CR-043 §3.1` #7：原載卡級路徑，更正為歸戶級） |
| limit | `POST /api/v1/limit-adjustments` | 額度調整 |
| limit | `GET /api/v1/limit-adjustments/{adjustmentId}` | 查詢調整 |
| limit | `POST /api/v1/limit-holds` | 建立佔用 |
| limit | `POST /api/v1/limit-holds/{holdId}/releases` | 釋放佔用 |
| posting | `POST /api/v1/postings` | 入帳 |
| posting | `GET /api/v1/postings/{postingId}` | 查詢入帳 |
| posting | `POST /api/v1/postings/{postingId}/reversals` | 錯帳沖正 |
| posting | `GET /api/v1/accounts/{accountId}/balances` | 帳務餘額 |
| posting | `GET /api/v1/accounts/{accountId}/transactions` | 交易明細 |
| posting | `POST /api/v1/posting-duplicate-checks` | 重複請款偵測 |
| payment | `POST /api/v1/payments` | 繳款受理 |
| payment | `GET /api/v1/payments/{paymentId}` | 查詢繳款 |
| payment | `POST /api/v1/auto-debit-enrollments` | 自扣設定 |
| payment | `POST /api/v1/payments/{paymentId}/reversals` | 繳款沖正 |
| card | `POST /api/v1/cards` | 發卡 |
| card | `GET /api/v1/cards/{cardId}` | 查詢卡片 |
| card | `POST /api/v1/cards/{cardId}/lifecycle-transitions` | 狀態轉換 |
| card | `POST /api/v1/cards/{cardId}/instant-issuance` | 快發快綁 |
| card | `POST /api/v1/cards/{cardId}/tokens` | Token 綁定 |
| card | `GET /api/v1/cards/{cardId}/tokens` | 查詢 Token |
| card | `POST /api/v1/card-applications` | 申請案件 |
| party | `GET /api/v1/accounts/{accountId}` | 查詢歸戶 |
| party | `GET /api/v1/parties/{partyId}` | 查詢持卡人 |
| party | `POST /api/v1/parties/{partyId}/flags` | 貼標 |
| party | `POST /api/v1/accounts/{accountId}/block-codes` | 管制碼 |
| product | `POST /api/v1/products` | 建立產品 |
| product | `GET /api/v1/products/{productCode}` | 查詢產品 |
| product | `PUT /api/v1/products/{productCode}` | 更新產品 |
| product | `GET /api/v1/parameters/{namespace}/{paramKey}` | 查詢參數 |
| product | `PUT /api/v1/parameters/{namespace}/{paramKey}` | 設定參數 |
| statement | `POST /api/v1/statement-cycles/{cycleCode}/runs` | 帳單批次 |
| statement | `GET /api/v1/statement-runs/{runId}` | 批次狀態 |
| statement | `GET /api/v1/accounts/{accountId}/statements` | 查詢帳單 |
| pricing | `POST /api/v1/pricing/interest-calculations` | 利息計算 |
| pricing | `GET /api/v1/pricing/interest-inquiries` | 利息查詢 |
| pricing | `GET /api/v1/pricing/fees` | 費用設定 |
| installment | `POST /api/v1/installments` | 建立分期 |
| installment | `GET /api/v1/installments/{planId}` | 查詢分期 |
| installment | `POST /api/v1/installments/{planId}/settlements` | 結清／違約 |
| rewards | `POST /api/v1/rewards/accruals` | 點數回饋 |
| rewards | `POST /api/v1/rewards/redemptions` | 點數兌換 |
| rewards | `GET /api/v1/rewards/accounts/{accountId}` | 點數帳戶 |
| rewards | `POST /api/v1/reward-rules` | 回饋規則 |
| clearing | `POST /api/v1/clearing/runs` | 清分比對 |
| clearing | `GET /api/v1/clearing/reconciliations/{runId}` | 差異明細 |
| clearing | `POST /api/v1/clearing/settlements` | 撥款與分錄 |
| dispute | `POST /api/v1/disputes` | 爭議受理 |
| dispute | `GET /api/v1/disputes/{disputeId}` | 查詢爭議 |
| dispute | `POST /api/v1/disputes/{disputeId}/chargebacks` | 扣款與仲裁 |
| merchant | `POST /api/v1/merchants` | 建立特店 |
| merchant | `GET /api/v1/merchants/{merchantId}` | 查詢特店 |
| merchant | `POST /api/v1/merchant-settlements` | 商店請款 |
| notification | `POST /api/v1/notifications` | 發送通知 |
| notification | `GET /api/v1/notifications/{notificationId}` | 查詢通知 |
| report | `POST /api/v1/report-jobs` | 產生報表 |
| report | `GET /api/v1/report-jobs/{jobId}` | 查詢報表 |
| migration | `POST /api/v1/migration-jobs` | 資料轉換 |
| migration | `GET /api/v1/legacy-accounts/{legacyId}` | 查詢舊資料 |
| **合計** | **65 端點** | |

---

## 7. 代表端點範例（請求／回應）

> 本節列出關鍵端點的**具體 JSON 範例**，使 Coder 無須反推欄位形狀。
> 全部 65 個端點的請求／回應 `example` 已內嵌於
> `cardcore/contracts/openapi/<service>.yaml`，並可經契約測試直接驗證。
> 所有回應皆套用 §2.2 之統一信封（成功 `error=null`；失敗 `data=null`）。

### 7.1 取授權　`POST /api/v1/authorizations`

**請求**

```json
{
  "stan": "20260917000001",
  "cardNo": "4111111234567890",
  "txType": "PURCHASE",
  "amount": 12345,
  "currency": "TWD",
  "mcc": "5411",
  "merchantId": "MERCH001",
  "merchantName": "便利商店",
  "entryMode": "CHIP",
  "txTime": "2026-09-17T02:15:30Z",
  "emvData": { "arqc": "A1B2C3D4" }
}
```

**成功回應（201）— `data` 主體**

```json
{
  "authorizationId": "018f2a3b-0000-0000-0000-000000000001",
  "authNo": "AUTH000123",
  "decision": "APPROVED",
  "respCode": "00",
  "holdId": "018f2a3b-0000-0000-0000-000000000002",
  "holdAmount": 12345,
  "availableLimit": 876550,
  "checkResults": [
    { "checkCode": "LIMIT", "checkOrder": 1, "result": "PASS", "reasonCode": null }
  ],
  "txTime": "2026-09-17T02:15:30Z"
}
```

### 7.2 入帳　`POST /api/v1/postings`

**請求**

```json
{
  "accountId": "018f2a3b-0000-0000-0000-000000000010",
  "cardId": "018f2a3b-0000-0000-0000-000000000020",
  "authorizationId": "018f2a3b-0000-0000-0000-000000000001",
  "txCode": "PUR",
  "txType": "PURCHASE",
  "amount": 12345,
  "currency": "TWD",
  "postingDate": "2026-09-17",
  "merchantId": "MERCH001",
  "businessIdempotencyKey": "4111111234567890|AUTH000123|2026-09-17|12345|TWD"
}
```

**成功回應（201）— `data` 主體（含分欄位與更新後餘額）**

```json
{
  "postingId": "018f2a3b-0000-0000-0000-000000000030",
  "txNo": "TXN20260917000001",
  "status": "POSTED",
  "amount": 12345,
  "currency": "TWD",
  "postingDate": "2026-09-17",
  "subAccountEntries": [
    { "subAccount": "PRINCIPAL", "amount": 12345, "beforeAmount": 0, "afterAmount": 12345 }
  ],
  "releasedHoldAmount": 12345,
  "balanceAfter": {
    "accountId": "018f2a3b-0000-0000-0000-000000000010",
    "principalAmount": 12345, "interestAmount": 0, "feeAmount": 0, "taxAmount": 0,
    "totalAmount": 12345, "minimumDue": 0, "paymentDueDate": null,
    "agingBucket": 0, "currency": "TWD", "asOf": "2026-09-17T02:20:00Z"
  }
}
```

### 7.3 繳款受理　`POST /api/v1/payments`

**請求**

```json
{
  "accountId": "018f2a3b-0000-0000-0000-000000000010",
  "amount": 50000,
  "currency": "TWD",
  "paymentMethod": "AUTO_DEBIT",
  "channel": "BATCH",
  "paymentDate": "2026-09-17",
  "externalRef": "AD20260917001"
}
```

**成功回應（201）— `data` 主體（即時恢復額度）**

```json
{
  "paymentId": "018f2a3b-0000-0000-0000-000000000040",
  "paymentNo": "PAY20260917000001",
  "status": "POSTED",
  "amount": 50000,
  "currency": "TWD",
  "allocations": [
    { "subAccount": "PRINCIPAL", "amount": 50000, "allocationOrder": 1 }
  ],
  "availableLimitAfter": 926550,
  "limitRestored": true,
  "postedAt": "2026-09-17T03:00:00Z"
}
```

### 7.4 建立額度佔用　`POST /api/v1/limit-holds`

**請求**

```json
{
  "accountId": "018f2a3b-0000-0000-0000-000000000010",
  "authorizationId": "018f2a3b-0000-0000-0000-000000000001",
  "holdAmount": 12345,
  "holdType": "GENERAL"
}
```

**成功回應（201）— `data` 主體**

```json
{
  "holdId": "018f2a3b-0000-0000-0000-000000000002",
  "accountId": "018f2a3b-0000-0000-0000-000000000010",
  "holdAmount": 12345,
  "releasedAmount": 0,
  "status": "HELD",
  "availableAmount": 864105
}
```

---

## 8. 變更記錄

| 版本 | 說明 |
|---|---|
| 1.0 | 初版：端點清單、通用規範、錯誤碼清單、事件契約 |
| 1.1 | 契約強化：65 端點全部補齊欄位級 `example` 與每端點業務規則／不變式（`OP_RULES`）；`reward-rules` 端點改用 `RewardRuleRequest`；詳見 `cardcore/tools/gen_openapi.py` |
| 1.2 | ①**修正表頭版本**：本文件表頭原停留於 1.0，但變更記錄已有 1.1 條目，兩者不一致；現更正為 1.2 並同步本記錄。②**新增錯誤碼 `CC-PLT-5000`（500 未預期系統錯誤）**——`CSP-003 §3` 明訂「內部錯誤一律轉為 `CC-PLT-5xxx` + 通用訊息」，但本表原無通用之未預期錯誤碼，致 `GlobalExceptionHandler` 之兜底分支無碼可用。依 `CSP-003 §2`「不得自創錯誤碼，新增碼須先擴充本節」之程序補登。③**新增錯誤碼 `CC-PLT-5004`（500 欄位加解密失敗）**——`SAD-005 §5` 要求卡號等個資欄位加密儲存；金鑰不存在或密文損毀屬**須告警**之基礎設施失敗，與「未分類之系統錯誤」處置不同，故獨立登錄。新碼均符合既有 pattern `^CC-[A-Z]{2,4}-[0-9]{4}$`，**無須調整 OpenAPI 之 `Error.code.pattern`** |
| 1.3 | **補登 401／403 授權類錯誤碼**：`CC-GW-1002`（401 權杖無效或過期）、`CC-GW-1003`（403 權限不足）。`CSP-003 §2` 規範「所有可用錯誤碼以本節為唯一登錄表」，但原表僅有 422 之 `CC-GW-1001`（缺少冪等鍵），**無任何 401／403 碼**；而 `CSP-007 §5.3.4` 之 JWT 驗證流程需要 401。依程序補登，並同步 `tools/gen_error_codes.py` 重新產生 `ErrorCodes.java`。新增碼符合既有 pattern，無須調整 OpenAPI `Error.code.pattern` |
| 1.4 | **補登狀態機與樂觀鎖類錯誤碼**（Sprint 3 之 `common-saga` 需要，依 `CSP-003 §2` 程序補登）：`CC-PLT-3001`（409 非法狀態轉換）、`CC-PLT-3002`（409 終態不可轉換）——`SDD-005 §14`（**1.7 起**；1.4 起為 §13；原 §12）明訂此二情境之錯誤碼為 `CC-<SVC>-3001`/`3002`，但平台層（Saga 狀態機）原無對應登錄；`CC-PLT-2002`（409 狀態版本衝突／樂觀鎖）——`SDD-005 SM-04` 要求「以狀態條件更新實作並發轉換」，衝突時需有明確錯誤碼。三碼皆符合既有 pattern `^CC-[A-Z]{2,4}-[0-9]{4}$`，無須調整 OpenAPI `Error.code.pattern`；已同步重新產生 `ErrorCodes.java`（**63 個錯誤碼**） |
| 1.5 | ①**移除重複登錄之 `CC-RW-*`（4 碼）**：`§3.2 服務代碼` 明訂 rewards-service 之代碼為 **`RWD`**（`§3.1` 亦規範 `SVC` 即服務代碼），但 `§3.4` 另存在 `CC-RW-1001/2001/4000/4040` 一組——**同一服務兩個代碼前綴，違反「一服務一服務代碼」**。經查此 4 碼係 2026-09-19 為 SCS-010 補登時未察覺 `CC-RWD-*` 已存在所致，屬登錄重複，故予移除（非功能變更；移除前已確認全庫 Java 無任何引用）。②**補登 `CC-RWD-4000`（400 請求驗證錯誤）、`CC-RWD-4040`（404 資源不存在）**：`CC-RWD-*` 原有 1001/1002/1003/2001/3001/3002，**缺類別位 4 之通用驗證碼與查無資源碼**，而 rewards-service 之 4 支端點（`SCS-010 §6`）需要此二碼。③**補登 `CC-RWD-1005`（422 回饋規則代碼重複）**：`POST /api/v1/reward-rules` 之 `rule_code` 於主檔為 `UNIQUE`，重複設定須有明確拒絕碼，不得以「靜默覆蓋」處理（覆蓋會使已生效之回饋條件無聲改變）。新碼均符合既有 pattern，無須調整 OpenAPI `Error.code.pattern`；已同步重新產生 `ErrorCodes.java`（**107 個錯誤碼**） |
| 1.6 | **擴充類別位 4 語意 ＋ 補登 5 個請求層錯誤碼**（`CR-004`，2026-09-19 BEAM PMO 核定）：<br>①**§3.3 類別位 4 由「資源與重複」擴充為「請求與資源」**（HTTP 400／404／405／409），並新增 **§3.3.1** 明訂四個子語意。理由：實測發現 Spring MVC 之請求層例外（缺標頭、壞 JSON、方法不支援、路徑不存在）**皆非 `DomainException` 子類**，全數落兜底回 **500**——既違反契約（應為 400／404／405），且每個客戶端錯誤都觸發 ERROR 告警（**告警疲勞**）。原類別位 4 僅有 404／409，**無處安放 400 與 405**。<br>②**補登 `CC-PLT-4001`（400 缺少必要請求標頭或參數）、`CC-PLT-4002`（400 請求主體格式錯誤）、`CC-PLT-4003`（400 請求參數驗證失敗）、`CC-PLT-4040`（404 資源或路徑不存在）、`CC-PLT-4050`（405 HTTP 方法不支援）**——由 `common-spring` 之 `GlobalExceptionHandler` 統一拋出，**服務不需自行實作**。<br>③實測影響：**17/17 服務受影響，自行補 `@ExceptionHandler` 者 0 個**（6 項情境中 4 項錯誤）。<br>新碼均符合既有 pattern `^CC-[A-Z]{2,4}-[0-9]{4}$`，無須調整 OpenAPI `Error.code.pattern`；已同步重新產生 `ErrorCodes.java`（**112 個錯誤碼**） |
| **1.7** | **補登 `CC-PLT-4010`（401 未認證）、`CC-PLT-4030`（403 無權限）**（`SCS-022`，2026-09-19；W-3 SSO 接線）：<br>①**缺口**：`CR-004`（本文件 1.6）之 5 碼僅涵蓋 400／404／405，**無 401 與 403**。當時看似無需——相關例外由 `GlobalExceptionHandler` 處理；但 **Spring Security 之認證攔截發生在進入控制器<b>之前</b>**，不在 `@ControllerAdvice` 之攔截範圍，故無碼可用。<br>②**若不補登**將被迫二選一：要麼回傳**空主體**（違反 `CSP-003 §3`「所有回應一律套用 `ApiResponse` 封套」），要麼**濫用** `CC-PLT-4001`（400 缺參數）回 401（碼與狀態不一致）。<br>③依 `CSP-003 §2`「不得自創錯誤碼」程序補登；後兩碼對齊 HTTP 狀態碼（401／403），與既有 `4040`／`4050` 慣例一致。<br>④實測：Keycloak 核發之真實權杖可通過、過期／偽造權杖皆回 401 且**主體含封套**（見 `SCS-022 §9.1`）。<br>新碼符合既有 pattern，無須調整 OpenAPI `Error.code.pattern`；已同步重新產生 `ErrorCodes.java`（**114 個錯誤碼**） |
| **1.8** | **補登 `CC-NOT-1002`／`CC-NOT-1003`／`CC-NOT-4040`**（`C-9`／`CR-017 §9.4`，2026-09-22）——`notification-service` 實作時發現既有 `CC-NOT-*` 僅 3 碼（`1001` 樣板不存在、`3001`／`3002` 狀態機），**缺**「無可用通道」（業務規則 422）與「查無通知」（協定層 404）→ 依 `CSP-003 §2` 程序補登 → 錯誤碼 **114 → 122** |
| **1.9** | **補登 `CC-AUTH-4040`（404 查無授權）**（`CR-021`，2026-09-22）——`authorization-service` 之授權明細／檢核明細端點需要；**併修訂 `check-results` 契約之陣列性**（原誤宣告單一物件 → `AuthCheckResult[]`）→ 錯誤碼 **122 → 123** |
| **1.10** | **補登 `CC-PLT-3003`（409 資源尚未就緒）**（`GOV-007 §4`，2026-10-01）——供**報表下載**之「作業未就緒（`QUEUED`／`RUNNING`／`FAILED`）」情境；原暫用 `CC-PLT-3001`（非法狀態轉換）**語意不符**，故另立。**與「狀態機非法轉換」語意區隔** → 錯誤碼 **123 → 124**；已重新產生 `ErrorCodes.java`（`--force`） |

