Otp / Token 模式 OK
This commit is contained in:
@@ -13,6 +13,7 @@ import { useOTPBind } from '../hooks/useOTPBind';
|
||||
interface OTPBindUIProps {
|
||||
walletType: WalletType;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
otpLength?: number;
|
||||
mobileLength?: number;
|
||||
passwordBeforeOtp?: boolean;
|
||||
@@ -32,6 +33,7 @@ interface OTPBindUIProps {
|
||||
export const OTPBindUI: React.FC<OTPBindUIProps> = ({
|
||||
walletType,
|
||||
title,
|
||||
subtitle,
|
||||
otpLength = 6,
|
||||
mobileLength = 10,
|
||||
passwordBeforeOtp = false,
|
||||
@@ -78,6 +80,7 @@ export const OTPBindUI: React.FC<OTPBindUIProps> = ({
|
||||
<View style={styles.overlay}>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
{!!subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
|
||||
{!!state.errorMessage && (
|
||||
<Text style={styles.errorText}>{state.errorMessage}</Text>
|
||||
)}
|
||||
@@ -182,6 +185,7 @@ export const OTPBindUI: React.FC<OTPBindUIProps> = ({
|
||||
<View style={styles.overlay}>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
{!!subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
|
||||
|
||||
{!showOtp && (
|
||||
<>
|
||||
@@ -287,6 +291,14 @@ const styles = StyleSheet.create({
|
||||
color: '#111',
|
||||
marginBottom: 18,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 12,
|
||||
color: '#888',
|
||||
textAlign: 'center',
|
||||
marginTop: -10,
|
||||
marginBottom: 14,
|
||||
lineHeight: 17,
|
||||
},
|
||||
label: {
|
||||
fontSize: 13,
|
||||
color: '#666',
|
||||
|
||||
@@ -1,8 +1,45 @@
|
||||
import React, { Component, useState, useEffect } from 'react';
|
||||
import { View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native';
|
||||
import { WalletType, FreechargePersonalBindResult, MobikwikPersonalBindResult, PaytmPersonalBindResult, PhonePePersonalBindResult, PhonePeBusinessBindResult, BharatPeBusinessBindResult, PaytmBusinessBindResult } from 'rnwalletman';
|
||||
import { Alert, View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native';
|
||||
import {
|
||||
WalletType,
|
||||
BindErrorCode,
|
||||
MobikwikPersonalBind,
|
||||
FreechargePersonalBindResult,
|
||||
MobikwikPersonalBindResult,
|
||||
PaytmPersonalBindResult,
|
||||
PhonePePersonalBindResult,
|
||||
PhonePeBusinessBindResult,
|
||||
BharatPeBusinessBindResult,
|
||||
PaytmBusinessBindResult,
|
||||
} from 'rnwalletman';
|
||||
import { OTPBindUI } from './OTPBindUI';
|
||||
|
||||
export function alertMobikwikAidlBindError(code: string, message: string, onClose?: () => void) {
|
||||
let msg = message || 'Bind failed';
|
||||
switch (code) {
|
||||
case BindErrorCode.NOT_INSTALLED:
|
||||
msg = 'Patched Mobikwik app not installed. Install mobikwik_ipay_2365.apk';
|
||||
break;
|
||||
case BindErrorCode.NOT_LOGGED_IN:
|
||||
msg = 'Please log in to the Mobikwik app first';
|
||||
break;
|
||||
case BindErrorCode.SERVICE_DISCONNECTED:
|
||||
msg = 'Mobikwik service unavailable. Open the app and try again';
|
||||
break;
|
||||
case BindErrorCode.NO_DATA:
|
||||
msg = 'No login data received';
|
||||
break;
|
||||
case BindErrorCode.BIND_ERROR:
|
||||
msg = 'Bind failed. Open Mobikwik manually and try again';
|
||||
break;
|
||||
case BindErrorCode.NATIVE_MODULE_UNAVAILABLE:
|
||||
msg = 'Native module not available';
|
||||
break;
|
||||
}
|
||||
Alert.alert('Bind Failed', msg);
|
||||
onClose?.();
|
||||
}
|
||||
|
||||
export class FreeChargeBind extends Component<{
|
||||
onRequestOTP: (walletType: WalletType, params: any) => Promise<any>;
|
||||
onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>;
|
||||
@@ -28,10 +65,38 @@ export class FreeChargeBind extends Component<{
|
||||
}
|
||||
}
|
||||
|
||||
export class MobikwikOTPBind extends Component<{
|
||||
/** Mobikwik 2365 AIDL bind */
|
||||
export class MobikwikPersonalTokenBind extends Component<{
|
||||
userToken: string;
|
||||
onSuccess: (result: MobikwikPersonalBindResult) => void;
|
||||
onClose?: () => void;
|
||||
isDebug?: boolean;
|
||||
}> {
|
||||
render() {
|
||||
const { userToken, onSuccess, onClose, isDebug = false } = this.props;
|
||||
return (
|
||||
<MobikwikPersonalBind
|
||||
processString="Binding Mobikwik..."
|
||||
userToken={userToken}
|
||||
isDebug={isDebug}
|
||||
onSuccess={(result: MobikwikPersonalBindResult) => {
|
||||
if (!result.hashId) {
|
||||
Alert.alert('Bind Failed', 'Please log in to the Mobikwik app first');
|
||||
return;
|
||||
}
|
||||
onSuccess(result);
|
||||
}}
|
||||
onError={(code: string, message: string) => alertMobikwikAidlBindError(code, message, onClose)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Mobikwik web OTP bind */
|
||||
export class MobikwikPersonalOTPBind extends Component<{
|
||||
onRequestOTP: (walletType: WalletType, params: any) => Promise<any>;
|
||||
onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>;
|
||||
onSuccess: (result: MobikwikPersonalBindResult) => void;
|
||||
onSuccess: (result: any) => void;
|
||||
onError: (error: string) => void;
|
||||
isDebug: boolean;
|
||||
initialMobile?: string;
|
||||
@@ -40,7 +105,8 @@ export class MobikwikOTPBind extends Component<{
|
||||
return (
|
||||
<OTPBindUI
|
||||
walletType={WalletType.MOBIKWIK_PERSONAL}
|
||||
title="Bind Mobikwik"
|
||||
title="Bind Mobikwik (OTP)"
|
||||
subtitle="Web OTP login. If you have the 2365 patched app, use Token Mode instead"
|
||||
otpLength={6}
|
||||
onRequestOTP={this.props.onRequestOTP}
|
||||
onVerifyOTP={this.props.onVerifyOTP}
|
||||
@@ -53,6 +119,9 @@ export class MobikwikOTPBind extends Component<{
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated Use MobikwikPersonalOTPBind */
|
||||
export const MobikwikOTPBind = MobikwikPersonalOTPBind;
|
||||
|
||||
export class PayTmPersonalOTPBind extends Component<{
|
||||
onRequestOTP: (walletType: WalletType, params: any) => Promise<any>;
|
||||
onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>;
|
||||
|
||||
255
components/WalletSelectModal.tsx
Normal file
255
components/WalletSelectModal.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Image,
|
||||
Modal,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import * as Animatable from 'react-native-animatable';
|
||||
|
||||
export type WalletBindAction = {
|
||||
key: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type WalletSelectRow = {
|
||||
walletType: string;
|
||||
label: string;
|
||||
actions: WalletBindAction[];
|
||||
};
|
||||
|
||||
export const WALLET_ICONS: Record<string, number> = {
|
||||
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'),
|
||||
amazonpay: require('../res/amazon.png'),
|
||||
};
|
||||
|
||||
export const WALLET_TYPE_COLORS: Record<string, string> = {
|
||||
paytm: '#002970',
|
||||
'paytm business': '#002970',
|
||||
phonepe: '#5a2d9c',
|
||||
'phonepe business': '#5a2d9c',
|
||||
'googlepay business': '#4285f4',
|
||||
'bharatpe business': '#e91e63',
|
||||
mobikwik: '#00bcd4',
|
||||
freecharge: '#ff5722',
|
||||
amazonpay: '#ff9900',
|
||||
};
|
||||
|
||||
const TOKEN_MODE = 'Token Mode';
|
||||
const OTP_MODE = 'Otp Mode';
|
||||
|
||||
export const WALLET_SELECT_ROWS: WalletSelectRow[] = [
|
||||
{
|
||||
walletType: 'paytm',
|
||||
label: 'Paytm Personal',
|
||||
actions: [
|
||||
{ key: 'paytm_personal_token', label: TOKEN_MODE },
|
||||
{ key: 'paytm_personal_otp', label: OTP_MODE },
|
||||
],
|
||||
},
|
||||
{
|
||||
walletType: 'paytm business',
|
||||
label: 'Paytm Business',
|
||||
actions: [{ key: 'paytm_business', label: OTP_MODE }],
|
||||
},
|
||||
{
|
||||
walletType: 'phonepe',
|
||||
label: 'PhonePe Personal',
|
||||
actions: [
|
||||
{ key: 'phonepe_personal_token', label: TOKEN_MODE },
|
||||
{ key: 'phonepe_personal_otp', label: OTP_MODE },
|
||||
],
|
||||
},
|
||||
{
|
||||
walletType: 'phonepe business',
|
||||
label: 'PhonePe Business',
|
||||
actions: [
|
||||
{ key: 'phonepe_business_web', label: TOKEN_MODE },
|
||||
{ key: 'phonepe_business_otp', label: OTP_MODE },
|
||||
],
|
||||
},
|
||||
{
|
||||
walletType: 'googlepay business',
|
||||
label: 'GooglePay Business',
|
||||
actions: [{ key: 'googlepay_business', label: TOKEN_MODE }],
|
||||
},
|
||||
{
|
||||
walletType: 'bharatpe business',
|
||||
label: 'BharatPe Business',
|
||||
actions: [{ key: 'bharatpe_business', label: OTP_MODE }],
|
||||
},
|
||||
{
|
||||
walletType: 'mobikwik',
|
||||
label: 'Mobikwik Personal',
|
||||
actions: [
|
||||
{ key: 'mobikwik_personal_token', label: TOKEN_MODE },
|
||||
{ key: 'mobikwik_personal', label: OTP_MODE },
|
||||
],
|
||||
},
|
||||
{
|
||||
walletType: 'freecharge',
|
||||
label: 'Freecharge Personal',
|
||||
actions: [
|
||||
{ key: 'freecharge_personal_token', label: TOKEN_MODE },
|
||||
{ key: 'freecharge_personal', label: OTP_MODE },
|
||||
],
|
||||
},
|
||||
{
|
||||
walletType: 'amazonpay',
|
||||
label: 'Amazon Pay',
|
||||
actions: [{ key: 'amazonpay_personal', label: OTP_MODE }],
|
||||
},
|
||||
];
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onSelectBind: (bindKey: string) => void;
|
||||
rows?: WalletSelectRow[];
|
||||
}
|
||||
|
||||
export const WalletSelectModal: React.FC<Props> = ({
|
||||
visible,
|
||||
onClose,
|
||||
onSelectBind,
|
||||
rows = WALLET_SELECT_ROWS,
|
||||
}) => (
|
||||
<Modal visible={visible} transparent animationType="none" onRequestClose={onClose}>
|
||||
<View style={styles.overlay}>
|
||||
<Animatable.View
|
||||
animation="zoomIn"
|
||||
duration={220}
|
||||
easing="ease-out-back"
|
||||
useNativeDriver
|
||||
style={styles.box}
|
||||
>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Select Wallet Type</Text>
|
||||
<TouchableOpacity onPress={onClose} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
|
||||
<Text style={styles.close}>✕</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ScrollView bounces={false}>
|
||||
{rows.map((row) => (
|
||||
<View key={row.label} style={styles.row}>
|
||||
{WALLET_ICONS[row.walletType] ? (
|
||||
<Image
|
||||
source={WALLET_ICONS[row.walletType]}
|
||||
style={styles.icon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
) : (
|
||||
<View
|
||||
style={[
|
||||
styles.dot,
|
||||
{ backgroundColor: WALLET_TYPE_COLORS[row.walletType] ?? '#888' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<Text style={styles.label} numberOfLines={1}>
|
||||
{row.label}
|
||||
</Text>
|
||||
<View style={styles.actions}>
|
||||
{row.actions.map((action) => (
|
||||
<TouchableOpacity
|
||||
key={action.key}
|
||||
style={styles.actionBtn}
|
||||
onPress={() => onSelectBind(action.key)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Text style={styles.actionBtnText}>{action.label}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</Animatable.View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
box: {
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: 12,
|
||||
padding: 16,
|
||||
width: '88%',
|
||||
maxHeight: '80%',
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
title: {
|
||||
fontSize: 17,
|
||||
fontWeight: '700',
|
||||
color: '#222',
|
||||
},
|
||||
close: {
|
||||
fontSize: 20,
|
||||
color: '#999',
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#f0f0f0',
|
||||
},
|
||||
icon: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 6,
|
||||
marginRight: 10,
|
||||
},
|
||||
dot: {
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
marginRight: 10,
|
||||
},
|
||||
label: {
|
||||
flex: 1,
|
||||
fontSize: 14,
|
||||
color: '#333',
|
||||
marginRight: 8,
|
||||
},
|
||||
actions: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
},
|
||||
actionBtn: {
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 6,
|
||||
backgroundColor: '#f0f4ff',
|
||||
borderWidth: 1,
|
||||
borderColor: '#d0daf0',
|
||||
alignItems: 'center',
|
||||
},
|
||||
actionBtnText: {
|
||||
fontSize: 11,
|
||||
fontWeight: '600',
|
||||
color: '#3355aa',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user