# 用户签约管理 - PRD

## 文档信息

| 项目 | 内容 |
|------|------|
| 文档版本 | v1.1 |
| 创建日期 | 2026-02-26 |
| 模块名称 | 用户签约管理 |
| 文档状态 | 已评审修订 |

---

## 一、模块概述

### 1.1 模块定位

用户签约管理模块负责管理用户与支付服务商之间的签约关系。某些支付场景(如银行托收、生活缴费)需要用户提前签约授权,才能进行支付。本模块提供签约流程管理和签约关系查询功能。

### 1.2 核心功能

1. **签约流程管理** - 发起签约、处理签约回调、记录签约结果
2. **签约关系管理** - 查询、展示、解约用户的签约关系
3. **签约验证服务** - 为支付交易提供签约状态验证

### 1.3 使用角色

- **C端用户** - 发起签约、查看签约状态、解约
- **运营平台管理员** - 查看签约列表、处理异常签约
- **系统服务** - 验证签约状态、处理签约回调

### 1.4 需要签约的场景

| 支付场景 | 支付方式 | 是否必须签约 | 说明 |
|---------|---------|------------|------|
| 普通支付 | 用户主动发起线上支付 | 否 | 无需签约,用户扫码或点击支付实时完成 |
| 生活缴费 | 系统统一唤起支付自动扣款 | 是 | 需签约授权,系统自动从微信/支付宝账户扣款 |
| 电子托收(银行托收) | 系统统一唤起支付自动扣款 | 是 | 需签约授权,系统自动从银行卡扣款 |
| 牌照支付 | 用户主动发起线上支付 | 否 | 无需签约,用户通过车牌号支付实时完成 |

**支付方式说明:**
- **用户主动支付**: 用户操作扫码/点击支付按钮,实时跳转支付页面完成支付
- **系统自动扣款**: 用户提前签约授权,系统按照业务规则(如每月固定日期)自动发起扣款,无需用户手动操作

---

## 二、功能详细说明

### 2.1 签约流程管理

#### 2.1.1 功能描述

用户发起签约请求后,支付中台调用服务商签约接口,引导用户完成签约(填写银行卡、验证身份等),签约成功后保存签约协议号,后续系统可统一唤起支付自动扣款。

**签约用途:**
- **生活缴费场景**: 签约后,系统可自动从微信/支付宝账户扣款,用户无需手动操作
- **电子托收场景**: 签约后,系统可自动从银行卡扣款,适用于定期缴费(如每月物业费)

**与普通支付的区别:**
- 普通支付:用户每次都需要手动扫码/点击支付
- 签约支付:用户一次签约,系统按规则自动扣款,无需用户干预

#### 2.1.2 签约流程

```
用户 (在业务系统中)
  │
  ├─ 1. 进入签约页面 (如:物业费托收签约)
  │
  ├─ 2. 点击"立即签约"按钮
  │
  ▼
业务系统
  │
  ├─ 3. 调用支付中台签约接口
  │    └─ 传递: user_id, community_id, provider_id, payment_method_id
  │
  ▼
支付中台 - 签约管理
  │
  ├─ 4. 查询是否已签约
  │    └─ 如果已有有效签约,直接返回签约成功
  │
  ├─ 5. 创建签约记录
  │    └─ 状态: signing (签约中)
  │
  ├─ 6. 调用服务商签约接口
  │    └─ 获取签约链接或二维码
  │
  ├─ 7. 返回签约信息给业务系统
  │    ├─ sign_id: 签约ID
  │    ├─ sign_url: 签约链接
  │    ├─ qrcode_url: 签约二维码
  │    └─ expire_time: 链接过期时间
  │
  ▼
业务系统
  │
  └─ 8. 引导用户完成签约
       ├─ 跳转到签约链接
       └─ 或展示签约二维码

用户
  │
  ├─ 9. 在服务商页面完成签约
  │    ├─ 填写银行卡号
  │    ├─ 填写持卡人信息
  │    ├─ 验证手机号
  │    └─ 输入验证码确认
  │
  ▼
服务商
  │
  ├─ 10. 签约成功,回调支付中台
  │     └─ 携带: 签约协议号、银行卡信息(脱敏)
  │
  ▼
支付中台 - 签约回调处理
  │
  ├─ 11. 验证回调签名
  │
  ├─ 12. 更新签约状态
  │     ├─ 状态: signing → signed
  │     ├─ 保存签约协议号
  │     └─ 保存银行卡信息(脱敏)
  │
  ├─ 13. 记录签约时间
  │
  ├─ 14. 异步通知业务系统
  │     └─ 签约成功通知
  │
  ├─ 15. 返回成功响应给服务商
  │
  ▼
业务系统
  │
  └─ 16. 更新签约状态,提示用户签约成功
```

#### 2.1.3 业务规则

1. **唯一性约束**
   - 一个用户在同一小区、同一服务商、同一支付方式下,只能有一条有效签约
   - **重要调整 (v1.1)**: 不使用数据库唯一索引约束签约唯一性,改为应用层控制
   - 发起签约前,先查询是否存在 sign_status = 'signed' 的记录
   - 如果已有有效签约,再次发起签约时直接返回已有签约信息

2. **签约有效期**
   - 签约成功后长期有效,除非用户主动解约或服务商解约
   - 签约链接有效期:30分钟

3. **签约状态及流转**
   - **signing**: 签约中(用户未完成签约)
   - **signed**: 已签约(用户完成签约,可正常使用自动扣款)
   - **cancelled**: 已解约(用户主动解约或管理员帮助解约)
   - **invalid**: 已失效(房屋退租/变更导致系统自动失效) (v1.1.1新增)

   **状态流转规则**:
   ```
   signing (签约中)
     │
     ├─→ signed (签约成功)
     │     │
     │     ├─→ cancelled (用户主动解约/管理员帮助解约)
     │     │
     │     └─→ invalid (房屋退租/变更,系统自动失效)
     │
     └─→ cancelled (用户取消签约/签约超时)
   ```

   **状态说明**:
   - signing → signed: 用户完成签约流程,服务商回调成功
   - signing → cancelled: 用户放弃签约或签约链接过期
   - signed → cancelled: 用户主动解约或管理员帮助解约
   - signed → invalid: 房屋退租/变更时系统自动失效
   - cancelled 和 invalid 为终态,不可再流转

4. **银行卡信息处理**
   - 银行卡号脱敏存储:显示前6位和后4位,中间用*代替
   - 持卡人姓名脱敏:仅显示姓,名用*代替 (如:张**)
   - 银行名称:保存完整银行名称

5. **签约验证规则**
   - 支付前验证签约状态必须为 signed
   - 验证签约的服务商和支付方式与本次支付一致
   - 验证签约所属小区与本次支付小区一致

6. **签约失效规则 (v1.1.1新增)**
   - 当房屋状态发生变更(退租/换租)时,该房屋关联用户的签约自动失效
   - 失效后的签约状态变为 invalid
   - 失效的签约不能用于发起扣款请求
   - 用户需要重新签约才能继续使用

#### 2.1.4 页面交互 (管理端)

**签约列表页**
- 展示所有用户签约记录
- 显示字段:用户ID、用户姓名、小区名称、服务商名称、支付方式、签约状态、签约时间、银行卡号(脱敏)、操作
- 筛选条件:
  - 小区名称
  - 服务商
  - 签约状态
  - 签约时间范围
- 支持操作:查看详情、解约(需确认)

**签约详情页**
- 基础信息
  - 签约ID
  - 用户信息(用户ID、用户姓名、手机号脱敏)
  - 小区信息(小区ID、小区名称)
  - 服务商信息(服务商名称、支付方式名称)

- 签约信息
  - 签约协议号
  - 签约状态
  - 签约时间
  - 解约时间(如已解约)

- 银行卡信息(脱敏)
  - 银行名称
  - 银行卡号(脱敏)
  - 持卡人姓名(脱敏)

- 操作日志
  - 签约时间、签约IP
  - 解约时间、解约原因(如有)

**解约操作**
- 点击"解约"按钮
- 弹窗确认:
  - 提示:"解约后用户将无法使用该支付方式自动扣款,确认解约?"
  - 解约原因(必填,文本域)
- 确认后:
  - 调用服务商解约接口
  - 更新签约状态为 cancelled
  - 记录解约时间和原因
  - 提示"解约成功"

#### 2.1.5 操作提示

| 操作 | 提示信息 |
|------|---------|
| 发起签约成功 | "签约请求已发起,请引导用户完成签约" |
| 签约已存在 | "用户已签约该支付方式,无需重复签约" |
| 签约回调成功 | "签约成功" (业务系统展示给用户) |
| 签约回调失败 | "签约失败:[服务商返回的失败原因]" |
| 解约确认 | "解约后用户将无法使用该支付方式自动扣款,确认解约?" |
| 解约成功 | "解约成功" |
| 解约失败 | "解约失败:[具体错误原因]" |

---

### 2.2 签约关系查询

#### 2.2.1 功能描述

为业务系统和管理端提供签约关系查询功能,支持按用户、小区、服务商等维度查询签约状态。

#### 2.2.2 查询场景

**场景1: 用户查看自己的签约状态**
- C端用户在业务系统中查看自己已签约的支付方式
- 展示:服务商名称、支付方式、签约时间、银行卡号(脱敏)
- 操作:解约

**场景2: 支付前验证签约状态**
- 业务系统调用支付接口时,如果使用签约支付方式,需验证用户是否已签约
- 查询参数:user_id, community_id, provider_id, payment_method_id
- 返回:签约状态、签约协议号

**场景3: 管理端查询签约列表**
- 运营平台管理员查看所有签约记录
- 支持多维度筛选和导出

#### 2.2.3 查询接口逻辑

```
查询签约状态:
输入:
  - user_id: 用户ID (必填)
  - community_id: 小区ID (必填)
  - provider_id: 服务商ID (必填)
  - payment_method_id: 支付方式ID (必填)

查询SQL:
SELECT * FROM user_payment_sign
WHERE user_id = ?
  AND community_id = ?
  AND provider_id = ?
  AND payment_method_id = ?
  AND sign_status = 'signed'
ORDER BY sign_time DESC
LIMIT 1

返回:
  - 存在有效签约: 返回签约信息
  - 不存在: 返回 null
```

---

### 2.3 解约流程

#### 2.3.1 功能描述

用户或管理员可以主动解除签约关系。解约后,用户无法继续使用该签约支付方式自动扣款,如需再次使用需重新签约。

**解约方式:**
- **用户主动解约**: 用户在C端(小程序/APP)中查看签约列表,选择解约
- **物业帮助解约**: 物业管理员在运营平台中代用户操作解约(用户投诉、特殊情况等)

**v1.1.2新增 - 房屋维度解约:**
- 用户在同一小区可能有多套房屋的签约
- 解约时需要先查询该用户所有关联房屋的签约
- 用户/管理员选择对哪些房屋进行解约

#### 2.3.2 解约流程

**用户主动解约流程(C端) - v1.1.2调整:**

```
用户 (小程序/APP)
  │
  ├─ 1. 进入"我的签约"页面
  │
  ├─ 2. 查看已签约列表
  │    └─ 按房屋分组显示:房屋名称、支付方式、签约时间、银行卡号(脱敏)、状态
  │
  ├─ 3. 点击某个签约的"解约"按钮
  │
  ├─ 4. 系统查询该用户在该小区所有关联房屋的签约
  │    └─ 返回:房屋列表及各房屋的签约状态
  │
  ├─ 5. 弹窗显示房屋选择
  │    ├─ 标题:"选择要解约的房屋"
  │    ├─ 房屋列表(多选):
  │    │    ☑ 1栋101室 - 微信生活缴费 - 已签约
  │    │    ☑ 1栋102室 - 微信生活缴费 - 已签约
  │    │    ☐ 2栋201室 - 易宝托收 - 已签约
  │    ├─ 提示:"解约后将无法使用自动扣款功能"
  │    └─ 解约原因(可选,下拉选择):"不再需要/更换支付方式/其他"
  │
  ├─ 6. 用户选择要解约的房屋,确认解约
  │
  ▼
业务系统
  │
  ├─ 7. 调用支付中台解约接口
  │    └─ 传递: user_id, sign_ids[], cancel_reason, operator_type='user'
  │
  ▼
支付中台 - 签约管理
  │
  ├─ 8. 验证签约所有权
  │    └─ 验证user_id与所有签约记录的user_id一致
  │
  ├─ 9. 批量验证签约状态
  │    └─ 验证所有签约状态为 signed
  │
  ├─ 10. 批量调用服务商解约接口
  │     └─ 传递各签约的签约协议号 sign_no
  │
  ├─ 11. 批量更新签约状态
  │     ├─ 状态: signed → cancelled
  │     ├─ cancel_time: 当前时间
  │     ├─ cancel_reason: 解约原因
  │     ├─ operator_id: 用户ID
  │     └─ operator_type: 'user' (用户主动解约)
  │
  ├─ 12. 返回解约结果
  │
  ▼
业务系统
  │
  └─ 13. 提示用户"解约成功,已解约X套房屋"
```

**物业帮助解约流程(管理端) - v1.1.2调整:**

```
物业管理员 (运营平台)
  │
  ├─ 1. 进入"签约管理"页面
  │
  ├─ 2. 搜索用户签约记录
  │    └─ 搜索条件:用户姓名/手机号/小区名称
  │
  ├─ 3. 找到目标用户,点击"解约"按钮
  │
  ├─ 4. 系统查询该用户在该小区所有关联房屋的签约
  │    └─ 返回:房屋列表及各房屋的签约状态
  │
  ├─ 5. 弹窗显示房屋选择
  │    ├─ 标题:"选择要解约的房屋"
  │    ├─ 用户信息:张三 (138****5678)
  │    ├─ 房屋列表(多选):
  │    │    ☑ 1栋101室 - 微信生活缴费 - 已签约
  │    │    ☑ 1栋102室 - 微信生活缴费 - 已签约
  │    │    ☐ 2栋201室 - 易宝托收 - 已签约
  │    ├─ 全选/取消全选 按钮
  │    ├─ 提示:"您正在帮用户解约,解约后用户将无法使用该支付方式自动扣款"
  │    └─ 解约原因(必填,文本域):需详细填写原因
  │         示例:"用户电话投诉要求解约" / "用户已退房" / "银行卡已过期"
  │
  ├─ 6. 管理员选择要解约的房屋,填写原因,确认解约
  │
  ▼
运营平台
  │
  ├─ 7. 调用支付中台解约接口
  │    └─ 传递: user_id, sign_ids[], cancel_reason, operator_type='admin', operator_id=管理员ID
  │
  ▼
支付中台 - 签约管理
  │
  ├─ 8. 批量验证签约状态
  │    └─ 验证所有签约状态为 signed
  │
  ├─ 9. 批量调用服务商解约接口
  │    └─ 传递各签约的签约协议号 sign_no
  │
  ├─ 10. 批量更新签约状态
  │     ├─ 状态: signed → cancelled
  │     ├─ cancel_time: 当前时间
  │     ├─ cancel_reason: 管理员填写的原因
  │     ├─ operator_id: 管理员ID
  │     └─ operator_type: 'admin' (管理员操作)
  │
  ├─ 11. 记录操作日志
  │     └─ 记录哪个管理员、何时、为哪个用户解约了哪些房屋
  │
  ├─ 12. 返回解约结果
  │
  ▼
运营平台
  │
  └─ 13. 提示管理员"解约成功,已解约X套房屋"
        └─ 可选:发送短信通知用户签约已解除
```

#### 2.3.3 解约业务规则

1. **解约权限**
   - **用户本人**: 只能解约自己的签约,需验证用户身份
   - **物业管理员**: 可以解约任何用户的签约,需填写详细原因并记录操作日志

2. **解约条件**
   - 签约状态必须为 signed (已签约状态才能解约)
   - cancelled(已解约)、invalid(已失效) 状态的签约不能再次解约
   - signing(签约中) 状态的签约可以取消,但不需要调用服务商解约接口

3. **解约后影响**
   - 签约状态变为 cancelled
   - 原签约记录保留,不删除(用于审计和查询)
   - 用户无法再使用该签约发起自动扣款
   - 用户如需再次使用自动扣款,需重新发起签约

4. **解约原因分类**
   - **用户主动解约原因(可选)**:
     - 不再需要
     - 更换支付方式
     - 其他
   - **管理员解约原因(必填)**:
     - 用户投诉要求解约
     - 用户已退房/退租
     - 银行卡已过期
     - 业务调整
     - 其他(需详细说明)

5. **解约失败处理**
   - 如果服务商解约接口调用失败,本地不更新状态
   - 提示具体错误原因供用户或管理员处理
   - 建议用户联系客服或管理员手动处理

6. **操作记录**
   - 所有解约操作必须记录:
     - 操作人ID (operator_id)
     - 操作人类型 (operator_type: 'user' 或 'admin')
     - 操作时间 (cancel_time)
     - 操作原因 (cancel_reason)
   - 管理员操作需要在操作日志表中额外记录详情

---


---

### 2.4 房屋状态变更时签约失效 (v1.1.1新增)

#### 2.4.1 功能描述

当房屋发生退租或换租等状态变更时,原租户在该**特定房屋**的签约信息需要自动失效,防止原租户继续使用签约信息发起扣款。

**对接说明:**
- 房屋系统在检测到房屋退租/换租时,需要调用支付中台签约失效功能
- **失效范围**: 仅失效该用户在该**特定房屋**的签约,不影响同一用户在该小区其他房屋的签约
- 具体接口对接方式由开发阶段确定,PRD阶段仅明确业务规则

#### 2.4.2 触发场景

| 场景 | 说明 | 处理方式 |
|------|------|----------|
| 房屋退租 | 租户退租,房屋状态变为空置 | 该租户在该**特定房屋**的签约失效 |
| 房屋换租 | 原租户退租,新租户入住 | 原租户在该**特定房屋**的签约失效,新租户需重新签约 |
| 房屋变更 | 房屋信息发生其他变更 | 根据业务规则决定是否失效 |

#### 2.4.3 失效逻辑

```
房屋系统/业务系统
  │
  ├─ 1. 检测到房屋状态变更(退租/换租)
  │
  ├─ 2. 调用支付中台签约失效接口
  │    └─ 传递: user_id, community_id, house_id (必填), invalid_reason
  │
  ▼
支付中台 - 签约失效处理
  │
  ├─ 3. 参数验证 (v1.1.1增强)
  │    ├─ 验证user_id是否存在
  │    │    └─ 不存在: 返回错误 PAY-3000-05 "用户不存在"
  │    ├─ 验证community_id是否存在
  │    │    └─ 不存在: 返回错误 PAY-3000-06 "小区不存在"
  │    ├─ 验证house_id必填
  │    │    └─ 为空: 返回错误 PAY-3000-07 "房屋ID不能为空"
  │    └─ 验证invalid_reason必填
  │         └─ 为空: 返回错误 PAY-3000-01 "失效原因不能为空"
  │
  ├─ 4. 幂等性检查 (v1.1.1新增)
  │    ├─ 基于user_id + community_id + house_id + invalid_reason生成幂等键
  │    ├─ 检查Redis中是否存在该幂等键 (TTL=10分钟)
  │    │    └─ 存在: 直接返回上次处理结果 (缓存的invalidated_count)
  │    └─ 不存在: 继续处理,并缓存结果
  │
  ├─ 5. 查询该用户在该小区该房屋的所有有效签约
  │    └─ SELECT * FROM user_payment_sign
  │        WHERE user_id = ?
  │        AND community_id = ?
  │        AND house_id = ?
  │        AND sign_status = 'signed'
  │
  ├─ 6. 判断签约数量
  │    ├─ 如果数量=0: 直接返回成功 (invalidated_count=0)
  │    └─ 如果数量>0: 继续批量更新
  │
  ├─ 7. 批量更新签约状态
  │    ├─ 状态: signed → invalid
  │    ├─ 记录失效时间: invalid_time = NOW()
  │    ├─ 记录失效原因: invalid_reason
  │    ├─ 记录操作人类型: operator_type = 'system'
  │    └─ 记录操作人ID: operator_id = NULL 或 0
  │
  ├─ 8. 记录操作日志
  │    └─ 记录user_id、community_id、house_id、失效数量、失效原因
  │
  ├─ 9. 缓存处理结果 (幂等性保证)
  │    └─ 将invalidated_count写入Redis (TTL=10分钟)
  │
  └─ 10. 返回失效结果
        └─ 返回失效的签约数量和详情
```

#### 2.4.4 业务规则

1. **失效范围** (v1.1.2调整)
   - 失效该用户在该小区**该特定房屋**的所有有效签约 (sign_status = 'signed')
   - 不影响该用户在该小区其他房屋的签约
   - 不影响其他用户的签约
   - 不影响该用户在其他小区的签约

2. **失效后状态**
   - 签约状态变为 invalid
   - 保留签约历史记录,不删除
   - 记录失效时间和失效原因
   - 记录operator_type为'system'

3. **支付校验**
   - 支付/扣款前验证签约状态必须为 signed
   - 如果签约状态为 invalid,返回错误:"签约已失效,请重新签约" (PAY-3000-02)
   - 系统不允许使用失效签约发起任何扣款请求

4. **重新签约**
   - 用户如需继续使用,必须重新发起签约流程
   - 重新签约会创建新的签约记录

5. **幂等性保证** (v1.1.1新增)
   - 使用Redis实现接口幂等性
   - 幂等键格式: `sign:invalid:{user_id}:{community_id}:{house_id}:{reason_hash}`
   - 幂等键TTL: 10分钟
   - 重复调用直接返回缓存结果,不重复更新数据库

6. **参数验证** (v1.1.2调整)
   - user_id: 必填,必须是系统中存在的用户
   - community_id: 必填,必须是系统中存在的小区
   - house_id: **必填**,用于精确失效特定房屋的签约
   - invalid_reason: 必填,最大长度500字符

7. **异常处理** (v1.1.1新增)
   - 用户不存在: 返回 PAY-3000-05,不执行失效操作
   - 小区不存在: 返回 PAY-3000-06,不执行失效操作
   - 房屋ID为空: 返回 PAY-3000-07,不执行失效操作
   - 该用户在该房屋无签约: 返回成功,invalidated_count=0
   - 数据库更新失败: 返回 PAY-6000-01 "系统异常,请稍后重试"
   - Redis不可用: 降级处理,允许继续执行但记录告警日志

#### 2.4.5 数据库变更

```sql
-- v1.1.1 新增字段
ALTER TABLE user_payment_sign
ADD COLUMN house_id VARCHAR(64) NULL COMMENT '房屋ID' AFTER community_id,
ADD COLUMN operator_id BIGINT NULL COMMENT '操作人ID(用户ID或管理员ID)' AFTER cancel_reason,
ADD COLUMN operator_type VARCHAR(20) NULL COMMENT '操作人类型:user/admin/system' AFTER operator_id,
ADD COLUMN invalid_time DATETIME NULL COMMENT '失效时间' AFTER operator_type,
ADD COLUMN invalid_reason VARCHAR(500) NULL COMMENT '失效原因' AFTER invalid_time;

-- 新增房屋维度索引
ALTER TABLE user_payment_sign
ADD INDEX idx_user_house (user_id, community_id, house_id, sign_status);

-- 修改sign_status枚举值(如果使用ENUM类型)
-- ALTER TABLE user_payment_sign
-- MODIFY COLUMN sign_status ENUM('signing','signed','cancelled','invalid');
```

#### 2.4.6 操作示例

**场景1: 租户退租 - 正常流程**

```
1. 房屋系统检测到租户A从房屋H001退租
2. 调用签约失效接口:
   POST /api/payment/sign/invalidate
   {
     "user_id": 1001,
     "community_id": 10001,
     "house_id": "H001",
     "invalid_reason": "房屋退租"
   }
3. 支付中台处理:
   - 参数验证通过
   - 幂等性检查:首次调用
   - 查询到该用户在该房屋有2条有效签约
   - 批量更新状态为 invalid
   - 记录失效时间、原因、operator_type='system'
   - 缓存处理结果
4. 返回结果:
   {
     "success": true,
     "invalidated_count": 2,
     "sign_ids": [10001, 10002],
     "message": "签约已失效"
   }
5. 注意:该用户在该小区其他房屋(如H002)的签约不受影响
```

**场景2: 用户不存在 - 异常处理 (v1.1.1新增)**

```
1. 房屋系统调用签约失效接口
2. 传递参数:
   {
     "user_id": 99999, (不存在的用户)
     "community_id": 10001,
     "house_id": "H001",
     "invalid_reason": "房屋退租"
   }
3. 支付中台处理:
   - 参数验证:user_id=99999不存在
   - 返回错误,不执行失效操作
4. 返回结果:
   {
     "success": false,
     "error_code": "PAY-3000-05",
     "error_message": "用户不存在",
     "invalidated_count": 0
   }
```

**场景3: 重复调用 - 幂等性保证 (v1.1.1新增)**

```
1. 房屋系统第一次调用签约失效接口
   - 成功失效2条签约
   - 结果缓存到Redis (TTL=10分钟)

2. 5分钟后,网络重试导致第二次调用(相同参数)
   - 幂等性检查:Redis中存在该幂等键
   - 直接返回缓存结果,不重复更新数据库

3. 返回结果(两次调用结果一致):
   {
     "success": true,
     "invalidated_count": 2,
     "sign_ids": [10001, 10002],
     "message": "签约已失效"
   }
```

**场景4: 用户无签约 - 正常返回 (v1.1.1新增)**

```
1. 房屋系统调用签约失效接口
2. 传递参数:
   {
     "user_id": 1005, (该用户未签约)
     "community_id": 10001,
     "invalid_reason": "房屋退租"
   }
3. 支付中台处理:
   - 参数验证通过
   - 查询签约:返回0条
   - 直接返回成功
4. 返回结果:
   {
     "success": true,
     "invalidated_count": 0,
     "sign_ids": [],
     "message": "该用户在该小区无有效签约"
   }
```

**场景5: 租户A尝试使用失效签约扣款**

```
1. 业务系统调用扣款接口
   {
     "sign_no": "YB202602250001",
     "amount": 10000
   }
2. 支付中台验证签约状态
   - 查询签约记录: sign_status = 'invalid'
   - 签约失效检查失败
3. 返回错误:
   {
     "success": false,
     "error_code": "PAY-3000-02",
     "error_message": "签约已失效,请重新签约",
     "invalid_reason": "房屋退租",
     "invalid_time": "2026-02-26 10:30:00"
   }
```

---

### 2.5 签约限额管理 (v1.2.0新增)

#### 2.5.1 功能描述

签约限额管理用于控制用户通过签约方式进行自动扣款的金额上限，保障用户资金安全，防止异常大额扣款。

#### 2.5.2 限额类型

| 限额类型 | 说明 | 默认值 | 可配置 |
|---------|------|--------|-------|
| 单笔限额 | 单次自动扣款金额上限 | 5,000元 | 是 |
| 单日限额 | 每日累计自动扣款上限 | 20,000元 | 是 |
| 单月限额 | 每月累计自动扣款上限 | 100,000元 | 是 |

#### 2.5.3 限额配置层级

**优先级从高到低:**
1. **签约级限额**: 用户签约时可自定义限额（不超过上级限额）
2. **支付方式级限额**: 不同支付方式的默认限额
3. **服务商级限额**: 服务商规定的限额上限
4. **系统级限额**: 系统默认限额

**服务商限额参考:**

| 服务商 | 场景 | 单笔限额 | 单日限额 | 说明 |
|-------|------|---------|---------|------|
| 微信支付 | 生活缴费 | 10,000元 | 50,000元 | 微信生活缴费规则 |
| 支付宝 | 生活缴费 | 10,000元 | 50,000元 | 支付宝代扣规则 |
| 易宝支付 | 银行托收 | 50,000元 | 200,000元 | 银行代扣限额 |
| 工商银行 | 代收代付 | 100,000元 | 500,000元 | 企业代收限额 |

#### 2.5.4 限额配置流程

**签约时设置限额:**
```
用户发起签约
  │
  ├─ 1. 展示限额设置选项
  │    ├─ 单笔限额: [默认5000] 元 (最高10000元)
  │    ├─ 单日限额: [默认20000] 元 (最高50000元)
  │    └─ 单月限额: [默认100000] 元 (最高500000元)
  │
  ├─ 2. 用户可调整限额
  │    └─ 限额不能超过服务商/系统上限
  │
  ├─ 3. 保存限额配置
  │    └─ 存储到签约记录中
  │
  └─ 4. 签约完成
```

**限额调整:**
```
用户/管理员调整限额
  │
  ├─ 1. 查看当前限额
  │
  ├─ 2. 修改限额
  │    ├─ 提高限额: 需要身份验证
  │    │    ├─ 短信验证码
  │    │    └─ 或银行卡验证
  │    │
  │    └─ 降低限额: 无需验证,立即生效
  │
  ├─ 3. 保存新限额
  │    └─ 记录修改日志
  │
  └─ 4. 生效时间: 即时生效
```

#### 2.5.5 限额校验流程

```
自动扣款请求
  │
  ├─ 1. 获取签约限额配置
  │    └─ 查询user_payment_sign表的限额字段
  │
  ├─ 2. 单笔限额校验
  │    ├─ 本次金额 ≤ 单笔限额: 通过
  │    └─ 本次金额 > 单笔限额: 拒绝
  │         └─ 错误码: PAY-3000-10 "扣款金额超过单笔限额"
  │
  ├─ 3. 单日限额校验
  │    ├─ 查询该签约当日已扣款金额
  │    │    └─ SELECT SUM(paid_amount) FROM payment_order
  │    │        WHERE sign_no = ? AND pay_time >= TODAY
  │    │        AND order_status = 'paid'
  │    │
  │    ├─ 当日已扣 + 本次金额 ≤ 单日限额: 通过
  │    └─ 超过: 拒绝
  │         └─ 错误码: PAY-3000-11 "扣款金额超过单日限额"
  │
  ├─ 4. 单月限额校验
  │    ├─ 查询该签约当月已扣款金额
  │    ├─ 当月已扣 + 本次金额 ≤ 单月限额: 通过
  │    └─ 超过: 拒绝
  │         └─ 错误码: PAY-3000-12 "扣款金额超过单月限额"
  │
  └─ 5. 校验通过,继续扣款流程
```

#### 2.5.6 数据库设计

**user_payment_sign表新增字段:**

| 字段名 | 类型 | 说明 |
|-------|------|------|
| single_limit | BIGINT | 单笔限额 (分),默认500000 |
| daily_limit | BIGINT | 单日限额 (分),默认2000000 |
| monthly_limit | BIGINT | 单月限额 (分),默认10000000 |

**签约限额变更记录表 (sign_limit_change_log):**

| 字段名 | 类型 | 说明 |
|-------|------|------|
| id | BIGINT | 主键ID |
| sign_id | BIGINT | 签约ID |
| limit_type | VARCHAR(20) | 限额类型: single/daily/monthly |
| old_limit | BIGINT | 原限额 |
| new_limit | BIGINT | 新限额 |
| change_reason | VARCHAR(200) | 变更原因 |
| operator_id | BIGINT | 操作人ID |
| operator_type | VARCHAR(20) | 操作人类型: user/admin |
| verify_method | VARCHAR(20) | 验证方式: sms/bank_card/none |
| created_at | DATETIME | 创建时间 |

#### 2.5.7 页面交互

**用户端 - 签约限额设置:**
```
┌─────────────────────────────────────────────────────────────┐
│  签约限额设置                                                │
├─────────────────────────────────────────────────────────────┤
│  当前签约: 微信生活缴费 - 1栋101室                           │
│  银行卡: 工商银行 (尾号1234)                                 │
├─────────────────────────────────────────────────────────────┤
│  限额设置:                                                   │
│  ├─ 单笔限额: [    5000    ] 元  (最高10000元)              │
│  ├─ 单日限额: [   20000    ] 元  (最高50000元)              │
│  └─ 单月限额: [  100000    ] 元  (最高500000元)             │
│                                                             │
│  提示: 提高限额需要短信验证                                   │
│                                                             │
│              [取消]  [保存]                                  │
└─────────────────────────────────────────────────────────────┘
```

**管理端 - 签约详情显示限额:**
```
签约详情
├─ 基础信息
│    ├─ 用户: 张三 (138****5678)
│    ├─ 房屋: 1栋101室
│    └─ 签约时间: 2026-02-25 10:30:00
│
├─ 限额信息
│    ├─ 单笔限额: 5,000元
│    ├─ 单日限额: 20,000元
│    ├─ 单月限额: 100,000元
│    │
│    ├─ 当日已扣: 3,500元 (剩余16,500元)
│    └─ 当月已扣: 12,000元 (剩余88,000元)
│
└─ 操作: [调整限额]
```
## 三、数据库设计

### 3.1 用户签约表 (user_payment_sign)

#### 3.1.1 表说明
存储用户与支付服务商的签约关系

#### 3.1.2 字段设计

| 字段名 | 类型 | 长度 | 允许空 | 默认值 | 说明 |
|-------|------|------|-------|-------|------|
| id | BIGINT | - | 否 | 自增 | 主键ID,即签约ID |
| user_id | BIGINT | - | 否 | - | 用户ID |
| community_id | BIGINT | - | 否 | - | 小区ID |
| house_id | VARCHAR | 64 | 是 | NULL | 房屋ID (v1.1.2新增) |
| provider_id | BIGINT | - | 否 | - | 支付服务商ID,外键关联payment_provider.id |
| payment_method_id | BIGINT | - | 否 | - | 支付方式ID,外键关联payment_method.id |
| sign_no | VARCHAR | 128 | 是 | NULL | 签约协议号(服务商返回,签约成功后填充) |
| sign_status | VARCHAR | 20 | 否 | signing | 签约状态:signing(签约中)/signed(已签约)/cancelled(已解约)/invalid(已失效) |
| sign_time | DATETIME | - | 是 | NULL | 签约时间(签约成功后填充) |
| cancel_time | DATETIME | - | 是 | NULL | 解约时间 |
| cancel_reason | VARCHAR | 500 | 是 | NULL | 解约原因 |
| operator_id | BIGINT | - | 是 | NULL | 操作人ID(解约操作人,用户ID或管理员ID) (v1.1.1新增) |
| operator_type | VARCHAR | 20 | 是 | NULL | 操作人类型:user(用户主动解约)/admin(管理员帮助解约)/system(系统自动失效) (v1.1.1新增) |
| invalid_time | DATETIME | - | 是 | NULL | 失效时间 (v1.1.1新增) |
| invalid_reason | VARCHAR | 500 | 是 | NULL | 失效原因 (v1.1.1新增) |
| bank_card_no | VARCHAR | 128 | 是 | NULL | 银行卡号(脱敏加密存储) |
| bank_name | VARCHAR | 100 | 是 | NULL | 银行名称 |
| card_holder | VARCHAR | 128 | 是 | NULL | 持卡人姓名(脱敏加密存储) |
| sign_ip | VARCHAR | 50 | 是 | NULL | 签约IP地址 |
| created_at | DATETIME | - | 否 | CURRENT_TIMESTAMP | 创建时间(发起签约时间) |
| updated_at | DATETIME | - | 否 | CURRENT_TIMESTAMP ON UPDATE | 更新时间 |
| remark | VARCHAR | 500 | 是 | NULL | 备注说明 |

#### 3.1.3 索引设计

| 索引名 | 索引类型 | 字段 | 说明 |
|-------|---------|------|------|
| PRIMARY | 主键 | id | 主键索引 |
| uk_sign_no | 唯一索引 | sign_no | 签约协议号唯一 (v1.1新增) |
| idx_user_sign_query | 复合索引 | user_id, community_id, provider_id, payment_method_id, sign_status | 支付前签约验证专用索引 (v1.1新增) |
| idx_user_house | 复合索引 | user_id, community_id, house_id, sign_status | 房屋维度签约查询索引 (v1.1.2新增) |
| idx_user_id | 普通索引 | user_id | 用户查询 |
| idx_community_id | 普通索引 | community_id | 小区查询 |
| idx_provider_id | 普通索引 | provider_id | 服务商查询 |
| idx_sign_status | 普通索引 | sign_status | 状态查询 |
| idx_sign_time | 普通索引 | sign_time | 时间排序 |

**索引调整说明 (v1.1):**
1. **删除**: uk_user_sign (原唯一索引包含sign_status字段,设计错误)
2. **新增**: uk_sign_no (签约协议号唯一索引)
3. **新增**: idx_user_sign_query (支付前签约验证专用复合索引)
4. **新增 (v1.1.2)**: idx_user_house (房屋维度签约查询索引)

#### 3.1.4 外键约束

```sql
ALTER TABLE user_payment_sign
ADD CONSTRAINT fk_sign_provider
FOREIGN KEY (provider_id) REFERENCES payment_provider(id)
ON DELETE RESTRICT ON UPDATE CASCADE;

ALTER TABLE user_payment_sign
ADD CONSTRAINT fk_sign_method
FOREIGN KEY (payment_method_id) REFERENCES payment_method(id)
ON DELETE RESTRICT ON UPDATE CASCADE;
```

#### 3.1.5 示例数据

```sql
INSERT INTO user_payment_sign (user_id, community_id, house_id, provider_id, payment_method_id, sign_no, sign_status, sign_time, bank_card_no, bank_name, card_holder, sign_ip, remark) VALUES
(1001, 10001, 'H001', 3, 7, 'YB202602250001', 'signed', '2026-02-25 10:30:00', '622202******1234', '中国工商银行', '张**', '192.168.1.100', '银行托收签约-1栋101室'),
(1001, 10001, 'H002', 3, 7, 'YB202602250002', 'signed', '2026-02-25 10:35:00', '622202******1234', '中国工商银行', '张**', '192.168.1.100', '银行托收签约-1栋102室'),
(1002, 10001, 'H003', 1, 1, 'WX202602250002', 'signed', '2026-02-25 11:00:00', NULL, NULL, NULL, '192.168.1.101', '微信生活缴费签约-2栋201室'),
(1003, 10001, 'H004', 3, 7, 'YB202602250003', 'signing', NULL, NULL, NULL, NULL, '192.168.1.102', '签约中'),
(1004, 10002, 'H005', 1, 1, 'WX202602240001', 'cancelled', '2026-02-24 14:00:00', NULL, NULL, NULL, '192.168.1.103', '已解约');
```

---

## 四、数据迁移 (v1.0 → v1.1)

### 4.1 索引调整脚本

```sql
-- Step 1: 删除错误的唯一索引
ALTER TABLE user_payment_sign
DROP INDEX uk_user_sign;

-- Step 2: 新增签约协议号唯一索引
ALTER TABLE user_payment_sign
ADD UNIQUE INDEX uk_sign_no (sign_no);

-- Step 3: 新增支付前签约验证专用复合索引
ALTER TABLE user_payment_sign
ADD INDEX idx_user_sign_query (user_id, community_id, provider_id, payment_method_id, sign_status);
```

### 4.2 数据验证

```sql
-- 验证签约协议号是否有重复
SELECT sign_no, COUNT(*) as cnt
FROM user_payment_sign
WHERE sign_no IS NOT NULL
GROUP BY sign_no
HAVING cnt > 1;

-- 如果有重复,需要人工处理后再执行索引添加
```

---

## 五、业务流程

### 5.1 完整签约流程图

```
┌─────────────────────────────────────────────────────────────────┐
│                         用户发起签约                              │
│  业务系统 → 支付中台签约接口                                       │
│  参数: user_id, community_id, provider_id, payment_method_id    │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│                   支付中台处理签约请求                             │
│  1. 查询是否已有有效签约                                          │
│     ├─ 已签约: 直接返回签约信息                                   │
│     └─ 未签约: 继续处理                                          │
│  2. 创建签约记录 (状态:signing)                                   │
│  3. 调用服务商签约接口                                            │
│  4. 返回签约链接/二维码                                           │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│                    用户在服务商页面签约                            │
│  1. 跳转到签约链接或扫描二维码                                     │
│  2. 填写银行卡信息                                                │
│  3. 验证手机号                                                    │
│  4. 确认签约                                                     │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│                   服务商回调支付中台                               │
│  POST /notify/sign/{provider_code}                              │
│  携带: 签约协议号、签约结果、银行卡信息(脱敏)                      │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│                   支付中台处理签约回调                             │
│  1. 验证回调签名                                                  │
│  2. 更新签约状态 (signing → signed)                               │
│  3. 保存签约协议号、银行卡信息                                     │
│  4. 记录签约时间                                                  │
│  5. 异步通知业务系统                                              │
│  6. 返回成功响应给服务商                                          │
└────────────────────────┬────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│                     签约完成                                      │
│  用户可以使用该签约支付方式进行支付                                 │
└─────────────────────────────────────────────────────────────────┘
```

### 5.2 签约验证流程(支付前)

```
业务系统发起支付
  │
  ├─ 传递: user_id, community_id, payment_method_id
  │
  ▼
支付中台
  │
  ├─ 1. 查询支付方式配置
  │    └─ 检查 payment_method.need_sign 字段
  │
  ├─ 2. 如果 need_sign = true
  │    ├─ 查询用户签约状态
  │    │    └─ SELECT * FROM user_payment_sign
  │    │        WHERE user_id = ?
  │    │        AND community_id = ?
  │    │        AND provider_id = ?
  │    │        AND payment_method_id = ?
  │    │        AND sign_status = 'signed'
  │    │
  │    ├─ 如果未签约
  │    │    └─ 返回错误: "该支付方式需要签约,请先完成签约"
  │    │
  │    └─ 如果已签约
  │         └─ 继续支付流程,使用签约协议号
  │
  └─ 3. 如果 need_sign = false
       └─ 直接进入支付流程
```

---

## 六、异常处理

### 6.1 签约异常

| 异常场景 | 处理方式 |
|---------|---------|
| 重复签约 | 查询已有签约,直接返回签约信息,不重复发起 |
| 签约超时 | 签约链接30分钟过期,用户需重新发起签约 |
| 签约失败 | 服务商返回失败原因,提示用户并保留签约记录(状态:signing) |
| 回调签名验证失败 | 记录日志,返回失败,不更新签约状态 |
| 用户未完成签约 | 签约记录保持 signing 状态,用户可重新发起 |

### 6.2 解约异常

| 异常场景 | 处理方式 |
|---------|---------|
| 解约服务商接口失败 | 提示具体错误,不更新本地状态 |
| 解约不存在的签约 | 提示"签约不存在" |
| 解约已解约的签约 | 提示"该签约已解约" |

### 6.3 验证异常

| 异常场景 | 处理方式 |
|---------|---------|
| 支付前签约验证失败 | 返回错误码+错误信息,阻止支付 |
| 签约服务商与支付服务商不一致 | 返回错误"签约服务商不匹配" |
| 签约小区与支付小区不一致 | 返回错误"签约小区不匹配" |

---

## 七、数据约束

### 7.1 必填字段

**签约记录**
- 用户ID
- 小区ID
- 支付服务商ID
- 支付方式ID
- 签约状态

**签约成功后必填**
- 签约协议号
- 签约时间

### 7.2 唯一性约束

| 表 | 唯一性约束 | 说明 |
|----|-----------|------|
| user_payment_sign | sign_no | 签约协议号全局唯一 (v1.1新增) |

**业务唯一性控制 (v1.1调整):**
- 同一用户在相同条件下只能有一条有效签约(sign_status='signed')
- 不使用数据库唯一索引约束,改为应用层控制
- 发起签约前,通过查询验证是否已有有效签约

### 7.3 外键约束

| 表 | 外键字段 | 引用表 | 引用字段 | 删除规则 |
|----|---------|--------|---------|---------|
| user_payment_sign | provider_id | payment_provider | id | RESTRICT |
| user_payment_sign | payment_method_id | payment_method | id | RESTRICT |

---

## 八、安全要求

### 8.1 数据加密

| 字段 | 加密方式 | 说明 |
|------|---------|------|
| bank_card_no | AES-256-GCM + 脱敏 | 加密存储,显示时脱敏 |
| card_holder | AES-256-GCM + 脱敏 | 加密存储,显示时脱敏 |

### 8.2 数据脱敏

| 字段 | 脱敏规则 | 示例 |
|------|---------|------|
| bank_card_no | 显示前6位+***+后4位 | 622202******1234 |
| card_holder | 显示姓+** | 张** |

### 8.3 操作日志

需记录以下操作日志:
- 发起签约(记录user_id、sign_id、IP)
- 签约成功(记录sign_id、sign_no)
- 签约失败(记录sign_id、失败原因)
- 解约操作(记录operator_id、sign_id、解约原因)

---

## 九、与其他模块的关系

### 9.1 依赖关系

本模块依赖以下模块:

1. **支付渠道管理模块**
   - 引用 payment_provider (签约服务商)
   - 引用 payment_method (签约支付方式)

2. **小区支付配置模块**
   - 引用小区配置的支付服务商

### 9.2 被依赖关系

本模块被以下模块依赖:

1. **支付交易管理模块**
   - 支付前验证签约状态
   - 使用签约协议号发起支付

### 9.3 数据流向

```
用户发起签约
  ↓
用户签约管理 (记录签约关系)
  ↓
支付交易管理 (验证签约状态,使用签约协议号支付)
```

---

## 十、验收标准

### 10.1 功能验收

- [ ] 支持用户发起签约
- [ ] 支持调用服务商签约接口
- [ ] 支持处理服务商签约回调
- [ ] 支持查询签约状态
- [ ] 支持用户解约
- [ ] 支持管理员解约
- [ ] 支持签约列表查询和筛选
- [ ] 支持签约详情查看
- [ ] 支持签约信息脱敏展示
- [ ] 支持重复签约检测
- [ ] 所有签约操作记录日志

### 10.2 性能验收

- [ ] 签约发起响应时间 ≤ 500ms
- [ ] 签约状态查询响应时间 ≤ 100ms (核心性能指标)
- [ ] 签约回调处理时间 ≤ 200ms
- [ ] 签约列表查询响应时间 ≤ 300ms

### 10.3 安全验收

- [ ] 银行卡号加密存储
- [ ] 持卡人姓名加密存储
- [ ] 敏感信息脱敏展示
- [ ] 签约回调签名验证
- [ ] 签约操作记录审计日志

---

## 十一、FAQ

### 11.1 为什么需要签约?

**回答**: 某些支付场景需要用户提前授权:
- **银行托收**: 需要用户授权从银行卡自动扣款,必须签约
- **生活缴费**: 签约后可享受服务商提供的优惠费率

### 11.2 签约一次可以在多个小区使用吗?

**回答**: 不可以。签约是绑定到具体小区的,用户在不同小区需要分别签约。这是因为:
- 不同小区的子商户号不同
- 不同小区可能使用不同的服务商

### 11.3 签约后是否可以更换银行卡?

**回答**: 需要解约后重新签约。更换银行卡流程:
1. 解约当前签约
2. 重新发起签约
3. 填写新的银行卡信息

### 11.4 签约失败了怎么办?

**回答**:
- 用户可以重新发起签约
- 原签约记录保持 signing 状态
- 管理员可以查看签约记录,分析失败原因

### 11.5 解约后多久可以重新签约?

**回答**: 解约后立即可以重新签约,没有时间限制。

### 11.6 签约是否有有效期?

**回答**: 签约成功后长期有效,除非:
- 用户主动解约
- 管理员解约
- 服务商解约(极少发生)

### 11.7 v1.1为什么调整签约唯一性约束?

**回答**: v1.0中的唯一索引设计错误:
- 错误设计: uk_user_sign包含sign_status字段
- 问题: 用户可能多次签约同一支付方式(解约后重新签约),sign_status会变化,导致唯一约束失效
- v1.1调整: 删除包含sign_status的唯一索引,改为应用层控制,允许保留历史签约记录

---


### 11.8 房屋退租后签约会怎样? (v1.1.1新增)

**回答**: 房屋退租后,原租户的签约会自动失效:
- 签约状态变为 invalid (已失效)
- 失效后无法继续使用该签约发起扣款
- 用户如需继续使用,需要重新签约
- 保留签约历史记录供查询

### 11.9 签约失效和签约解约有什么区别? (v1.1.1新增)

**回答**:
- **解约 (cancelled)**: 用户或管理员主动解除签约,需调用服务商解约接口
- **失效 (invalid)**: 由于房屋状态变更等外部原因导致签约不可用,系统自动失效
- 两者都不能继续使用,都需要重新签约

## 变更记录

| 版本 | 日期 | 修改人 | 修改内容 |
|------|------|-------|---------|
| v1.0 | 2026-02-25 | 产品经理 | 初始版本 |
| v1.1 | 2026-02-26 | 产品经理 | 1. 删除错误的uk_user_sign唯一索引<br>2. 新增uk_sign_no唯一索引(签约协议号唯一)<br>3. 新增idx_user_sign_query复合索引(支付前验证专用)<br>4. 调整签约唯一性约束说明(改为应用层控制)<br>5. 新增数据迁移章节<br>6. 完善业务规则说明 |
| v1.1.1 | 2026-02-26 | 产品经理 | 1. 新增签约失效规则(房屋退租/换租)<br>2. 新增2.4节-房屋状态变更时签约失效<br>3. 新增invalid_time和invalid_reason字段<br>4. sign_status增加invalid状态<br>5. 更新FAQ增加签约失效相关问答 |
