339 lines
7.4 KiB
Markdown
339 lines
7.4 KiB
Markdown
# WalletMan 后端对接文档
|
||
|
||
> 前端集成见 [walletman前端对接.md](./walletman前端对接.md)
|
||
|
||
---
|
||
|
||
## 1. 架构
|
||
|
||
```
|
||
Android 宿主 ◄── HTTP / WS ──► walletman (:16000 或反代) ◄── 代理 TCP ──► 钱包 API
|
||
◄── FCM data ──► Firebase
|
||
```
|
||
|
||
| 链路 | 用途 |
|
||
|------|------|
|
||
| HTTP REST | 登录、绑钱包、查流水等业务 |
|
||
| WebSocket `/ws` | 设备注册、TCP 代理、心跳 |
|
||
| FCM data | WS 断线拉活、token 过期重绑 |
|
||
|
||
**clientId**:设备唯一 ID,WS 注册 key + FCM 目标 key,须与 App `DeviceInfo.getUniqueIdSync()` 一致。
|
||
|
||
---
|
||
|
||
## 2. 启动与配置
|
||
|
||
```bash
|
||
cd servers/walletman/cmd/server
|
||
go run . -tls android
|
||
```
|
||
|
||
- 默认监听 `:16000`
|
||
- 测试页:`http://localhost:16000/test/index.html`
|
||
- 钱包持久化:`cmd/wallets.json`
|
||
|
||
**FCM 服务账号**(二选一):
|
||
|
||
```
|
||
servers/walletman/cmd/server/config/fcm-service-account.json
|
||
```
|
||
|
||
或环境变量 `FCM_SERVICE_ACCOUNT=/path/to/json`。须与宿主 App 的 `google-services.json` **同一 Firebase 项目**。
|
||
|
||
---
|
||
|
||
## 3. 通用约定
|
||
|
||
**响应格式:**
|
||
|
||
```json
|
||
{ "success": true, "message": "说明", "data": {} }
|
||
```
|
||
|
||
**需登录接口请求头:**
|
||
|
||
```
|
||
Content-Type: application/json
|
||
X-User-ID: 10000
|
||
```
|
||
|
||
---
|
||
|
||
## 4. HTTP 接口
|
||
|
||
### POST `/login`
|
||
|
||
```json
|
||
// req
|
||
{ "username": "test123", "password": "123456" }
|
||
// data
|
||
{ "userId": 10000, "userToken": "10000" }
|
||
```
|
||
|
||
### POST `/register`
|
||
|
||
Token 模式绑钱包 / 静默重绑落库。
|
||
|
||
```json
|
||
{
|
||
"walletType": "paytm",
|
||
"params": {
|
||
"type": "paytm",
|
||
"token": "...",
|
||
"refreshToken": "...",
|
||
"mobile": "9876543210",
|
||
"deviceId": "...",
|
||
"chType": "ipay",
|
||
"chVersion": "3"
|
||
}
|
||
}
|
||
```
|
||
|
||
```json
|
||
// data
|
||
{ "walletId": "paytm_9876543210", "walletType": "paytm" }
|
||
```
|
||
|
||
`walletId` = `{walletType}_{phone}`。
|
||
|
||
**支持的 walletType**(见 `walletman.GetAllWalletTypes()`):
|
||
|
||
| walletType | 说明 |
|
||
|------------|------|
|
||
| `paytm` | Paytm Personal(ipay AIDL) |
|
||
| `phonepe` | PhonePe Personal(ipay AIDL) |
|
||
| `mobikwik` | Mobikwik Personal |
|
||
| `freecharge` | Freecharge Personal |
|
||
| `amazonpay` | Amazon Pay Personal |
|
||
| `paytm business` | Paytm Business |
|
||
| `phonepe business` | PhonePe Business |
|
||
| `googlepay business` | Google Pay Business |
|
||
| `bharatpe business` | BharatPe Business |
|
||
|
||
各类型 `params` 字段以对应 `*_personal.go` / `*_business.go` 及前端 Bind 组件为准。
|
||
|
||
### POST `/request-otp` / POST `/verify-otp`
|
||
|
||
OTP 绑钱包。重绑场景若钱包已存在,会复用已有 params(保留 deviceId 等),见 `requestOTPHTTP`。
|
||
|
||
### GET `/wallets`
|
||
|
||
```json
|
||
{
|
||
"wallets": [{
|
||
"id": "paytm_9876543210",
|
||
"walletType": "paytm",
|
||
"phone": "9876543210",
|
||
"upi": "xxx@paytm",
|
||
"status": "active",
|
||
"otpMode": false
|
||
}]
|
||
}
|
||
```
|
||
|
||
`status=inactive` 且 `otpMode=false` 时,可能触发 FCM 重绑(见 §6)。
|
||
|
||
### POST `/fcm/register`
|
||
|
||
```json
|
||
{ "clientId": "设备ID", "fcmToken": "..." }
|
||
```
|
||
|
||
与 WS `register.data.fcmToken` 等价,均写入 FCM token 存储。
|
||
|
||
### POST `/fcm/send-wake`
|
||
|
||
```json
|
||
{ "clientId": "xxx" }
|
||
// 或
|
||
{ "userId": 10000 }
|
||
```
|
||
|
||
下发 `wake_proxy`,客户端重连 WS。
|
||
|
||
### POST `/fcm/send-rebind`
|
||
|
||
```json
|
||
{
|
||
"walletId": "paytm_9876543210",
|
||
"walletType": "paytm",
|
||
"phone": "9876543210",
|
||
"retry": false,
|
||
"notify": true
|
||
}
|
||
```
|
||
|
||
| 字段 | 说明 |
|
||
|------|------|
|
||
| `clientId` | 可省略,按 wallet owner 查 `GetFcmClientID` |
|
||
| `retry` | 传给客户端;**phonepe 类型服务端会强制 `retry=true`** |
|
||
| `notify` | `true` 时在 data 中带 `msg` 文案;客户端 AIDL 失败才本地弹通知 |
|
||
|
||
### GET `/fcm/clients`
|
||
|
||
已注册 FCM 的 clientId 列表。
|
||
|
||
### POST `/wallet/set-status`
|
||
|
||
手动改钱包状态;设为 inactive 时会 `maybeNotifyWalletRebind`。
|
||
|
||
---
|
||
|
||
## 5. WebSocket `/ws`
|
||
|
||
URL:`ws://host:16000/ws` 或 `wss://host/ws`(反代)
|
||
|
||
### 客户端 → 服务端
|
||
|
||
**register(连接后立即发送)**
|
||
|
||
```json
|
||
{
|
||
"type": "register",
|
||
"messageId": "register_1700000000000",
|
||
"clientId": "设备ID",
|
||
"data": { "userId": 10000, "fcmToken": "可选" }
|
||
}
|
||
```
|
||
|
||
- 绑定 `clientId → Client`
|
||
- `userManager.SetProxy(userId, clientId, ...)`
|
||
- 同 clientId 新连接踢旧连接
|
||
|
||
**ping(建议 10s)**
|
||
|
||
```json
|
||
{ "type": "ping", "messageId": "ping_xxx" }
|
||
```
|
||
|
||
**proxyReady / proxyData / proxyClose**
|
||
|
||
| type | 说明 |
|
||
|------|------|
|
||
| proxyReady | TCP 已连 host:port |
|
||
| proxyData | `data.data` = base64 |
|
||
| proxyClose | 结束本次代理 |
|
||
|
||
### 服务端 → 客户端
|
||
|
||
**proxyRequest**
|
||
|
||
```json
|
||
{
|
||
"type": "proxyRequest",
|
||
"messageId": "req_1700000000000",
|
||
"data": { "host": "api.phonepe.com", "port": 443 }
|
||
}
|
||
```
|
||
|
||
443 端口在服务端侧做 TLS,再 HTTP/2 转发。
|
||
|
||
### 服务端行为
|
||
|
||
| 事件 | 行为 |
|
||
|------|------|
|
||
| WS 断开 | `cleanupClient` → `sendFcmWake(clientId)` |
|
||
| 45s 无活动 | 心跳超时清理 + wake |
|
||
| 代理时 client 离线 | `requestProxy` 失败 → wake |
|
||
| 钱包 API 出网 | `proxyRoundTripper` → `requestProxy` |
|
||
|
||
代码入口:`websocket_handler.go`、`proxy.go`、`fcm.go`。
|
||
|
||
---
|
||
|
||
## 6. FCM 下发
|
||
|
||
重绑走 **data-only**(不发 FCM `notification` 块),文案放在 `data.msg`,由客户端决定何时弹本地通知。
|
||
|
||
| data key | 说明 |
|
||
|----------|------|
|
||
| `msg` | 通知文案(`notify=true` 时有值) |
|
||
| `cmd` | `wake_proxy` \| `rebind_wallet` |
|
||
| `params` | JSON 字符串 |
|
||
|
||
### wake_proxy
|
||
|
||
```json
|
||
{ "cmd": "wake_proxy", "params": "{}" }
|
||
```
|
||
|
||
### rebind_wallet
|
||
|
||
```json
|
||
{
|
||
"cmd": "rebind_wallet",
|
||
"msg": "Paytm 已过期,点我自动重绑",
|
||
"params": "{\"walletId\":\"paytm_9876543210\",\"walletType\":\"paytm\",\"phone\":\"9876543210\",\"retry\":false,\"tryGtkAnyway\":false}"
|
||
}
|
||
```
|
||
|
||
**params 字段:**
|
||
|
||
| 字段 | 说明 |
|
||
|------|------|
|
||
| `walletId` | 钱包 ID |
|
||
| `walletType` | 钱包类型 |
|
||
| `phone` | 手机号 |
|
||
| `retry` | PhonePe 是否走 gtk 重试;phonepe 类型服务端默认 true |
|
||
| `tryGtkAnyway` | 客户端扩展,当前服务端固定 false |
|
||
|
||
### 自动触发
|
||
|
||
1. `POST /wallet/set-status` 设为 inactive → `maybeNotifyWalletRebind(..., retry=false)`
|
||
2. 拉流水后 token 失效变 inactive → `persistWalletIfChanged` → phonepe 且 inactiveCount>1 时 `retry=true`
|
||
|
||
条件:`GetOTPMode()==false` 且 `GetStatus()==inactive`。
|
||
|
||
### 发送实现
|
||
|
||
`sendFcmToClient` → FCM HTTP v1 `projects/{id}/messages:send`。
|
||
|
||
---
|
||
|
||
## 7. 时序
|
||
|
||
**代理请求**
|
||
|
||
```
|
||
walletman HTTP 出网
|
||
→ GetProxyClientID(userId)
|
||
→ requestProxy(clientId, host, 443)
|
||
→ WS proxyRequest → 等 proxyReady
|
||
→ 双向 proxyData
|
||
```
|
||
|
||
**断线拉活**
|
||
|
||
```
|
||
WS cleanupClient
|
||
→ sendFcmWake(clientId)
|
||
→ 客户端 wakeFromPrefs → 重连 WS register
|
||
```
|
||
|
||
**重绑**
|
||
|
||
```
|
||
inactive 检测 / POST /fcm/send-rebind
|
||
→ FCM rebind_wallet(data.msg + params)
|
||
→ 客户端静默 AIDL → registerWallet → POST /register
|
||
→ 失败 → 客户端本地通知 → 用户点击 → JS handler 打开 Bind
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 调试
|
||
|
||
| 方式 | 说明 |
|
||
|------|------|
|
||
| `http://localhost:16000/test/index.html` | 发 wake / rebind、看 WS |
|
||
| GET `/fcm/clients` | 已注册设备 |
|
||
| POST `/fcm/send-wake` | 手动拉活 |
|
||
| POST `/fcm/send-rebind` | 手动重绑 |
|
||
|
||
| 现象 | 排查 |
|
||
|------|------|
|
||
| FCM send failed | 服务账号路径、project_id |
|
||
| no FCM token for clientId | App 未 POST `/fcm/register` 且 WS 未带 fcmToken |
|
||
| Client not connected | WS 未连;先发 wake |
|
||
| 重绑发了客户端无反应 | Firebase 项目是否与 App 一致;logcat `ProxyFcmService` |
|