From 628dc6bbc3fadda61d7e49bd9e4e0e4419499849 Mon Sep 17 00:00:00 2001 From: TQCasey <494294315@qq.com> Date: Sat, 18 Apr 2026 16:27:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/rnwalletman | 2 +- screens/HomeScreen.tsx | 1663 ++++++++++++++++++++++++++++++++-------- servers/walletman | 2 +- upi.html | 704 +++++++++-------- 说明.txt | 10 + 5 files changed, 1721 insertions(+), 660 deletions(-) create mode 100644 说明.txt diff --git a/libs/rnwalletman b/libs/rnwalletman index f4bdf90..732f4c9 160000 --- a/libs/rnwalletman +++ b/libs/rnwalletman @@ -1 +1 @@ -Subproject commit f4bdf909483ca1d081bfc2e98772a76d7ff95938 +Subproject commit 732f4c90f3374cd65fe2c887c35c74a02a30c6dc diff --git a/screens/HomeScreen.tsx b/screens/HomeScreen.tsx index 1b8dbed..fc6d307 100644 --- a/screens/HomeScreen.tsx +++ b/screens/HomeScreen.tsx @@ -1,9 +1,19 @@ -import React, { Component } from "react"; +import React, { Component } from 'react'; import { - Alert, AppState, AppStateStatus, FlatList, Image, Modal, - ScrollView, StyleSheet, Text, TextInput, - TouchableOpacity, View, ActivityIndicator, -} from "react-native"; + Alert, + AppState, + AppStateStatus, + FlatList, + Image, + Modal, + ScrollView, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, + ActivityIndicator, +} from 'react-native'; import * as Animatable from 'react-native-animatable'; import DeviceInfo from 'react-native-device-info'; import { @@ -13,7 +23,6 @@ import { PaytmBusinessBindResult, PhonePeBusinessBindResult, PaytmPersonalBind, - PaytmPersonalBindResult, MobikwikPersonalBindResult, FreechargePersonalBindResult, GooglePayBusinessBindResult, @@ -23,11 +32,10 @@ import { stopSmsListener, checkSmsPermission, requestSmsPermission, - PhonePePersonalBindResult, PhonePePersonalBind, SmsMessage, proxyBackgroundService, -} from "rnwalletman"; +} from 'rnwalletman'; import { FreeChargeBind, @@ -38,43 +46,98 @@ import { PaytmBusinessOTPBind, } from '../components/WalletBindComponents'; -import Api, { WalletItem, loadServerDomain, saveServerDomain, getServerDomain } from '../services/api'; +import Api, { + WalletItem, + loadServerDomain, + saveServerDomain, + getServerDomain, +} from '../services/api'; // key matches server WalletType string (see types.go) const WALLET_ICONS: Record = { - 'paytm': require('../res/paytm.png'), - 'paytm business': require('../res/paytm-business.png'), - 'phonepe': require('../res/phonepe.webp'), - 'phonepe business': require('../res/phonepe-business.webp'), + paytm: require('../res/paytm.png'), + 'paytm business': require('../res/paytm-business.png'), + phonepe: require('../res/phonepe.webp'), + 'phonepe business': require('../res/phonepe-business.webp'), 'googlepay business': require('../res/googlepay-business.webp'), - 'bharatpe business': require('../res/bharatpe-business.webp'), - 'mobikwik': require('../res/mobikwik.png'), - 'freecharge': require('../res/freecharge.png'), + 'bharatpe business': require('../res/bharatpe-business.webp'), + mobikwik: require('../res/mobikwik.png'), + freecharge: require('../res/freecharge.png'), }; const WALLET_TYPE_COLORS: Record = { - 'paytm': '#002970', - 'paytm business': '#002970', - 'phonepe': '#5a2d9c', - 'phonepe business': '#5a2d9c', + paytm: '#002970', + 'paytm business': '#002970', + phonepe: '#5a2d9c', + 'phonepe business': '#5a2d9c', 'googlepay business': '#4285f4', - 'bharatpe business': '#e91e63', - 'mobikwik': '#00bcd4', - 'freecharge': '#ff5722', + 'bharatpe business': '#e91e63', + mobikwik: '#00bcd4', + freecharge: '#ff5722', }; // wallet type display info (walletType matches server) const WALLET_TYPE_OPTIONS = [ - { key: 'paytm_personal_otp', walletType: 'paytm', label: 'Paytm Personal (OTP)', mode: 'otp' }, - { key: 'paytm_personal_token', walletType: 'paytm', label: 'Paytm Personal (Token)', mode: 'token' }, - { key: 'paytm_business', walletType: 'paytm business', label: 'Paytm Business (OTP)', mode: 'otp' }, - { key: 'phonepe_personal_otp', walletType: 'phonepe', label: 'PhonePe Personal (OTP)', mode: 'otp' }, - { key: 'phonepe_personal_token', walletType: 'phonepe', label: 'PhonePe Personal (Token)', mode: 'token' }, - { key: 'phonepe_business', walletType: 'phonepe business', label: 'PhonePe Business (OTP-WEB)', mode: 'otp' }, - { key: 'googlepay_business', walletType: 'googlepay business', label: 'GooglePay Business', mode: 'token' }, - { key: 'bharatpe_business', walletType: 'bharatpe business', label: 'BharatPe Business (OTP)', mode: 'otp' }, - { key: 'mobikwik_personal', walletType: 'mobikwik', label: 'Mobikwik Personal (OTP)', mode: 'otp' }, - { key: 'freecharge_personal', walletType: 'freecharge', label: 'Freecharge Personal (OTP)', mode: 'otp' }, + { + key: 'paytm_personal_otp', + walletType: 'paytm', + label: 'Paytm Personal (OTP)', + mode: 'otp', + }, + { + key: 'paytm_personal_token', + walletType: 'paytm', + label: 'Paytm Personal (Token)', + mode: 'token', + }, + { + key: 'paytm_business', + walletType: 'paytm business', + label: 'Paytm Business (OTP)', + mode: 'otp', + }, + { + key: 'phonepe_personal_otp', + walletType: 'phonepe', + label: 'PhonePe Personal (OTP)', + mode: 'otp', + }, + { + key: 'phonepe_personal_token', + walletType: 'phonepe', + label: 'PhonePe Personal (Token)', + mode: 'token', + }, + { + key: 'phonepe_business', + walletType: 'phonepe business', + label: 'PhonePe Business (OTP-WEB)', + mode: 'otp', + }, + { + key: 'googlepay_business', + walletType: 'googlepay business', + label: 'GooglePay Business', + mode: 'token', + }, + { + key: 'bharatpe_business', + walletType: 'bharatpe business', + label: 'BharatPe Business (OTP)', + mode: 'otp', + }, + { + key: 'mobikwik_personal', + walletType: 'mobikwik', + label: 'Mobikwik Personal (OTP)', + mode: 'otp', + }, + { + key: 'freecharge_personal', + walletType: 'freecharge', + label: 'Freecharge Personal (OTP)', + mode: 'otp', + }, ]; interface HomeScreenState { @@ -110,8 +173,11 @@ interface HomeScreenState { export default class HomeScreen extends Component { private deviceId: string; + private tuneUserId: string; + private clientId: string = ''; + private appStateSubscription?: any; constructor(props: any) { @@ -148,7 +214,8 @@ export default class HomeScreen extends Component { await this.setupPermissions(); const doLogin = () => { - Api.instance.login('test123', '123456') + Api.instance + .login('test123', '123456') .then(async () => { await this.startProxyClient(); this.fetchWallets(); @@ -159,7 +226,10 @@ export default class HomeScreen extends Component { }); }; doLogin(); - this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange); + this.appStateSubscription = AppState.addEventListener( + 'change', + this.handleAppStateChange, + ); } componentWillUnmount() { @@ -172,7 +242,7 @@ export default class HomeScreen extends Component { if (nextAppState === 'active') { this.fetchWallets(); } - } + }; async setupPermissions() { const hasSms = await checkSmsPermission(); @@ -197,8 +267,10 @@ export default class HomeScreen extends Component { reconnectInterval: 5000, reconnectMaxAttempts: Infinity, onConnected: () => this.setState({ proxyStatus: 'connected' }), - onDisconnected: () => this.setState({ proxyStatus: 'disconnected' }), - onError: (error: string) => this.setState({ proxyStatus: 'error', proxyError: error }), + onDisconnected: () => + this.setState({ proxyStatus: 'disconnected' }), + onError: (error: string) => + this.setState({ proxyStatus: 'error', proxyError: error }), }); } catch (error) { console.error('[Proxy] init failed:', error); @@ -206,7 +278,11 @@ export default class HomeScreen extends Component { } stopProxyClient() { - try { proxyBackgroundService.stop(); } catch {} + try { + proxyBackgroundService.stop(); + } catch { + /* ignore */ + } } fetchWallets = async () => { @@ -247,9 +323,11 @@ export default class HomeScreen extends Component { await Api.instance.setCurrentVpa(vpaModalWallet.id, idx); const walletId = vpaModalWallet.id; const vpa = vpaModalSelected; - this.setState(s => ({ + this.setState((s) => ({ vpaModalWallet: null, - wallets: s.wallets.map(w => w.id === walletId ? { ...w, upi: vpa } : w), + wallets: s.wallets.map((w) => + w.id === walletId ? { ...w, upi: vpa } : w, + ), })); } catch (e) { Alert.alert('Set Failed', (e as Error).message); @@ -258,135 +336,542 @@ export default class HomeScreen extends Component { // ---- bind handlers ---- - handleBindSuccess = (key: keyof HomeScreenState, msg: string) => async (result: any) => { - try { - const typeMap: Record = { - showPaytmPersonalBind: WalletType.PAYTM_PERSONAL, - showPaytmBusinessBind: WalletType.PAYTM_BUSINESS, - showPhonePePersonalBind: WalletType.PHONEPE_PERSONAL, - showPhonePeBusinessBind: WalletType.PHONEPE_BUSINESS, - showGooglePayBusinessBind: WalletType.GOOGLEPAY_BUSINESS, - showBharatPeBusinessBind: WalletType.BHARATPE_BUSINESS, - showMobikwikPersonalBind: WalletType.MOBIKWIK_PERSONAL, - showFreechargePersonalBind: WalletType.FREECHARGE_PERSONAL, - }; - const wt = typeMap[key as string]; - if (wt) await Api.instance.register(wt as WalletType, result); - this.setState({ [key]: false } as any); + /** Token 等需客户端 register 的流程(与 OTP 同 modal key 时勿用推断 map) */ + handleBindSuccess = + ( + key: keyof HomeScreenState, + walletType: WalletType, + msg: string, + ) => + async (result: any) => { + try { + await Api.instance.register(walletType, result); + this.setState({ [key]: false } as any); + Alert.alert('Bind Success', msg); + this.fetchWallets(); + } catch (error) { + this.setState({ [key]: false } as any); + Alert.alert('Bind Failed', (error as Error).message); + } + }; + + /** OTP:服务端 verify 已注册,只提示并关弹窗 */ + onOtpBindSuccess = + (key: keyof HomeScreenState, msg: string) => () => { Alert.alert('Bind Success', msg); - this.fetchWallets(); - } catch (error) { this.setState({ [key]: false } as any); - Alert.alert('Bind Failed', (error as Error).message); - } - }; + this.fetchWallets(); + }; // ---- modals ---- renderBindModal = () => { - const { showPaytmPersonalBind, paytmPersonalBindType, showPhonePePersonalBind, - phonePePersonalBindType, showPaytmBusinessBind, showPhonePeBusinessBind, - showGooglePayBusinessBind, showBharatPeBusinessBind, showMobikwikPersonalBind, - showFreechargePersonalBind } = this.state; + const { + showPaytmPersonalBind, + paytmPersonalBindType, + showPhonePePersonalBind, + phonePePersonalBindType, + showPaytmBusinessBind, + showPhonePeBusinessBind, + showGooglePayBusinessBind, + showBharatPeBusinessBind, + showMobikwikPersonalBind, + showFreechargePersonalBind, + } = this.state; - const close = (key: keyof HomeScreenState) => () => this.setState({ [key]: false } as any); + const close = (key: keyof HomeScreenState) => () => + this.setState({ [key]: false } as any); - if (showPaytmPersonalBind && paytmPersonalBindType === 'tokenMode') return ( - - { Alert.alert('Bind Failed', e); close('showPaytmPersonalBind')(); }} /> - - ); - if (showPaytmPersonalBind) return ( - - { try { return await Api.instance.requestOTP(wt, p.mobile, {}); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onVerifyOTP={async (wt, p) => { try { return await Api.instance.verifyOTP(wt, p.mobile, p.otp, { sessionId: p.sessionId }); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onSuccess={(r: PaytmPersonalBindResult) => { Alert.alert('Bind Success', 'Paytm Personal OTP'); this.setState({ showPaytmPersonalBind: false }); this.fetchWallets(); }} - onError={(e: string) => { Alert.alert('Bind Failed', e); close('showPaytmPersonalBind')(); }} - /> - - ); - if (showPhonePePersonalBind && phonePePersonalBindType === 'tokenMode') return ( - - { Alert.alert('Bind Failed', e); close('showPhonePePersonalBind')(); }} /> - - ); - if (showPhonePePersonalBind) return ( - - { try { return await Api.instance.requestOTP(wt, p.mobile, {}); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onVerifyOTP={async (wt, p) => { try { return await Api.instance.verifyOTP(wt, p.mobile, p.otp, { sessionId: p.sessionId }); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onSuccess={(r: PhonePePersonalBindResult) => { Alert.alert('Bind Success', 'PhonePe Personal OTP'); this.setState({ showPhonePePersonalBind: false }); this.fetchWallets(); }} - onError={(e: string) => { Alert.alert('Bind Failed', e); close('showPhonePePersonalBind')(); }} - /> - - ); - if (showPaytmBusinessBind) return ( - - { try { return await Api.instance.requestOTP(wt, p.mobile, { password: p.password }); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onVerifyOTP={async (wt, p) => { try { return await Api.instance.verifyOTP(wt, p.mobile, p.otp, { sessionId: p.sessionId }); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onSuccess={this.handleBindSuccess('showPaytmBusinessBind', 'Paytm Business bound successfully') as any} - onError={(e: string) => { Alert.alert('Bind Failed', e); close('showPaytmBusinessBind')(); }} - /> - - ); - if (showPhonePeBusinessBind) return ( - - { Alert.alert('Bind Failed', e); close('showPhonePeBusinessBind')(); }} - onRenderBottomView={({ showOtpInput, loading, formError, phone, otp, onPhoneChange, onOtpChange, onGetOtp, onSubmitOtp }) => ( - - {!showOtpInput ? (<> - - {!!formError && {formError}} - {loading ? 'Loading...' : 'GET OTP'} - ) : (<> - - {!!formError && {formError}} - {loading ? 'Loading...' : 'Verify OTP'} - )} - - )} - /> - - ); - if (showGooglePayBusinessBind) return ( - - { Alert.alert('Bind Failed', e); close('showGooglePayBusinessBind')(); }} /> - - ); - if (showBharatPeBusinessBind) return ( - - { try { return await Api.instance.requestOTP(wt, p.mobile); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onVerifyOTP={async (wt, p) => { try { return await Api.instance.verifyOTP(wt, p.mobile, p.otp, { sessionId: p.sessionId }); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onSuccess={this.handleBindSuccess('showBharatPeBusinessBind', 'BharatPe Business bound successfully') as any} - onError={(e: string) => { Alert.alert('Bind Failed', e); close('showBharatPeBusinessBind')(); }} - /> - - ); - if (showMobikwikPersonalBind) return ( - - { try { return await Api.instance.requestOTP(wt, p.mobile, { deviceId: p.deviceId, tuneUserId: p.tuneUserId }); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onVerifyOTP={async (wt, p) => { try { return await Api.instance.verifyOTP(wt, p.mobile, p.otp, { sessionId: p.sessionId, deviceId: p.deviceId, tuneUserId: p.tuneUserId, nid: p.nid }); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onSuccess={this.handleBindSuccess('showMobikwikPersonalBind', 'Mobikwik bound successfully') as any} - onError={(e: string) => { Alert.alert('Bind Failed', e); close('showMobikwikPersonalBind')(); }} - /> - - ); - if (showFreechargePersonalBind) return ( - - { try { return await Api.instance.requestOTP(wt, p.mobile); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onVerifyOTP={async (wt, p) => { try { return await Api.instance.verifyOTP(wt, p.mobile, p.otp, { otpId: p.otpId, deviceId: p.deviceId, csrfId: p.csrfId, appFc: p.appFc }); } catch (e) { return { success: false, message: (e as Error).message }; } }} - onSuccess={this.handleBindSuccess('showFreechargePersonalBind', 'Freecharge bound successfully') as any} - onError={(e: string) => { Alert.alert('Bind Failed', e); close('showFreechargePersonalBind')(); }} - /> - - ); + if (showPaytmPersonalBind && paytmPersonalBindType === 'tokenMode') { + return ( + + { + Alert.alert('Bind Failed', e); + close('showPaytmPersonalBind')(); + }} + /> + + ); + } + if (showPaytmPersonalBind) { + return ( + + { + try { + return await Api.instance.requestOTP( + wt, + p.mobile, + {}, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onVerifyOTP={async (wt, p) => { + try { + return await Api.instance.verifyOTP( + wt, + p.mobile, + p.otp, + { sessionId: p.sessionId }, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onSuccess={this.onOtpBindSuccess( + 'showPaytmPersonalBind', + 'Paytm Personal OTP', + )} + onError={(e: string) => { + Alert.alert('Bind Failed', e); + close('showPaytmPersonalBind')(); + }} + /> + + ); + } + if ( + showPhonePePersonalBind && + phonePePersonalBindType === 'tokenMode' + ) { + return ( + + { + Alert.alert('Bind Failed', e); + close('showPhonePePersonalBind')(); + }} + /> + + ); + } + if (showPhonePePersonalBind) { + return ( + + { + try { + return await Api.instance.requestOTP( + wt, + p.mobile, + {}, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onVerifyOTP={async (wt, p) => { + try { + return await Api.instance.verifyOTP( + wt, + p.mobile, + p.otp, + { sessionId: p.sessionId }, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onSuccess={this.onOtpBindSuccess( + 'showPhonePePersonalBind', + 'PhonePe Personal OTP', + )} + onError={(e: string) => { + Alert.alert('Bind Failed', e); + close('showPhonePePersonalBind')(); + }} + /> + + ); + } + if (showPaytmBusinessBind) { + return ( + + { + try { + return await Api.instance.requestOTP( + wt, + p.mobile, + { password: p.password }, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onVerifyOTP={async (wt, p) => { + try { + return await Api.instance.verifyOTP( + wt, + p.mobile, + p.otp, + { sessionId: p.sessionId }, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onSuccess={this.onOtpBindSuccess( + 'showPaytmBusinessBind', + 'Paytm Business bound successfully', + )} + onError={(e: string) => { + Alert.alert('Bind Failed', e); + close('showPaytmBusinessBind')(); + }} + /> + + ); + } + if (showPhonePeBusinessBind) { + return ( + + { + Alert.alert('Bind Failed', e); + close('showPhonePeBusinessBind')(); + }} + onRenderBottomView={({ + showOtpInput, + loading, + formError, + phone, + otp, + onPhoneChange, + onOtpChange, + onGetOtp, + onSubmitOtp, + }) => ( + + {!showOtpInput ? ( + <> + + {!!formError && ( + + {formError} + + )} + + + {loading + ? 'Loading...' + : 'GET OTP'} + + + + ) : ( + <> + + {!!formError && ( + + {formError} + + )} + + + {loading + ? 'Loading...' + : 'Verify OTP'} + + + + )} + + )} + /> + + ); + } + if (showGooglePayBusinessBind) { + return ( + + { + Alert.alert('Bind Failed', e); + close('showGooglePayBusinessBind')(); + }} + /> + + ); + } + if (showBharatPeBusinessBind) { + return ( + + { + try { + return await Api.instance.requestOTP( + wt, + p.mobile, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onVerifyOTP={async (wt, p) => { + try { + return await Api.instance.verifyOTP( + wt, + p.mobile, + p.otp, + { sessionId: p.sessionId }, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onSuccess={this.onOtpBindSuccess( + 'showBharatPeBusinessBind', + 'BharatPe Business bound successfully', + )} + onError={(e: string) => { + Alert.alert('Bind Failed', e); + close('showBharatPeBusinessBind')(); + }} + /> + + ); + } + if (showMobikwikPersonalBind) { + return ( + + { + try { + return await Api.instance.requestOTP( + wt, + p.mobile, + { + deviceId: p.deviceId, + tuneUserId: p.tuneUserId, + }, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onVerifyOTP={async (wt, p) => { + try { + return await Api.instance.verifyOTP( + wt, + p.mobile, + p.otp, + { + sessionId: p.sessionId, + deviceId: p.deviceId, + tuneUserId: p.tuneUserId, + nid: p.nid, + }, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onSuccess={this.onOtpBindSuccess( + 'showMobikwikPersonalBind', + 'Mobikwik bound successfully', + )} + onError={(e: string) => { + Alert.alert('Bind Failed', e); + close('showMobikwikPersonalBind')(); + }} + /> + + ); + } + if (showFreechargePersonalBind) { + return ( + + { + try { + return await Api.instance.requestOTP( + wt, + p.mobile, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onVerifyOTP={async (wt, p) => { + try { + return await Api.instance.verifyOTP( + wt, + p.mobile, + p.otp, + { + otpId: p.otpId, + deviceId: p.deviceId, + csrfId: p.csrfId, + appFc: p.appFc, + }, + ); + } catch (e) { + return { + success: false, + message: (e as Error).message, + }; + } + }} + onSuccess={this.onOtpBindSuccess( + 'showFreechargePersonalBind', + 'Freecharge bound successfully', + )} + onError={(e: string) => { + Alert.alert('Bind Failed', e); + close('showFreechargePersonalBind')(); + }} + /> + + ); + } return null; }; @@ -394,23 +879,60 @@ export default class HomeScreen extends Component { this.setState({ showAddWallet: false }); setTimeout(() => { switch (key) { - case 'paytm_personal_otp': this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'otpMode' }); break; - case 'paytm_personal_token': this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'tokenMode' }); break; - case 'paytm_business': this.setState({ showPaytmBusinessBind: true }); break; - case 'phonepe_personal_otp': this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'otpMode' }); break; - case 'phonepe_personal_token': this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'tokenMode' }); break; - case 'phonepe_business': this.setState({ showPhonePeBusinessBind: true }); break; - case 'googlepay_business': this.setState({ showGooglePayBusinessBind: true }); break; - case 'bharatpe_business': this.setState({ showBharatPeBusinessBind: true }); break; - case 'mobikwik_personal': this.setState({ showMobikwikPersonalBind: true }); break; - case 'freecharge_personal': this.setState({ showFreechargePersonalBind: true }); break; + case 'paytm_personal_otp': + this.setState({ + showPaytmPersonalBind: true, + paytmPersonalBindType: 'otpMode', + }); + break; + case 'paytm_personal_token': + this.setState({ + showPaytmPersonalBind: true, + paytmPersonalBindType: 'tokenMode', + }); + break; + case 'paytm_business': + this.setState({ showPaytmBusinessBind: true }); + break; + case 'phonepe_personal_otp': + this.setState({ + showPhonePePersonalBind: true, + phonePePersonalBindType: 'otpMode', + }); + break; + case 'phonepe_personal_token': + this.setState({ + showPhonePePersonalBind: true, + phonePePersonalBindType: 'tokenMode', + }); + break; + case 'phonepe_business': + this.setState({ showPhonePeBusinessBind: true }); + break; + case 'googlepay_business': + this.setState({ showGooglePayBusinessBind: true }); + break; + case 'bharatpe_business': + this.setState({ showBharatPeBusinessBind: true }); + break; + case 'mobikwik_personal': + this.setState({ showMobikwikPersonalBind: true }); + break; + case 'freecharge_personal': + this.setState({ showFreechargePersonalBind: true }); + break; } }, 300); }; renderAddWalletModal() { return ( - this.setState({ showAddWallet: false })}> + this.setState({ showAddWallet: false })} + > { useNativeDriver style={s.addModalBox} > - - Select Wallet Type - this.setState({ showAddWallet: false })}> - + + + Select Wallet Type + + + this.setState({ showAddWallet: false }) + } + > + + ✕ + - {WALLET_TYPE_OPTIONS.map(opt => ( - this.openWalletBind(opt.key)} activeOpacity={0.7}> - {WALLET_ICONS[opt.walletType] - ? - : - } - {opt.label} + {WALLET_TYPE_OPTIONS.map((opt) => ( + this.openWalletBind(opt.key)} + activeOpacity={0.7} + > + {WALLET_ICONS[opt.walletType] ? ( + + ) : ( + + )} + + {opt.label} + ))} @@ -443,40 +1002,128 @@ export default class HomeScreen extends Component { } renderServerSettingsModal() { - const { showServerSettings, settingsHost, settingsPort } = this.state; + const { showServerSettings, settingsHost, settingsPort } = + this.state; const presets = [ { label: 'aa.pfgame.org', host: 'aa.pfgame.org', port: '443' }, - { label: '192.168.1.117:16000', host: '192.168.1.117', port: '16000' }, + { + label: '192.168.1.117:16000', + host: '192.168.1.117', + port: '16000', + }, ]; return ( - + Server Settings - - {presets.map(p => ( - this.setState({ settingsHost: p.host, settingsPort: p.port })} - style={[s.presetBtn, settingsHost === p.host && settingsPort === p.port && s.presetBtnActive]}> - {p.label} + + {presets.map((p) => ( + + this.setState({ + settingsHost: p.host, + settingsPort: p.port, + }) + } + style={[ + s.presetBtn, + settingsHost === p.host && + settingsPort === p.port && + s.presetBtnActive, + ]} + > + + {p.label} + ))} Host - this.setState({ settingsHost: t })} placeholder="192.168.1.198" autoCapitalize="none" /> + + this.setState({ settingsHost: t }) + } + placeholder="192.168.1.198" + autoCapitalize="none" + /> Port - this.setState({ settingsPort: t })} placeholder="16000" keyboardType="number-pad" /> - - this.setState({ showServerSettings: false })} style={{ paddingHorizontal: 16, paddingVertical: 8, marginRight: 10 }}> + + this.setState({ settingsPort: t }) + } + placeholder="16000" + keyboardType="number-pad" + /> + + + this.setState({ showServerSettings: false }) + } + style={{ + paddingHorizontal: 16, + paddingVertical: 8, + marginRight: 10, + }} + > Cancel - { - const { settingsHost, settingsPort } = this.state; - const domain = settingsPort ? `${settingsHost}:${settingsPort}` : settingsHost; - await saveServerDomain(domain, settingsPort === '443'); - this.setState({ showServerSettings: false }); - Alert.alert('Saved', 'Restart app to take effect'); - }} style={s.saveBtn}> - Save + { + const { settingsHost, settingsPort } = + this.state; + const domain = settingsPort + ? `${settingsHost}:${settingsPort}` + : settingsHost; + await saveServerDomain( + domain, + settingsPort === '443', + ); + this.setState({ + showServerSettings: false, + }); + Alert.alert( + 'Saved', + 'Restart app to take effect', + ); + }} + style={s.saveBtn} + > + + Save + @@ -489,31 +1136,85 @@ export default class HomeScreen extends Component { const color = WALLET_TYPE_COLORS[item.walletType] ?? '#888'; const isActive = item.status === 'ACTIVE'; return ( - this.openVpaModal(item)} activeOpacity={0.8}> - - - {WALLET_ICONS[item.walletType] - ? - : - {item.walletType.split('_')[0]} - - } + this.openVpaModal(item)} + activeOpacity={0.8} + > + + + {WALLET_ICONS[item.walletType] ? ( + + ) : ( + + + {item.walletType.split('_')[0]} + + + )} - {item.phone || '—'} - {item.upi || 'No UPI'} + + {item.phone || '—'} + + + {item.upi || 'No UPI'} + - + ); }; renderVpaModal() { - const { vpaModalWallet, vpaModalVpas, vpaModalLoading, vpaModalSelected } = this.state; - const color = WALLET_TYPE_COLORS[vpaModalWallet?.walletType ?? ''] ?? '#3498db'; + const { + vpaModalWallet, + vpaModalVpas, + vpaModalLoading, + vpaModalSelected, + } = this.state; + const color = + WALLET_TYPE_COLORS[vpaModalWallet?.walletType ?? ''] ?? '#3498db'; return ( - + { style={s.vpaModalBox} > Select VPA - {vpaModalWallet?.phone} + + {vpaModalWallet?.phone} + - {vpaModalLoading - ? - : vpaModalVpas.length === 0 - ? No VPA data - : vpaModalVpas.map(vpa => { - const selected = vpa === vpaModalSelected; - return ( - this.setState({ vpaModalSelected: vpa })} - activeOpacity={0.7} + {vpaModalLoading ? ( + + ) : vpaModalVpas.length === 0 ? ( + + No VPA data + + ) : ( + vpaModalVpas.map((vpa) => { + const selected = vpa === vpaModalSelected; + return ( + + this.setState({ + vpaModalSelected: vpa, + }) + } + activeOpacity={0.7} + > + - - {selected && } - - {vpa} - - ); - }) - } + {selected && ( + + )} + + + {vpa} + + + ); + }) + )} - - Cancel + + + Cancel + - Confirm + + Confirm + @@ -568,13 +1343,14 @@ export default class HomeScreen extends Component { } render() { - const { proxyStatus, proxyError, wallets, loadingWallets } = this.state; + const { proxyStatus, proxyError, wallets, loadingWallets } = + this.state; const proxyCfg: Record = { - idle: { label: 'Idle', color: '#95a5a6' }, - connecting: { label: 'Connecting…', color: '#f39c12' }, - connected: { label: 'Connected', color: '#2ecc71' }, + idle: { label: 'Idle', color: '#95a5a6' }, + connecting: { label: 'Connecting…', color: '#f39c12' }, + connected: { label: 'Connected', color: '#2ecc71' }, disconnected: { label: 'Disconnected', color: '#e74c3c' }, - error: { label: 'Error', color: '#e74c3c' }, + error: { label: 'Error', color: '#e74c3c' }, }; const { label, color } = proxyCfg[proxyStatus]; @@ -582,27 +1358,68 @@ export default class HomeScreen extends Component { {/* top bar */} - - + + - Proxy {label}{proxyStatus === 'error' && proxyError ? `: ${proxyError}` : ''} + Proxy {label} + {proxyStatus === 'error' && proxyError + ? `: ${proxyError}` + : ''} - + { const domain = getServerDomain(); const colonIdx = domain.lastIndexOf(':'); - const host = colonIdx > 0 ? domain.substring(0, colonIdx) : domain; - const port = colonIdx > 0 ? domain.substring(colonIdx + 1) : ''; - this.setState({ showServerSettings: true, settingsHost: host, settingsPort: port }); + const host = + colonIdx > 0 + ? domain.substring(0, colonIdx) + : domain; + const port = + colonIdx > 0 + ? domain.substring(colonIdx + 1) + : ''; + this.setState({ + showServerSettings: true, + settingsHost: host, + settingsPort: port, + }); + }} + hitSlop={{ + top: 12, + bottom: 12, + left: 12, + right: 12, }} - hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }} style={{ padding: 6 }} > - + + ⚙ + - this.setState({ showAddWallet: true })} style={s.addBtn}> + + this.setState({ showAddWallet: true }) + } + style={s.addBtn} + > + Add @@ -611,20 +1428,37 @@ export default class HomeScreen extends Component { {/* wallet list */} Bound Wallets - - {loadingWallets ? 'Refreshing…' : 'Refresh'} + + + {loadingWallets ? 'Refreshing…' : 'Refresh'} + item.id} + keyExtractor={(item) => item.id} renderItem={this.renderWalletItem} ListEmptyComponent={ - - {loadingWallets ? 'Loading…' : 'No wallets. Tap + Add to get started.'} + + + {loadingWallets + ? 'Loading…' + : 'No wallets. Tap + Add to get started.'} + } /> @@ -639,118 +1473,289 @@ export default class HomeScreen extends Component { } const s = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#f0f0f0' }, + container: { + flex: 1, + backgroundColor: '#f0f0f0', + }, topBar: { - flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', - paddingHorizontal: 14, paddingVertical: 8, backgroundColor: '#fff', - borderBottomWidth: 1, borderBottomColor: '#e0e0e0', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 14, + paddingVertical: 8, + backgroundColor: '#fff', + borderBottomWidth: 1, + borderBottomColor: '#e0e0e0', }, addBtn: { - backgroundColor: '#3498db', borderRadius: 6, - paddingHorizontal: 10, paddingVertical: 4, + backgroundColor: '#3498db', + borderRadius: 6, + paddingHorizontal: 10, + paddingVertical: 4, + }, + addBtnText: { + color: '#fff', + fontSize: 13, + fontWeight: '600', }, - addBtnText: { color: '#fff', fontSize: 13, fontWeight: '600' }, listHeader: { - flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - paddingHorizontal: 14, paddingVertical: 10, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 14, + paddingVertical: 10, + }, + listHeaderText: { + fontSize: 14, + fontWeight: '600', + color: '#333', }, - listHeaderText: { fontSize: 14, fontWeight: '600', color: '#333' }, walletCard: { - backgroundColor: '#fff', borderRadius: 10, padding: 14, - marginBottom: 10, elevation: 1, - shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 4, shadowOffset: { width: 0, height: 2 }, + backgroundColor: '#fff', + borderRadius: 10, + padding: 14, + marginBottom: 10, + elevation: 1, + shadowColor: '#000', + shadowOpacity: 0.06, + shadowRadius: 4, + shadowOffset: { width: 0, height: 2 }, }, walletBadge: { - width: 52, height: 52, borderRadius: 10, overflow: 'hidden', - alignItems: 'center', justifyContent: 'center', + width: 52, + height: 52, + borderRadius: 10, + overflow: 'hidden', + alignItems: 'center', + justifyContent: 'center', backgroundColor: '#f5f5f5', }, - walletIcon: { width: 52, height: 52, borderRadius: 10 }, + walletIcon: { + width: 52, + height: 52, + borderRadius: 10, + }, walletIconInactive: { opacity: 0.3 }, walletBadgeInactive: { backgroundColor: '#f0f0f0' }, walletIconFallback: { - width: 52, height: 52, borderRadius: 10, - alignItems: 'center', justifyContent: 'center', + width: 52, + height: 52, + borderRadius: 10, + alignItems: 'center', + justifyContent: 'center', + }, + walletBadgeText: { + color: '#fff', + fontSize: 9, + fontWeight: '700', + textAlign: 'center', + }, + walletPhone: { + fontSize: 15, + fontWeight: '600', + color: '#222', + }, + walletUpi: { + fontSize: 12, + color: '#888', + marginTop: 2, + }, + statusDot: { + width: 8, + height: 8, + borderRadius: 4, }, - walletBadgeText: { color: '#fff', fontSize: 9, fontWeight: '700', textAlign: 'center' }, - walletPhone: { fontSize: 15, fontWeight: '600', color: '#222' }, - walletUpi: { fontSize: 12, color: '#888', marginTop: 2 }, - statusDot: { width: 8, height: 8, borderRadius: 4 }, vpaModalBox: { - backgroundColor: '#fff', borderRadius: 20, width: '88%', - maxHeight: '72%', overflow: 'hidden', - shadowColor: '#000', shadowOpacity: 0.18, shadowRadius: 20, shadowOffset: { width: 0, height: 8 }, + backgroundColor: '#fff', + borderRadius: 20, + width: '88%', + maxHeight: '72%', + overflow: 'hidden', + shadowColor: '#000', + shadowOpacity: 0.18, + shadowRadius: 20, + shadowOffset: { width: 0, height: 8 }, elevation: 10, }, - vpaModalTitle: { fontSize: 17, fontWeight: '700', color: '#222', paddingHorizontal: 20, paddingTop: 22 }, - vpaModalSub: { fontSize: 13, color: '#999', paddingHorizontal: 20, marginTop: 3, marginBottom: 14 }, - vpaModalList: { paddingHorizontal: 14, maxHeight: 260 }, + vpaModalTitle: { + fontSize: 17, + fontWeight: '700', + color: '#222', + paddingHorizontal: 20, + paddingTop: 22, + }, + vpaModalSub: { + fontSize: 13, + color: '#999', + paddingHorizontal: 20, + marginTop: 3, + marginBottom: 14, + }, + vpaModalList: { + paddingHorizontal: 14, + maxHeight: 260, + }, vpaOptionRow: { - flexDirection: 'row', alignItems: 'center', - paddingVertical: 12, paddingHorizontal: 12, - borderRadius: 8, borderWidth: 1.5, borderColor: '#eee', + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 12, + paddingHorizontal: 12, + borderRadius: 8, + borderWidth: 1.5, + borderColor: '#eee', marginBottom: 8, }, radioOuter: { - width: 20, height: 20, borderRadius: 10, borderWidth: 2, - borderColor: '#ccc', alignItems: 'center', justifyContent: 'center', marginRight: 12, + width: 20, + height: 20, + borderRadius: 10, + borderWidth: 2, + borderColor: '#ccc', + alignItems: 'center', + justifyContent: 'center', + marginRight: 12, + }, + radioInner: { + width: 10, + height: 10, + borderRadius: 5, + }, + vpaOptionText: { + fontSize: 14, + color: '#333', + flex: 1, }, - radioInner: { width: 10, height: 10, borderRadius: 5 }, - vpaOptionText: { fontSize: 14, color: '#333', flex: 1 }, vpaModalFooter: { - flexDirection: 'row', borderTopWidth: 1, borderTopColor: '#f0f0f0', marginTop: 8, + flexDirection: 'row', + borderTopWidth: 1, + borderTopColor: '#f0f0f0', + marginTop: 8, }, vpaModalCancelBtn: { - flex: 1, paddingVertical: 16, alignItems: 'center', - borderRightWidth: 1, borderRightColor: '#f0f0f0', + flex: 1, + paddingVertical: 16, + alignItems: 'center', + borderRightWidth: 1, + borderRightColor: '#f0f0f0', }, vpaModalConfirmBtn: { - flex: 1, paddingVertical: 16, alignItems: 'center', borderRadius: 0, + flex: 1, + paddingVertical: 16, + alignItems: 'center', + borderRadius: 0, }, modalOverlay: { - flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', - justifyContent: 'center', alignItems: 'center', + flex: 1, + backgroundColor: 'rgba(0,0,0,0.5)', + justifyContent: 'center', + alignItems: 'center', }, addModalBox: { - backgroundColor: '#fff', borderRadius: 12, padding: 20, - width: '88%', maxHeight: '70%', + backgroundColor: '#fff', + borderRadius: 12, + padding: 20, + width: '88%', + maxHeight: '70%', + }, + addModalTitle: { + fontSize: 16, + fontWeight: '700', + color: '#222', }, - addModalTitle: { fontSize: 16, fontWeight: '700', color: '#222' }, walletTypeRow: { - flexDirection: 'row', alignItems: 'center', - paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#f0f0f0', + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 12, + borderBottomWidth: 1, + borderBottomColor: '#f0f0f0', + }, + walletTypeIcon: { + width: 32, + height: 32, + borderRadius: 6, + marginRight: 12, + }, + walletTypeDot: { + width: 10, + height: 10, + borderRadius: 5, + marginRight: 12, + }, + walletTypeLabel: { + fontSize: 14, + color: '#333', }, - walletTypeIcon: { width: 32, height: 32, borderRadius: 6, marginRight: 12 }, - walletTypeDot: { width: 10, height: 10, borderRadius: 5, marginRight: 12 }, - walletTypeLabel: { fontSize: 14, color: '#333' }, settingsBox: { - backgroundColor: '#fff', borderRadius: 10, padding: 20, width: '85%', + backgroundColor: '#fff', + borderRadius: 10, + padding: 20, + width: '85%', + }, + settingsTitle: { + fontSize: 16, + fontWeight: 'bold', + marginBottom: 12, }, - settingsTitle: { fontSize: 16, fontWeight: 'bold', marginBottom: 12 }, presetBtn: { - paddingHorizontal: 10, paddingVertical: 5, borderRadius: 6, backgroundColor: '#f0f0f0', + paddingHorizontal: 10, + paddingVertical: 5, + borderRadius: 6, + backgroundColor: '#f0f0f0', }, presetBtnActive: { backgroundColor: '#3498db' }, - inputLabel: { fontSize: 13, color: '#666', marginBottom: 4 }, + inputLabel: { + fontSize: 13, + color: '#666', + marginBottom: 4, + }, textInput: { - borderWidth: 1, borderColor: '#ddd', borderRadius: 6, - paddingHorizontal: 10, paddingVertical: 8, marginBottom: 12, fontSize: 14, + borderWidth: 1, + borderColor: '#ddd', + borderRadius: 6, + paddingHorizontal: 10, + paddingVertical: 8, + marginBottom: 12, + fontSize: 14, }, saveBtn: { - backgroundColor: '#3498db', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 6, + backgroundColor: '#3498db', + paddingHorizontal: 16, + paddingVertical: 8, + borderRadius: 6, }, otpBar: { - position: 'absolute', bottom: 0, left: 0, right: 0, - backgroundColor: '#fff', padding: 16, borderTopWidth: 1, - borderTopColor: '#e0e0e0', gap: 10, + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + backgroundColor: '#fff', + padding: 16, + borderTopWidth: 1, + borderTopColor: '#e0e0e0', + gap: 10, }, otpInput: { - borderWidth: 1, borderColor: '#ccc', borderRadius: 8, - paddingHorizontal: 12, paddingVertical: 10, fontSize: 15, color: '#333', + borderWidth: 1, + borderColor: '#ccc', + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 10, + fontSize: 15, + color: '#333', }, otpBtn: { - backgroundColor: '#5a2d9c', borderRadius: 8, paddingVertical: 12, alignItems: 'center', + backgroundColor: '#5a2d9c', + borderRadius: 8, + paddingVertical: 12, + alignItems: 'center', + }, + otpBtnText: { + color: '#fff', + fontSize: 15, + fontWeight: '600', + }, + errText: { + color: '#e53935', + fontSize: 13, }, - otpBtnText: { color: '#fff', fontSize: 15, fontWeight: '600' }, - errText: { color: '#e53935', fontSize: 13 }, }); diff --git a/servers/walletman b/servers/walletman index 4ba9727..e5299b7 160000 --- a/servers/walletman +++ b/servers/walletman @@ -1 +1 @@ -Subproject commit 4ba972771c875c28318b45eb4d5c2372dbbe4a9b +Subproject commit e5299b7665b7ad30eb7c6d5b68bac86fd07c01dd diff --git a/upi.html b/upi.html index 7520cb1..34f8711 100644 --- a/upi.html +++ b/upi.html @@ -1,353 +1,399 @@ + - - -UPI Pay Test - + + + UPI Pay Test + + -

UPI Pay Test

+

UPI Pay Test

-
-
- - -
-
- - -
-
- - -
-
-
- Receiver (Payee) - +
+
+ + +
+
+ + +
+
+ + +
+
+
+ Receiver (Payee) + +
+
+ UPI ID + +
+
+ Amount + +
+
+
+
+ + +
+ Pay Now
-
- UPI ID - + +
+

UPI QR

+
-
- Amount - -
-
-
-
- - -
- Pay Now -
-
-

UPI QR

-
-
+ + - + document.getElementById('amount').addEventListener('input', update); + update(); + - + + \ No newline at end of file diff --git a/说明.txt b/说明.txt new file mode 100644 index 0000000..d95588d --- /dev/null +++ b/说明.txt @@ -0,0 +1,10 @@ +代收支持 tn 的 + +1.除 mobikwik 和 bhartepe business 之外的钱包都支持 tn 代收 +2.mobikwik 是如果对方付的是 mobikwik ,那么也支持 tn + +代付: +paytm 魔改包,支持 tn +mobikwik 自动拉起,但是 tn 号不会回传(我测试下) +phonepe 魔改包,目前没支持,后续支持 +freecharge 目前没支持直接拉起,后续支持(iOS 可以正常拉起) \ No newline at end of file