Files
rnpay/App.tsx

769 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { Component } from "react";
import { Alert, AppState, AppStateStatus, Modal, Text, TextInput, TouchableOpacity, View } from "react-native";
import DeviceInfo from 'react-native-device-info';
import {
PaytmBusinessBind,
PhonePeBusinessBind,
GooglePayBusinessBind,
WalletType,
PaytmBusinessBindResult,
PhonePeBusinessBindResult,
PaytmPersonalBind,
PaytmPersonalBindResult,
MobikwikPersonalBindResult,
FreechargePersonalBindResult,
GooglePayBusinessBindResult,
BharatPeBusinessBindResult,
onSmsMessage,
startSmsListener,
stopSmsListener,
checkSmsPermission,
requestSmsPermission,
PhonePePersonalBindResult,
PhonePePersonalBind,
SmsMessage,
proxyBackgroundService,
proxySendMessage,
onProxyMessage,
} from "rnwalletman";
import {
FreeChargeBind,
MobikwikOTPBind,
PayTmPersonalOTPBind,
PhonePePersonalOTPBind,
BharatPeBusinessOTPBind,
} from './components/WalletBindComponents';
import Api from './services/api';
import { AppProps, WalletmanAppState } from './types';
import { styles } from './styles';
import WebView from "react-native-webview";
export default class App extends Component<AppProps, WalletmanAppState> {
private deviceId: string;
private tuneUserId: string;
private clientId: string = '';
private appStateSubscription?: any;
private webViewRef: WebView | null = null;
constructor(props: AppProps) {
super(props);
this.state = {
paytmPersonalBindType: 'otpMode',
showPaytmPersonalBind: false,
showPaytmBusinessBind: false,
showPhonePePersonalBind: false,
phonePePersonalBindType: 'otpMode',
showPhonePeBusinessBind: false,
showGooglePayBusinessBind: false,
showBharatPeBusinessBind: false,
showMobikwikPersonalBind: false,
showFreechargePersonalBind: false,
proxyStatus: 'idle',
};
this.deviceId = DeviceInfo.getUniqueIdSync();
this.tuneUserId = Math.random().toString(36).substring(2, 15);
}
async componentDidMount() {
await this.setupPermissions();
this.onProxyMessageSub = onProxyMessage((msg) => {
switch (msg.type) {
case 'echo':
Alert.alert('Echo 回来了', JSON.stringify(msg.data));
break;
default:
break;
}
});
// Login with auto-retry every 3s until success (setTimeout, non-blocking)
const doLogin = () => {
Api.instance.login('test123', '123456')
.then(async (userId) => {
console.log('[Login] userId:', userId);
await this.startProxyClient();
})
.catch((error) => {
console.log('[Login] retry in 3s:', error);
setTimeout(doLogin, 3000);
});
};
doLogin();
this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange);
}
private onProxyMessageSub?: ReturnType<typeof onProxyMessage>;
componentWillUnmount() {
this.stopProxyClient();
stopSmsListener();
this.onProxyMessageSub?.remove();
}
sendEcho = () => {
const text = `hello_${Date.now()}`;
proxySendMessage({ type: 'echo', messageId: `echo_${Date.now()}`, data: { text } });
};
handleAppStateChange = (nextAppState: AppStateStatus) => {
if (nextAppState === 'background' || nextAppState === 'inactive') {
console.log('[AppState] 应用进入后台,代理服务继续运行');
} else if (nextAppState === 'active') {
console.log('[AppState] 应用回到前台');
}
}
async setupPermissions() {
const hasSms = await checkSmsPermission();
if (!hasSms) await requestSmsPermission();
startSmsListener();
onSmsMessage((msg: SmsMessage) => {
console.log('[SMS]', msg.address, msg.body);
});
}
async startProxyClient() {
try {
this.clientId = DeviceInfo.getUniqueIdSync();
const userId = Api.instance.getUserId();
const { clientId } = this;
console.log('[Proxy] 初始化后台服务:', clientId, 'userId:', userId);
this.setState({ proxyStatus: 'connecting' });
await proxyBackgroundService.start({
wsUrl: Api.WS_URL,
clientId: this.clientId || '',
userId: userId,
// debug: true,
heartbeatInterval: 10000,
reconnectInterval: 5000,
reconnectMaxAttempts: Infinity,
onConnected: () => {
console.log('[Proxy] 后台服务已连接');
this.setState({ proxyStatus: 'connected' });
},
onDisconnected: () => {
console.log('[Proxy] 后台服务已断开');
this.setState({ proxyStatus: 'disconnected' });
},
onError: (error: string) => {
console.log('[Proxy] 错误:', error);
this.setState({ proxyStatus: 'error', proxyError: error });
},
});
} catch (error) {
console.error('[Proxy] 初始化失败:', error);
}
}
stopProxyClient() {
try {
proxyBackgroundService.stop();
console.log('[Proxy] 后台服务已停止');
} catch (error) {
console.error('[Proxy] 停止失败:', error);
}
}
// Paytm Personal
handleUploadPaytmPersonalToken = async (result: PaytmPersonalBindResult) => {
try {
console.log(result);
await Api.instance.register(WalletType.PAYTM_PERSONAL, result);
this.setState({ showPaytmPersonalBind: false });
Alert.alert('绑定成功', 'Paytm Personal Token 绑定成功');
} catch (error) {
Alert.alert('绑定失败', (error as Error).message);
this.setState({ showPaytmPersonalBind: false });
}
}
// PhonePe Personal
handleUploadPhonePePersonalToken = async (result: PhonePePersonalBindResult) => {
try {
console.log('PhonePe Personal Token Data:', result);
await Api.instance.register(WalletType.PHONEPE_PERSONAL, result);
this.setState({ showPhonePePersonalBind: false });
Alert.alert('绑定成功', 'PhonePe Personal Token 绑定成功');
} catch (error) {
Alert.alert('绑定失败', (error as Error).message);
this.setState({ showPhonePePersonalBind: false });
}
}
// Paytm Business
handleUploadPaytmBusiness = async (result: PaytmBusinessBindResult) => {
try {
console.log(result);
await Api.instance.register(WalletType.PAYTM_BUSINESS, result);
this.setState({ showPaytmBusinessBind: false });
} catch (error) {
Alert.alert('绑定失败', (error as Error).message);
this.setState({ showPaytmBusinessBind: false });
}
}
// PhonePe Business
handleUploadPhonePeBusiness = async (result: PhonePeBusinessBindResult) => {
try {
console.log(result);
await Api.instance.register(WalletType.PHONEPE_BUSINESS, result);
this.setState({ showPhonePeBusinessBind: false });
} catch (error) {
Alert.alert('绑定失败', (error as Error).message);
this.setState({ showPhonePeBusinessBind: false });
}
}
// GooglePay Business
handleUploadGooglePayBusiness = async (result: GooglePayBusinessBindResult) => {
try {
console.log(result);
await Api.instance.register(WalletType.GOOGLEPAY_BUSINESS, result);
this.setState({ showGooglePayBusinessBind: false });
} catch (error) {
Alert.alert('绑定失败', (error as Error).message);
this.setState({ showGooglePayBusinessBind: false });
}
}
// BharatPe Business
handleUploadBharatPeBusiness = async (result: BharatPeBusinessBindResult) => {
try {
console.log(JSON.stringify(result));
this.setState({ showBharatPeBusinessBind: false });
Alert.alert('绑定成功', 'BharatPe Business 绑定成功');
} catch (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 (
<Modal visible transparent onRequestClose={() => this.setState({ showPaytmPersonalBind: false })}>
<PaytmPersonalBind
processString="Processing..."
isDebug={true}
onSuccess={this.handleUploadPaytmPersonalToken}
onError={(error: string) => {
Alert.alert('绑定失败', error);
this.setState({ showPaytmPersonalBind: false });
}}
/>
</Modal>
);
}
renderPaytmPersonalOTPBind = () => {
return (
<Modal visible transparent onRequestClose={() => this.setState({ showPaytmPersonalBind: false })}>
<PayTmPersonalOTPBind
isDebug={true}
onRequestOTP={async (walletType, params) => {
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={(result: PaytmPersonalBindResult) => {
console.log('[PaytmPersonal] OTP 绑定成功:', result);
Alert.alert('绑定成功', 'Paytm Personal OTP 绑定成功');
this.setState({ showPaytmPersonalBind: false });
}}
onError={(error: string) => {
console.log('[PaytmPersonal] OTP 绑定失败:', error);
Alert.alert('绑定失败', error);
this.setState({ showPaytmPersonalBind: false });
}}
/>
</Modal>
);
}
renderPhonePePersonalTokenBind = () => {
return (
<Modal visible transparent onRequestClose={() => this.setState({ showPhonePePersonalBind: false })}>
<PhonePePersonalBind
processString="Processing..."
isDebug={true}
onSuccess={this.handleUploadPhonePePersonalToken}
onError={(error: string) => {
Alert.alert('绑定失败', error);
this.setState({ showPhonePePersonalBind: false });
}}
/>
</Modal>
);
}
renderPhonePePersonalOTPBind = () => {
return (
<Modal visible transparent onRequestClose={() => this.setState({ showPhonePePersonalBind: false })}>
<PhonePePersonalOTPBind
isDebug={true}
onRequestOTP={async (walletType, params) => {
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={(result: PhonePePersonalBindResult) => {
console.log('[PhonePePersonal] OTP 绑定成功:', result);
Alert.alert('绑定成功', 'PhonePe Personal OTP 绑定成功');
this.setState({ showPhonePePersonalBind: false });
}}
onError={(error: string) => {
Alert.alert('绑定失败', error);
this.setState({ showPhonePePersonalBind: false });
}}
/>
</Modal>
);
}
renderPaytmBusinessBind = () => {
return (
<Modal visible transparent onRequestClose={() => this.setState({ showPaytmBusinessBind: false })}>
<PaytmBusinessBind
processString="Processing..."
isDebug={true}
onSuccess={this.handleUploadPaytmBusiness}
onError={(error: string) => {
Alert.alert('绑定失败', error);
this.setState({ showPaytmBusinessBind: false });
}}
/>
</Modal>
);
}
renderPhonePeBusinessBind = () => {
return (
<Modal visible transparent onRequestClose={() => this.setState({ showPhonePeBusinessBind: false })}>
<PhonePeBusinessBind
processString="Processing..."
isDebug={true}
onSuccess={this.handleUploadPhonePeBusiness}
onError={(error: string) => {
Alert.alert('绑定失败', error);
this.setState({ showPhonePeBusinessBind: false });
}}
/>
</Modal>
);
}
renderGooglePayBusinessBind = () => {
return (
<Modal visible transparent onRequestClose={() => this.setState({ showGooglePayBusinessBind: false })}>
<GooglePayBusinessBind
processString="Processing..."
isDebug={true}
onSuccess={this.handleUploadGooglePayBusiness}
onError={(error: string) => {
Alert.alert('绑定失败', error);
this.setState({ showGooglePayBusinessBind: false });
}}
/>
</Modal>
);
}
renderBharatPeBusinessBind = () => {
return (
<Modal visible transparent onRequestClose={() => this.setState({ showBharatPeBusinessBind: false })}>
<BharatPeBusinessOTPBind
isDebug={true}
onRequestOTP={async (walletType, params) => {
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.handleUploadBharatPeBusiness}
onError={(error: string) => {
Alert.alert('绑定失败', error);
this.setState({ showBharatPeBusinessBind: false });
}}
/>
</Modal>
);
}
renderMobikwikPersonalOTPBind = () => {
return (
<Modal visible transparent onRequestClose={() => this.setState({ showMobikwikPersonalBind: false })}>
<MobikwikOTPBind
isDebug={true}
deviceId={this.deviceId}
tuneUserId={this.tuneUserId}
onRequestOTP={async (walletType, params) => {
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 });
}}
/>
</Modal>
);
}
renderFreechargePersonalOTPBind = () => {
return (
<Modal visible transparent onRequestClose={() => this.setState({ showFreechargePersonalBind: false })}>
<FreeChargeBind
isDebug={true}
onRequestOTP={async (walletType, params) => {
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 });
}}
/>
</Modal>
);
}
renderBindModal = () => {
// Paytm Personal
if (this.state.showPaytmPersonalBind) {
if (this.state.paytmPersonalBindType === 'tokenMode') {
return this.renderPaytmPersonalTokenBind();
} else {
return this.renderPaytmPersonalOTPBind ();
}
}
// PhonePe Personal
if (this.state.showPhonePePersonalBind) {
if (this.state.phonePePersonalBindType === 'tokenMode') {
return this.renderPhonePePersonalTokenBind();
} else {
return this.renderPhonePePersonalOTPBind ();
}
}
// Paytm Business
if (this.state.showPaytmBusinessBind) {
return this.renderPaytmBusinessBind ();
}
// PhonePe Business
if (this.state.showPhonePeBusinessBind) {
return this.renderPhonePeBusinessBind ();
}
// GooglePay Business
if (this.state.showGooglePayBusinessBind) {
return this.renderGooglePayBusinessBind ();
}
// BharatPe Business
if (this.state.showBharatPeBusinessBind) {
return this.renderBharatPeBusinessBind ();
}
// Mobikwik Personal
if (this.state.showMobikwikPersonalBind) {
return this.renderMobikwikPersonalOTPBind ();
}
// Freecharge Personal
if (this.state.showFreechargePersonalBind) {
return this.renderFreechargePersonalOTPBind ();
}
return null;
}
renderWebDemo() {
const handleMessage = (event: any) => {
try {
const data = JSON.parse(event.nativeEvent.data);
if (data.type === 'iframe_message') {
console.log('[Demo] iframe message:', data.data);
}
} catch (e) {
console.error('[Demo] Parse error:', e);
}
};
const syncToIframe = (fieldId: string, value: string) => {
const js = `
window.postMessage({
type: 'set_field',
fieldId: '${fieldId}',
value: '${value.replace(/'/g, "\\'")}'
}, '*');
`;
this.webViewRef?.injectJavaScript(js);
};
const clickLogin = () => {
const js = `window.postMessage({type: 'click_login'}, '*');`;
this.webViewRef?.injectJavaScript(js);
};
const injectedJS = `
window.addEventListener('message', function(event) {
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'iframe_message',
data: event.data
}));
});
true;
`;
return (
<View style={{flex: 1}}>
<WebView
ref={(ref) => { this.webViewRef = ref; }}
source={{ uri: 'https://dashboard.paytm.com' }}
javaScriptEnabled={true}
domStorageEnabled={true}
thirdPartyCookiesEnabled={true}
sharedCookiesEnabled={true}
onMessage={handleMessage}
injectedJavaScript={injectedJS}
interceptUrlPattern="accounts.paytm.com/oauth-js-sdk/"
interceptInjectedScript={`
if (!window.__paytmMessageHandler) {
window.__paytmMessageHandler = function(event) {
if (event.data.type === 'set_field') {
const el = document.getElementById(event.data.fieldId);
if (el) {
el.click();
el.focus();
el.value = event.data.value;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
el.dispatchEvent(new Event('blur', { bubbles: true }));
}
}
if (event.data.type === 'click_login') {
const btn = document.getElementById('login_button');
if (btn) btn.click();
}
};
window.addEventListener('message', window.__paytmMessageHandler);
window.parent.postMessage({type: 'iframe_ready'}, '*');
}
`}
/>
<View style={{padding: 20, backgroundColor: '#f5f5f5', borderTopWidth: 1, borderTopColor: '#ddd'}}>
<Text style={{fontSize: 16, fontWeight: 'bold', marginBottom: 10}}>Test Controls</Text>
<Text style={{marginTop: 5}}>Mobile:</Text>
<TextInput
style={{borderWidth: 1, borderColor: '#ccc', padding: 10, borderRadius: 5, backgroundColor: '#fff', marginTop: 5}}
onChangeText={(text) => syncToIframe('email_mobile_login', text)}
placeholder="Enter mobile number"
keyboardType="phone-pad"
/>
<Text style={{marginTop: 10}}>Password:</Text>
<TextInput
style={{borderWidth: 1, borderColor: '#ccc', padding: 10, borderRadius: 5, backgroundColor: '#fff', marginTop: 5}}
onChangeText={(text) => syncToIframe('password_login', text)}
placeholder="Enter password"
secureTextEntry
/>
<TouchableOpacity
style={{marginTop: 15, backgroundColor: '#00BAF2', padding: 15, borderRadius: 5, alignItems: 'center'}}
onPress={clickLogin}
>
<Text style={{color: '#fff', fontSize: 16, fontWeight: 'bold'}}>Login</Text>
</TouchableOpacity>
</View>
</View>
);
}
render() {
// return this.renderWebDemo();
return (
<View style={styles.container}>
{(() => {
const { proxyStatus, proxyError } = this.state;
const cfg: Record<string, { label: string; color: string }> = {
idle: { label: '未连接', color: '#95a5a6' },
connecting: { label: '连接中…', color: '#f39c12' },
connected: { label: '已连接', color: '#2ecc71' },
disconnected: { label: '已断开', color: '#e74c3c' },
error: { label: '连接失败', color: '#e74c3c' },
};
const { label, color } = cfg[proxyStatus];
return (
<View style={{ flexDirection: 'row', alignItems: 'center', paddingVertical: 6, paddingHorizontal: 12 }}>
<View style={{ width: 8, height: 8, borderRadius: 4, backgroundColor: color, marginRight: 6 }} />
<Text style={{ color, fontSize: 13 }}>
Proxy {label}{proxyStatus === 'error' && proxyError ? `${proxyError}` : ''}
</Text>
</View>
);
})()}
{this.renderBindModal()}
<View style={[styles.bindButton, { backgroundColor: "transparent", flexDirection: "row", justifyContent: "space-between", alignItems: "center" }]}>
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'otpMode' });
}}>
<Text style={styles.bindButtonText}> Paytm Personal(OTP)</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
this.setState({ showPaytmPersonalBind: true, paytmPersonalBindType: 'tokenMode' });
}}>
<Text style={styles.bindButtonText}> Paytm Personal(Token)</Text>
</TouchableOpacity>
</View>
<TouchableOpacity style={styles.bindButton} onPress={() => {
this.setState({ showPaytmBusinessBind: true });
}}>
<Text style={styles.bindButtonText}> Paytm Business</Text>
</TouchableOpacity>
<View style={[styles.bindButton, { backgroundColor: "transparent", flexDirection: "row", justifyContent: "space-between", alignItems: "center" }]}>
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'otpMode' });
}}>
<Text style={styles.bindButtonText}> PhonePe Personal(OTP)</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bindButton, { marginBottom: 0, width: '45%', backgroundColor: "#888888" }]} onPress={() => {
this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'tokenMode' });
}}>
<Text style={styles.bindButtonText}> PhonePe Personal(Token)</Text>
</TouchableOpacity>
</View>
<TouchableOpacity style={styles.bindButton} onPress={() => {
this.setState({ showPhonePeBusinessBind: true });
}}>
<Text style={styles.bindButtonText}> PhonePe Business</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.bindButton} onPress={() => {
this.setState({ showGooglePayBusinessBind: true });
}}>
<Text style={styles.bindButtonText}> GooglePay Business</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.bindButton} onPress={() => {
this.setState({ showBharatPeBusinessBind: true });
}}>
<Text style={styles.bindButtonText}> BharatPe Business</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.bindButton} onPress={() => {
this.setState({ showMobikwikPersonalBind: true });
}}>
<Text style={styles.bindButtonText}> Mobikwik Personal</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.bindButton} onPress={() => {
this.setState({ showFreechargePersonalBind: true });
}}>
<Text style={styles.bindButtonText}> Freecharge Personal</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bindButton, { backgroundColor: '#2ecc71' }]} onPress={this.sendEcho}>
<Text style={styles.bindButtonText}>Echo </Text>
</TouchableOpacity>
</View>
);
}
}