# Research: 商家 ezPay 发票开通管理

**Phase**: Phase 0 — 关键技术决策与依据
**Date**: 2026-06-15

> spec 无 NEEDS CLARIFICATION 标记（brainstorming 阶段已与用户确认全部决策）。本文档固化这些决策的"为什么"，供 plan/tasks 与实现阶段参考。

## 决策 1：ezPay 商店注册不做 API 对接，走线下人工

- **Decision**: 平台不在系统内调用任何"注册新商家/申请商店"接口；运营到 ezPay 官网（测试 `cinv.ezpay.com.tw` / 正式 `inv.ezpay.com.tw`）人工完成注册申请，拿到 `MerchantID`/`HashKey`/`HashIV` 后回平台后台录入。
- **Rationale**: ezPay 官方 API（INVI 发票 / 字轨 / BDV 验证 / 批次）**不提供注册类接口**（见 `[[reference-ezpay-invoice-api]]`）。注册涉及工商凭证、人工审核，本就不是 API 能完成的。
- **Alternatives**: ①商家自助注册后录入凭证——被否，用户明确"平台代申请（线下）"；②对接 ezPay 开放注册 API——不存在，否。

## 决策 2：凭证按门店（pos_store）绑定，独立关联表存储

- **Decision**: 新增表 `pos_store_ezpay`，与 `pos_store` 1:1，按门店存一组 ezPay 凭证。不把凭证字段塞进 `pos_store`。
- **Rationale**: 用户选择"独立关联表"。凭证（HashKey/HashIV）是敏感金钥，独立表便于权限收敛与将来扩展；夜市（userType=3）本身不开票、夜市下摊位门店需开票，门店是正确的开票主体单位。
- **Alternatives**: ①凭证字段直接加到 `pos_store`——被否，污染主表且金钥混在通用字段中；②按商家账号（InfoUser）绑定——被否，用户明确按门店。

## 决策 3：状态机 = 三态 + 启用开关

- **Decision**: `ezpay_status` 0未申请 / 1申请中 / 2已开通；另设 `is_enabled` 0停用 / 1启用（仅 status=2 有意义）。
- **Rationale**: 用户选"三态+启用开关（推荐）"。"还没开通"= status∈{0,1}；"还要去开通"= status=0；启用开关满足"已开通后临时停用"无需重走申请。
- **Alternatives**: ①只两态——缺"申请中"过程态，运营无法区分已提交待审 vs 未提交；②五态含"已拒绝"——被否，用户选三态，拒绝的门店回归申请中即可。

## 决策 4：凭证验证用 invoice_search 只读调用，避开 CheckValue

- **Decision**: 录入凭证点"验证并开通"时，后端用 `EzPayConfig(merchantId, hashKey, hashIV)` 调 `EzPay.doPost(BASE_TEST + URL_SEARCH, ...)`，传测试用假发票号+随机码。回应含 `KEY1xxxx`（加解密/金钥错误）→ 凭证无效、拒绝；回应是业务错误（如发票不存在 `INVxxxxx`）或成功 → 加密链路通、凭证有效 → 标记已开通。
- **Rationale**: 发票接口（issue/search/invalid）**请求只发 `MerchantID_`+`PostData_`，不带 CheckValue**（见 `[[reference-ezpay-invoice-api]]` 文档勘误）。而 BDV（checkBarCode/checkLoveCode）需 CheckValue，且其公式官方示例存疑（"接入若校验失败→改 HashIV 在前重试"）。用 `invoice_search` 能干净地只测金钥对错，不受 CheckValue 不确定性影响，且只读不产生真实发票。业务错误（查不到发票）恰恰证明凭证可解密=有效。
- **Alternatives**: ①checkLoveCode 测试——需 CheckValue，公式不确定，否；②不验证——用户体验差（录错到开票才暴露），用户选"录入时验证"，否。

## 决策 5：免用发票属性挂 pos_store，不挂 ezPay 表

- **Decision**: `pos_store` 加 `invoice_exempt`（0需开票/1免用发票）。该属性决定门店是否纳入 ezPay 流程，并供将来自动开票判断"开票 vs 只开收据"。
- **Rationale**: "免用发票"是门店的通用税务属性（小规模商家/部分摊位），非 ezPay 专属。免用门店根本不需要 ezPay 行，放 ezPay 表语义不通。将来订单完成开票也需读此字段，pos_store 是正确归属（与"凭证放独立表"不冲突——那是 ezPay 专属数据）。
- **Alternatives**: ①放 `pos_store_ezpay.need_invoice`——免用门店不需 ezPay 行却要建行表达"不需要"，语义别扭；②新建门店税务表——过度设计，YAGNI。

## 决策 6：标记免用发票由平台运营操作，非商家

- **Decision**: `invoice_exempt` 由平台运营在后台切换；商家端不提供此开关，只提供上传统编。
- **Rationale**: 免用发票是税务判定（月营业额、国税局核定），运营掌握；与"平台代申请"模型一致。商家自助误标会导致漏开票合规风险。

## 依赖与集成

- **复用现有**：`EzPay`/`EzPayConfig`/`EzPayEncryptUtil`（2026-06-15 已实现并经官方数据验证），本期不改其内部，仅业务层调用 `EzPay.doPost` + `EzPayConfig` 构造。
- **若依框架**：`BaseController`/`AjaxResult`/`TableDataInfo`/`@PreAuthorize`/`startPage()` 分页、`@Log` 审计、`MessageUtils.message()` 国际化消息。
- **前端**：Element UI 表格 + 弹窗；vue-i18n 四语言（vi/zh/tw/en），新 key 加到 `storeEzpay:{}` 对象层级（遵循 `[[feedback-i18n-key-naming]]`）。
