This commit is contained in:
2026-05-27 12:12:03 +08:00
parent 07f4b9cd4c
commit 38b89d240d
2 changed files with 204 additions and 31 deletions

View File

@@ -1,6 +1,6 @@
import React, { Component, useState, useEffect } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native';
import { WalletType, FreechargePersonalBindResult, MobikwikPersonalBindResult, PaytmPersonalBindResult, PhonePePersonalBindResult, BharatPeBusinessBindResult, PaytmBusinessBindResult } from 'rnwalletman';
import { WalletType, FreechargePersonalBindResult, MobikwikPersonalBindResult, PaytmPersonalBindResult, PhonePePersonalBindResult, PhonePeBusinessBindResult, BharatPeBusinessBindResult, PaytmBusinessBindResult } from 'rnwalletman';
import { OTPBindUI } from './OTPBindUI';
export class FreeChargeBind extends Component<{
@@ -223,6 +223,31 @@ const ptStyles = StyleSheet.create({
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="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>;

View File

@@ -3,7 +3,6 @@ import {
Alert,
AppState,
AppStateStatus,
FlatList,
Image,
Modal,
ScrollView,
@@ -18,8 +17,8 @@ import {
import * as Animatable from 'react-native-animatable';
import DeviceInfo from 'react-native-device-info';
import {
PhonePeBusinessBind,
GooglePayBusinessBind,
PhonePeBusinessBind,
WalletType,
PaytmBusinessBindResult,
PaytmPersonalBind,
@@ -48,6 +47,7 @@ import {
PhonePePersonalOTPBind,
BharatPeBusinessOTPBind,
PaytmBusinessOTPBind,
PhonePeBusinessOTPBind,
} from '../components/WalletBindComponents';
import Api, {
@@ -113,11 +113,17 @@ const WALLET_TYPE_OPTIONS = [
mode: 'token',
},
{
key: 'phonepe_business',
key: 'phonepe_business_otp',
walletType: 'phonepe business',
label: 'PhonePe Business (OTP-WEB)',
label: 'PhonePe Business (OTP)',
mode: 'otp',
},
{
key: 'phonepe_business_web',
walletType: 'phonepe business',
label: 'PhonePe Business (Web)',
mode: 'token',
},
{
key: 'googlepay_business',
walletType: 'googlepay business',
@@ -150,6 +156,26 @@ const WALLET_TYPE_OPTIONS = [
},
];
function formatWalletTypeLabel(walletType: string) {
return walletType.replace(/\b\w/g, (c) => c.toUpperCase());
}
function groupBoundWallets(wallets: WalletItem[]) {
const order: string[] = [];
const map = new Map<string, WalletItem[]>();
for (const w of wallets) {
if (!map.has(w.walletType)) {
map.set(w.walletType, []);
order.push(w.walletType);
}
map.get(w.walletType)!.push(w);
}
return order.map((walletType) => ({
walletType,
items: map.get(walletType)!,
}));
}
function getBindKeyForWallet(item: WalletItem): string | null {
const otp = item.otpMode === true;
switch (item.walletType) {
@@ -160,7 +186,7 @@ function getBindKeyForWallet(item: WalletItem): string | null {
case 'paytm business':
return 'paytm_business';
case 'phonepe business':
return 'phonepe_business';
return otp ? 'phonepe_business_otp' : 'phonepe_business_web';
case 'googlepay business':
return 'googlepay_business';
case 'bharatpe business':
@@ -182,6 +208,7 @@ interface HomeScreenState {
showPhonePePersonalBind: boolean;
phonePePersonalBindType: 'otpMode' | 'tokenMode';
showPhonePeBusinessBind: boolean;
phonePeBusinessBindType: 'otpMode' | 'webviewMode';
freechargePersonalBindType: 'otpMode' | 'tokenMode';
showGooglePayBusinessBind: boolean;
showBharatPeBusinessBind: boolean;
@@ -205,6 +232,7 @@ interface HomeScreenState {
// add wallet
showAddWallet: boolean;
bindPrefillMobile: string;
expandedBoundWalletGroups: Record<string, boolean>;
}
export default class HomeScreen extends Component<any, HomeScreenState> {
@@ -226,6 +254,7 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
showPhonePePersonalBind: false,
phonePePersonalBindType: 'otpMode',
showPhonePeBusinessBind: false,
phonePeBusinessBindType: 'otpMode',
showGooglePayBusinessBind: false,
showBharatPeBusinessBind: false,
showMobikwikPersonalBind: false,
@@ -243,6 +272,7 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
vpaModalSelected: '',
showAddWallet: false,
bindPrefillMobile: '',
expandedBoundWalletGroups: {},
};
this.deviceId = DeviceInfo.getUniqueIdSync();
this.tuneUserId = "yz8mxybytus";//Math.random().toString(36).substring(2, 15);
@@ -410,7 +440,7 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
renderBindModal = () => {
const {
showPaytmPersonalBind, paytmPersonalBindType, showPhonePePersonalBind, phonePePersonalBindType,
showPaytmBusinessBind, showPhonePeBusinessBind, showGooglePayBusinessBind, showBharatPeBusinessBind,
showPaytmBusinessBind, showPhonePeBusinessBind, phonePeBusinessBindType, showGooglePayBusinessBind, showBharatPeBusinessBind,
showMobikwikPersonalBind, showFreechargePersonalBind, freechargePersonalBindType,
bindPrefillMobile,
} = this.state;
@@ -606,14 +636,14 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
</Modal>
);
}
if (showPhonePeBusinessBind) {
if (showPhonePeBusinessBind && phonePeBusinessBindType === 'otpMode') {
return (
<Modal
visible
transparent
onRequestClose={close('showPhonePeBusinessBind')}
>
<PhonePeBusinessBind
<PhonePeBusinessOTPBind
isDebug
initialMobile={bindPrefillMobile}
onRequestOTP={async (wt, p) => {
@@ -628,6 +658,23 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
</Modal>
);
}
if (showPhonePeBusinessBind && phonePeBusinessBindType === 'webviewMode') {
return (
<Modal
visible
transparent
onRequestClose={close('showPhonePeBusinessBind')}
>
<PhonePeBusinessBind
processString="Processing..."
isDebug
clearCookie
onSuccess={this.handleBindSuccess('showPhonePeBusinessBind', WalletType.PHONEPE_BUSINESS, 'PhonePe Business bound successfully') as any}
onError={(e: string) => { Alert.alert('Bind Failed', e); close('showPhonePeBusinessBind')(); }}
/>
</Modal>
);
}
if (showGooglePayBusinessBind) {
return (
<Modal
@@ -748,8 +795,11 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
case 'phonepe_personal_token':
this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'tokenMode' });
break;
case 'phonepe_business':
this.setState({ showPhonePeBusinessBind: true });
case 'phonepe_business_otp':
this.setState({ showPhonePeBusinessBind: true, phonePeBusinessBindType: 'otpMode' });
break;
case 'phonepe_business_web':
this.setState({ showPhonePeBusinessBind: true, phonePeBusinessBindType: 'webviewMode' });
break;
case 'googlepay_business':
this.setState({ showGooglePayBusinessBind: true });
@@ -770,6 +820,15 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
}, 300);
};
toggleBoundWalletGroup = (walletType: string) => {
this.setState((prev) => ({
expandedBoundWalletGroups: {
...prev.expandedBoundWalletGroups,
[walletType]: !prev.expandedBoundWalletGroups[walletType],
},
}));
};
renderAddWalletModal() {
return (
<Modal
@@ -885,7 +944,7 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
);
}
renderWalletItem = ({ item }: { item: WalletItem }) => {
renderWalletItem = (item: WalletItem) => {
const color = WALLET_TYPE_COLORS[item.walletType] ?? '#888';
const isActive = item.status === 'ACTIVE';
return (
@@ -928,6 +987,75 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
);
};
renderBoundWalletList = () => {
const { wallets, loadingWallets, expandedBoundWalletGroups } = this.state;
if (loadingWallets && wallets.length === 0) {
return (
<View style={{ alignItems: 'center', marginTop: 60 }}>
<Text style={{ color: '#aaa', fontSize: 14 }}>Loading</Text>
</View>
);
}
if (wallets.length === 0) {
return (
<View style={{ alignItems: 'center', marginTop: 60 }}>
<Text style={{ color: '#aaa', fontSize: 14 }}>No wallets. Tap + Add to get started.</Text>
</View>
);
}
const groups = groupBoundWallets(wallets);
return (
<ScrollView
style={{ flex: 1, width: '100%' }}
contentContainerStyle={{ paddingHorizontal: 14, paddingBottom: 20 }}
bounces={false}
>
{groups.map(({ walletType, items }) => {
if (items.length === 1) {
return this.renderWalletItem(items[0]);
}
const expanded = !!expandedBoundWalletGroups[walletType];
const color = WALLET_TYPE_COLORS[walletType] ?? '#888';
return (
<View key={walletType} style={s.boundWalletGroup}>
<TouchableOpacity
style={s.boundWalletGroupHeader}
onPress={() => this.toggleBoundWalletGroup(walletType)}
activeOpacity={0.7}
>
<View style={s.walletBadge}>
{WALLET_ICONS[walletType] ? (
<Image source={WALLET_ICONS[walletType]} style={s.walletIcon} resizeMode="contain" />
) : (
<View style={[s.walletIconFallback, { backgroundColor: color }]}>
<Text style={s.walletBadgeText}>{walletType.split(' ')[0]}</Text>
</View>
)}
</View>
<View style={{ flex: 1, marginLeft: 12 }}>
<Text style={s.boundWalletGroupTitle}>{formatWalletTypeLabel(walletType)}</Text>
<Text style={s.boundWalletGroupSub}>{items.length} accounts</Text>
</View>
<Text style={s.walletGroupChevron}>{expanded ? '▼' : '▶'}</Text>
</TouchableOpacity>
{expanded && items.map((item) => (
<View key={`${item.id}-wrap`} style={s.boundWalletGroupItem}>
{this.renderWalletItem(item)}
</View>
))}
</View>
);
})}
</ScrollView>
);
};
renderVpaModal() {
const { vpaModalWallet, vpaModalVpas, vpaModalLoading, vpaModalSelected } = this.state;
const color = WALLET_TYPE_COLORS[vpaModalWallet?.walletType ?? ''] ?? '#3498db';
@@ -990,7 +1118,7 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
}
render() {
const { proxyStatus, proxyError, wallets, loadingWallets } = this.state;
const { proxyStatus, proxyError, loadingWallets } = this.state;
const proxyCfg: Record<string, { label: string; color: string }> = {
idle: { label: 'Idle', color: '#95a5a6' },
connecting: { label: 'Connecting…', color: '#f39c12' },
@@ -1041,20 +1169,7 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
</TouchableOpacity>
</View>
<FlatList
style={{ flex: 1, width: '100%' }}
contentContainerStyle={{ paddingHorizontal: 14, paddingBottom: 20 }}
data={wallets}
keyExtractor={(item) => item.id}
renderItem={this.renderWalletItem}
ListEmptyComponent={
<View style={{ alignItems: 'center', marginTop: 60 }}>
<Text style={{ color: '#aaa', fontSize: 14 }}>
{loadingWallets ? 'Loading…' : 'No wallets. Tap + Add to get started.'}
</Text>
</View>
}
/>
{this.renderBoundWalletList()}
{this.renderBindModal()}
{this.renderServerSettingsModal()}
@@ -1283,6 +1398,43 @@ const s = StyleSheet.create({
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0',
},
walletTypeLabel: {
fontSize: 14,
color: '#333',
},
boundWalletGroup: {
marginBottom: 4,
},
boundWalletGroupHeader: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
borderRadius: 10,
padding: 12,
marginBottom: 8,
borderWidth: 1,
borderColor: '#eee',
},
boundWalletGroupTitle: {
fontSize: 15,
fontWeight: '600',
color: '#222',
},
boundWalletGroupSub: {
fontSize: 12,
color: '#888',
marginTop: 2,
},
boundWalletGroupItem: {
marginLeft: 10,
},
walletGroupChevron: {
fontSize: 12,
color: '#999',
marginLeft: 8,
width: 16,
textAlign: 'right',
},
walletTypeIcon: {
width: 32,
height: 32,
@@ -1295,10 +1447,6 @@ const s = StyleSheet.create({
borderRadius: 5,
marginRight: 12,
},
walletTypeLabel: {
fontSize: 14,
color: '#333',
},
settingsBox: {
backgroundColor: '#fff',
borderRadius: 10,