373 lines
15 KiB
TypeScript
373 lines
15 KiB
TypeScript
import React, { Component, useState, useEffect } from 'react';
|
|
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>;
|
|
onSuccess: (result: FreechargePersonalBindResult) => void;
|
|
onError: (error: string) => void;
|
|
isDebug: boolean;
|
|
initialMobile?: string;
|
|
}> {
|
|
render() {
|
|
return (
|
|
<OTPBindUI
|
|
walletType={WalletType.FREECHARGE_PERSONAL}
|
|
title="Bind Freecharge"
|
|
otpLength={4}
|
|
onRequestOTP={this.props.onRequestOTP}
|
|
onVerifyOTP={this.props.onVerifyOTP}
|
|
onSuccess={this.props.onSuccess}
|
|
onError={this.props.onError}
|
|
isDebug={this.props.isDebug}
|
|
initialMobile={this.props.initialMobile}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
/** 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: any) => void;
|
|
onError: (error: string) => void;
|
|
isDebug: boolean;
|
|
initialMobile?: string;
|
|
}> {
|
|
render() {
|
|
return (
|
|
<OTPBindUI
|
|
walletType={WalletType.MOBIKWIK_PERSONAL}
|
|
title="Bind Mobikwik (OTP)"
|
|
subtitle="App OTP login via Mobikwik API"
|
|
otpLength={6}
|
|
onRequestOTP={this.props.onRequestOTP}
|
|
onVerifyOTP={this.props.onVerifyOTP}
|
|
onSuccess={this.props.onSuccess}
|
|
onError={this.props.onError}
|
|
isDebug={this.props.isDebug}
|
|
initialMobile={this.props.initialMobile}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
/** @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>;
|
|
onSuccess: (result: PaytmPersonalBindResult) => void;
|
|
onError: (error: string) => void;
|
|
isDebug: boolean;
|
|
initialMobile?: string;
|
|
}> {
|
|
render() {
|
|
return (
|
|
<OTPBindUI
|
|
walletType={WalletType.PAYTM_PERSONAL}
|
|
title="Bind Paytm"
|
|
otpLength={6}
|
|
onRequestOTP={this.props.onRequestOTP}
|
|
onVerifyOTP={this.props.onVerifyOTP}
|
|
onSuccess={this.props.onSuccess}
|
|
onError={this.props.onError}
|
|
isDebug={this.props.isDebug}
|
|
initialMobile={this.props.initialMobile}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BharatPeBusinessOTPBind extends Component<{
|
|
onRequestOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onSuccess: (result: BharatPeBusinessBindResult) => void;
|
|
onError: (error: string) => void;
|
|
isDebug: boolean;
|
|
initialMobile?: string;
|
|
}> {
|
|
render() {
|
|
return (
|
|
<OTPBindUI
|
|
walletType={WalletType.BHARATPE_BUSINESS}
|
|
title="Bind BharatPe"
|
|
otpLength={4}
|
|
onRequestOTP={this.props.onRequestOTP}
|
|
onVerifyOTP={this.props.onVerifyOTP}
|
|
onSuccess={this.props.onSuccess}
|
|
onError={this.props.onError}
|
|
isDebug={this.props.isDebug}
|
|
initialMobile={this.props.initialMobile}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class PaytmBusinessOTPBind extends Component<{
|
|
onRequestOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onSuccess: (result: PaytmBusinessBindResult) => void;
|
|
onError: (error: string) => void;
|
|
isDebug: boolean;
|
|
initialMobile?: string;
|
|
}> {
|
|
render() {
|
|
return (
|
|
<PaytmBusinessForm
|
|
onRequestOTP={this.props.onRequestOTP}
|
|
onVerifyOTP={this.props.onVerifyOTP}
|
|
onSuccess={this.props.onSuccess}
|
|
onError={this.props.onError}
|
|
isDebug={this.props.isDebug}
|
|
initialMobile={this.props.initialMobile}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
function PaytmBusinessForm({ onRequestOTP, onVerifyOTP, onSuccess, onError, isDebug, initialMobile = '' }: {
|
|
onRequestOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onSuccess: (result: PaytmBusinessBindResult) => void;
|
|
onError: (error: string) => void;
|
|
isDebug: boolean;
|
|
initialMobile?: string;
|
|
}) {
|
|
const [step, setStep] = useState<'credentials' | 'otp'>('credentials');
|
|
const [mobile, setMobile] = useState(initialMobile);
|
|
const [otp, setOtp] = useState('');
|
|
const [sessionToken, setSessionToken] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [errorMsg, setErrorMsg] = useState('');
|
|
|
|
useEffect(() => {
|
|
if (initialMobile) setMobile(initialMobile);
|
|
}, [initialMobile]);
|
|
|
|
const log = (...args: any[]) => { if (isDebug) console.log('[PaytmBusiness]', ...args); };
|
|
|
|
const handleRequestOTP = async () => {
|
|
if (!mobile || mobile.length !== 10) { setErrorMsg('Please enter a 10-digit mobile number'); return; }
|
|
setLoading(true); setErrorMsg('');
|
|
try {
|
|
const res = await onRequestOTP(WalletType.PAYTM_BUSINESS, { mobile });
|
|
log('RequestOTP:', res);
|
|
if (res.success) { setSessionToken(res.data?.sessionToken || ''); setStep('otp'); }
|
|
else { const msg = res.message || 'Failed to send OTP'; setErrorMsg(msg); onError(msg); }
|
|
} catch (e) {
|
|
const msg = e instanceof Error ? e.message : 'Failed to send OTP';
|
|
setErrorMsg(msg); onError(msg);
|
|
} finally { setLoading(false); }
|
|
};
|
|
|
|
const handleVerifyOTP = async () => {
|
|
if (!otp || otp.length !== 6) { setErrorMsg('Please enter the 6-digit verification code'); return; }
|
|
setLoading(true); setErrorMsg('');
|
|
try {
|
|
const res = await onVerifyOTP(WalletType.PAYTM_BUSINESS, { mobile, otp, sessionToken });
|
|
log('VerifyOTP:', res);
|
|
if (res.success) {
|
|
onSuccess({ type: WalletType.PAYTM_BUSINESS, success: true, cookie: res.data?.cookie || '', xCsrfToken: res.data?.xCsrfToken || '', qrData: res.data?.qrData || [] });
|
|
} else {
|
|
setErrorMsg(res.message || 'Failed to verify OTP');
|
|
setStep('otp');
|
|
}
|
|
} catch (e) {
|
|
setErrorMsg(e instanceof Error ? e.message : 'Failed to verify OTP');
|
|
setStep('otp');
|
|
} finally { setLoading(false); }
|
|
};
|
|
|
|
|
|
return (
|
|
<View style={ptStyles.container}>
|
|
<View style={ptStyles.form}>
|
|
<Text style={ptStyles.title}>Bind Paytm Business</Text>
|
|
{errorMsg ? <Text style={ptStyles.errorText}>{errorMsg}</Text> : null}
|
|
{step === 'credentials' && (
|
|
<>
|
|
<TextInput style={ptStyles.input} placeholder="Mobile number" placeholderTextColor="#999" keyboardType="phone-pad" maxLength={10} value={mobile} onChangeText={t => { setMobile(t); setErrorMsg(''); }} editable={!loading} />
|
|
<TouchableOpacity style={[ptStyles.button, loading && ptStyles.buttonDisabled]} onPress={handleRequestOTP} disabled={loading}>
|
|
{loading ? <ActivityIndicator color="#fff" /> : <Text style={ptStyles.buttonText}>Send OTP</Text>}
|
|
</TouchableOpacity>
|
|
</>
|
|
)}
|
|
{step === 'otp' && (
|
|
<>
|
|
<Text style={ptStyles.hint}>OTP sent to {mobile}</Text>
|
|
<TextInput style={ptStyles.input} placeholder="6-digit code" placeholderTextColor="#999" keyboardType="number-pad" maxLength={6} value={otp} onChangeText={t => { setOtp(t); setErrorMsg(''); }} editable={!loading} />
|
|
<TouchableOpacity style={[ptStyles.button, loading && ptStyles.buttonDisabled]} onPress={handleVerifyOTP} disabled={loading}>
|
|
{loading ? <ActivityIndicator color="#fff" /> : <Text style={ptStyles.buttonText}>Verify & Bind</Text>}
|
|
</TouchableOpacity>
|
|
<TouchableOpacity style={ptStyles.linkButton} onPress={() => setStep('credentials')} disabled={loading}>
|
|
<Text style={ptStyles.linkText}>Change mobile number</Text>
|
|
</TouchableOpacity>
|
|
</>
|
|
)}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const ptStyles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: 'rgba(0,0,0,0.8)', justifyContent: 'center', alignItems: 'center' },
|
|
form: { width: '92%', backgroundColor: '#fff', borderRadius: 20, padding: 28 },
|
|
title: { fontSize: 22, fontWeight: '700', textAlign: 'center', color: '#111', marginBottom: 24 },
|
|
input: { borderWidth: 1.5, borderColor: '#e0e0e0', borderRadius: 10, paddingHorizontal: 14, paddingVertical: 14, fontSize: 17, color: '#111', marginBottom: 18, backgroundColor: '#fafafa' },
|
|
errorText: { color: '#ff3b30', fontSize: 13, marginBottom: 12, textAlign: 'center' },
|
|
button: { backgroundColor: '#007AFF', borderRadius: 12, paddingVertical: 16, alignItems: 'center', marginTop: 4 },
|
|
buttonDisabled: { backgroundColor: '#a0c4ff' },
|
|
buttonText: { color: '#fff', fontSize: 17, fontWeight: '600' },
|
|
linkButton: { marginTop: 16, alignItems: 'center' },
|
|
linkText: { color: '#007AFF', fontSize: 14 },
|
|
hint: { fontSize: 14, color: '#555', marginBottom: 18, textAlign: 'center' },
|
|
});
|
|
|
|
export class PhonePeBusinessOTPBind extends Component<{
|
|
onRequestOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onSuccess: (result: PhonePeBusinessBindResult) => void;
|
|
onError: (error: string) => void;
|
|
isDebug: boolean;
|
|
initialMobile?: string;
|
|
}> {
|
|
render() {
|
|
return (
|
|
<OTPBindUI
|
|
walletType={WalletType.PHONEPE_BUSINESS}
|
|
title="Bind PhonePe Business"
|
|
otpLength={5}
|
|
onRequestOTP={this.props.onRequestOTP}
|
|
onVerifyOTP={this.props.onVerifyOTP}
|
|
onSuccess={this.props.onSuccess}
|
|
onError={this.props.onError}
|
|
isDebug={this.props.isDebug}
|
|
initialMobile={this.props.initialMobile}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class PhonePePersonalOTPBind extends Component<{
|
|
onRequestOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onSuccess: (result: PhonePePersonalBindResult) => void;
|
|
onError: (error: string) => void;
|
|
isDebug: boolean;
|
|
initialMobile?: string;
|
|
}> {
|
|
render() {
|
|
return (
|
|
<OTPBindUI
|
|
walletType={WalletType.PHONEPE_PERSONAL}
|
|
title="Bind PhonePe"
|
|
otpLength={5}
|
|
onRequestOTP={this.props.onRequestOTP}
|
|
onVerifyOTP={this.props.onVerifyOTP}
|
|
onSuccess={this.props.onSuccess}
|
|
onError={this.props.onError}
|
|
isDebug={this.props.isDebug}
|
|
initialMobile={this.props.initialMobile}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
export class AmazonPayOTPBind extends Component<{
|
|
onRequestOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>;
|
|
onSuccess: (result: any) => void;
|
|
onError: (error: string) => void;
|
|
isDebug: boolean;
|
|
initialMobile?: string;
|
|
}> {
|
|
render() {
|
|
return (
|
|
<OTPBindUI
|
|
walletType={WalletType.AMAZONPAY_PERSONAL}
|
|
title="Bind Amazon Pay"
|
|
otpLength={6}
|
|
passwordBeforeOtp
|
|
passwordLabel="Amazon password"
|
|
passwordPlaceholder="Enter Amazon account password"
|
|
passwordRequiredMsg="Please enter your Amazon account password"
|
|
onRequestOTP={this.props.onRequestOTP}
|
|
onVerifyOTP={this.props.onVerifyOTP}
|
|
onSuccess={this.props.onSuccess}
|
|
onError={this.props.onError}
|
|
isDebug={this.props.isDebug}
|
|
initialMobile={this.props.initialMobile}
|
|
/>
|
|
);
|
|
}
|
|
}
|