# 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 ``` `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 模式(Personal,ipay 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 负责拉起 App;prefs 解决 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 版本与服务端/文档一致