From 01e597ac939e412781a04e230f96309914f23daf Mon Sep 17 00:00:00 2001 From: TQCasey <494294315@qq.com> Date: Thu, 5 Feb 2026 00:09:57 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=20demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.tsx | 1052 +++++++++++---------------- components/OTPBindUI.tsx | 185 +++++ components/WalletBindComponents.tsx | 101 +++ hooks/useOTPBind.ts | 129 ++++ libs/rnwalletman | 2 +- servers/walletman | 2 +- services/api.ts | 77 ++ styles/index.ts | 46 ++ types/index.ts | 25 + 9 files changed, 981 insertions(+), 638 deletions(-) create mode 100644 components/OTPBindUI.tsx create mode 100644 components/WalletBindComponents.tsx create mode 100644 hooks/useOTPBind.ts create mode 100644 services/api.ts create mode 100644 styles/index.ts create mode 100644 types/index.ts diff --git a/App.tsx b/App.tsx index bdcc482..b99118d 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,5 @@ import React, { Component } from "react"; -import { Alert, AppState, AppStateStatus, Modal, StyleSheet, Text, TouchableOpacity, View } from "react-native"; +import { Alert, AppState, AppStateStatus, Modal, Text, TouchableOpacity, View } from "react-native"; import DeviceInfo from 'react-native-device-info'; import { PaytmBusinessBind, @@ -11,17 +11,12 @@ import { PhonePeBusinessBindResult, PaytmPersonalBind, PaytmPersonalBindResult, - MobikwikPersonalBind, MobikwikPersonalBindResult, - FreechargePersonalBind, FreechargePersonalBindResult, GooglePayBusinessBindResult, BharatPeBusinessBindResult, - paytmPay, onSmsMessage, onNotificationMessage, - getAllSms, - getAllNotifications, startSmsListener, startNotificationListener, stopSmsListener, @@ -30,165 +25,25 @@ import { checkNotificationPermission, openNotificationSettings, requestSmsPermission, - PhonePePersonalBindResult, + PhonePePersonalBindResult, PhonePePersonalBind, SmsMessage, NotificationMessage, proxyManager, } from "rnwalletman"; - import BarcodeScanning from '@react-native-ml-kit/barcode-scanning'; import RNFS from 'react-native-fs'; -interface AppProps { +import { + FreeChargeBind, + MobikwikOTPBind, + PayTmPersonalOTPBind, + PhonePePersonalOTPBind +} from './components/WalletBindComponents'; -} - -interface WalletmanAppState { - - /* Paytm Personal */ - showPaytmPersonalBind: boolean; - paytmPersonalBindType: 'otpMode' | 'authMode'; - showPaytmBusinessBind: boolean; - - /* PhonePe Personal */ - showPhonePePersonalBind: boolean; - phonePePersonalBindType: 'otpMode' | 'authMode'; - showPhonePeBusinessBind: boolean; - - /* GooglePay Business */ - showGooglePayBusinessBind: boolean; - - /* BharatPe Business */ - showBharatPeBusinessBind: boolean; - - /* Mobikwik Personal */ - showMobikwikPersonalBind: boolean; - - /* Freecharge Personal */ - showFreechargePersonalBind: boolean; -} - - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: "center", - alignItems: "center", - }, - button: { - padding: 10, - backgroundColor: "lightblue", - borderRadius: 5, - width: 200, - height: 55, - }, - text: { - fontSize: 20, - fontWeight: "bold", - }, - modal: { - flex: 1, - justifyContent: "center", - alignItems: "center", - }, - modalContent: { - flex: 1, - justifyContent: "center", - alignItems: "center", - }, - bindButton: { - borderRadius: 5, - width: 350, - height: 50, - justifyContent: "center", - alignItems: "center", - marginBottom : 10, - }, - bindButtonText: { - fontSize: 15, - fontWeight: "bold", - textAlign: "center", - textAlignVertical: "center", - }, -}); - -class Api { - - public static readonly BASE_URL = 'http://192.168.1.117:16000'; - private static _instance: Api | null = null; - 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; - } - - private headers(): Record { - const h: Record = { 'Content-Type': 'application/json' }; - if (this.userId) h['X-User-ID'] = String(this.userId); - return h; - } - - public async login(username: string, password: string): Promise { - 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; - } - -} +import Api from './services/api'; +import { AppProps, WalletmanAppState } from './types'; +import { styles } from './styles'; export default class App extends Component { private deviceId: string; @@ -200,163 +55,33 @@ export default class App extends Component { super(props); this.state = { paytmPersonalBindType: 'otpMode', - showPaytmBusinessBind: false, showPaytmPersonalBind: false, + showPaytmBusinessBind: false, + showPhonePePersonalBind: false, phonePePersonalBindType: 'otpMode', showPhonePeBusinessBind: false, - showPhonePePersonalBind: false, showGooglePayBusinessBind: false, showBharatPeBusinessBind: false, showMobikwikPersonalBind: false, showFreechargePersonalBind: false, }; - // 临时使用测试成功的固定 ID - this.deviceId = 'B6C1AB6DA4B659C287EA76AA96EC154B80E8D28D'; - this.tuneUserId = 'b5bfa7df-e571-4ac8-bb51-90afc05d1d59'; + this.deviceId = DeviceInfo.getUniqueIdSync(); + this.tuneUserId = Math.random().toString(36).substring(2, 15); } async componentDidMount() { - /* 获取真实 Android ID */ - try { - this.clientId = await DeviceInfo.getAndroidId(); - console.log('[设备ID]', this.clientId); - } catch (error) { - this.clientId = `android_${Date.now()}`; - console.warn('[设备ID] 获取失败,使用随机ID:', this.clientId); - } - /* 监听应用状态 */ - this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange); - - /* 登录获取 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.setupPermissions(); 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)); - } + this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange); } - private wsHeartbeatTimer?: NodeJS.Timeout; - - /** - * 初始化代理客户端 - */ - async initProxyClient() { - const wsUrl = `ws://${Api.BASE_URL.replace('http://', '')}/ws`; - - proxyManager.start({ - wsUrl, - clientId: this.clientId, - userId: Api.instance.getUserId(), - debug: true, - onConnected: () => { - console.log('[代理] ✅ 已连接'); - }, - onDisconnected: () => { - console.log('[代理] ❌ 已断开'); - }, - onError: (error) => { - console.error('[代理] 错误:', error); - }, - // 自定义注册逻辑(可选) - onRegister: (ws, clientId, userId) => { - // 可以在这里自定义发送的注册消息 - ws.send(JSON.stringify({ - type: 'register', - clientId, - messageId: 'register_' + Date.now(), - data: { - userId, - // 可以添加额外参数 - deviceInfo: { - platform: 'android', - version: '1.0.0' - } - } - })); - } - }).catch(err => { - console.error('[代理] 启动失败:', err); - }); - } - - /** - * 停止代理客户端 - */ - stopProxyClient() { - proxyManager.stop(); - } - - componentWillUnmount(): void { - // 清理应用状态监听 - this.appStateSubscription?.remove(); - - // 停止代理 + componentWillUnmount() { this.stopProxyClient(); - stopSmsListener(); stopNotificationListener(); } - /** - * 处理应用状态变化 - */ handleAppStateChange = (nextAppState: AppStateStatus) => { if (nextAppState === 'background' || nextAppState === 'inactive') { console.log('[AppState] 应用进入后台,关闭代理'); @@ -367,415 +92,450 @@ export default class App extends Component { } } + async setupPermissions() { + const hasSms = await checkSmsPermission(); + if (!hasSms) await requestSmsPermission(); + + const hasNotif = await checkNotificationPermission(); + if (!hasNotif) await openNotificationSettings(); + + startSmsListener(); + startNotificationListener(); + + onSmsMessage((msg: SmsMessage) => { + console.log('[SMS]', msg.address, msg.body); + }); + + onNotificationMessage((msg: NotificationMessage) => { + console.log('[Notification]', msg.packageName, msg.title, msg.text); + }); + } + + async initProxyClient() { + try { + this.clientId = DeviceInfo.getUniqueIdSync(); + console.log('[Proxy] 初始化客户端:', this.clientId); + await proxyManager.start({ + wsUrl: 'ws://192.168.1.117:16001/ws', + clientId: this.clientId || '', + userId: 1, + debug: true, + heartbeatInterval: 10000, + reconnectInterval: 5000, + reconnectMaxAttempts: 3, + onConnected: () => { + console.log('[Proxy] 客户端已连接'); + }, + onDisconnected: () => { + console.log('[Proxy] 客户端已断开'); + }, + onError: (error: string) => { + console.error('[Proxy] 错误:', error); + }, + onRegister: (ws: WebSocket, clientId: string, userId: number) => { + console.log('[Proxy] 客户端已注册:', clientId, userId); + }, + }); + console.log('[Proxy] 客户端已连接'); + } catch (error) { + console.error('[Proxy] 初始化失败:', error); + } + } + + stopProxyClient() { + try { + proxyManager.stop(); + console.log('[Proxy] 客户端已断开'); + } catch (error) { + console.error('[Proxy] 断开失败:', error); + } + } + 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; - } + if (barcodes.length > 0) return barcodes[0].value; } catch (e) { console.error(e); return null; } }; - /* 绑定 Paytm Personal */ - handlePaytmPersonalBind = (type: 'otpMode' | 'authMode') => { - this.setState({ showPaytmPersonalBind: true ,paytmPersonalBindType: type}); - } - - /* 上传 Paytm Personal 到服务器 */ + // Paytm Personal handleUploadPaytmPersonal = async (result: PaytmPersonalBindResult) => { try { console.log(result); - const response = await Api.instance.register(WalletType.PAYTM_PERSONAL, result); - console.log(response); + await Api.instance.register(WalletType.PAYTM_PERSONAL, result); this.setState({ showPaytmPersonalBind: false }); } catch (error) { - Alert.alert('Bind Paytm Personal Error', (error as Error).message || 'Unknown error'); + Alert.alert('绑定失败', (error as Error).message); this.setState({ showPaytmPersonalBind: false }); } } - /* 绑定 Paytm Business */ - handlePaytmBusinessBind = () => { - this.setState({ showPaytmBusinessBind: true }); + // PhonePe Personal + handleUploadPhonePePersonal = async (result: PhonePePersonalBindResult) => { + try { + console.log(result); + await Api.instance.register(WalletType.PHONEPE_PERSONAL, result); + this.setState({ showPhonePePersonalBind: false }); + } catch (error) { + Alert.alert('绑定失败', (error as Error).message); + this.setState({ showPhonePePersonalBind: false }); + } } - /* 上传 Paytm Business 到服务器 */ + // Paytm Business handleUploadPaytmBusiness = async (result: PaytmBusinessBindResult) => { try { console.log(result); - const response = await Api.instance.register(WalletType.PAYTM_BUSINESS, result); - console.log(response); + await Api.instance.register(WalletType.PAYTM_BUSINESS, result); this.setState({ showPaytmBusinessBind: false }); } catch (error) { - Alert.alert('Bind Paytm Business Error', (error as Error).message || 'Unknown error'); + Alert.alert('绑定失败', (error as Error).message); 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 = (type: 'otpMode' | 'authMode') => { - this.setState({ showPhonePePersonalBind: true ,phonePePersonalBindType: type}); - } - - /* 上传 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 到服务器 */ + // 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); + console.log(result); + await Api.instance.register(WalletType.PHONEPE_BUSINESS, result); this.setState({ showPhonePeBusinessBind: false }); } catch (error) { - Alert.alert('Bind PhonePe Business Error', (error as Error).message || 'Unknown error'); + Alert.alert('绑定失败', (error as Error).message); this.setState({ showPhonePeBusinessBind: false }); } } - /* 绑定 GooglePay Business */ - handleGooglePayBusinessBind = () => { - this.setState({ showGooglePayBusinessBind: true }); - } - - /* 上传 GooglePay Business 到服务器 */ + // 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); + console.log(result); + await Api.instance.register(WalletType.GOOGLEPAY_BUSINESS, result); this.setState({ showGooglePayBusinessBind: false }); } catch (error) { - Alert.alert('Bind GooglePay Business Error', (error as Error).message || 'Unknown error'); + Alert.alert('绑定失败', (error as Error).message); this.setState({ showGooglePayBusinessBind: false }); } } - /* 绑定 BharatPe Business */ - handleBharatPeBusinessBind = () => { - this.setState({ showBharatPeBusinessBind: true }); - } - - /* 上传 BharatPe Business 到服务器 */ + // 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); + await Api.instance.register(WalletType.BHARATPE_BUSINESS, result); this.setState({ showBharatPeBusinessBind: false }); } catch (error) { - Alert.alert('Bind BharatPe Business Error', (error as Error).message || 'Unknown error'); + Alert.alert('绑定失败', (error as Error).message); this.setState({ showBharatPeBusinessBind: false }); } } + // Mobikwik Personal + handleUploadMobikwikPersonal = async (result: MobikwikPersonalBindResult) => { + try { + console.log(JSON.stringify(result)); + this.setState({ showMobikwikPersonalBind: false }); + Alert.alert('绑定成功', 'Mobikwik Personal 绑定成功'); + } catch (error) { + Alert.alert('绑定失败', (error as Error).message); + this.setState({ showMobikwikPersonalBind: false }); + } + } + + // Freecharge Personal + handleUploadFreechargePersonal = async (result: FreechargePersonalBindResult) => { + try { + console.log(JSON.stringify(result)); + this.setState({ showFreechargePersonalBind: false }); + Alert.alert('绑定成功', 'Freecharge Personal 绑定成功'); + } catch (error) { + Alert.alert('绑定失败', (error as Error).message); + this.setState({ showFreechargePersonalBind: false }); + } + } + + renderPaytmPersonalTokenBind = () => { + return ( + this.setState({ showPaytmPersonalBind: false })}> + { + Alert.alert('绑定失败', error); + this.setState({ showPaytmPersonalBind: false }); + }} + /> + + ); + } + + renderPaytmPersonalOTPBind = () => { + return ( + this.setState({ showPaytmPersonalBind: false })}> + { + try { + return await Api.instance.requestOTP(walletType, params.mobile, {}); + } catch (error) { + return { success: false, message: (error as Error).message }; + } + }} + onVerifyOTP={async (walletType, params) => { + try { + return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, { + sessionId: params.sessionId, + }); + } catch (error) { + return { success: false, message: (error as Error).message }; + } + }} + onSuccess={this.handleUploadPaytmPersonal} + onError={(error: string) => { + Alert.alert('绑定失败', error); + this.setState({ showPaytmPersonalBind: false }); + }} + /> + + ); + } + + renderPhonePePersonalTokenBind = () => { + return ( + this.setState({ showPhonePePersonalBind: false })}> + { + Alert.alert('绑定失败', error); + this.setState({ showPhonePePersonalBind: false }); + }} + /> + + ); + } + + renderPhonePePersonalOTPBind = () => { + return ( + this.setState({ showPhonePePersonalBind: false })}> + { + try { + return await Api.instance.requestOTP(walletType, params.mobile, {}); + } catch (error) { + return { success: false, message: (error as Error).message }; + } + }} + onVerifyOTP={async (walletType, params) => { + try { + return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, { + sessionId: params.sessionId, + }); + } catch (error) { + return { success: false, message: (error as Error).message }; + } + }} + onSuccess={this.handleUploadPhonePePersonal} + onError={(error: string) => { + Alert.alert('绑定失败', error); + this.setState({ showPhonePePersonalBind: false }); + }} + /> + + ); + } + + renderPaytmBusinessBind = () => { + return ( + this.setState({ showPaytmBusinessBind: false })}> + { + Alert.alert('绑定失败', error); + this.setState({ showPaytmBusinessBind: false }); + }} + /> + + ); + } + + renderPhonePeBusinessBind = () => { + return ( + this.setState({ showPhonePeBusinessBind: false })}> + { + Alert.alert('绑定失败', error); + this.setState({ showPhonePeBusinessBind: false }); + }} + /> + + ); + } + + renderGooglePayBusinessBind = () => { + return ( + this.setState({ showGooglePayBusinessBind: false })}> + { + Alert.alert('绑定失败', error); + this.setState({ showGooglePayBusinessBind: false }); + }} + /> + + ); + } + + renderBharatPeBusinessBind = () => { + return ( + this.setState({ showBharatPeBusinessBind: false })}> + { + Alert.alert('绑定失败', error); + this.setState({ showBharatPeBusinessBind: false }); + }} + /> + + ); + } + + renderMobikwikPersonalOTPBind = () => { + return ( + this.setState({ showMobikwikPersonalBind: false })}> + { + try { + return await Api.instance.requestOTP(walletType, params.mobile, { + deviceId: params.deviceId, + tuneUserId: params.tuneUserId + }); + } catch (error) { + return { success: false, message: (error as Error).message }; + } + }} + onVerifyOTP={async (walletType, params) => { + try { + return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, { + deviceId: params.deviceId, + tuneUserId: params.tuneUserId, + nid: params.nid + }); + } catch (error) { + return { success: false, message: (error as Error).message }; + } + }} + onSuccess={this.handleUploadMobikwikPersonal} + onError={(error: string) => { + Alert.alert('绑定失败', error); + this.setState({ showMobikwikPersonalBind: false }); + }} + /> + + ); + } + + renderFreechargePersonalOTPBind = () => { + return ( + this.setState({ showFreechargePersonalBind: false })}> + { + try { + return await Api.instance.requestOTP(walletType, params.mobile); + } catch (error) { + return { success: false, message: (error as Error).message }; + } + }} + onVerifyOTP={async (walletType, params) => { + try { + return await Api.instance.verifyOTP(walletType, params.mobile, params.otp, { + otpId: params.otpId, + deviceId: params.deviceId, + csrfId: params.csrfId, + appFc: params.appFc + }); + } catch (error) { + return { success: false, message: (error as Error).message }; + } + }} + onSuccess={this.handleUploadFreechargePersonal} + onError={(error: string) => { + Alert.alert('绑定失败', error); + this.setState({ showFreechargePersonalBind: false }); + }} + /> + + ); + } + renderBindModal = () => { - - /* 绑定 Paytm Personal */ + // Paytm Personal if (this.state.showPaytmPersonalBind) { - return ( - this.setState({ showPaytmPersonalBind: false })}> - { - 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: PaytmPersonalBindResult) => { - this.handleUploadPaytmPersonal(result); - }} - onError={(error) => { - console.log(error); - this.setState({ showPaytmPersonalBind: false }); - }} - /> - - ); + if (this.state.paytmPersonalBindType === 'tokenMode') { + return this.renderPaytmPersonalTokenBind(); + } else { + return this.renderPaytmPersonalOTPBind (); + } } - /* 绑定 PhonePe Personal */ + // PhonePe Personal if (this.state.showPhonePePersonalBind) { - return ( - this.setState({ showPhonePePersonalBind: false })}> - { - 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 }); - }} - /> - - ); + if (this.state.phonePePersonalBindType === 'tokenMode') { + return this.renderPhonePePersonalTokenBind(); + } else { + return this.renderPhonePePersonalOTPBind (); + } } - /* 绑定 Paytm Business */ + // Paytm Business if (this.state.showPaytmBusinessBind) { - return ( - this.setState({ showPaytmBusinessBind: false })}> - { - this.handleUploadPaytmBusiness(result); - }} - onError={(error: string) => { - console.log(error); - this.setState({ showPaytmBusinessBind: false }); - }} - /> - - ); + return this.renderPaytmBusinessBind (); } - /* 绑定 PhonePe Business */ + // PhonePe Business if (this.state.showPhonePeBusinessBind) { - return ( - this.setState({ showPhonePeBusinessBind: false })}> - { - this.handleUploadPhonePeBusiness(result); - }} - onError={(error) => { - console.log(error); - this.setState({ showPhonePeBusinessBind: false }); - }} - /> - - ); + this.renderPhonePeBusinessBind (); } - /* 绑定 GooglePay Business */ + // GooglePay Business if (this.state.showGooglePayBusinessBind) { - return ( - this.setState({ showGooglePayBusinessBind: false })}> - { - this.handleUploadGooglePayBusiness(result); - }} - onError={(error: string) => { - console.log(error); - this.setState({ showGooglePayBusinessBind: false }); - }} - /> - - ); + return this.renderGooglePayBusinessBind (); } - /* 绑定 BharatPe Business */ + // BharatPe Business if (this.state.showBharatPeBusinessBind) { - return ( - this.setState({ showBharatPeBusinessBind: false })}> - { - this.handleUploadBharatPeBusiness(result); - }} - onError={(error) => { - console.log(error); - this.setState({ showBharatPeBusinessBind: false }); - }} - /> - - ); + return this.renderBharatPeBusinessBind (); } - /* 绑定 Mobikwik Personal */ + // Mobikwik Personal if (this.state.showMobikwikPersonalBind) { - return ( - this.setState({ showMobikwikPersonalBind: false })}> - { - 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 }); - }} - /> - - ); + return this.renderMobikwikPersonalOTPBind (); } - /* 绑定 Freecharge Personal */ + // Freecharge Personal if (this.state.showFreechargePersonalBind) { - return ( - this.setState({ showFreechargePersonalBind: false })}> - { - 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 }); - }} - /> - - ); + return this.renderFreechargePersonalOTPBind (); } return null; @@ -786,40 +546,60 @@ export default class App extends Component { {this.renderBindModal()} - + { + this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'otpMode' }); + }}> 绑定 Paytm Personal(OTP) - - 绑定 Paytm Personal(授权) + { + this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'tokenMode' }); + }}> + 绑定 Paytm Personal(Token) - + { + this.setState({ showPaytmBusinessBind: true }); + }}> 绑定 Paytm Business - + { + this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'otpMode' }); + }}> 绑定 PhonePe Personal(OTP) - - 绑定 PhonePe Personal(授权) + { + this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'tokenMode' }); + }}> + 绑定 PhonePe Personal(Token) - + { + this.setState({ showPhonePeBusinessBind: true }); + }}> 绑定 PhonePe Business - + { + this.setState({ showGooglePayBusinessBind: true }); + }}> 绑定 GooglePay Business - + { + this.setState({ showBharatPeBusinessBind: true }); + }}> 绑定 BharatPe Business - + { + this.setState({ showMobikwikPersonalBind: true }); + }}> 绑定 Mobikwik Personal - + { + this.setState({ showFreechargePersonalBind: true }); + }}> 绑定 Freecharge Personal ); } -} \ No newline at end of file +} diff --git a/components/OTPBindUI.tsx b/components/OTPBindUI.tsx new file mode 100644 index 0000000..2f5bbed --- /dev/null +++ b/components/OTPBindUI.tsx @@ -0,0 +1,185 @@ +import React from 'react'; +import { View, TextInput, TouchableOpacity, Text, StyleSheet, ActivityIndicator } from 'react-native'; +import { WalletType } from 'rnwalletman'; +import { useOTPBind } from '../hooks/useOTPBind'; + +interface OTPBindUIProps { + walletType: WalletType; + title: string; + otpLength?: number; + mobileLength?: number; + onRequestOTP: (walletType: WalletType, params: any) => Promise; + onVerifyOTP: (walletType: WalletType, params: any) => Promise; + onSuccess: (result: any) => void; + onError: (error: string) => void; + isDebug: boolean; + additionalParams?: any; +} + +export const OTPBindUI: React.FC = ({ + walletType, + title, + otpLength = 6, + mobileLength = 10, + onRequestOTP, + onVerifyOTP, + onSuccess, + onError, + isDebug, + additionalParams = {}, +}) => { + const [state, actions] = useOTPBind( + walletType, + { + onRequestOTP, + onVerifyOTP, + onSuccess, + onError, + isDebug, + }, + { + otpLength, + mobileLength, + additionalParams, + } + ); + + if (state.step === 'processing') { + return ( + + + 处理中... + + ); + } + + return ( + + + {title} + + {state.step === 'mobile' && ( + <> + + + {state.loading ? ( + + ) : ( + 获取验证码 + )} + + + )} + + {state.step === 'otp' && ( + <> + 验证码已发送至 {state.mobile} + + + {state.loading ? ( + + ) : ( + 验证并绑定 + )} + + + 重新输入手机号 + + + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: 'rgba(0,0,0,0.8)', + justifyContent: 'center', + alignItems: 'center', + }, + form: { + width: '80%', + backgroundColor: '#fff', + borderRadius: 10, + padding: 20, + }, + title: { + fontSize: 20, + fontWeight: 'bold', + textAlign: 'center', + marginBottom: 20, + }, + input: { + borderWidth: 1, + borderColor: '#ddd', + borderRadius: 5, + padding: 12, + fontSize: 16, + marginBottom: 15, + }, + button: { + backgroundColor: '#007AFF', + borderRadius: 5, + padding: 15, + alignItems: 'center', + }, + buttonDisabled: { + backgroundColor: '#ccc', + }, + buttonText: { + color: '#fff', + fontSize: 16, + fontWeight: 'bold', + }, + linkButton: { + marginTop: 10, + alignItems: 'center', + }, + linkText: { + color: '#007AFF', + fontSize: 14, + }, + hint: { + fontSize: 14, + color: '#666', + marginBottom: 10, + textAlign: 'center', + }, + processingText: { + color: '#fff', + fontSize: 16, + marginTop: 10, + }, +}); diff --git a/components/WalletBindComponents.tsx b/components/WalletBindComponents.tsx new file mode 100644 index 0000000..4df7f1a --- /dev/null +++ b/components/WalletBindComponents.tsx @@ -0,0 +1,101 @@ +import React, { Component } from 'react'; +import { WalletType, FreechargePersonalBindResult, MobikwikPersonalBindResult, PaytmPersonalBindResult, PhonePePersonalBindResult } from 'rnwalletman'; +import { OTPBindUI } from './OTPBindUI'; + +export class FreeChargeBind extends Component<{ + onRequestOTP: (walletType: WalletType, params: any) => Promise; + onVerifyOTP: (walletType: WalletType, params: any) => Promise; + onSuccess: (result: FreechargePersonalBindResult) => void; + onError: (error: string) => void; + isDebug: boolean; +}> { + render() { + return ( + + ); + } +} + +export class MobikwikOTPBind extends Component<{ + onRequestOTP: (walletType: WalletType, params: any) => Promise; + onVerifyOTP: (walletType: WalletType, params: any) => Promise; + onSuccess: (result: MobikwikPersonalBindResult) => void; + onError: (error: string) => void; + isDebug: boolean; + deviceId: string; + tuneUserId: string; +}> { + render() { + return ( + + ); + } +} + +export class PayTmPersonalOTPBind extends Component<{ + onRequestOTP: (walletType: WalletType, params: any) => Promise; + onVerifyOTP: (walletType: WalletType, params: any) => Promise; + onSuccess: (result: PaytmPersonalBindResult) => void; + onError: (error: string) => void; + isDebug: boolean; +}> { + render() { + return ( + + ); + } +} + +export class PhonePePersonalOTPBind extends Component<{ + onRequestOTP: (walletType: WalletType, params: any) => Promise; + onVerifyOTP: (walletType: WalletType, params: any) => Promise; + onSuccess: (result: PhonePePersonalBindResult) => void; + onError: (error: string) => void; + isDebug: boolean; +}> { + render() { + return ( + + ); + } +} diff --git a/hooks/useOTPBind.ts b/hooks/useOTPBind.ts new file mode 100644 index 0000000..38d6f35 --- /dev/null +++ b/hooks/useOTPBind.ts @@ -0,0 +1,129 @@ +import { useState } from 'react'; +import { WalletType } from 'rnwalletman'; + +export interface OTPBindCallbacks { + onRequestOTP: (walletType: WalletType, params: any) => Promise; + onVerifyOTP: (walletType: WalletType, params: any) => Promise; + onSuccess: (result: any) => void; + onError: (error: string) => void; + isDebug?: boolean; +} + +export interface OTPBindState { + mobile: string; + otp: string; + step: 'mobile' | 'otp' | 'processing'; + loading: boolean; + otpData: any; +} + +export interface OTPBindActions { + setMobile: (mobile: string) => void; + setOtp: (otp: string) => void; + requestOTP: () => Promise; + verifyOTP: () => Promise; + resetToMobile: () => void; +} + +export function useOTPBind( + walletType: WalletType, + callbacks: OTPBindCallbacks, + config?: { + otpLength?: number; + mobileLength?: number; + additionalParams?: any; + } +): [OTPBindState, OTPBindActions] { + const [mobile, setMobile] = useState(''); + const [otp, setOtp] = useState(''); + const [step, setStep] = useState<'mobile' | 'otp' | 'processing'>('mobile'); + const [loading, setLoading] = useState(false); + const [otpData, setOtpData] = useState(null); + + const { onRequestOTP, onVerifyOTP, onSuccess, onError, isDebug = false } = callbacks; + const { otpLength = 6, mobileLength = 10, additionalParams = {} } = config || {}; + + const log = (...args: any[]) => { + if (isDebug) console.log(`[${walletType}]`, ...args); + }; + + const error = (...args: any[]) => { + if (isDebug) console.error(`[${walletType}]`, ...args); + }; + + const requestOTP = async () => { + if (!mobile || mobile.length !== mobileLength) { + onError('Invalid mobile number'); + return; + } + + setLoading(true); + log('Requesting OTP for:', mobile); + + try { + const response = await onRequestOTP(walletType, { + mobile, + ...additionalParams, + }); + log('OTP response:', response); + + if (response.success) { + setOtpData(response.data); + setStep('otp'); + } else { + error('OTP request failed:', response.message); + onError(response.message || 'Failed to request OTP'); + } + } catch (e) { + error('Request OTP error:', e); + onError(e instanceof Error ? e.message : 'Failed to request OTP'); + } finally { + setLoading(false); + } + }; + + const verifyOTP = async () => { + if (!otp || otp.length !== otpLength) { + onError(`Invalid OTP (expected ${otpLength} digits)`); + return; + } + + setLoading(true); + setStep('processing'); + log('Verifying OTP:', otp); + + try { + const response = await onVerifyOTP(walletType, { + mobile, + otp, + ...additionalParams, + ...(otpData || {}), + }); + log('Verify response:', response); + + if (response.success) { + onSuccess(response.data); + } else { + error('Verify failed:', response.message); + setStep('otp'); + onError(response.message || 'Failed to verify OTP'); + } + } catch (e) { + error('Verify OTP error:', e); + setStep('otp'); + onError(e instanceof Error ? e.message : 'Failed to verify OTP'); + } finally { + setLoading(false); + } + }; + + const resetToMobile = () => { + setStep('mobile'); + setOtp(''); + }; + + return [ + { mobile, otp, step, loading, otpData }, + { setMobile, setOtp, requestOTP, verifyOTP, resetToMobile }, + ]; +} diff --git a/libs/rnwalletman b/libs/rnwalletman index ad66622..51280d4 160000 --- a/libs/rnwalletman +++ b/libs/rnwalletman @@ -1 +1 @@ -Subproject commit ad666221617d441b948b8194013b41a932ef2960 +Subproject commit 51280d4a128c46666e95b031565c20a6c7ddc1ce diff --git a/servers/walletman b/servers/walletman index aa50a65..ef6bc89 160000 --- a/servers/walletman +++ b/servers/walletman @@ -1 +1 @@ -Subproject commit aa50a6588d90c6470f29b5e0297a9945b047f0b0 +Subproject commit ef6bc8907cefe9786ab795102c180d2816f365cf diff --git a/services/api.ts b/services/api.ts new file mode 100644 index 0000000..653b7eb --- /dev/null +++ b/services/api.ts @@ -0,0 +1,77 @@ +import { WalletType } from 'rnwalletman'; + +class Api { + public static readonly BASE_URL = 'http://192.168.1.117:16000'; + private static _instance: Api | null = null; + 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; + } + + private headers(): Record { + const h: Record = { 'Content-Type': 'application/json' }; + if (this.userId) h['X-User-ID'] = String(this.userId); + return h; + } + + public async login(username: string, password: string): Promise { + 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 Api; diff --git a/styles/index.ts b/styles/index.ts new file mode 100644 index 0000000..e424a60 --- /dev/null +++ b/styles/index.ts @@ -0,0 +1,46 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + button: { + padding: 10, + backgroundColor: "lightblue", + borderRadius: 5, + width: 200, + height: 55, + }, + text: { + fontSize: 20, + fontWeight: "bold", + }, + modal: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + modalContent: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + bindButton: { + padding: 10, + marginTop: 10, + marginBottom: 10, + backgroundColor: "#007AFF", + borderRadius: 5, + width: "90%", + height: 55, + alignItems: "center", + justifyContent: "center", + }, + bindButtonText: { + fontSize: 16, + fontWeight: "bold", + color: "#fff", + }, +}); diff --git a/types/index.ts b/types/index.ts new file mode 100644 index 0000000..48836d0 --- /dev/null +++ b/types/index.ts @@ -0,0 +1,25 @@ +export interface AppProps {} + +export interface WalletmanAppState { + /* Paytm Personal */ + showPaytmPersonalBind: boolean; + paytmPersonalBindType: 'otpMode' | 'tokenMode'; + showPaytmBusinessBind: boolean; + + /* PhonePe Personal */ + showPhonePePersonalBind: boolean; + phonePePersonalBindType: 'otpMode' | 'tokenMode'; + showPhonePeBusinessBind: boolean; + + /* GooglePay Business */ + showGooglePayBusinessBind: boolean; + + /* BharatPe Business */ + showBharatPeBusinessBind: boolean; + + /* Mobikwik Personal */ + showMobikwikPersonalBind: boolean; + + /* Freecharge Personal */ + showFreechargePersonalBind: boolean; +}