Files
rnpay/docs/walletman前端对接.md
2026-06-17 01:57:49 +08:00

334 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WalletMan 前端对接文档
> 后端协议见 [walletman后端对接.md](./walletman后端对接.md)
宿主 App**rnpay**)依赖 **rnwalletman**,负责 Proxy 常驻、WS 连接、FCM 收令、绑钱包 UI。
---
## 1. 职责分工
| 模块 | 职责 |
|------|------|
| `ProxyBackgroundService` | JS 层启停 Proxy、WS 状态、rebind/patch 配置、通知点击 |
| `BaseProxyService` | Android 前台 Service、WS、TCP 代理、FCM 静默重绑 |
| `ProxyFcmService` | 收 FCM、拉活、静默重绑、失败弹通知、点击 → prefs → JS |
| 宿主 `*ProxyService` | 继承 `BaseProxyService`,实现 `registerWallet` → POST `/register` |
| Bind 组件 | 各钱包 token / webview / OTP 绑定 UI |
**clientId** `DeviceInfo.getUniqueIdSync()`,与后端 WS/FCM 一致。
---
## 2. 依赖
```json
"rnwalletman": "file:libs/rnwalletman"
```
修改 lib 后同步:
```bash
rsync -a --delete libs/rnwalletman/ node_modules/rnwalletman/
```
需配置 Firebase`google-services.json` 与服务端 FCM **同一项目**
Personal 绑钱包需安装对应 **ipay 魔改包**chType/chVersion 与 `PATCH_EXPECT` 一致)。
---
## 3. Android 配置
### Application
```java
// MainApplication.onCreate
BaseProxyService.setServiceClass(RnpayProxyService.class);
```
### Manifest
```xml
<service
android:name=".RnpayProxyService"
android:exported="false"
android:foregroundServiceType="dataSync"
android:stopWithTask="false" />
<!-- 避免 Firebase 默认 Service 抢 FCM -->
<service
android:name="com.google.firebase.messaging.FirebaseMessagingService"
tools:node="remove" />
```
`ProxyFcmService` 由 rnwalletman manifest merge宿主无需重复声明。
### MainActivity
```java
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ProxyFcmService.handleLaunchIntent(this);
}
@Override protected void onResume() {
super.onResume();
ProxyFcmService.handleLaunchIntent(this);
}
@Override public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
ProxyFcmService.handleLaunchIntent(this);
}
```
推荐 `launchMode="singleTask"`
---
## 4. 宿主 registerWallet
FCM 静默重绑成功后native 调用宿主实现的 HTTP
```java
@Override
protected void registerWallet(Context ctx, String walletId, String walletType,
String phone, JSONObject params) throws Exception {
// POST {baseUrl}/register
// body: { "walletType": walletType, "params": params }
// header: X-User-ID
}
```
`baseUrl` / `userId` / `userToken` 来自 JS `syncRebindConfig``proxy_service_prefs`
参考:`android/app/src/main/java/com/rnpay/RnpayProxyService.java`
---
## 5. JS 对接
### 启动顺序
```typescript
import {
proxyBackgroundService,
type RebindConfig,
type PatchConfig,
type FcmNotificationTapPayload,
} from 'rnwalletman';
import DeviceInfo from 'react-native-device-info';
const PATCH_EXPECT: PatchConfig = {
chType: 'ipay',
paytmChVersion: '3',
phonepeChVersion: '1',
mobikwikChVersion: '1',
};
async function bootstrap(userId: number, userToken: string) {
const clientId = DeviceInfo.getUniqueIdSync();
// ① 尽早注册componentDidMount 第一行)
proxyBackgroundService.setNotificationTapHandler(handleNotificationTap);
await proxyBackgroundService.syncPatchConfig(PATCH_EXPECT);
await proxyBackgroundService.syncRebindConfig({
baseUrl: Api.BASE_URL,
userId,
userToken,
onRebound: () => fetchWallets(),
});
await proxyBackgroundService.start({
wsUrl: Api.WS_URL, // 如 wss://domain/ws
clientId,
userId,
heartbeatInterval: 10000,
reconnectInterval: 5000,
registerFcmToken: (cid, token) =>
Api.instance.registerFcmToken(cid, token),
onConnected: () => setProxyStatus('connected'),
onDisconnected: () => setProxyStatus('disconnected'),
onError: (msg) => setProxyStatus('error', msg),
});
}
function handleNotificationTap(payload: FcmNotificationTapPayload) {
if (payload.cmd !== 'rebind_wallet') return;
const { walletId, walletType, phone } = payload.params;
// 按 walletType 打开对应 Bind 弹窗(手动重绑)
}
```
App 回前台补读 pending 通知点击:
```typescript
AppState.addEventListener('change', (s) => {
if (s === 'active') void proxyBackgroundService.syncPendingNotificationTap();
});
```
### ProxyBackgroundService API
| 方法 | 作用 |
|------|------|
| `start(config)` | 启动前台 Proxy + WS |
| `stop()` | 停止 |
| `syncRebindConfig(config \| null)` | 写入 native 重绑 HTTP 配置 |
| `syncPatchConfig(config)` | ipay chType/chVersion 校验FCM 重绑共用) |
| `setNotificationTapHandler(fn \| null)` | 通知点击回调 |
| `syncPendingNotificationTap()` | 消费 prefs 中 pending 点击 |
### 其他导出
| 导出 | 作用 |
|------|------|
| `proxySendMessage` / `onProxyMessage` | WS 自定义消息(`ProxyBridge` |
| `startActivity` / `IntentFlags` | 拉起第三方 App deeplink |
| `openPaytmPayToBank` 等 | 银行转账 deeplink 封装 |
### Native → JS 事件
| 事件 | data |
|------|------|
| ProxyServiceConnected | - |
| ProxyServiceDisconnected | - |
| ProxyServiceRebound | walletId |
| ProxyServiceNotificationTap | JSON `{cmd, params}` |
| ProxyServiceCustomCommand | WS 自定义消息 |
| ProxyServiceFcmToken | FCM token 刷新 |
---
## 6. 绑钱包
### Token 模式Personalipay AIDL
| 组件 | walletType |
|------|------------|
| `PaytmPersonalBind` | `paytm` |
| `PhonePePersonalBind` | `phonepe`(需传 `userToken` |
| `MobikwikPersonalBind` | `mobikwik`(需传 `userToken` |
| `FreechargePersonalBind` | `freecharge` |
流程:打开 Bind 组件 → native AIDL 取 token → 校验 `chType/chVersion` === `PATCH_EXPECT``POST /register`
### Business 模式
| 组件 | 说明 |
|------|------|
| `PhonePeBusinessBind` | webview 绑 PhonePe Business |
| `GooglePayBusinessBind` | Google Pay Business |
Paytm / BharatPe Business 等在 rnpay 内走 OTP 组件,非 rnwalletman 导出。
### OTP 模式
`POST /request-otp``POST /verify-otp`
HTTP 封装参考:`services/api.ts`
---
## 7. FCM 客户端行为
### 收到 FCM
| cmd | 行为 |
|-----|------|
| `wake_proxy` | `BaseProxyService.wakeFromPrefs()` 重连 WS |
| `rebind_wallet` + `msg` 非空 | 先 `handleRebindDeferredNotify`:静默 AIDL → 成功 `registerWallet`;失败弹本地通知 |
| `rebind_wallet` + `msg` 为空 | 直接后台跑重绑,失败不弹通知 |
PhonePe`params.retry=true` 时走 gtk`iwphonepegtk`)→ 再 AIDL → register。
### 点击通知
```
PendingIntent(cmd + params)
→ MainActivity onCreate / onNewIntent / onResume
→ handleLaunchIntent
→ 写 prefs pending_notif_tap
→ emit 或等 JS syncPendingNotificationTap
→ setNotificationTapHandler 回调 → 打开 Bind UI
```
Intent 负责拉起 Appprefs 解决 JS 未 ready 的时序问题。
---
## 8. 时序(客户端)
**冷启动**
```
componentDidMount
→ setNotificationTapHandler
→ login → syncPatch / syncRebind → start
→ WS register { clientId, userId, fcmToken }
→ POST /fcm/register
→ GET /wallets
```
**TCP 代理**
```
收到 proxyRequest → TCP host:port → proxyReady → proxyData 双向
```
**重绑**
```
FCM rebind_wallet
→ 静默 AIDL → registerWallet → POST /register → ProxyServiceRebound
→ 失败 → 本地通知 → 点击 → handleNotificationTap → 手动 Bind
```
---
## 9. 调试
```bash
adb logcat -s ProxyFcmService ProxyService WalletRebind ProxyServiceModule
```
```bash
adb shell run-as com.rnpay cat shared_prefs/proxy_service_prefs.xml | grep pending
```
| 现象 | 排查 |
|------|------|
| FCM 无日志 | manifest 去掉默认 MessagingService勿 force-stop |
| 通知点击无回调 | logcat `notification tap`handler 是否第一行注册 |
| Proxy 一直 Connecting | WS 是否可达;`syncConnectionState` |
| version outdated | `PATCH_EXPECT` 与 ipay 包 chVersion 不一致 |
| 代理失败 | WS 是否 connected等 wake 后再试 |
| PhonePe 重绑 gtk 无效 | 后台 BAL 限制;需 App 在前台或用户点通知进 App |
服务端测试页:`http://server:16000/test/index.html`
---
## 10. rnpay 参考文件
| 文件 | 职责 |
|------|------|
| `services/api.ts` | BASE_URL / WS_URL / HTTP |
| `screens/HomeScreen.tsx` | Proxy、PATCH_EXPECT、notificationTap、Bind 弹窗 |
| `RnpayProxyService.java` | registerWallet |
| `MainApplication.java` | setServiceClass |
| `MainActivity.java` | handleLaunchIntent |
---
## 11. 新宿主 Checklist
1. 依赖 rnwalletman + Firebase同项目 service account
2. `setServiceClass` + Manifest Service + 移除默认 FCM MessagingService
3. MainActivity 三处 `handleLaunchIntent`
4. 继承 `BaseProxyService`,实现 `registerWallet`
5. JS`setNotificationTapHandler``syncPatchConfig``syncRebindConfig``start`
6. 各钱包 Bind UI + `POST /register`(或 OTP 流程)
7. 安装 ipay 魔改包PATCH 版本与服务端/文档一致