# WalletMan 前端对接文档 > 后端协议见 [walletman后端对接.md](./walletman后端对接.md) 宿主 App(如 **rnpay**)依赖 **rnwalletman**,负责 Proxy 常驻、WS 连接、FCM 收令、绑钱包 UI。 --- ## 1. 职责 | 模块 | 职责 | |------|------| | `ProxyBackgroundService` | 启停 Proxy、WS 状态、FCM/rebind 配置 | | `BaseProxyService` | 前台 Service、WS、TCP 代理、FCM 静默重绑 | | `ProxyFcmService` | 收 FCM、拉活、通知点击 → prefs → JS | | 宿主 `*ProxyService` | 实现 `registerWallet` → POST `/register` | | Bind 组件 | 各钱包 token/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 同项目。 --- ## 3. Android 配置 ### Application ```java // MainApplication.onCreate BaseProxyService.setServiceClass(RnpayProxyService.class); ``` ### Manifest ```xml ``` `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 调用: ```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, 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(); }); ``` ### API | 方法 | 作用 | |------|------| | `start(config)` | 前台 Proxy + WS | | `stop()` | 停止 | | `syncRebindConfig(config \| null)` | native 重绑 HTTP 配置 | | `syncPatchConfig(config)` | ipay chType/chVersion 校验 | | `setNotificationTapHandler(fn \| null)` | 通知点击回调 | | `syncPendingNotificationTap()` | 消费 prefs pending | ### Native 事件 | 事件 | data | |------|------| | ProxyServiceConnected | - | | ProxyServiceDisconnected | - | | ProxyServiceRebound | walletId | | ProxyServiceNotificationTap | JSON `{cmd,params}` | | ProxyServiceCustomCommand | WS 自定义消息 | --- ## 6. 绑钱包 **Token 模式** 1. 打开 `PaytmPersonalBind` / `PhonePePersonalBind` 等 2. 校验 `chType/chVersion` === `PATCH_EXPECT` 3. `POST /register` **OTP 模式** `POST /request-otp` → `POST /verify-otp` **HTTP 封装参考:** `services/api.ts` --- ## 7. FCM 客户端行为 ### 收到 FCM | cmd | 行为 | |-----|------| | wake_proxy | `BaseProxyService.wakeFromPrefs()` | | rebind_wallet | 先静默 AIDL → 成功 `registerWallet`;失败弹本地通知 | ### 点击通知 ``` PendingIntent(cmd+params) → MainActivity onCreate/onNewIntent/onResume → handleLaunchIntent 读 extra → 写 prefs pending_notif_tap → emit 或等 JS syncPendingNotificationTap → setNotificationTapHandler 回调 ``` Intent 负责拉起 App;prefs 解决 JS 未 ready 的时序问题。 --- ## 8. 时序(客户端视角) **冷启动** ``` componentDidMount → setNotificationTapHandler → login → syncPatch/Rebind → start → WS register { clientId, userId, fcmToken } → POST /fcm/register → GET /wallets ``` **代理** ``` 收到 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 已连但未 emit Connected(已修复 sync) | | version outdated | PATCH_EXPECT 与 ipay 包不一致 | | 代理失败 | WS 是否 connected;等 wake 后再试 | 服务端测试页可发 wake/rebind:`http://server:16000/test/index.html` --- ## 10. rnpay 参考 | 文件 | 职责 | |------|------| | `services/api.ts` | BASE_URL / WS_URL / HTTP | | `screens/HomeScreen.tsx` | Proxy、PATCH_EXPECT、notificationTap | | `RnpayProxyService.java` | registerWallet | | `MainApplication.java` | setServiceClass | | `MainActivity.java` | handleLaunchIntent | --- ## 11. 新宿主 checklist 1. 依赖 rnwalletman + Firebase 2. `setServiceClass` + Manifest Service + 去默认 FCM Service 3. MainActivity 处理 launch intent 4. 继承 `BaseProxyService` 实现 `registerWallet` 5. JS:`setNotificationTapHandler` → `syncPatch` → `syncRebind` → `start` 6. 各钱包 Bind UI + `POST /register`