Files
rnpay/App.tsx
2026-01-30 22:09:17 +08:00

809 lines
31 KiB
TypeScript

import React, { Component } from "react";
import { Alert, Modal, StyleSheet, Text, TouchableOpacity, View } from "react-native";
import {
PaytmBusinessBind,
PhonePeBusinessBind,
GooglePayBusinessBind,
BharatPeBusinessBind,
WalletType,
PaytmBusinessBindResult,
PhonePeBusinessBindResult,
PaytmPersonalBind,
PaytmPersonalBindResult,
MobikwikPersonalBind,
MobikwikPersonalBindResult,
FreechargePersonalBind,
FreechargePersonalBindResult,
GooglePayBusinessBindResult,
BharatPeBusinessBindResult,
paytmPay,
onSmsMessage,
onNotificationMessage,
getAllSms,
getAllNotifications,
startSmsListener,
startNotificationListener,
stopSmsListener,
stopNotificationListener,
checkSmsPermission,
checkNotificationPermission,
openNotificationSettings,
requestSmsPermission,
PhonePePersonalBindResult,
PhonePePersonalBind,
SmsMessage,
NotificationMessage,
TcpProxy,
} from "rnwalletman";
import BarcodeScanning from '@react-native-ml-kit/barcode-scanning';
import RNFS from 'react-native-fs';
interface AppProps {
}
interface AppState {
showPaytmBusinessBind: boolean;
showPaytmPersonalBind: boolean;
showPhonePePersonalBind: boolean;
showPhonePeBusinessBind: boolean;
showGooglePayBusinessBind: boolean;
showBharatPeBusinessBind: boolean;
showMobikwikPersonalBind: boolean;
showFreechargePersonalBind: boolean;
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
button: {
padding: 10,
backgroundColor: "lightblue",
borderRadius: 5,
width: 200,
height: 50,
},
text: {
fontSize: 20,
fontWeight: "bold",
},
modal: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
modalContent: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
bindButton: {
padding: 10,
borderRadius: 5,
width: 250,
height: 40,
justifyContent: "center",
alignItems: "center",
margin: 10,
},
bindButtonText: {
fontSize: 15,
fontWeight: "bold",
},
});
class Api {
public static readonly BASE_URL = 'http://192.168.1.111:16000';
private static _instance: Api | null = null;
private ws: WebSocket | null = null;
private clientId: string = '';
private userId: number = 0;
private constructor() {
}
public setUserId(userId: number) {
this.userId = userId;
}
public getUserId(): number {
return this.userId;
}
public static get instance() {
if (Api._instance === null) {
Api._instance = new Api();
}
return Api._instance;
}
public setWebSocket(ws: WebSocket, clientId: string) {
this.ws = ws;
this.clientId = clientId;
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data);
if (msg.type === 'proxyRequest') {
const { host, port } = msg.data;
TcpProxy.createProxy(
msg.messageId,
host,
port,
(data: string) => {
ws.send(JSON.stringify({ type: 'proxyData', messageId: msg.messageId, data: { data } }));
},
() => { ws.send(JSON.stringify({ type: 'proxyClose', messageId: msg.messageId })); },
() => { ws.send(JSON.stringify({ type: 'proxyClose', messageId: msg.messageId })); }
).then(success => {
if (success) ws.send(JSON.stringify({ type: 'proxyReady', messageId: msg.messageId }));
}).catch(() => {
ws.send(JSON.stringify({ type: 'proxyClose', messageId: msg.messageId }));
});
}
if (msg.type === 'proxyData' && msg.data?.data) {
TcpProxy.writeProxy(msg.messageId, msg.data.data).catch(() => {
ws.send(JSON.stringify({ type: 'proxyClose', messageId: msg.messageId }));
});
}
if (msg.type === 'proxyClose') {
TcpProxy.closeProxy(msg.messageId).catch(() => {});
}
} catch (e) {
console.error('[API]', e, event.data);
}
};
}
private headers(): Record<string, string> {
const h: Record<string, string> = { 'Content-Type': 'application/json' };
if (this.userId) h['X-User-ID'] = String(this.userId);
return h;
}
public async login(username: string, password: string): Promise<number> {
const res = await fetch(`${Api.BASE_URL}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
const data = await res.json();
if (!data.success) throw new Error(data.message);
this.userId = data.data.userId;
return this.userId;
}
public async register(walletType: WalletType, params: any) {
const res = await fetch(`${Api.BASE_URL}/register`, {
method: 'POST',
headers: this.headers(),
body: JSON.stringify({ walletType, params }),
});
const data = await res.json();
if (!data.success) throw new Error(data.message);
return data;
}
public async requestOTP(walletType: WalletType, mobile: string, params: any = {}) {
const res = await fetch(`${Api.BASE_URL}/request-otp`, {
method: 'POST',
headers: this.headers(),
body: JSON.stringify({ walletType, mobile, ...params }),
});
const data = await res.json();
if (!data.success) throw new Error(data.message);
return data;
}
public async verifyOTP(walletType: WalletType, mobile: string, otp: string, params: any = {}) {
const res = await fetch(`${Api.BASE_URL}/verify-otp`, {
method: 'POST',
headers: this.headers(),
body: JSON.stringify({ walletType, mobile, otp, ...params }),
});
const data = await res.json();
if (!data.success) throw new Error(data.message);
return data;
}
}
export default class App extends Component<AppProps, AppState> {
private deviceId: string;
private tuneUserId: string;
private clientId: string;
constructor(props: AppProps) {
super(props);
this.state = {
showPaytmBusinessBind: false,
showPaytmPersonalBind: false,
showPhonePeBusinessBind: false,
showPhonePePersonalBind: false,
showGooglePayBusinessBind: false,
showBharatPeBusinessBind: false,
showMobikwikPersonalBind: false,
showFreechargePersonalBind: false,
};
// 临时使用测试成功的固定 ID
this.deviceId = 'B6C1AB6DA4B659C287EA76AA96EC154B80E8D28D';
this.tuneUserId = 'b5bfa7df-e571-4ac8-bb51-90afc05d1d59';
this.clientId = `android_${Date.now()}`;
}
async componentDidMount() {
/* 登录获取 userId */
try {
await Api.instance.login('test123', '123456');
console.log('[登录成功] userId:', Api.instance.getUserId());
} catch (error) {
console.error('[登录失败]', error);
Alert.alert('登录失败', String(error));
return;
}
/* 初始化代理客户端 */
await this.initProxyClient();
/* 权限申请 */
let smsPermission = await checkSmsPermission();
let notificationPermission = await checkNotificationPermission();
if (!smsPermission) {
const granted = await requestSmsPermission();
smsPermission = await checkSmsPermission();
console.log('smsPermission:', smsPermission);
if (!smsPermission) {
Alert.alert('需要短信权限', '请在系统设置中授予短信权限');
}
}
if (!notificationPermission) {
Alert.alert(
'需要通知监听权限',
'点击确定后将打开设置页面,请找到本应用并授予通知访问权限',
[
{
text: '确定',
onPress: () => openNotificationSettings()
},
{ text: '取消' }
]
);
return;
}
// 启动监听
startSmsListener();
startNotificationListener();
onSmsMessage((message: SmsMessage) => {
console.log('[SMS]', message);
});
onNotificationMessage((notification: NotificationMessage) => {
console.log('[Notification]', notification);
});
if (smsPermission) {
getAllSms().then((sms: SmsMessage[]) => {
console.log('[所有短信]', sms.length, '条');
}).catch(err => console.error('[获取短信失败]', err));
}
if (notificationPermission) {
getAllNotifications().then((notifications: NotificationMessage[]) => {
console.log('[所有通知]', notifications.length, '条');
}).catch(err => console.error('[获取通知失败]', err));
}
}
private wsHeartbeatTimer?: NodeJS.Timeout;
/**
* 初始化代理客户端
*/
async initProxyClient() {
try {
const serverUrl = `ws://${Api.BASE_URL.replace('http://', '')}/ws`;
console.log(`[WebSocket] 连接服务器: ${serverUrl}, 客户端ID: ${this.clientId}`);
// 创建WebSocket连接
const ws = new WebSocket(serverUrl);
ws.onopen = () => {
console.log('[WebSocket] ✅ 已连接');
ws.send(JSON.stringify({
type: 'register',
clientId: this.clientId,
messageId: 'register_' + Date.now(),
data: { userId: Api.instance.getUserId() }
}));
Api.instance.setWebSocket(ws, this.clientId);
// 启动心跳
this.startHeartbeat(ws);
};
ws.onerror = (error) => {
console.error('[WebSocket] 错误:', error);
};
ws.onclose = () => {
console.log('[WebSocket] 连接关闭');
this.stopHeartbeat();
// 3秒后重连
setTimeout(() => this.initProxyClient(), 3000);
};
} catch (error) {
console.error('[WebSocket] 连接失败:', error);
}
}
/**
* 启动心跳
*/
startHeartbeat(ws: WebSocket) {
this.stopHeartbeat();
this.wsHeartbeatTimer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'ping',
messageId: 'ping_' + Date.now(),
clientId: this.clientId
}));
}
}, 20000); // 每20秒发送一次心跳
}
/**
* 停止心跳
*/
stopHeartbeat() {
if (this.wsHeartbeatTimer) {
clearInterval(this.wsHeartbeatTimer);
this.wsHeartbeatTimer = undefined;
}
}
componentWillUnmount(): void {
// 停止心跳
this.stopHeartbeat();
stopSmsListener();
stopNotificationListener();
}
decodeQRFromUrl = async (url: string) => {
const localPath = `${RNFS.CachesDirectoryPath}/temp_qr_${Date.now()}.jpg`;
try {
// 1. 下载图片
await RNFS.downloadFile({ fromUrl: url, toFile: localPath }).promise;
// 2. 识别二维码
const barcodes = await BarcodeScanning.scan(`file://${localPath}`);
if (barcodes.length > 0) {
return barcodes[0].value;
}
} catch (e) {
console.error(e);
return null;
}
};
/* 绑定 Paytm Personal */
handlePaytmPersonalBind = () => {
this.setState({ showPaytmPersonalBind: true });
}
/* 上传 Paytm Personal 到服务器 */
handleUploadPaytmPersonal = async (result: PaytmPersonalBindResult) => {
try {
console.log(result);
const response = await Api.instance.register(WalletType.PAYTM_PERSONAL, result);
console.log(response);
this.setState({ showPaytmPersonalBind: false });
} catch (error) {
Alert.alert('Bind Paytm Personal Error', (error as Error).message || 'Unknown error');
this.setState({ showPaytmPersonalBind: false });
}
}
/* 绑定 Paytm Business */
handlePaytmBusinessBind = () => {
this.setState({ showPaytmBusinessBind: true });
}
/* 上传 Paytm Business 到服务器 */
handleUploadPaytmBusiness = async (result: PaytmBusinessBindResult) => {
try {
console.log(result);
const response = await Api.instance.register(WalletType.PAYTM_BUSINESS, result);
console.log(response);
this.setState({ showPaytmBusinessBind: false });
} catch (error) {
Alert.alert('Bind Paytm Business Error', (error as Error).message || 'Unknown error');
this.setState({ showPaytmBusinessBind: false });
}
}
/* 绑定 Mobikwik Personal */
handleMobikwikPersonalBind = () => {
this.setState({ showMobikwikPersonalBind: true });
}
/* 上传 Mobikwik Personal 到服务器 */
handleUploadMobikwikPersonal = async (result: MobikwikPersonalBindResult) => {
try {
console.log(JSON.stringify(result));
// 已在 verifyOTP 中完成注册
this.setState({ showMobikwikPersonalBind: false });
Alert.alert('绑定成功', 'Mobikwik Personal 绑定成功');
} catch (error) {
Alert.alert('Bind Mobikwik Personal Error', (error as Error).message || 'Unknown error');
this.setState({ showMobikwikPersonalBind: false });
}
}
/* 绑定 Freecharge Personal */
handleFreechargePersonalBind = () => {
this.setState({ showFreechargePersonalBind: true });
}
/* 上传 Freecharge Personal 到服务器 */
handleUploadFreechargePersonal = async (result: FreechargePersonalBindResult) => {
try {
console.log(JSON.stringify(result));
// 已经在 FreechargePersonalBind 中完成注册
this.setState({ showFreechargePersonalBind: false });
Alert.alert('绑定成功', 'Freecharge Personal 绑定成功');
} catch (error) {
Alert.alert('Bind Freecharge Personal Error', (error as Error).message || 'Unknown error');
this.setState({ showFreechargePersonalBind: false });
}
}
/* 绑定 PhonePe Personal */
handlePhonePePersonalBind = () => {
this.setState({ showPhonePePersonalBind: true });
}
/* 上传 PhonePe Personal 到服务器 */
handleUploadPhonePePersonal = async (result: PhonePePersonalBindResult) => {
try {
console.log(JSON.stringify(result));
Alert.alert('绑定成功', 'PhonePe Personal 绑定成功');
this.setState({ showPhonePePersonalBind: false });
} catch (error) {
Alert.alert('Bind PhonePe Personal Error', (error as Error).message || 'Unknown error');
this.setState({ showPhonePePersonalBind: false });
}
}
/* 绑定 PhonePe Business */
handlePhonePeBusinessBind = () => {
this.setState({ showPhonePeBusinessBind: true });
}
/* 上传 PhonePe Business 到服务器 */
handleUploadPhonePeBusiness = async (result: PhonePeBusinessBindResult) => {
try {
console.log(JSON.stringify(result));
const response = await Api.instance.register(WalletType.PHONEPE_BUSINESS, result);
console.log(response);
this.setState({ showPhonePeBusinessBind: false });
} catch (error) {
Alert.alert('Bind PhonePe Business Error', (error as Error).message || 'Unknown error');
this.setState({ showPhonePeBusinessBind: false });
}
}
/* 绑定 GooglePay Business */
handleGooglePayBusinessBind = () => {
this.setState({ showGooglePayBusinessBind: true });
}
/* 上传 GooglePay Business 到服务器 */
handleUploadGooglePayBusiness = async (result: GooglePayBusinessBindResult) => {
try {
console.log(JSON.stringify(result));
const response = await Api.instance.register(WalletType.GOOGLEPAY_BUSINESS, result);
console.log(response);
this.setState({ showGooglePayBusinessBind: false });
} catch (error) {
Alert.alert('Bind GooglePay Business Error', (error as Error).message || 'Unknown error');
this.setState({ showGooglePayBusinessBind: false });
}
}
/* 绑定 BharatPe Business */
handleBharatPeBusinessBind = () => {
this.setState({ showBharatPeBusinessBind: true });
}
/* 上传 BharatPe Business 到服务器 */
handleUploadBharatPeBusiness = async (result: BharatPeBusinessBindResult) => {
try {
console.log(result);
const qrCode = await this.decodeQRFromUrl(result.qrUrl || '');
console.log('qrCode:', qrCode);
const response = await Api.instance.register(WalletType.BHARATPE_BUSINESS, {
cookie: result.cookie,
accessToken: result.accessToken,
merchantId: result.merchantId,
userName: result.userName,
email: result.email,
mobile: result.mobile,
qrCode: qrCode,
});
console.log(response);
this.setState({ showBharatPeBusinessBind: false });
} catch (error) {
Alert.alert('Bind BharatPe Business Error', (error as Error).message || 'Unknown error');
this.setState({ showBharatPeBusinessBind: false });
}
}
renderBindModal = () => {
/* 绑定 PhonePe Personal */
if (this.state.showPhonePePersonalBind) {
return (
<Modal visible={this.state.showPhonePePersonalBind} transparent={true} onRequestClose={() => this.setState({ showPhonePePersonalBind: false })}>
<PhonePePersonalBind
processString="Processing PhonePe Personal..."
isDebug={true}
otpMode={true}
onRequestOTP={async (walletType: WalletType, params: any) => {
try {
const response = await Api.instance.requestOTP(walletType, params.mobile, {});
return response;
} catch (error) {
return { success: false, message: (error as Error).message };
}
}}
onVerifyOTP={async (walletType: WalletType, params: any) => {
try {
const response = await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
sessionId: params.sessionId, // 只需要传 sessionId
});
return response;
} catch (error) {
return { success: false, message: (error as Error).message };
}
}}
onSuccess={(result: PhonePePersonalBindResult) => {
this.handleUploadPhonePePersonal(result);
}}
onError={(error: string) => {
console.log(error);
Alert.alert('绑定失败', error);
this.setState({ showPhonePePersonalBind: false });
}}
/>
</Modal>
);
}
/* 绑定 Paytm Business */
if (this.state.showPaytmBusinessBind) {
return (
<Modal visible={this.state.showPaytmBusinessBind} transparent={true} onRequestClose={() => this.setState({ showPaytmBusinessBind: false })}>
<PaytmBusinessBind
processString="Processing Paytm Business..."
// isDebug={true}
onSuccess={(result: PaytmBusinessBindResult) => {
this.handleUploadPaytmBusiness(result);
}}
onError={(error: string) => {
console.log(error);
this.setState({ showPaytmBusinessBind: false });
}}
/>
</Modal>
);
}
/* 绑定 PhonePe Business */
if (this.state.showPhonePeBusinessBind) {
return (
<Modal visible={this.state.showPhonePeBusinessBind} transparent={true} onRequestClose={() => this.setState({ showPhonePeBusinessBind: false })}>
<PhonePeBusinessBind
processString="Processing PhonePe Business..."
isDebug={true}
onSuccess={(result: PhonePeBusinessBindResult) => {
this.handleUploadPhonePeBusiness(result);
}}
onError={(error) => {
console.log(error);
this.setState({ showPhonePeBusinessBind: false });
}}
/>
</Modal>
);
}
/* 绑定 GooglePay Business */
if (this.state.showGooglePayBusinessBind) {
return (
<Modal visible={this.state.showGooglePayBusinessBind} transparent={true} onRequestClose={() => this.setState({ showGooglePayBusinessBind: false })}>
<GooglePayBusinessBind
processString="Processing GooglePay Business..."
// isDebug={true}
onSuccess={(result: GooglePayBusinessBindResult) => {
this.handleUploadGooglePayBusiness(result);
}}
onError={(error: string) => {
console.log(error);
this.setState({ showGooglePayBusinessBind: false });
}}
/>
</Modal>
);
}
/* 绑定 BharatPe Business */
if (this.state.showBharatPeBusinessBind) {
return (
<Modal visible={this.state.showBharatPeBusinessBind} transparent={true} onRequestClose={() => this.setState({ showBharatPeBusinessBind: false })}>
<BharatPeBusinessBind
processString="Processing BharatPe Business..."
// isDebug={true}
onSuccess={(result) => {
this.handleUploadBharatPeBusiness(result);
}}
onError={(error) => {
console.log(error);
this.setState({ showBharatPeBusinessBind: false });
}}
/>
</Modal>
);
}
/* 绑定 Paytm Personal */
if (this.state.showPaytmPersonalBind) {
return (
<Modal visible={this.state.showPaytmPersonalBind} transparent={true} onRequestClose={() => this.setState({ showPaytmPersonalBind: false })}>
<PaytmPersonalBind
processString="Processing Paytm Personal..."
isDebug={true}
onSuccess={(result: PaytmPersonalBindResult) => {
this.handleUploadPaytmPersonal(result);
}}
onError={(error) => {
console.log(error);
this.setState({ showPaytmPersonalBind: false });
}}
/>
</Modal>
);
}
/* 绑定 Mobikwik Personal */
if (this.state.showMobikwikPersonalBind) {
return (
<Modal visible={this.state.showMobikwikPersonalBind} transparent={true} onRequestClose={() => this.setState({ showMobikwikPersonalBind: false })}>
<MobikwikPersonalBind
processString="Processing Mobikwik Personal..."
isDebug={true}
deviceId={this.deviceId}
tuneUserId={this.tuneUserId}
onRequestOTP={async (walletType: WalletType, params: { mobile: string; deviceId?: string; tuneUserId?: string }) => {
try {
const response = await Api.instance.requestOTP(walletType, params.mobile, {
deviceId: params.deviceId,
tuneUserId: params.tuneUserId
});
return response;
} catch (error) {
return { success: false, message: (error as Error).message };
}
}}
onVerifyOTP={async (walletType: WalletType, params: { mobile: string, otp: string, deviceId?: string, tuneUserId?: string, nid?: string }) => {
try {
const response = await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
deviceId: params.deviceId,
tuneUserId: params.tuneUserId,
nid: params.nid
});
return response;
} catch (error) {
return { success: false, message: (error as Error).message };
}
}}
onSuccess={(result: MobikwikPersonalBindResult) => {
this.handleUploadMobikwikPersonal(result);
}}
onError={(error) => {
console.log(error);
this.setState({ showMobikwikPersonalBind: false });
}}
/>
</Modal>
);
}
/* 绑定 Freecharge Personal */
if (this.state.showFreechargePersonalBind) {
return (
<Modal visible={this.state.showFreechargePersonalBind} transparent={true} onRequestClose={() => this.setState({ showFreechargePersonalBind: false })}>
<FreechargePersonalBind
onRequestOTP={async (walletType: WalletType, params: { mobile: string }) => {
try {
const response = await Api.instance.requestOTP(walletType, params.mobile);
return response;
} catch (error) {
return { success: false, message: (error as Error).message };
}
}}
onVerifyOTP={async (walletType: WalletType, params: { mobile: string, otp: string, otpId?: string, deviceId?: string, csrfId?: string, appFc?: string }) => {
try {
const response = await Api.instance.verifyOTP(walletType, params.mobile, params.otp, {
otpId: params.otpId,
deviceId: params.deviceId,
csrfId: params.csrfId,
appFc: params.appFc
});
return response;
} catch (error) {
return { success: false, message: (error as Error).message };
}
}}
processString="Processing Freecharge Personal..."
// isDebug={true}
onSuccess={(result: FreechargePersonalBindResult) => {
this.handleUploadFreechargePersonal(result);
}}
onError={(error) => {
console.log(error);
this.setState({ showFreechargePersonalBind: false });
}}
/>
</Modal>
);
}
return null;
}
render() {
return (
<View style={styles.container}>
{this.renderBindModal()}
<TouchableOpacity style={[styles.bindButton, { backgroundColor: "purple" }]} onPress={this.handlePaytmPersonalBind}>
<Text style={styles.bindButtonText}> Paytm Personal</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bindButton, { backgroundColor: "lightblue" }]} onPress={this.handlePaytmBusinessBind}>
<Text style={styles.bindButtonText}> Paytm Business</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bindButton, { backgroundColor: "#1890ff" }]} onPress={this.handlePhonePePersonalBind}>
<Text style={styles.bindButtonText}> PhonePe Personal</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bindButton, { backgroundColor: "lightgreen" }]} onPress={this.handlePhonePeBusinessBind}>
<Text style={styles.bindButtonText}> PhonePe Business</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bindButton, { backgroundColor: "#ff5555" }]} onPress={this.handleGooglePayBusinessBind}>
<Text style={styles.bindButtonText}> GooglePay Business</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bindButton, { backgroundColor: "pink" }]} onPress={this.handleBharatPeBusinessBind}>
<Text style={styles.bindButtonText}> BharatPe Business</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bindButton, { backgroundColor: "brown" }]} onPress={this.handleMobikwikPersonalBind}>
<Text style={styles.bindButtonText}> Mobikwik Personal</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bindButton, { backgroundColor: "#AA6600" }]} onPress={this.handleFreechargePersonalBind}>
<Text style={styles.bindButtonText}> Freecharge Personal</Text>
</TouchableOpacity>
</View>
);
}
}