# 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]]`)。