# CubeIM 商户接入

服务地址：`https://kbz-cubeim.wjtop.cc`

管理员创建商户后将商户专属 API 密钥交给商户后端。密钥只在创建或重置时显示一次，妥善保存；不要放入 URL 或前端代码。管理员密钥不能作为商户密钥使用。

## 公共 HTTP Header

```http
Authorization: Bearer <商户API密钥>
Content-Type: application/json
```

商户身份由密钥识别，不需要传 merchant_id。所有账号和通知查询自动限定为该商户。每家商户可独立使用相同内部 account_id；同一个KBZ手机号不能在本服务多个商户下重复绑定。手机号统一使用同一种规范格式。

## 1. 绑定账号或更新 Token

```http
PUT /v1/accounts/<商户内部账号ID>
```

```json
{
  "account": "<KBZ手机号>",
  "token": "<该账号的Merch_Access_Token>"
}
```

账号ID只能使用1至80位字母、数字、下划线、短横线。重复 PUT 同一账号会更新凭证并重新连接；不允许把已有账号ID改成另一个手机号。202表示受理，online才表示上游鉴权成功。Token来自卡台在KBZ登录后调用PGWGetAccessToken，不是KBZ登录Token，也不是本服务API密钥。

可选回调字段：

```json
{
  "account": "<KBZ手机号>",
  "token": "<Merch_Access_Token>",
  "callback_url": "https://card.example.com/hooks/cubeim",
  "callback_secret": "<至少32字符的随机签名密钥>"
}
```

回调域名须在创建商户时的白名单中；不填回调时只存储通知。添加回调只影响新事件，历史“仅保存”记录不会自动补推。已入队的旧事件仍使用原有URL和签名密钥。

## 2. 查看账号

```http
GET /v1/accounts
GET /v1/accounts/<账号ID>
```

账号包含 account_id、account、enabled、connection；connection.state可能是connecting、online、reconnecting、auth_failed、kicked_out、stopped。列表最多3001条，与当前默认3000个启用账号上限相适配；历史停止账号也包含在列表中。鉴权失败或被踢后需提交新的有效Token恢复。

## 3. 查看通知

```http
GET /v1/notifications?offset=0
GET /v1/notifications?account_id=<账号ID>&offset=0
```

每页50条，offset依次为0、50、100；has_more表示是否有下一页。通知包括event正文、delivery_status和attempts。按接收时间倒序；有新通知写入时offset分页可能重复，使用event.event_id去重。

- transaction.notification：账单通知。
- connection.state：连接事件。
- stored：仅保存；pending：等待回调；delivered：已成功回调；dead：重试耗尽。

网页入口：`https://kbz-cubeim.wjtop.cc/notifications`，输入商户密钥即可查看该商户通知。

## 4. 停止账号

```http
DELETE /v1/accounts/<账号ID>
```

停止连接并禁用账号，保留已收通知及已排队回调。再次 PUT 可恢复。

## 回调验签

收到HTTP POST时读取原始正文及请求头：

- X-CubeIM-Event-ID
- X-CubeIM-Timestamp（Unix秒）
- X-CubeIM-Signature（小写十六进制）

计算 `HMAC-SHA256(callback_secret, timestamp + "." + event_id + "." + 原始正文)`，恒定时间比较签名并检查时间戳窗口（如±300秒）。以event_id幂等落库后返回HTTP 2xx。回调至少一次投递，失败最多重试20次，不保证顺序。

## 错误与限制

401：密钥无效；400：参数错误；404：账号不存在或不属于该商户；409：账号绑定冲突或达到容量上限；500：服务异常。

服务尚未完成真实KBZ账号联调；不自动补拉断线期间的通知，也不自动刷新KBZ凭证。卡台需保留查账补偿；账单通知仅用于触发查账，不作为最终入账依据。商户重置API密钥后，旧密钥的新请求立即失效；已鉴权的在途请求及已建立的上游连接不受影响。新密钥不会在列表或通知接口返回，丢失后由管理员重置。
