# SCS-023　卡片商品組合與測試卡片檔產生規格

| 項 | 內容 |
|---|---|
| 編號 | `SCS-023` |
| 版本 | **1.4** |
| 日期 | 2026-09-20 |
| 作者 | WorkBuddy AI |
| 核定 | 待 BEAM PMO 核定 |
| 對應需求 | BEAM PMO 2026-09-20 指示（進件缺口 ＋ 測試基石）；`BRD` 商品設定；`NFR` 可測試性 |
| 前置文件 | `SDD-001 §3.3`（product／parameter）、`SCS-003`（卡片服務）、`CSP-001 §8`（組態與產生器紀律） |

---

## 1. 目的與範圍

本案過去之測試卡片是**手刻幾筆**、且維度固定（個人／公司、主附卡、額度模型…），
有兩個結構性問題：

1. **用的是列舉（enumeration），不是組合（composition）**——維度與取值寫死在程式或文件裡，
   新增一種商品就要改程式。這**違背「商品可以組合」之需求**：
   例如客戶之 **eLOAN 無擔保信貸**，即是以發卡系統之**虛擬卡**承作，
   它既不是「信用卡」也不是「簽帳卡」，而是「既有能力之重新組合」。
2. **規模不足**——無法支撐「各類交易場景」之批次測試。

本規格定義：

- §3～§4：**商品組合模型**（能力維度 ＋ 參數化落地），維度**可擴充、非固定列舉**；
- §5：**商品範本（Profile）**——只是「常用組合之預設值」，不是枚舉；
- §6：**測試卡片檔產生器**——依組合產生具規模、Luhn 合法之卡片檔；
- §7：**Stripe 對接方式**與**無金鑰時之降級做法**。

> **本文件不處理**：進件流程本身（受理／徵信／製卡）之狀態機——
> 該部分另立規格，本文件僅定義「商品組合」與「卡片檔產生」此一**測試基石**。

---

## 2. 名詞

| 名詞 | 說明 |
|---|---|
| **商品（Product）** | 可發行之卡片產品。`product-service` 之 `products` 表一列。 |
| **能力維度（Capability dimension）** | 描述商品「能做什麼／怎麼做」之**一個參數**，如 `issuance.form`。**可增減。** |
| **商品組合（Composition）** | 一組「維度鍵 → 值」之集合，即一個商品之完整定義。 |
| **商品範本（Profile）** | 常用組合之**預設值集合**，供快速套用；**可被覆寫**，故不是枚舉。 |
| **卡片檔（Card file）** | 依商品組合產生之測試卡片清單（JSON／CSV），供批次發卡與場景測試使用。 |
| **測試 BIN** | 不使用客戶真實 BIN。本期採 **Stripe 共用測試 BIN `424242`**（見 §6.2）。 |

---

## 3. 設計原則：組合優於列舉（**BEAM PMO 2026-09-20 明確指示**）

> BEAM PMO 原話：「卡片情境矩陣是符合現有客戶的架構，但不適用他行。
> 從未來參數設定的角度，**不應該局限維度與取值**，因為違背了『商品可以組合』的需求。」

### 3.1 為何不能用 enum

若把「個人／公司」「主附卡」「單一／共用額度」等寫成 enum 欄位：

- 新增一種商品（如 eLOAN 虛擬卡）→ **必須改 DDL、改程式、改文件**；
- 他行／新客戶的商品維度不同 → 舊 enum **不適用**，又得再改；
- 「同一張卡在某情境可分期、某情境不可」這類**條件式能力**無法表達。

### 3.2 改為「參數集合」

商品 = **一組參數**。新增商品 = **新增一組參數值**，不必改結構。

落地位置：**既有基礎設施**——`product-service` 之 `parameters` 表
（`namespace`／`param_key`／`param_value` JSON／`value_type`／`effective_from`／`effective_to`）。

> ⚠️ **不新增 enum 欄位**。`products` 表既有之 `org`／`card_grade`／`ticket_type` 為
> 「對外呈現用之摘要」，**非能力定義**；能力一律進 `parameters`，避免兩處打架。
> （後續可評估將該三欄降為「由參數推導」，屬重構，本期不做。）

### 3.3 命名與型別規範

- 維度鍵採 **點分式小寫**，如 `issuance.form`、`limit.model`；
- `value_type` 沿用 `parameters` 既有列舉：`STRING`／`NUMBER`／`BOOLEAN`／`JSON`／`AMOUNT`／`RATE`；
- `namespace` 填 **`product_code`**，使參數可隨商品版本生效（`effective_from`／`effective_to`）；
- **維度鍵之增減不視為結構變更**——此即「可組合」之核心。

---

## 4. 能力維度（`CAPABILITY_KEYS`）

> ⚠️ **本表是「目前收錄」，不是「全部允許」。** 新增維度只需加一列參數，
> **不需改結構、不需改本文件之結構章節**——這是刻意設計。

| 維度鍵 | 型別 | 說明 | 預設 |
|---|---|---|---|
| `issuance.form` | STRING | 發卡形式：`PHYSICAL`／`VIRTUAL`／`BOTH` | `PHYSICAL` |
| `issuance.virtual_delivery` | STRING | 虛擬卡交付方式：`API`／`WALLET`／`NONE` | `NONE` |
| `limit.model` | STRING | 額度模型：`SINGLE`／`SHARED`／`SUB_LIMIT`／`NONE` | `SINGLE` |
| `limit.shared_scope` | STRING | 共用額度歸屬：`ACCOUNT`／`FAMILY`／`CORP`／`NONE` | `NONE` |
| `limit.grant_required` | BOOLEAN | 是否須徵信給額（預付卡／eLOAN 虛擬卡可為 false） | `true` |
| `revolving.enabled` | BOOLEAN | 循環信用（可否只繳最低） | `true` |
| `revolving.rate_annual` | RATE | 循環年利率 | — |
| `payment.grace_days` | NUMBER | 寬限期天數 | `20` |
| `payment.auto_debit` | BOOLEAN | 是否自動扣抵（帳戶連結） | `false` |
| `installment.enabled` | BOOLEAN | 可分期 | `false` |
| `cash_advance.enabled` | BOOLEAN | 可預借現金 | `false` |
| `mcc.allowed` | JSON | 允許之 MCC 清單（null＝不限） | `null` |
| `mcc.blocked` | JSON | 禁止之 MCC 清單 | `null` |
| `ticket.enabled` | BOOLEAN | 電子票證聯名 | `false` |
| `ticket.type` | STRING | `EASYCARD`／`IPASS`／`ICASH`／`null`（對應 `cards.ticket_type`） | `null` |
| `supplementary.allowed` | BOOLEAN | 可發附卡 | `false` |
| `supplementary.max` | NUMBER | 附卡上限 | `0` |
| `supplementary.share_limit` | BOOLEAN | 附卡是否共享主卡額度 | `false` |
| `cross_border.enabled` | BOOLEAN | 可跨境交易 | `true` |
| `dcc.enabled` | BOOLEAN | 動態貨幣轉換 | `false` |
| `tokenization.platforms` | JSON | 可綁定之平台（APPLE_PAY…） | `[]` |
| `billing.cycle_day` | NUMBER | 結帳日 | `1` |
| `billing.currency` | STRING | 幣別 | `TWD` |
| `kyc.level` | STRING | 徵信等級需求 | `STANDARD` |
| `network_allowed` | JSON | 可用國際組織 | `["VISA"]` |

**條件式能力**：值可為 `JSON`，用以表達「某情境才可」，
例如 `installment.enabled` 給 `{"merchant_scope":"ALL","min_amount":1000}`——
**不改結構即可表達條件**，這是選 JSON 而非純 BOOLEAN 之理由。

---

## 5. 商品範本（Profile）

> **範本只是「常用組合的預設值」，允許逐鍵覆寫**。因此「新增商品」＝「新增範本或覆寫參數」，
> 不是新增 enum。

| 範本 | 組合重點 | 代表情境 |
|---|---|---|
| `REVOLVING_CREDIT` | `revolving.enabled=true`、`limit.model=SINGLE`、`installment.enabled=true` | 一般信用卡 |
| `CHARGE_CARD` | `revolving.enabled=false`、`payment.grace_days=0` | 簽帳卡（須一次付清） |
| `VIRTUAL_LOAN_CARD` | `issuance.form=VIRTUAL`、`limit.grant_required=false`、`revolving.enabled=true`、`issuance.virtual_delivery=API` | **eLOAN 無擔保信貸之虛擬卡** |
| `PURCHASE_CARD` | `mcc.allowed=[…]`、`cash_advance.enabled=false`、`limit.model=SHARED`、`limit.shared_scope=CORP` | 採購卡（P-Card） |
| `COBRAND_TICKET` | `ticket.enabled=true`、`ticket.type=EASYCARD` | 悠遊聯名卡 |
| `PREPAID` | `limit.model=NONE`、`limit.grant_required=false`、`kyc.level=BASIC` | 預付卡 |
| `CORP_TRAVEL` | `limit.model=SHARED`、`limit.shared_scope=CORP`、`cross_border.enabled=true`、`dcc.enabled=true` | 商務旅遊卡 |

---

## 6. 測試卡片檔產生

### 6.1 為何需要產生器（而非手刻）

- 場景測試需要**規模**；手刻無法覆蓋組合；
- 卡號必須**通過 Luhn**，否則下游（授權、發卡行檢核）會視為無效卡號；
- 產生器須**可重現**（同一 seed → 同一批卡號），否則測試不可重跑。

### 6.2 BIN 之選用（⚠️ 重要前提）

**不得使用客戶真實 BIN。** 本期採 **Stripe 共用測試 BIN `424242`**。

> **查證結果（2026-09-20，官方文件）**：
> - Stripe 官方 API 範例（`livemode:false`）之 Issuing card 為 `brand=Visa`、`last4=4242`
>   → 測試卡使用**共用**測試 BIN（`4242 42…`）；
> - 「dedicated BIN」在官方文件中**僅出現於正式環境**（Autopilot／STIP）；
> - 官方明載：Issuing 測試卡僅能用於**自己帳號內**測試，不得用於外部消費。
>
> ⚠️ 因此「由 Stripe 分配專屬虛擬測試 BIN 範圍」之假設**不成立**——
> 產生器以共用測試 BIN 產生卡號，僅供**本系統內部**批次與場景測試，
> **不可視為對外可交易之卡號**。

### 6.3 卡號結構（與既有程式一致）

對齊 `CardNumberIssuer`：

- PAN 長度 **16**，主體 **15** 碼，末 1 碼為 **Luhn 檢查碼**；
- 主體 = **BIN（前 6 碼 `424242`）** + 序號 9 碼；
- `bin` 欄位（`cards.bin CHAR(8)`）取 `CardNumberIssuer.bin(pan)` 之前 6 碼。

### 6.4 產生器

`tools/gen_test_cards.py`——比照既有產生器之紀律：

| 參數 | 說明 |
|---|---|
| `--profile` | 商品範本（§5）；可重複給，或給 `--profile ALL` |
| `--count` | 每一組合之卡片數 |
| `--seed` | 亂數種子（**可重現**） |
| `--out` | 輸出檔（JSON／CSV 依副檔名） |
| `--check` | 只驗證既有卡片檔，不寫入 |
| `--force` | 覆寫已存在之輸出檔 |

**覆蓋策略**：對每一（商品範本 × 情境）產生卡片，情境含
持卡人型別（個人／公司）、卡片角色（主卡／附卡）、狀態（未開卡／正常／鎖卡／掛失）。

---

## 7. Stripe 對接

### 7.1 發卡側：我們是「授權決策引擎」

Stripe 產生測試授權後，送 `issuing_authorization.request` webhook 予我方，
**須於 2 秒內**回應核准／拒絕；逾時依 Stripe 設定自動核准或拒絕。

```
POST /v1/test_helpers/issuing/authorizations     # 產生測試授權（Stripe 端）
  → issuing_authorization.request                # 送至我方 webhook
  → 我方以 limit.model／block_code／mcc 等參數決策
  → 回應 approve / decline
```

此接法才是真正測到本系統：決策邏輯位於**真實訊息流**之中間，而非自我呼叫。

### 7.2 收單側：以官方測試卡號模擬他行卡

使用 Stripe 官方測試卡號表（已於 2026-09-20 查證）：

| 情境 | 卡號 |
|---|---|
| 成功（Visa／MC／Amex／UnionPay） | `4242424242424242`／`5555555555554444`／`378282246310005`／`6200000000000005` |
| 拒付（generic／餘額不足／遺失／被盜） | `4000000000000002`／`…9995`／`…9987`／`…9979` |
| 過期／CVC 錯誤／卡號錯誤 | `4000000000000069`／`…0127`／`4242424242424241` |
| 3DS 必做／支援／不支援 | `4000000000003220`／`4000000000003055`／`378282246310005` |
| 爭議（詐欺／未收到／ inquiry） | `4000000000000259`／`…2685`／`…1976` |

### 7.2.1 依卡組織分類之測試卡號（BEAM PMO 2026-09-20 提供，Stripe 回覆）

> ⚠️ **用途釐清（重要）**：本表為 **Stripe 收單（payment）測試卡號**，
> 用途是「**模擬持卡人持他行卡前來刷卡付款**」，供我們驗證
> 授權／拒付／3DS／爭議等**收單側**路徑。
> **本表不得作為 embossing file（製卡檔）之卡號來源**——
> 製卡檔是我們發卡時交付製卡廠之檔案，卡號應來自我們自己的 BIN，
> 即 §6 之 `tools/gen_test_cards.py`（92 張、Luhn 全過、可重現）。
> （Stripe 回覆中「可用於 generating embossing file」之說明，
> 研判係將我方需求理解為「測試付款」，與我們發卡之用途不同。）

| 卡組織 | 測試卡號 | CVC | 有效期 | 位數 |
|---|---|---|---|---:|
| Visa | `4242424242424242` | 任意 3 碼 | 任意未來日期 | 16 |
| Visa（簽帳卡） | `4000056655665556` | 任意 3 碼 | 任意未來日期 | 16 |
| Mastercard | `5555555555554444` | 任意 3 碼 | 任意未來日期 | 16 |
| **Mastercard（2 系列）** | `2223003122003222` | 任意 3 碼 | 任意未來日期 | 16 |
| Mastercard（簽帳卡） | `5200828282828210` | 任意 3 碼 | 任意未來日期 | 16 |
| Mastercard（預付卡） | `5105105105105100` | 任意 3 碼 | 任意未來日期 | 16 |
| American Express | `378282246310005` | **任意 4 碼** | 任意未來日期 | **15** |
| American Express | `371449635398431` | **任意 4 碼** | 任意未來日期 | **15** |
| Discover | `6011111111111117` | 任意 3 碼 | 任意未來日期 | 16 |
| Discover | `6011000990139424` | 任意 3 碼 | 任意未來日期 | 16 |
| Discover（簽帳卡） | `6011981111111113` | 任意 3 碼 | 任意未來日期 | 16 |
| Diners Club | `3056930009020004` | 任意 3 碼 | 任意未來日期 | 16 |
| **Diners Club（14 位）** | `36227206271667` | 任意 3 碼 | 任意未來日期 | **14** |
| JCB | `3566002020360505` | 任意 3 碼 | 任意未來日期 | 16 |
| UnionPay（銀聯） | `6200000000000005` | 任意 3 碼 | 任意未來日期 | 16 |
| UnionPay（簽帳卡） | `6200000000000047` | 任意 3 碼 | 任意未來日期 | 16 |
| **UnionPay（19 位）** | `6205500000000000004` | 任意 3 碼 | 任意未來日期 | **19** |

**測試重點（卡組織識別／BIN 判定）**：

1. **Mastercard 2 系列（`2223003122003222`）必測**：Mastercard 於 2016 年起啟用
   `2221–2720` 號段（原與 `51–55` 併存）。由 Mainframe 轉型之系統常見
   「只認 `51–55`、把 2 系列誤判為他組織」之缺陷——**這是本次測試最應優先覆蓋者**。
2. **長度非常態者必測**：Amex **15 位**、Diners **14 位**、UnionPay **19 位**。
   卡號長度若被假設為固定 16，這三種會被截斷或驗證失敗。
3. **CVC 長度**：Amex 為 **4 碼**，其餘為 3 碼——不可一律以 3 碼檢核。
4. 有效期限「任意未來日期」即可（如 `12/34`）；其餘表單欄位可用任意值。

**與 §7.2 之關係**：§7.2 依**情境**分類（成功／拒付／過期／3DS／爭議），
本節依**卡組織**分類——兩者**正交且互補**：
- 驗證「拒絕碼路徑是否正確」→ 用 §7.2
- 驗證「卡組織識別與號段判定是否正確」→ 用本節
- 完整覆蓋宜**交叉組合**（如：以 JCB 卡號觸發餘額不足之拒付）。

### 7.3 憑證之存放（**2026-09-20 BEAM PMO 提供測試金鑰**）

| 項目 | 做法 |
|---|---|
| 存放位置 | `~/.config/cardcore/stripe-test.env`——**repo 之外**，權限 **600** |
| 載入方式 | `ops/local/env.sh` **若存在才載入**，匯出 `STRIPE_SECRET_KEY`／`STRIPE_PUBLISHABLE_KEY` |
| repo 內 | `ops/local/.env.example` 只放**佔位符**（`sk_test_REPLACE_ME`）；新增 `.gitignore` 排除 `.env`／`*.env`／`secrets/` |
| 禁止事項 | 不得提交至版控、不得寫進任何文件／簡報、不得用於 SIT／UAT／PROD |

> **為何放 repo 之外**：憑證一旦進過版控，即使事後刪除仍留存於歷史。
> 與「停用只能顯式宣告」同一思路——**讓疏忽難以發生**，而非依賴人記得。

### 7.4 ⚠️ 實測發現：本帳號 **FinancialAccount 狀態 pending 卡住發卡**

2026-09-20 BEAM PMO 啟用 Issuing sandbox 後重測，進展：

| 項目 | 結果 |
|---|---|
| 帳號 | `acct_1UHczARxdfYl5r2o`（US、測試模式、`charges_enabled=false`） |
| Issuing 卡 | ✅ **已啟用**（`/v1/issuing/cardholders?limit=1` 可呼叫） |
| 持卡人 | ✅ **可建**（`POST /v1/issuing/cardholders` 成功）→ `ich_1UHdO4Rx…` |
| 持卡人 individual requirements | ✅ **可補**（`first_name`／`last_name`／`dob`／`card_issuing.user_terms_acceptance`）— `past_due=[]`、`disabled_reason=under_review`（不再擋發卡） |
| **FinancialAccount** | ⚠️ **auto-create 後狀態 `pending`，非 `open`**（`fa_test_65VR1GsTfNeWTlEj1e716VR0wWTzSQi39y3bVMxqUICDNY`） |
| 開卡 | ❌ **被擋**：`You cannot create a new card for FinancialAccount fa_test_… because its status is pending. Please try again with an open FinancialAccount.` |

**已嘗試且失敗之啟用手段**（避免 BEAM PMO 重複測）：
1. `POST /v2/money_management/financial_accounts/{id}/open` → 404
2. `POST /v2/money_management/financial_accounts/{id}/activate` → 404
3. `POST /v2/money_management/financial_accounts/{id}`（Update，僅改 `display_name`/`metadata`）→ 200 但 status 仍 `pending`
4. `status_details` → `null`（無提示）
5. API 版本掃描：`2025-08-27.basil` 支援 POST、`2025-12-15.preview` 支援 GET——**不同 verb 對不同版本**，無單一版本同時支援 GET+POST+Update（Stripe v2 文件混亂所致）

**判斷**：從 `pending → open` 之轉變**不是 API 動作**，是 Stripe **Dashboard 之「首次 Issuing 設定」流程**——
需於後台對 `fa_test_…` 加入 funding source（測試模式通常為銀行帳戶／top-up）後自動轉 `open`。
具體步驟見 Dashboard 之 Issuing → 該 financial account → "Add funding source"／"Activate"。

> **因此「測試卡號之 BIN」仍未取得本帳號之實證**——先前「共用測試 BIN（4242 42…）」
> 之說法仍僅來自官方文件與 API 範例。
> 待 BEAM PMO 於 Dashboard 完成 FA 設定後重跑 `tools/stripe_smoke.py`，即可拿到真實卡號。

### 7.5 收單側實測：Stripe decline code → ISO 8583 → cardcore（**建議草案**）

以 `tools/stripe_smoke.py`（真實呼叫）實測之結果：

| 情境（token） | Stripe `decline_code` | ISO 8583 | cardcore | 判定 |
|---|---|---|---|---|
| `tok_visa` | —（成功） | — | — | ✅ |
| `tok_chargeDeclined` | `generic_decline` | `05` | `CC-AUTH-1001` | ✅ |
| `tok_chargeDeclinedInsufficientFunds` | `insufficient_funds` | `51` | `CC-AUTH-1003` | ✅ |
| `tok_chargeDeclinedLostCard` | `lost_card` | `41` | `CC-AUTH-1002` | ✅ |
| `tok_chargeDeclinedStolenCard` | `stolen_card` | `43` | `CC-AUTH-1002` | ✅ |
| `tok_chargeDeclinedExpiredCard` | `expired_card` | `54` | `CC-AUTH-1002` | ✅ |
| `tok_chargeDeclinedIncorrectCvc` | `incorrect_cvc` | `N7` | `CC-AUTH-1006` | ✅ |

> ⚠️ **ISO 8583／cardcore 欄為本文件提出之建議對照**，與 `CheckCode` 既有之
> ISO 碼（`LIMIT=51`、`CARD_EXPIRY=54`、`CVV=N7`）方向一致，
> 但**尚未登錄為權威**——若採行，須依 `CSP-003 §2` 程序補登 `SDD-006 §3.4`
> 後重跑 `gen_error_codes.py --write`，**不得於程式內自創**。

### 7.6 對接腳本 `tools/stripe_smoke.py`

| 性質 | 說明 |
|---|---|
| 可重複 | 換環境重跑即可，結果不依賴「誰記得上次測了什麼」 |
| 金鑰安全 | 只從環境變數讀；**只印前綴與長度**，輸出不含金鑰（已實測 `grep` 命中 0） |
| 降級明確 | 未設金鑰時結束碼 **2** 並說明原因，**不做假成功** |
| 能力探測 | 先探 Issuing／原始卡號 API，不可用就**明講**，不靜默略過 |

---

## 8. 實測證據

以 `--profile ALL --seed 42`（`--count` 取預設＝狀態數）產生，輸出 `ops/local/testdata/cards.json`：

| # | 項目 | 實測結果 |
|---:|---|---|
| 1 | 產生之卡號全部通過 Luhn | ✅ **92/92 通過**；PAN 重複 **0** |
| 2 | 同一 seed 重跑產生結果一致（可重現） | ✅ 兩次產生之 SHA-256 **完全相同**（`f7cceed350419b71…`） |
| 3 | 換 seed 產生不同卡號 | ✅ `seed=42` 與 `seed=7` 之 PAN 交集 **0** |
| 4 | 情境覆蓋（商品範本 × 持卡人 × 角色 × 狀態） | ✅ **7** 種範本、**23** 組組合、狀態 **4/4** 全覆蓋（`INACTIVE`／`ACTIVE`／`LOCKED`／`LOST`）；合計 **92** 張 |
| 5 | `--check` 自檢通過 | ✅ `Luhn／BIN／唯一性／組合可行性皆正確` |
| 6 | **組合彈性**：不改程式，以 `--set` 疊出原本沒有的維度 | ✅ `--set loyalty.enabled=true --set loyalty.program=ELOAN_POINT --set installment.max_terms=72 --set mcc.blocked=7995,6051` 皆生效——證明維度**可任意擴充**，不需更動產生器結構 |

**Stripe 對接實測（2026-09-20，真實 `sk_test_`；`tools/stripe_smoke.py`）**

| # | 項目 | 實測結果 |
|---:|---|---|
| 7 | 金鑰可用（帳號識別） | ✅ `acct_1UHczARxdfYl5r2o`；測試模式 |
| 8 | **Issuing 是否啟用** | ❌ **未啟用**（`not set up to use Issuing`）→ 發卡側**無法實測** |
| 9 | 原始卡號 API | ❌ 未開放 → 一律改用 `tok_*`，行為等價 |
| 10 | 收單側 7 情境 | ✅ 全數與預期相符，取得真實 `decline_code`（見 §7.5） |
| 11 | 腳本不洩漏金鑰 | ✅ 輸出中完整金鑰 `grep` 命中 **0** |
| 12 | 無金鑰時降級 | ✅ 結束碼 **2** 並明示原因，**非假成功** |

> ⚠️ **實測中發現並已修之一處缺陷**：`--count` 預設為 3，小於狀態數 4，
> 使 `LOST` **永遠不會出現**，而摘要仍印「情境狀態 4 種」——
> 屬「**號稱有、實際沒有**」的靜默缺陷。已改為：①`--count` 預設改為狀態數
> ②摘要改印**實際出現**之狀態數（`4/4`）③不足時**主動警告**並提示如何提高 `--count`。

---

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

1. ~~**無 Stripe 金鑰**~~ → 已於 v1.2 解除；
   ⚠️ **目前卡在 v2 FinancialAccount 狀態 `pending`**（§7.4）——需於 Dashboard 完成首次資金帳戶設定後重跑。
   **「測試卡號之 BIN」因此仍無本帳號實證**。
   已將對應之 smoke 流程加進 `tools/stripe_smoke.py`（`--scenario issuing`）：完成後自動揭露真實 PAN／CVC／BIN／Luhn。
2. **共用測試 BIN**：產生之卡號僅供本系統內部測試，不具對外可用性（§6.2）。
3. **進件流程未納入**：受理／徵信／製卡之狀態機另立規格，本期未做。
4. **參數尚未落地 `parameters` 表**：本期先落於產生器之設定檔；
   與 `product-service` 之整合（以 `namespace=product_code` 寫入）待後續。

---

## 10. 修訂記錄

| 版本 | 日期 | 修訂者 | 說明 |
|---|---|---|---|
| 1.0 | 2026-09-20 | WorkBuddy AI | 初版。依 BEAM PMO 2026-09-20 指示「朝商品組合角度設計」建立：§3 組合優於列舉、§4 能力維度（可擴充）、§5 商品範本、§6 測試卡片檔產生（Stripe 共用測試 BIN ＋ Luhn）、§7 Stripe 對接與無金鑰降級。已更正「Stripe 分配專屬測試 BIN」之假設（查官方文件） |
| 1.1 | 2026-09-20 | WorkBuddy AI | 產生器實測完成（§8 #1–#6）：92 張卡 Luhn 全過、可重現、換 seed 不重疊、狀態 4/4 覆蓋、`--check` 通過、`--set` 任意維度疊加生效。**修一處靜默缺陷**：`--count` 預設 3 < 狀態數 4 使 `LOST` 永不出現而摘要仍號稱 4 種——改為預設＝狀態數、摘要印實際值、不足時警告。§9 #1 補明「無金鑰」之查證過程與責任歸屬 |
| 1.2 | 2026-09-20 | WorkBuddy AI | **BEAM PMO 提供 `sk_test_` 測試金鑰**：存放於 repo 外之 `~/.config/cardcore/stripe-test.env`（權限 600），`env.sh` 若存在才載入；`.env.example` 只放佔位符；新增 `.gitignore` 排除 `.env`／`*.env`／`secrets/`。新增 `tools/stripe_smoke.py`（金鑰只從環境讀、**只印前綴**、無金鑰時結束碼 2 不做假成功、先探能力再測）。⚠️ **實測兩項新發現**：①本帳號 **Issuing 未啟用** → 發卡側無法實測，「測試 BIN」仍無本帳號實證；②**原始卡號 API 未開放** → 一律改用 `tok_*`。收單側 7 情境全數取得真實 `decline_code`，並提出 `decline_code → ISO 8583 → cardcore` **建議對照**（§7.5，須補登 `SDD-006 §3.4` 方為權威）。§9 #1 改列「發卡側仍被擋」 |
| 1.3 | 2026-09-20 | WorkBuddy AI | **BEAM PMO 啟用 Issuing sandbox 後重測**（§7.4）：持卡人可建可補、`past_due=[]`，但 v2 `FinancialAccount` 自動建出後狀態卡在 `pending`（非 `open`），開卡仍被擋。**已嘗試五種 API 啟用手段皆未成功**（列於 §7.4），判斷為 Stripe Dashboard 之「首次 Issuing 設定」流程（需加 funding source），非 API 動作可解。`tools/stripe_smoke.py` 新增`--scenario issuing` 流程（持卡人→補 individual→取 FA→開卡→retrieve 揭露 PAN/CVC/BIN/Luhn）；§9 #1 改列「卡在 FA pending」。版本探測之副發現：Stripe v2 不同 endpoint 對 API 版本敏感（basil 支援 POST、preview 支援 GET），文件示例與實測不一致。 |
| 1.4 | 2026-09-20 | WorkBuddy AI | 新增 **§7.2.1 依卡組織分類之測試卡號**（BEAM PMO 提供、Stripe 回覆，17 張）。⚠️ **並釐清用途**：本表為 **Stripe 收單（payment）測試卡號**，供模擬「持卡人持他行卡前來刷卡」，**不得**作為 embossing file（製卡檔）之卡號來源——製卡檔應用 §6 `gen_test_cards.py` 產生之自有 BIN 卡號（研判 Stripe 係將我方需求理解為「測試付款」）。本節補足 §7.2 情境表欠缺之 **Discover／Diners Club／JCB／MC 2 系列／各類 debit·prepaid**，並標明四項測試重點：**MC 2 系列（`2221–2720`）最優先**（Mainframe 轉型系統常見誤判）、長度非常態者（Amex 15／Diners 14／UnionPay 19）、Amex CVC 4 碼、以及與 §7.2 情境表之正交互補關係（宜交叉組合測試） |
