# client-wechat-login Specification

## ADDED Requirements

### Requirement: A2 微信公众号登录接口

系统 MUST 提供 `POST /api/c/v1/auth/wechat-login`，使用公众号 OAuth code + `asset_token` 完成登录。

- HTTP Method + Path: `POST /api/c/v1/auth/wechat-login`
- 请求体字段：
  - `code` string，MUST，微信 OAuth 授权码
  - `asset_token` string，MUST，A1 返回的资产令牌
- 响应体字段：
  - `token` string，MUST，登录 JWT
  - `need_bind_phone` bool，MUST，是否需要绑定手机号
  - `is_new_user` bool，MUST，是否新创建用户
- 错误码：
  - `1002` token 无效或过期（asset_token/JWT）
  - `1040` 微信授权失败
  - `1006` 参数错误

#### Scenario: 公众号登录成功
- **WHEN** 客户端提交有效 `code` 与有效 `asset_token`
- **THEN** 系统 SHALL 调用公众号 OAuth 获取 `openid` 与可选 `unionid`
- **THEN** 系统 SHALL 执行客户查找/创建/合并逻辑
- **THEN** 系统 SHALL 绑定资产并签发登录 token

### Requirement: A3 微信小程序登录接口

系统 MUST 提供 `POST /api/c/v1/auth/miniapp-login`，使用小程序 `jscode2session` + `asset_token` 完成登录。

- HTTP Method + Path: `POST /api/c/v1/auth/miniapp-login`
- 请求体字段：
  - `code` string，MUST，小程序登录凭证
  - `asset_token` string，MUST，A1 返回的资产令牌
- 响应体字段：
  - `token` string，MUST，登录 JWT
  - `need_bind_phone` bool，MUST
  - `is_new_user` bool，MUST
- 错误码：
  - `1002` token 无效或过期
  - `1040` 微信授权失败
  - `1006` 参数错误

#### Scenario: 小程序登录成功
- **WHEN** 客户端提交有效小程序 `code` 与有效 `asset_token`
- **THEN** 系统 SHALL 调用 `jscode2session` 获取 `openid` 与可选 `unionid`
- **THEN** 系统 SHALL 执行与 A2 一致的客户查找/创建/合并、资产绑定与签发逻辑

### Requirement: asset_token 校验与资产解析

系统 SHALL 在 A2/A3 登录前强制校验 `asset_token`，并解析出 `asset_type` + `asset_id`。

#### Scenario: asset_token 无效
- **WHEN** `asset_token` 签名不合法或已过期
- **THEN** 系统 MUST 拒绝登录并返回 `1002`

#### Scenario: asset_token 有效
- **WHEN** `asset_token` 可被成功解析
- **THEN** 系统 SHALL 使用解析出的资产信息继续登录流程

### Requirement: 客户查找/创建/合并逻辑

系统 MUST 按以下顺序处理客户归属：

1. 先查 `PersonalCustomerOpenID`：`(app_id, open_id)`；
2. 未命中且存在 `unionid` 时按 `unionid` 回查并复用客户；
3. 仍未命中时创建新 `PersonalCustomer` 与 OpenID 记录。

#### Scenario: openid 命中既有客户
- **WHEN** `(app_id, open_id)` 已存在
- **THEN** 系统 SHALL 直接复用对应 `customer_id`

#### Scenario: openid 未命中但 unionid 命中
- **WHEN** `(app_id, open_id)` 不存在且 `unionid` 命中历史记录
- **THEN** 系统 SHALL 复用已存在客户
- **THEN** 系统 SHALL 新增当前 `app_id + open_id` 记录

#### Scenario: openid/unionid 均未命中
- **WHEN** 无任何匹配记录
- **THEN** 系统 SHALL 创建新客户并写入 OpenID 记录

### Requirement: 登录后资产绑定

系统 SHALL 在 A2/A3 每次登录时创建一条 `PersonalCustomerDevice` 绑定记录，且 MUST 允许同一资产被多个客户绑定。

#### Scenario: 已有绑定时再次登录
- **WHEN** 同一客户再次登录同一资产
- **THEN** 系统 SHALL 记录本次登录绑定关系（按实现可去重或追加历史）

#### Scenario: 不同客户绑定同一资产
- **WHEN** 资产已被其他客户绑定
- **THEN** 系统 MUST 允许新增绑定，不得覆盖已有客户绑定关系

### Requirement: 登录响应与手机号绑定开关

系统 MUST 在登录响应中返回 `need_bind_phone`，该值由 `client.require_phone_binding` 与客户手机号绑定状态共同决定。

#### Scenario: 要求手机号绑定且未绑定
- **WHEN** 配置 `client.require_phone_binding=true` 且客户未绑定手机号
- **THEN** 登录响应 MUST 返回 `need_bind_phone=true`

#### Scenario: 已绑定手机号或配置关闭
- **WHEN** 客户已绑定手机号或 `client.require_phone_binding=false`
- **THEN** 登录响应 MUST 返回 `need_bind_phone=false`
