# Data Model: 商家 ezPay 发票开通管理 **Phase**: Phase 1 — 表结构、字段、状态机 **Date**: 2026-06-15 ## 变更一:pos_store 加列(已有表) `pos_store` 新增一列,按全栈清单走(实体 + resultMap + select/insert/update + 前端 + i18n + SQL)。 | 字段 | 类型 | 默认 | 说明 | |------|------|------|------| | `invoice_exempt` | `tinyint(1)` | `0` | 是否免用发票(不需开票):0需开票 / 1免用发票 | > 不在本表加任何 ezPay 凭证字段(凭证进独立表,见决策 5)。 ## 变更二:新增表 pos_store_ezpay(与 pos_store 1:1) ```sql -- 2026-06-15 商家 ezPay 发票开通管理 CREATE TABLE pos_store_ezpay ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', store_id BIGINT NOT NULL COMMENT '关联 pos_store.id(门店)', ezpay_status TINYINT NOT NULL DEFAULT 0 COMMENT '申请状态:0未申请/1申请中/2已开通', is_enabled TINYINT NOT NULL DEFAULT 1 COMMENT '启用开关:0停用/1启用(仅status=2有意义)', merchant_id VARCHAR(32) DEFAULT NULL COMMENT 'ezPay 商店代号 MerchantID_', hash_key VARCHAR(64) DEFAULT NULL COMMENT 'ezPay HashKey(32字节)', hash_iv VARCHAR(64) DEFAULT NULL COMMENT 'ezPay HashIV(16字节)', company_id VARCHAR(32) DEFAULT NULL COMMENT 'ezPay 会员编号 CompanyID_(字轨用,可空)', ubn VARCHAR(16) DEFAULT NULL COMMENT '统一编号(统编,商家上传)', apply_time DATETIME DEFAULT NULL COMMENT '提交申请时间(0->1)', approved_time DATETIME DEFAULT NULL COMMENT '开通时间(->2)', last_verify_result VARCHAR(255) DEFAULT NULL COMMENT '最近一次凭证验证结果', remark VARCHAR(500) DEFAULT NULL COMMENT '备注', create_time DATETIME DEFAULT NULL COMMENT '创建时间', update_time DATETIME DEFAULT NULL COMMENT '更新时间', create_by VARCHAR(64) DEFAULT NULL COMMENT '创建人', update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人', PRIMARY KEY (id), UNIQUE KEY uk_store_id (store_id), KEY idx_ezpay_status (ezpay_status) ) ENGINE=InnoDB DEFAULT CHARSET=utf88mb4 COMMENT='门店 ezPay 发票开通配置'; ``` ### 字段语义 - `store_id`:唯一键,一门店一行。**门店无此行 = 未申请**(列表 LEFT JOIN,无行按 status=0 处理)。 - `ezpay_status`:核心状态机字段(见下)。 - `is_enabled`:运行时开关,仅 `ezpay_status=2` 时有意义;停用不影响 status。 - `merchant_id`/`hash_key`/`hash_iv`:运营录入的 ezPay 凭证;`company_id` 字轨用可空。 - `ubn`:商家在商家端上传的统一编号。 - `last_verify_result`:记录最近验证的 ezPay 回应摘要,便于排查。 ### 实体(PosStoreEzpay.java) `ruoyi-system/.../domain/PosStoreEzpay.java`,MyBatis-Plus `@TableName("pos_store_ezpay")`、`@TableId(type=IdType.AUTO)`,字段与上表一一对应(驼峰)。 ## 状态机 ``` ┌─────────────────────────────┐ ▼ │ 重置/重新申请 未申请(0) ──发起申请──▶ 申请中(1) ──录入凭证+验证通过──▶ 已开通(2) │ │ │ 验证失败/网络异常 │ 停用 ▼ (保持 1) ▼ (原地,回传错误) 已开通+停用(enabled=0) │ 恢复 ▼ 已开通+启用(enabled=1) ``` **不变量**: - `ezpay_status=2` 的前置:必须经过一次成功的凭证验证(`merchant_id`/`hash_key`/`hash_iv` 非空且 `last_verify_result` 为通过)。 - `is_enabled` 仅在 `ezpay_status=2` 时可切换为有效语义;其它状态下恒视为"不可开票"。 - **免用发票(pos_store.invoice_exempt=1)优先于 ezPay 状态**:免用门店即使有 `pos_store_ezpay` 行也视为不开票,且不进入待办过滤。 ## 列表查询口径 平台后台列表 = `pos_store` LEFT JOIN `pos_store_ezpay`,过滤"实际卖货门店": - 纳入:普通店铺(`is_stall=0`)+ 摊位(`is_stall=1`,含夜市下摊位) - 排除:夜市(userType=3)本身不经 pos_store 开票;纯夜市容器门店按实现时数据口径确认 筛选维度: - `invoice_exempt=1` → 免用发票 - `invoice_exempt=0 AND ezpay_status=0` → 未申请("还要去开通") - `invoice_exempt=0 AND ezpay_status=1` → 申请中 - `invoice_exempt=0 AND ezpay_status=2` → 已开通 - "还没开通" = `invoice_exempt=0 AND ezpay_status IN (0,1)` - "还要去开通" = `invoice_exempt=0 AND ezpay_status=0` ## 验证规则 - 录入凭证:`merchant_id` 必填、`hash_key` 必填且 32 字节、`hash_iv` 必填且 16 字节(长度校验,ezPay 固定规格)。 - 验证调用:超时 10s;网络异常按"未通过"处理,不抛 500,回传可重试提示。 - 统编 `ubn`:商家端非免用门店建议必填(8 位数字),免用门店不强制。**发起申请(apply)时强制校验:非空且为 8 位数字,不满足则拒绝推进为"申请中"。**