research.md 5.1 KB

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_storeinvoice_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]])。