334 lines
9.0 KiB
Markdown
334 lines
9.0 KiB
Markdown
# 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 模式(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 版本与服务端/文档一致
|