Files
rnpay/docs/INTEGRATION.md
T
2026-06-27 13:41:44 +08:00

11 KiB
Raw Blame History

rnwalletman 接入文档

1. Android 宿主接入

1.1 新建 Service 子类

宿主 App 必须继承 BaseProxyService 并实现 registerWallet

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

<!-- 前台服务 -->
<service
    android:name=".AppProxyService"
    android:exported="false"
    android:foregroundServiceType="dataSync"
    android:stopWithTask="false" />

<!-- 来电 Activity(仅 fullscreen 模式需要) -->
<activity
    android:name=".IncomingCallActivity"
    android:exported="false"
    android:theme="@style/AppCallTheme"
    android:showWhenLocked="true"
    android:turnScreenOn="true"
    android:launchMode="singleInstance"
    android:excludeFromRecents="true"
    android:screenOrientation="portrait" />

<!-- 来电通知按钮接收器 -->
<receiver
    android:name=".CallActionReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="com.yourapp.CALL_ACCEPT" />
        <action android:name="com.yourapp.CALL_DECLINE" />
    </intent-filter>
</receiver>

<!-- FCM 消息接收 -->
<service
    android:name="com.rnwalletman.ProxyFcmService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

<!-- 权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

1.3 Application.onCreate 注册

@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

示例:

{
  "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 的任意字段

示例:

{
  "cmd": "incoming_call",
  "params": "{\"caller\":\"客服\",\"ringCount\":2,\"ringDuration\":30,\"mode\":\"fullscreen\",\"extra\":{\"orderId\":\"ORD001\"}}"
}

3. JS/TS 层 API

3.1 启动服务

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 用)

await proxyBackgroundService.syncRebindConfig({
    baseUrl: 'https://your-server',
    userId: 12345,
    userToken: 'jwt-token',
    onRebound: () => { /* 刷新 UI */ },
});

3.3 来电结果回调

接听 / 拒接 / 超时 均通过此回调返回,与通知点击回调相互独立

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 通知点击回调

proxyBackgroundService.setNotificationTapHandler((payload) => {
    const { cmd, params } = payload;
    // cmd: 'rebind_wallet' 等
});

3.5 其他方法

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

{ "clientId": "device-id", "fcmToken": "firebase-token" }

POST /fcm/send-wake

{ "clientId": "device-id" }

POST /fcm/send-rebind

{
  "clientId": "device-id",      // 可选,不传则按 walletId 查找绑定设备
  "walletId": "abc123",         // 必填
  "walletType": "phonepe",      // 可选,不传则自动查找
  "phone": "9876543210",        // 可选
  "retry": false,               // 是否重试(phonepe 强制 true
  "notify": false               // true=弹系统通知,false=静默重绑
}

POST /fcm/send-call

{
  "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

客户端 → 服务端

所有消息格式:

{ "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 示例

{
  "type": "register",
  "messageId": "init-001",
  "clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "data": {
    "userId": 12345,
    "fcmToken": "firebase-device-token"
  }
}