From 7cbf86c42ccb9d6b53a10ed4410f48eb18faf751 Mon Sep 17 00:00:00 2001
From: TQCasey <494294315@qq.com>
Date: Sat, 27 Jun 2026 13:41:44 +0800
Subject: [PATCH] fix
---
.../java/com/rnpay/IncomingCallActivity.java | 6 +-
docs/INTEGRATION.md | 395 ++++++++++++++++++
2 files changed, 400 insertions(+), 1 deletion(-)
create mode 100644 docs/INTEGRATION.md
diff --git a/android/app/src/main/java/com/rnpay/IncomingCallActivity.java b/android/app/src/main/java/com/rnpay/IncomingCallActivity.java
index 25eda7f..3621959 100644
--- a/android/app/src/main/java/com/rnpay/IncomingCallActivity.java
+++ b/android/app/src/main/java/com/rnpay/IncomingCallActivity.java
@@ -126,13 +126,15 @@ public class IncomingCallActivity extends Activity {
rowLp.rightMargin = dp(13);
btnRow.setLayoutParams(rowLp);
- // 红色:挂断
+ // 红色:挂断(也进 app,让 JS 处理 decline 逻辑)
final String finalCaller = caller;
ImageView btnDecline = makeCircleBtn(0xFFe53935,
android.R.drawable.ic_menu_close_clear_cancel);
btnDecline.setOnClickListener(v -> {
resultDelivered = true;
+ IncomingCallNotificationHelper.cancel(this);
IncomingCallNotificationHelper.deliverCallResult(this, "decline");
+ launchMainApp(finalCaller);
finish();
});
btnRow.addView(btnDecline);
@@ -160,8 +162,10 @@ public class IncomingCallActivity extends Activity {
@Override public void onFinish() {
if (!resultDelivered) {
resultDelivered = true;
+ IncomingCallNotificationHelper.cancel(IncomingCallActivity.this);
IncomingCallNotificationHelper.deliverCallResult(
IncomingCallActivity.this, "decline");
+ launchMainApp(finalCaller);
}
finish();
}
diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md
new file mode 100644
index 0000000..5431ef3
--- /dev/null
+++ b/docs/INTEGRATION.md
@@ -0,0 +1,395 @@
+# rnwalletman 接入文档
+
+## 1. Android 宿主接入
+
+### 1.1 新建 Service 子类
+
+宿主 App 必须继承 `BaseProxyService` 并实现 `registerWallet`:
+
+```java
+package com.yourapp;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import com.rnwalletman.BaseProxyService;
+import org.json.JSONObject;
+import okhttp3.*;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+public class AppProxyService extends BaseProxyService {
+
+ private static final OkHttpClient HTTP = new OkHttpClient.Builder()
+ .connectTimeout(15, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .build();
+
+ @Override
+ protected void registerWallet(Context ctx, String walletId, String walletType,
+ String phone, JSONObject params) throws Exception {
+ SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ String baseUrl = prefs.getString("rebind_baseUrl", null); // JS 侧通过 syncRebindConfig 写入
+ int userId = prefs.getInt("rebind_userId", 0);
+
+ JSONObject body = new JSONObject();
+ body.put("walletType", walletType);
+ body.put("params", params);
+
+ Request req = new Request.Builder()
+ .url(baseUrl + "/register")
+ .header("X-User-ID", String.valueOf(userId))
+ .header("Content-Type", "application/json")
+ .post(RequestBody.create(body.toString(), MediaType.parse("application/json")))
+ .build();
+
+ try (Response resp = HTTP.newCall(req).execute()) {
+ String body2 = resp.body() != null ? resp.body().string() : "";
+ JSONObject json = new JSONObject(body2);
+ if (!json.optBoolean("success", false)) {
+ throw new IOException(json.optString("message", "register failed"));
+ }
+ }
+ }
+}
+```
+
+### 1.2 AndroidManifest.xml
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### 1.3 Application.onCreate 注册
+
+```java
+@Override
+public void onCreate() {
+ super.onCreate();
+ // 指定宿主 Service 类
+ BaseProxyService.setServiceClass(AppProxyService.class);
+ // 指定来电全屏 Activity(有来电功能时)
+ BaseProxyService.setCallActivityClass(IncomingCallActivity.class);
+}
+```
+
+---
+
+## 2. FCM 消息格式
+
+服务端发 **data-only** FCM(无 `notification` 块),data 字段:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `cmd` | string | 命令类型(见下表) |
+| `msg` | string | 可选,通知文案(非空则弹系统通知) |
+| `params` | string (JSON) | 命令参数 |
+
+### 命令列表
+
+#### `wake_proxy`
+唤醒后台 WebSocket 服务。`params` 为空 `{}`。
+
+---
+
+#### `rebind_wallet`
+触发钱包 token 重绑(AIDL 拉取 → `registerWallet` 回调)。
+
+`params` 字段:
+
+| 字段 | 类型 | 必填 | 默认 | 说明 |
+|------|------|------|------|------|
+| `walletId` | string | ✅ | — | 钱包 ID |
+| `walletType` | string | ✅ | — | `phonepe` / `paytm` / `mobikwik` 等 |
+| `phone` | string | — | `""` | 绑定手机号 |
+| `retry` | bool | — | `false` | 是否允许重试 |
+| `tryGtkAnyway` | bool | — | `false` | PhonePe 强制拉 GTK |
+
+示例:
+```json
+{
+ "cmd": "rebind_wallet",
+ "params": "{\"walletId\":\"abc123\",\"walletType\":\"phonepe\",\"phone\":\"9876543210\",\"retry\":true}"
+}
+```
+
+---
+
+#### `incoming_call`
+触发来电通知 / 全屏来电界面。
+
+`params` 字段:
+
+| 字段 | 类型 | 必填 | 默认 | 说明 |
+|------|------|------|------|------|
+| `caller` | string | — | `"Unknown"` | 来电显示名称 |
+| `ringCount` | int | — | `1` | 呼叫次数(≥1) |
+| `ringDuration` | int | — | `30` | 每次响铃时长(秒,≥5) |
+| `mode` | string | — | `"fullscreen"` | `"fullscreen"` 或 `"notification"` |
+| `extra` | object | — | — | 透传给 JS 的任意字段 |
+
+示例:
+```json
+{
+ "cmd": "incoming_call",
+ "params": "{\"caller\":\"客服\",\"ringCount\":2,\"ringDuration\":30,\"mode\":\"fullscreen\",\"extra\":{\"orderId\":\"ORD001\"}}"
+}
+```
+
+---
+
+## 3. JS/TS 层 API
+
+### 3.1 启动服务
+
+```typescript
+import { proxyBackgroundService } from 'rnwalletman';
+
+await proxyBackgroundService.start({
+ wsUrl: 'wss://your-server/ws',
+ clientId: 'device-unique-id',
+ userId: 12345,
+ heartbeatInterval: 20000, // ms,默认 20000
+ reconnectInterval: 5000, // ms
+ onConnected: () => {},
+ onDisconnected: () => {},
+ onError: (err) => {},
+ onCustomCommand: (msg) => {},
+ // 可选:上报 FCM token 到服务端
+ registerFcmToken: async (clientId, fcmToken) => {
+ await api.post('/fcm/register', { clientId, fcmToken });
+ },
+});
+```
+
+### 3.2 配置重绑(native 侧 AIDL 用)
+
+```typescript
+await proxyBackgroundService.syncRebindConfig({
+ baseUrl: 'https://your-server',
+ userId: 12345,
+ userToken: 'jwt-token',
+ onRebound: () => { /* 刷新 UI */ },
+});
+```
+
+### 3.3 来电结果回调
+
+接听 / 拒接 / 超时 均通过此回调返回,**与通知点击回调相互独立**:
+
+```typescript
+proxyBackgroundService.setCallResultHandler((payload) => {
+ const { action, caller, mode, ringDuration, ringCount, extra } = payload;
+ // action: 'accept' | 'decline'
+ if (action === 'accept') {
+ // 进入游戏 / 业务逻辑
+ }
+});
+```
+
+payload 类型:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `action` | `'accept' \| 'decline'` | 操作结果 |
+| `caller` | string | 来电显示名 |
+| `mode` | string | `fullscreen` / `notification` |
+| `ringDuration` | number | 响铃时长(秒) |
+| `ringCount` | number | 呼叫次数 |
+| `extra` | object? | 服务端透传的额外参数 |
+
+### 3.4 FCM 通知点击回调
+
+```typescript
+proxyBackgroundService.setNotificationTapHandler((payload) => {
+ const { cmd, params } = payload;
+ // cmd: 'rebind_wallet' 等
+});
+```
+
+### 3.5 其他方法
+
+```typescript
+await proxyBackgroundService.stop();
+
+// 引导用户忽略电池优化(Android)
+ProxyBackgroundService.openBatteryOptimizationSettings();
+```
+
+---
+
+## 4. JS 事件(DeviceEventEmitter)
+
+| 事件名 | payload | 说明 |
+|--------|---------|------|
+| `ProxyServiceConnected` | — | WS 已连接 |
+| `ProxyServiceDisconnected` | — | WS 断开 |
+| `ProxyServiceError` | `{ data: string }` | 错误信息 |
+| `ProxyServiceCustomCommand` | `{ data: string (JSON) }` | 服务端自定义消息 |
+| `ProxyServiceTick` | — | 每 ~60s 一次心跳 tick |
+| `ProxyServiceRebound` | `{ data: walletId }` | 重绑成功 |
+| `ProxyServiceNotificationTap` | `{ data: JSON }` | FCM 通知被点击 |
+| `ProxyServiceFcmToken` | `{ data: token }` | FCM token 更新 |
+| `ProxyServiceCallResult` | `{ data: JSON }` | 来电结果(accept/decline) |
+
+---
+
+## 5. 服务端发来电接口(phecda_hall gateway)
+
+```
+POST http://gateway:12881/fcm/send-call
+Content-Type: application/json
+
+{
+ "clientId": "device-client-id", // 或 "userId": 12345
+ "caller": "客服",
+ "ringCount": 1,
+ "ringDuration": 30,
+ "mode": "fullscreen", // "fullscreen" | "notification"
+ "extra": { "orderId": "ORD001" } // 可选,透传到 JS
+}
+```
+
+FCM token 注册:
+```
+POST http://gateway:12881/fcm/register
+Content-Type: application/json
+
+{
+ "clientId": "device-client-id",
+ "fcmToken": "firebase-device-token"
+}
+```
+
+> **Firebase 服务账号**:放到 `config/fcm-service-account.json`,或设环境变量 `FCM_SERVICE_ACCOUNT=/path/to/file.json`
+
+---
+
+## 6. walletman 服务端 HTTP 接口(rnpay,端口 16000)
+
+### FCM 管理
+
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| POST | `/fcm/register` | 注册设备 FCM token |
+| POST | `/fcm/send-wake` | 发送唤醒指令 |
+| POST | `/fcm/send-rebind` | 发送钱包重绑指令 |
+| POST | `/fcm/send-call` | 发送来电推送 |
+| GET | `/fcm/clients` | 列出所有已注册设备 |
+
+#### `POST /fcm/register`
+```json
+{ "clientId": "device-id", "fcmToken": "firebase-token" }
+```
+
+#### `POST /fcm/send-wake`
+```json
+{ "clientId": "device-id" }
+```
+
+#### `POST /fcm/send-rebind`
+```json
+{
+ "clientId": "device-id", // 可选,不传则按 walletId 查找绑定设备
+ "walletId": "abc123", // 必填
+ "walletType": "phonepe", // 可选,不传则自动查找
+ "phone": "9876543210", // 可选
+ "retry": false, // 是否重试(phonepe 强制 true)
+ "notify": false // true=弹系统通知,false=静默重绑
+}
+```
+
+#### `POST /fcm/send-call`
+```json
+{
+ "clientId": "device-id", // 必填
+ "caller": "客服", // 默认 "Customer Service"
+ "ringCount": 1, // 默认 1
+ "ringDuration": 30, // 秒,默认 30
+ "mode": "fullscreen", // "fullscreen" | "notification"
+ "extra": { "key": "val" } // 可选,透传到 JS
+}
+```
+
+---
+
+## 7. WebSocket 协议(`wss://server/ws`)
+
+### 客户端 → 服务端
+
+所有消息格式:
+```json
+{ "type": "<类型>", "messageId": "<唯一ID>", "clientId": "<设备ID>", "data": {} }
+```
+
+| type | data 字段 | 说明 |
+|------|-----------|------|
+| `register` | `{ userId, fcmToken }` | 连接后首条消息,注册设备身份 |
+| `ping` | — | 心跳,服务端回 `pong` |
+| `echo` | 任意 | 测试回显 |
+| `proxyReady` | — | 通知服务端 TCP 代理隧道就绪 |
+| `proxyData` | `{ data: base64 }` | 转发代理数据(按 `messageId` 路由) |
+| `proxyClose` | — | 通知代理数据发送完毕 |
+
+### 服务端 → 客户端
+
+| type | data 字段 | 说明 |
+|------|-----------|------|
+| `response` | `{ success, message, data }` | 通用应答 |
+| `echo` | 原样 | 回显 |
+| `proxyRequest` | `{ host, port }` | 服务端请求建立 TCP 代理隧道 |
+| `proxyData` | `{ data: base64 }` | 服务端 → 客户端代理数据 |
+| `proxyClose` | — | 服务端通知关闭代理隧道 |
+
+### register 示例
+```json
+{
+ "type": "register",
+ "messageId": "init-001",
+ "clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "data": {
+ "userId": 12345,
+ "fcmToken": "firebase-device-token"
+ }
+}
+```