# API Contracts: 商家 ezPay 发票开通管理

**Phase**: Phase 1 — REST 接口契约
**Date**: 2026-06-15

> 平台后台接口走若依鉴权 `@PreAuthorize("@ss.hasPermi('chanting:storeEzpay:*')")`；商家端接口走 `@Auth`+JWT。返回统一 `AjaxResult`/`TableDataInfo`。

## 一、平台后台（PosStoreEzpayController，`/system/storeEzpay`）

### 1. 列表（分页 + 筛选）

`GET /system/storeEzpay/list`

| 参数 | 类型 | 说明 |
|------|------|------|
| pageNum / pageSize | int | 分页 |
| ezpayStatus | int | 可选：0未申请/1申请中/2已开通；不传=全部（含免用） |
| invoiceExempt | int | 可选：0需开票/1免用发票 |
| quickFilter | string | 可选：`notEnabled`=还没开通、`needApply`=还要去开通 |
| posName | string | 可选：门店名模糊 |
| isStall | int | 可选：0店铺/1摊位 |

**返回** `TableDataInfo`，每行含：门店基础信息（id/posName/userId/userName/isStall/invoiceExempt）+ ezPay 信息（ezpayStatus/isEnabled/ubn/merchantId 是否已填/applyTime/approvedTime/lastVerifyResult）。无 ezPay 行的门店 ezpayStatus 视为 0。

### 2. 详情

`GET /system/storeEzpay/{storeId}` → `AjaxResult`，门店 + ezPay 配置（凭证字段是否回显由权限决定，建议列表/详情回显，敏感字段不下发商家端）。

### 3. 发起申请（0→1）

`PUT /system/storeEzpay/apply/{storeId}`
- 前置：`ezpay_status=0`；建议校验 `ubn` 已填（未填则提示商家先补统编）。
- 效果：`ezpay_status=1`、`apply_time=now`。

### 4. 录入凭证并验证（→2）

`PUT /system/storeEzpay/saveCredentials`
```json
{ "storeId": 123, "merchantId": "3482911", "hashKey": "...", "hashIv": "...", "companyId": null }
```
- 后端：构造 `EzPayConfig` → 调 `EzPay.doPost(BASE_TEST + URL_SEARCH, cfg, {假发票号+随机码})`。
- 判读：回应含 `KEY1xxxx` → 凭证无效，**保持 status 不变**，`last_verify_result` 记错误码，返回 error（含中文说明）。回应业务错误或成功 → `ezpay_status=2`、`is_enabled=1`、`approved_time=now`、保存凭证、`last_verify_result` 记通过。
- 网络异常：返回 error 可重试，不改状态。

### 5. 切换启用开关

`PUT /system/storeEzpay/toggleEnable/{storeId}`
- 前置：`ezpay_status=2`。翻转 `is_enabled`（1↔0）。返回新状态。

### 6. 标记/恢复免用发票

`PUT /system/storeEzpay/markExempt/{storeId}`
```json
{ "invoiceExempt": 1 }   // 1=免用发票, 0=恢复需开票
```
- 效果：更新 `pos_store.invoice_exempt`。免用后该门店退出待办过滤（已有 ezPay 行保留）。

### 7. 重置状态（可选）

`PUT /system/storeEzpay/reset/{storeId}`
- 将 `ezpay_status` 回退到 0 或 1（凭证作废/重新申请场景），清 `approved_time`。

## 二、商家端（上传统编）

挂在门店设置流程。两种实现选一（实现时定）：

**方案 A（推荐，最少改动）**：复用 `POST /chanting/store/addmendian`（已 `saveOrUpdate` 整个 PosStore）——前端门店表单加"统一编号"字段，提交时一并写入 `pos_store_ezpay.ubn`（后端在保存门店时 upsert ezPay 行的 ubn）。

**方案 B**：新增 `POST /chanting/store/saveUbn` `{ storeId, ubn }`，仅商家本人门店（JWT 校验 storeId 归属）。

> 无论哪种：商家端只能写 `ubn`，不能改 ezPay 状态/凭证/免用标记。

## 三、权限菜单

新增菜单/权限键（写入 `sys_menu`，SQL 进 `updatesql/sql.md`）：
- `chanting:storeEzpay:list` / `:query` / `:apply` / `:saveCredentials` / `:toggleEnable` / `:markExempt` / `:reset`

## 四、错误约定

| 场景 | HTTP | 业务码/消息 |
|------|------|-------------|
| 凭证金钥错误 | 200 | error，`last_verify_result` 记 KEY1xxxx，消息"ezPay 凭证无效，请检查 HashKey/HashIV" |
| 验证网络超时 | 200 | error，"ezPay 验证服务暂不可用，请稍后重试"，状态不变 |
| 非法状态迁移 | 200 | error，"当前状态不允许此操作" |
| 商家越权改门店 | 200 | error，"无权操作该门店" |
