# SCS-012　爭議帳款服務（dispute-service）程式設計規格

| 項目 | 內容 |
|---|---|
| 文件編號 | SCS-012 |
| 版本 | **1.5**（**`CR-033`：更正 §7 與 §10 #9 之消費者**——原載「**`posting`**／`notification`」，經查**現行 `SAD-004 §5.2` 未列 `posting`**（`SDD-003 §15` 之 `Dispute → Posting` 屬**結案**路徑且為同步，非 `opened` 之消費）→ **`posting` 非本事件之消費者**；並載明真正阻塞為「**無對應模板**」。前版 **1.4**（`CR-027` 批次 A-4 事件輸出——`dispute.case.opened.v1`） |
| 狀態 | **已核定（規格先行）**；1.1 依 `CR-003` 補狀態機對照（`SDD-005 §7`）；1.2 依 `CR-017` 補雙軸拆分；1.3 依 `CR-026` 補統計端點；1.4 依 `CR-027` 補事件輸出 |
| 對應需求 | ⚠️ 爭議／拒付需求（對應契約 `/disputes` 與 `/chargebacks` 端點）；**`BRD-002` 追蹤表未列顯式 FR 編號，待補** |
| 服務埠號 | 待骨架產生時依 `CSP-006 §4` 分配（自 8081 起編） |
| 套件根 | `com.beam.cardcore.dis` |
| 權威 schema | `CC_DIS` |
| 關聯文件 | `SDD-001 §5.8`、`SDD-003`、`BRD-002`（待補 FR）、`GOV-005` |

---

## 1. 對應需求

| FR 編號 | 需求名稱 | 來源 |
|---|---|---|
| **（待補）** | 爭議帳款／拒付（chargeback）處理 | `BRD-002` 追蹤表未列；建議新增 `DIS-*`（如 `DIS-1` 爭議受理、`DIS-2` 拒付表示）並回填本表 |

> ⚠️ **追溯缺口**：經查 `BRD-002` 與 `BRD-003` 全目錄，無 `拒付`/`DISPUTE`/`CHARGEBACK` 之顯式 FR 編號，但契約 `dispute-service.yaml` 已定義 `POST /disputes`、`POST /disputes/{id}/chargebacks`。此為需求側缺口，須由 SA 於 `BRD-002` 補 FR 並回填本 SCS（否則違反「無 SCS 不寫程式」之可追溯性要求）。

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

| 圖種 | 文件 | 數量（實測） | 本服務相關 |
|---|---|---|---|
| **ER-Diagram** | `SDD-001_領域模型與ER-Diagram` | 16 個 `erDiagram` | `DISPUTE_CASES`/`DISPUTE_TIMELINES` 實體（`SDD-001 §5.8`） |
| **DFD** | `SDD-002_DFD與流程分解` | 10 個 flowchart | 爭議處理流 |
| **Sequence** | `SDD-003_Sequence-Diagrams` | 15 個 `sequenceDiagram` | 爭議／拒付時序 |
| **狀態機** | `SDD-005_狀態機設計` | 10 個 `stateDiagram` | ⚠️ `DISPUTE_CASES.status`／`STAGE` 以 CHECK 控管，未定義正式狀態機（**強烈建議補**，見 §10） |
| **API Contract** | `SDD-006` + `contracts/openapi/dispute-service.yaml` | 17 份 | 本服務 3 支端點 |

---

## 2. 服務職責與邊界

**承作**：
- 爭議案件（`DISPUTE_CASES`：查詢、舉證、責任判斷、時限）
- 爭議時序（`DISPUTE_TIMELINES`：各階段動作稽核留痕）
- 拒付表示（chargeback representation）

**不做（屬他服務）**：
- 原交易來源 → `posting-service`／`authorization-service`
- 清算對帳 → `clearing-service`

---

## 3. 領域模型

- 聚合：`DisputeCase`（爭議案件）、`DisputeEvidence`（舉證，依 `services.yaml` aggregates）
- 值物件：`DisputeType`、`Stage`、`Liability`、`OrgCode`、`Money`、`Currency`
- 狀態：`DISPUTE_CASES.status ∈ {OPEN, EVIDENCE_PENDING, REPRESENTED, WON, LOST, CLOSED}`（CHECK，預設 OPEN）；`STAGE ∈ {INQUIRY, FIRST_PRESENTMENT, SECOND_PRESENTMENT, PRE_ARB, ARBITRATION, CLOSED}`（預設 INQUIRY）；`LIABILITY ∈ {ISSUER, ACQUIRER, MERCHANT, PENDING}`

---

## 4. 程式結構

| 套件／類別 | 職責 | 對應需求 |
|---|---|---|
| `domain/model/DisputeCase` `DisputeEvidence` | 聚合與值物件 | （待 BRD-002 補 FR） |
| `domain/port/DisputeRepository` | 持久化埠 | — |
| `application/DisputeService` | 受理／舉證／責任判斷／拒付表示 | （待補 FR） |
| `infrastructure/jdbc/DisputeRepositoryImpl` | JDBC 實作 | — |
| `interfaces/rest/DisputeController` | REST 端點 | （待補 FR） |

---

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

| 表 | 關鍵欄位 | 約束（實測） |
|---|---|---|
| `DISPUTE_CASES` | `DISPUTE_NO` VARCHAR2(32) **UNIQUE**；`ACCOUNT_ID`；`CARD_ID`；`TRANSACTION_ID`；`AUTHORIZATION_ID`；`ORG`；`ARN`；`REASON_CODE`；`DISPUTE_TYPE`；**`DISPUTE_REASON`**（V2）；`STAGE` DEFAULT `'INQUIRY'`；**`WORKFLOW_STAGE`**（V2）；`CURRENCY` CHAR(3) DEFAULT `'TWD'`；`AMOUNT` NUMBER(19)；`EVIDENCE_REQUIRED`/`EVIDENCE_SUBMITTED` JSON DEFAULT `'[]'`；`REPRESENTMENT_COUNT` NUMBER(5)；`LIABILITY`；`RESPONSE_DEADLINE` TIMESTAMP(6) WITH TIME ZONE；**`CHARGEBACK_TYPE`**（V2）；**`CHARGEBACK_REASON`**（V2）；`STATUS` DEFAULT `'OPEN'`；`OPENED_AT`/`CLOSED_AT`；`ASSIGNED_TO` | `CK_DISPUTE_CASES_TYPE`：`dispute_type IN ('CHARGEBACK','INQUIRY','RETROACTIVE','FRAUD','DUPLICATE')`；**`CK_DISPUTE_CASES_REASON`**：`dispute_reason IN ('UNAUTHORIZED','DUPLICATE','GOODS_SERVICES','OTHER')`；**`CK_DISPUTE_CASES_WORKFLOW_STAGE`**：7 值；**`CK_DISPUTE_CASES_CHARGEBACK_TYPE`**：3 值；`org IN (7 值，含 INTERNAL)`；`stage IN (6 值)`；`amount >= 0`；`liability IN (4 值)`；`status IN (6 值)` |
| `DISPUTE_TIMELINES` | `DISPUTE_ID`（FK→`CASES.ID`）；`STAGE`；`ACTION` VARCHAR2(32)；`ACTOR` VARCHAR2(40)；`OCCURRED_AT`；`DETAIL` JSON | FK(`DISPUTE_ID`) REFERENCES `CASES`(`ID`) |

> 含 `CREATED_AT`/`UPDATED_AT` TIMESTAMP(6) WITH TIME ZONE、`VERSION` 樂觀鎖。
>
> **⚠️ 兩軸之拆分（`CR-017`，BEAM PMO 2026-09-22 裁定採雙欄位）**：
> 契約之 `disputeType`（**爭議原因**）對映 **`DISPUTE_REASON`**；
> 契約之 `stage`（**本行內部作業階段**）對映 **`WORKFLOW_STAGE`**。
> `DISPUTE_TYPE` 回歸**程序類型**語意（`CHARGEBACK`／`INQUIRY`／`RETROACTIVE`；
> `FRAUD`／`DUPLICATE` 為過渡期遺留值），`STAGE` 保留**卡組織程序階段**
> （`SDD-005 §7.1`）並以契約新欄 `orgStage` 回傳。
>
> **V1 基線已凍結**（`SAD-003 §9`）；V2 變更見 `V2__dispute_reason_workflow_stage_chargeback.sql`。

---

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

| 端點 | 方法 | 說明 | 對應 FR |
|---|---|---|---|
| `/api/v1/disputes` | POST | 爭議受理 | （待 BRD-002 補 FR） |
| `/api/v1/disputes/{disputeId}` | GET | 爭議查詢 | （待補 FR） |
| `/api/v1/disputes/{disputeId}/chargebacks` | POST | 拒付表示 | （待補 FR） |
| `/api/v1/disputes/stats` | GET | 爭議業務統計（`CR-026`；供 `/dashboard` 業務 KPI） | （待 BRD-002 補 FR） |

> 錯誤碼：`404` 找不到案件、`400` 舉證不完整、`409` 時限外。

---

## 7. 關鍵邏輯

- **舉證時限**：`RESPONSE_DEADLINE` 由組織規範推算（VISA/MC/NCCC 各異），逾期自動標 `LOST`。
- **稽核留痕**：每階段動作寫 `DISPUTE_TIMELINES`（`DETAIL` JSON 載明證據）。
- **責任判斷**：依舉證結果與組織規則定 `LIABILITY`。

**`CR-017`（BEAM PMO 2026-09-22 裁定）後定案之實作要點**

1. **三軸並存**：`DISPUTE_REASON`（爭議原因，契約 `disputeType`）／`STAGE`（卡組織程序階段，
   契約 `orgStage`）／`WORKFLOW_STAGE`（本行作業階段，契約 `stage`）；
   另有 `STATUS`（案件處理，`SDD-005 §7.2`）與 `LIABILITY`（正交屬性）**契約未回傳**。
2. **開案**：`STAGE = INQUIRY`（`SDD-005 §7.1` 轉換 1 之「受理並調單」）、
   `WORKFLOW_STAGE = OPENED`、`STATUS = OPEN`；`DISPUTE_TYPE` 寫入 `INQUIRY`（程序類型）。
   **`ORG` 預設 `INTERNAL`**（契約未帶 org，見 §10）。
3. **扣款／仲裁**：依 `FIRST → SECOND → ARBITRATION` **順序**推進（`SM-01`，跳號回
   `CC-DSP-3001`）；**兩軸同步前進**——`STAGE` 依 `§7.1`、`WORKFLOW_STAGE` 扣款 → `UNDER_REVIEW`、
   仲裁 → `ARBITRATION`、`STATUS` → `REPRESENTED`；並記錄 `CHARGEBACK_TYPE`／`CHARGEBACK_REASON`／
   `EVIDENCE_SUBMITTED`、`REPRESENTMENT_COUNT + 1`。終態（`CLOSED`）→ `CC-DSP-3002`。
4. **冪等**：開案以冪等鍵**派生主鍵**；扣款提交若「同類型且已達目標階段」→ **回傳現況**
   （`SM-03` 重複執行同一轉換），不重複計次、不拋錯。

### 7.x 統計端點（`CR-026`，供 `/dashboard` 業務 KPI）

**端點**：`GET /api/v1/disputes/stats` → `DisputeStats`（★ `CR-043 §3.1` #6：原 `DisputeStatsView`，與實作 `domain/model/DisputeStats` 統一）

**聚合邏輯**（純查詢、`GROUP BY`、不快取；直查 `DISPUTE_CASES`）：

| 欄位 | 來源 | 說明 |
|---|---|---|
| `total` | `COUNT(*)` | 案件總數（由後端計算；前端不得自行相加分組） |
| `byStatus` | `GROUP BY STATUS` | 依 `STATUS` 之真實分佈（**不補 0**；無資料之狀態不列入） |
| `openCases` | `COUNT(*) WHERE STATUS NOT IN ('WON','LOST','CLOSED')` | 處理中案件數 |

> ⚠️ **`openCases` 定義不得自行推導**：`OPEN`／`EVIDENCE_PENDING`／`REPRESENTED` 計入；
> `WON`／`LOST`／`CLOSED` 不計入（終態）。與 `byStatus` 之關係：
> `openCases = byStatus['OPEN'] + byStatus['EVIDENCE_PENDING'] + byStatus['REPRESENTED']`。

> ⚠️ 無查詢參數、不快取、不補零——與 `SCS-003 §7.9`、`SCS-001 §7.x`（授權）一致。

### 7.y 事件輸出（Transactional Outbox）

**掛點**：`POST /api/v1/disputes`（爭議受理）→ **同交易**寫入 `outbox`（`SDD-001 §3.15`）

**事件**：`dispute.case.opened.v1`（`SAD-002 §3.9`、`SDD-006 §4.2`）

| 項目 | 值 | 依據 |
|---|---|---|
| Kafka 主題 | `cc.dispute.case.opened`（＝`cc.<aggregateType>.<eventType 去版本>`） | `OutboxPublisher.topicFor` |
| 分區鍵 | `aggregateId` ＝ **`disputeId`** | `SAD-004 §5.4`（同聚合有序） |
| `aggregate_type` | `dispute` | — |
| `event_type`（outbox 欄） | `dispute.case.opened`（**不含**版本） | `SDD-001 §3.15` |
| `event_version` | `1` | — |
| payload | `disputeId`／`accountId`／`transactionId`／`disputeType`／`amount`（**恰 5 欄**） | `SDD-006 §4.2` |

**寫入時機**：`DisputeService.open` 於 `repository.save(dispute)` **之後**、**同一交易**（`@Transactional`）內 append——
否則會出現「爭議已受理、事件未發」之落差。

**冪等**：開案以冪等鍵**派生主鍵**；同鍵重送**於 `save` 之前即早退**（回現況），故**不重複發射事件**。

> ⚠️ **`amount` 為最小單位（分）**（`SDD-006 §2.5`）——**禁止浮點**。

> ⚠️ **`disputeType` 為契約軸（爭議原因）**：寫入 payload 者為 `DISPUTE_REASON`（`UNAUTHORIZED`／`DUPLICATE`／
> `GOODS_SERVICES`／`OTHER`），**非** `DISPUTE_TYPE`（程序類型，開案恆為 `INQUIRY`）——見 §5 之雙軸拆分。

**`dispute.case.closed.v1` 尚未發射（誠實揭露）**：其 payload 為 `disputeId`／`resolution`／`adjustAmount`，
惟 `Dispute` 聚合**無 `resolution`／`adjustAmount` 欄位**，且契約僅 3 端點、**無結案路徑可達**（見 §10 #3）
→ **不臆造欄位**；待裁定後補（`CR-027 §6`）。

**消費者**：`notification`——⚠️ **尚未實作**（`CR-029 §5 C-6`：**無對應模板**，需**業務文案**）。

> ★ **更正（`CR-033`，2026-10-02）**：本節原載消費者含 **`posting`**——**不成立**：
> 現行權威清單 `SAD-004 §5.2` **未列 `posting`**；`SDD-003 §15` 之
> `Dispute → Posting`（帳務調整：暫付／扣回）屬**結案**（`case.closed`）路徑且為**同步**，
> **非本事件（`opened`）之消費**。→ **`posting` 非 `dispute.case.opened.v1` 之消費者。**

---

## 8. 外部依賴與降級

| 依賴 | 失敗處理（Stand-in） |
|---|---|
| `posting-service`/`authorization-service`（原交易） | 經查詢取得；交易資料缺失標 EXCEPTION 轉人工 |

---

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

- 爭議受理→舉證→責任判斷→結案
- 拒付表示（chargeback representation）、時限外拒絕
- 舉證 JSON 結構校驗、組織別（ORG）規範差異

---

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

- **`BRD-002` 缺爭議／拒付之顯式 FR 編號**（追溯缺口，須 SA 補 `DIS-*` 並回填）。
- 各組織（VISA/MC/NCCC）之拒付時限與舉證規範須引用組織文件，待確認。
- ~~`dispute-service` 骨架與埠號尚未產生（規格先行）~~ → **已產生，埠號 8093，已啟動實測**。

**`CR-017` 執行後新增之限制（2026-09-22）**

1. **卡組織（`ORG`）由服務端預設 `INTERNAL`**：契約之 `OpenDisputeRequest` **未帶 `org`**
   （DDL 為 `NOT NULL`）；待契約擴充後改由請求帶入。
2. **卡組織時限未參數化**：`RESPONSE_DEADLINE` 固定為「開案 + 30 日」
   （契約示例之天數）；組織別時限（VISA／MC／NCCC 各異）待規範文件接入。
3. **無結案端點**：契約僅 3 端點（開案／查詢／扣款）；`WON`／`LOST` → `CLOSED`
   之流轉（`SDD-005 §7.2` 轉換 7～11）**無 API 路徑可達**——本期不為其發明端點。
4. **`DISPUTE_TIMELINES` 未寫入**：稽核軌跡表已存在，惟本期**未寫入**階段動作
   （`§7` 原載「每階段動作寫 TIMELINES」）——待與狀態轉換一併實作。
5. **`LIABILITY` 恆為 `PENDING`**：責任判斷需舉證結果與組織規則，本期不具備。
6. **`REASON_CODE`／`ARN` 未接**：卡組織代碼與調單參考號需組織檔案／訊息接入。
7. **逾期自動標 `LOST` 未實作**：需排程掃描 `RESPONSE_DEADLINE`（同 `#A-79` 之批次手法），
   本期未做。

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

8. **`dispute.case.closed.v1` 不可發射**：契約（`SDD-006 §4.2`）之 payload 為 `disputeId`／
   `resolution`／`adjustAmount`，惟 `Dispute` 聚合**無該二欄位**，且 §10 #3 已載「**無結案端點**」
   → **無結案路徑可達**。**不臆造欄位**；須先裁定「補聚合欄位」或「改契約 payload」
   （已記 `CR-027 §6 批次 A-4`）。本期**僅實作 `opened.v1`**。
9. **事件消費者未實作**：`dispute.case.opened.v1` 之消費者為 **`notification`**，
   惟**尚未訂閱** → 現階段為「**有人發、沒人收**」。⚠️ **阻塞為「無對應模板」**
   （需**業務文案**，`CR-029 §5 C-6`／`CR-030 §4.2`）。
   ★ **更正（`CR-033`）**：原載消費者含 `posting`——**不成立**（見 §7 之更正）。
10. **`sequenceNo` 為「同 JVM 內遞增之時間戳基底序號」**：**多實例**部署可能重複；
    與 `CR-027 §5 #12`（`card` 服務之同型問題）**同一類**——正解為「聚合層級持久化序號」
    （`SAD-004 §5.4`），本期未做。

---

## 11. 修訂記錄

| 版本 | 日期 | 修訂者 | 說明 |
|---|---|---|---|
| 1.0 | 2026-09-19 | 開發團隊 | 初版（規格先行）；載明四項已知限制 |
| 1.1 | 2026-09-19 | 開發團隊 | 依 **`CR-003`** 更新：①§10 移除「狀態機建議建模」之限制（已完成），新增 §10「規格補記」5 項——狀態機三欄對照、`WON`／`LOST` 與 `liability` 對應待確認、組織時限參數未取得、轉換全未實作、`CR-004` 平台缺口；②**新增本修訂記錄區**（原文件欠缺） |
| 1.2 | 2026-09-22 | WorkBuddy AI | **`CR-017` 執行完成（BEAM PMO 裁定：A／B 採雙欄位、C 補欄）**——①**§5 資料模型**：補 `DISPUTE_REASON`／`WORKFLOW_STAGE`／`CHARGEBACK_TYPE`／`CHARGEBACK_REASON` 與 4 個具名約束，並載明**兩軸之拆分**；②**§7** 新增實作要點（三軸並存、開案初始值、扣款順序與兩軸同步、冪等）；③**§10** 移除「骨架未產生」、新增 7 項限制；④**補建本修訂記錄區**（原文件欠缺）。單測 19 例 0 失敗；真機 e2e 六項（含雙軸同列獨立之 DB 複核） |
| 1.3 | 2026-10-01 | WorkBuddy AI | **`CR-026` 執行**：①**§6 API 表**新增 `GET /api/v1/disputes/stats`；②**§7.x** 新增統計端點規格（`total`＝`COUNT(*)`、`byStatus`＝`GROUP BY STATUS` 不補零、`openCases`＝`STATUS NOT IN ('WON','LOST','CLOSED')`）；③契約 `dispute-service.yaml` 新增 `DisputeStatsView`（端點數 3→4）；④`DisputeStats` domain model＋`DisputeRepository.stats()`＋`JdbcDisputeRepository`＋`DisputeService`＋`DisputeController`＋`DisputeServiceTest` 真測 `statsReflectsState`。單測 20 例 0 失敗 |
| 1.4 | 2026-10-01 | WorkBuddy AI | **`CR-027` 批次 A-4（`#A-133`）**：①**§7.y 新增事件輸出規格**（`dispute.case.opened.v1`——主題／分區鍵／payload 5 欄／寫入時機／冪等；併載 `closed.v1` 不可發射之理由）；②**§10 新增 3 項限制**（`closed.v1` 不可發射、消費者未實作、`sequenceNo` 多實例風險）；③★ **併修文件缺陷**——本檔原有**兩個 `## 11. 修訂記錄`**（1.0／1.1 與 1.2／1.3 分列兩處），已**合併為單一表** |

**規格補記（2026-09-19，`CR-003`）**：

1. **✅ 狀態機已建模**：原「`DISPUTE_CASES.status`／`STAGE` 轉換**強烈建議**建模為
   `SDD-005` 正式狀態機」之建議**已完成**。`SDD-005 §7` 已**拆分為兩個狀態機**
   ＋ 一個正交屬性：

   | 小節 | 名稱 | 對應欄位 | 值域（實測 CHECK） |
   |---|---|---|---|
   | §7.1 | 爭議階段機 | `DISPUTE_CASES.stage` | `INQUIRY`／`FIRST_PRESENTMENT`／`SECOND_PRESENTMENT`／`PRE_ARB`／`ARBITRATION`／`CLOSED` |
   | §7.2 | 爭議案件狀態機 | `DISPUTE_CASES.status` | `OPEN`／`EVIDENCE_PENDING`／`REPRESENTED`／`WON`／`LOST`／`CLOSED` |
   | §7.3 | **正交屬性**（非狀態機） | `DISPUTE_CASES.liability` | `ISSUER`／`ACQUIRER`／`MERCHANT`／`PENDING` |

   **原單一狀態機之值域與 DDL 幾乎無交集**（僅 `CLOSED` 一值相同）——
   因其將「卡組織階段」與「案件處理／結果」**兩個正交分類軸**繪於一圖，
   且 `ARBITRATION`（階段）與 `RESOLVED_*`（責任歸屬）分屬不同欄位。
   實體模型本即正確（三欄分立），**無須變更 DDL**。
2. **`WON`／`LOST` 與 `liability` 之精確對應待確認**：`SDD-005 §7.3` 已載明
   `WON`／`LOST` 定案後寫入 `liability`，但「孰為 `ACQUIRER`、孰為 `MERCHANT`」
   須依卡組織規則確認，不得由實作自行認定。
3. **`PRE_ARB`／`ARBITRATION` 之組織時限參數尚未取得**：`BRD-001` BF-09 僅至
   「提交卡組織仲裁」；`BR-09-01` 要求受卡組織時限控管，惟各階段時限值未提供，
   實作前須補（與上列第 2 點之組織文件同一來源）。
4. **狀態機轉換全部尚未實作**：本服務目前僅有骨架（無領域模型），
   `SDD-005 §7.1` 之 10 條與 `§7.2` 之 11 條轉換均無程式路徑。
5. **平台層請求層例外之狀態碼缺口**：缺必要標頭、非法 JSON、不支援之方法等
   會回 **500 `CC-PLT-5000`**（契約要求 400／405）。屬**全平台**問題，
   已立 **`CR-004`**（待核定）。

> ⚠️ **本檔原於此處另有一個 `## 11. 修訂記錄`（1.0／1.1）**，與前段（1.2／1.3）**重複**——
> 已於 **1.4**（`#A-133`）**合併為單一表**，本區塊移除。
| 1.5 | 2026-10-02 | **`CR-033`：更正事件消費者之記載**——①**§7** 原載「消費者：**`posting`**／`notification`」→ 更正為**僅 `notification`**（現行 `SAD-004 §5.2` 未列 `posting`；`SDD-003 §15` 之 `Dispute → Posting`（帳務調整）屬**結案**路徑且為同步，非 `opened` 之消費）；②**§10 #9** 同步更正，並載明真正阻塞為「**無對應模板**」（需**業務文案**）。★ **零程式變更**（純文件更正） |
