You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

734 lines
22 KiB

import React from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
TextInput,
ScrollView,
Alert,
ActivityIndicator,
Platform,
StatusBar,
SafeAreaView,
BackHandler,
Modal,
FlatList,
} from "react-native";
import useCreateOrderStore from "../../store/createOrder";
import BackIcon from "../../components/BackIcon";
import {
useNavigation,
useRoute,
RouteProp,
useFocusEffect,
CommonActions,
} from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useState, useEffect } from "react";
import useUserStore from "../../store/user";
import { Order } from "../../services/api/orders";
import { payApi } from "../../services/api/payApi";
import { settingApi } from "../../services/api/setting";
import { CountryList } from "../../constants/countries";
import { useTranslation } from "react-i18next";
import fontSize from "../../utils/fontsizeUtils";
import { getBurialPointData } from "../../store/burialPoint";
import useBurialPointStore from "../../store/burialPoint";
import AsyncStorage from "@react-native-async-storage/async-storage";
// 定义本地存储的国家数据类型
interface LocalCountryData {
code: string;
flag: string;
name: string;
phoneCode: string;
userCount: number;
}
// Define the param list for navigation
type RootStackParamList = {
PreviewOrder: {
data: Order;
payMethod: string;
currency: string;
amount: number;
};
Pay: { payUrl: string; method: string; order_id: string };
OrderDetails: { orderId?: number };
PaymentSuccessScreen: any;
MainTabs: undefined;
};
export const PreviewOrder = () => {
const { orderData, setOrderData } = useCreateOrderStore();
const navigation =
useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const [phoneNumber, setPhoneNumber] = useState("");
const [showPhoneInput, setShowPhoneInput] = useState(false);
const [countryList, setCountryList] = useState<CountryList[]>([]);
const [selectedCountry, setSelectedCountry] = useState<CountryList | null>(null);
const [localSelectedCountry, setLocalSelectedCountry] = useState<LocalCountryData | null>(null);
const [showCountryModal, setShowCountryModal] = useState(false);
const [loadingCountries, setLoadingCountries] = useState(false);
const route = useRoute<RouteProp<RootStackParamList, "PreviewOrder">>();
const [loading, setLoading] = useState(false);
const { user } = useUserStore();
const { t } = useTranslation();
const { logPreviewOrder } = useBurialPointStore();
useEffect(() => {
if (!user.user_id) {
return Alert.alert(t("order.preview.login_required"));
}
if (route.params.payMethod === "mobile_money") {
setShowPhoneInput(true);
// 获取国家列表
loadCountryList();
} else {
setShowPhoneInput(false);
}
}, [route.params.payMethod, user.user_id, t]);
// 获取国家列表
const loadCountryList = async () => {
setLoadingCountries(true);
try {
// 首先尝试读取本地存储的国家数据
const savedLocalCountry = await AsyncStorage.getItem("@selected_country");
if (savedLocalCountry) {
try {
const parsedLocalCountry: LocalCountryData = JSON.parse(savedLocalCountry);
setLocalSelectedCountry(parsedLocalCountry);
console.log('使用本地存储的国家:', parsedLocalCountry);
} catch (e) {
console.error("解析本地存储国家数据失败:", e);
}
}
const response = await settingApi.getSendSmsCountryList();
if (response && Array.isArray(response)) {
setCountryList(response);
// 如果没有本地存储的国家,则使用API返回的数据进行匹配
if (!savedLocalCountry) {
// 如果用户有国家信息,自动选择对应的国家
if (user?.country_en) {
const userCountry = response.find((country: CountryList) =>
country.name_en.toLowerCase() === user.country_en.toLowerCase()
);
if (userCountry) {
setSelectedCountry(userCountry);
}
}
}
}
} catch (error) {
console.error('获取国家列表失败:', error);
} finally {
setLoadingCountries(false);
}
};
// 格式化电话号码
const formatPhoneNumber = (phone: string, localCountry: LocalCountryData | null, apiCountry: CountryList | null): string => {
if (!phone) return phone;
// 移除电话号码中的空格、破折号等
const cleanPhone = phone.replace(/[\s\-\(\)]/g, '');
// 如果已经有+号开头,直接返回
if (cleanPhone.startsWith('+')) {
return cleanPhone;
}
// 优先使用本地存储的国家数据的 phoneCode
let countryCode = '';
if (localCountry?.phoneCode) {
countryCode = localCountry.phoneCode;
} else if (apiCountry?.country) {
countryCode = `+${apiCountry.country}`;
} else {
return phone; // 如果都没有,返回原始电话号码
}
// 如果电话号码以0开头,移除0
const phoneWithoutLeadingZero = cleanPhone.startsWith('0') ? cleanPhone.substring(1) : cleanPhone;
return `${countryCode}${phoneWithoutLeadingZero}`;
};
// 处理系统返回键
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
// 拦截系统返回键事件,导航到OrderDetails页面
navigation.navigate("OrderDetails", { orderId: 1 });
return true; // 返回true表示已处理返回事件
};
// 添加返回键监听(Android)
BackHandler.addEventListener("hardwareBackPress", onBackPress);
return () =>
BackHandler.removeEventListener("hardwareBackPress", onBackPress);
}, [navigation])
);
const handleSubmit = () => {
if (showPhoneInput && !phoneNumber) {
Alert.alert(t("error"), t("order.preview.phone_required") || "Phone number is required for Mobile Money");
return;
}
if (showPhoneInput && !localSelectedCountry && !selectedCountry) {
Alert.alert(t("error"), "请选择国家");
return;
}
console.log(route.params.currency);
// 格式化电话号码,添加国家前缀
const formattedPhone = showPhoneInput && phoneNumber
? formatPhoneNumber(phoneNumber, localSelectedCountry, selectedCountry)
: '';
const data = {
order_id: route.params.data.order_id,
method: route.params.payMethod,
currency: route.params.currency
? route.params.currency
: route.params.data.currency,
amount: route.params.data.actual_amount,
...(showPhoneInput && formattedPhone && {
extra: { phone_number: formattedPhone }
}),
};
console.log('发送的电话号码:', formattedPhone);
setLoading(true);
payApi
.getPayInfo(data)
.then((res) => {
if (res.success) {
// 记录埋点数据
logPreviewOrder(
navigation.getState().routes[navigation.getState().index - 1]
?.name as string
);
console.log("埋点数据: " +getBurialPointData());
// 所有成功的支付都跳转到 ProfileScreen
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{
name: 'MainTabs',
state: {
routes: [
{ name: 'Home' },
{ name: 'productCollection' },
{ name: 'Chat' },
{ name: 'Cart' },
{ name: 'Profile' },
],
index: 4, // Profile tab 的索引
},
},
],
})
);
} else {
// 处理失败情况
if (route.params.payMethod === "balance") {
Alert.alert(t("order.preview.Insufficient_balance"));
} else {
Alert.alert(t("order.preview.payment_failed"));
}
}
})
.catch((err) => {
setLoading(false);
Alert.alert(t("order.preview.payment_failed"));
})
.finally(() => {
setLoading(false);
});
};
// 获取显示的国家代码
const getDisplayCountryCode = () => {
if (loadingCountries) return "...";
if (localSelectedCountry?.phoneCode) {
return localSelectedCountry.phoneCode;
}
if (selectedCountry?.country) {
return `+${selectedCountry.country}`;
}
return '+86'; // 默认值
};
return (
<SafeAreaView style={[styles.safeArea]}>
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
<View style={styles.safeAreaContent}>
<View style={styles.container}>
<View style={styles.titleContainer}>
<View style={styles.backIconContainer}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<BackIcon size={fontSize(20)} />
</TouchableOpacity>
</View>
<Text style={styles.titleHeading}>
{t("order.preview.pay_now")}
</Text>
</View>
<ScrollView style={styles.scrollContainer}>
<View style={styles.mainContent}>
{/* Payment Method Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>
{t("order.preview.payment_method")}
</Text>
<View style={styles.paymentMethodContainer}>
<Text style={styles.paymentMethodText}>
{route.params.payMethod}
</Text>
</View>
{showPhoneInput && (
<View style={styles.phoneInputContainer}>
<Text style={styles.phoneInputLabel}>
{t("order.preview.enter_phone")}
</Text>
{/* 电话号码输入行 - 国家选择器在左侧 */}
<View style={styles.phoneInputRow}>
{/* 国家代码选择器 */}
<TouchableOpacity
style={styles.countryCodeSelector}
onPress={() => setShowCountryModal(true)}
disabled={loadingCountries}
>
<Text style={styles.countryCodeText}>
{getDisplayCountryCode()}
</Text>
<Text style={styles.countryCodeArrow}></Text>
</TouchableOpacity>
{/* 分隔线 */}
<View style={styles.phoneSeparator} />
{/* 电话号码输入框 */}
<TextInput
style={styles.phoneInput}
value={phoneNumber}
onChangeText={setPhoneNumber}
placeholder={t("order.preview.phone_placeholder")}
keyboardType="phone-pad"
/>
</View>
</View>
)}
</View>
{/* Order Info Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>
{t("order.preview.payment_info")}
</Text>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>
{t("order.preview.name")}
</Text>
<Text style={styles.infoValue}>
{route.params?.data.receiver_name || "N/A"}
</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>
{t("order.preview.phone")}
</Text>
<Text style={styles.infoValue}>
{route.params?.data.receiver_phone || "N/A"}
</Text>
</View>
{route.params.data?.whatsapp_number && (
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>WhatsApp</Text>
<Text style={styles.infoValue}>
{route.params.data?.whatsapp_number}
</Text>
</View>
)}
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>
{t("order.preview.country")}
</Text>
<Text style={styles.infoValue}>
{route.params?.data.receiver_country || "N/A"}
</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>
{t("order.preview.shipping_address")}
</Text>
<Text style={styles.infoValue}>
{route.params?.data.receiver_address || "N/A"}
</Text>
</View>
{/* <View style={styles.infoRow}>
<Text style={styles.infoLabel}>{t("order.preview.order_amount")}</Text>
<Text style={styles.infoValue}>
{route.params.amount || "N/A"} {route.params.data.currency}
</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>{t("order.preview.shipping_fee")}</Text>
<Text style={styles.infoValue}>
{(
route.params.data.domestic_shipping_fee +
route.params.data.shipping_fee
).toFixed(2) || "N/A"}{" "}
{route.params.data.currency}
</Text>
</View> */}
</View>
{/* Order Summary Section */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>
{t("order.preview.order_total")}
</Text>
<View style={styles.totalRow}>
<Text style={styles.totalLabel}>
{t("order.preview.total_amount")}
</Text>
<Text style={styles.totalValue}>
{route.params.data.actual_amount}{" "}
{route.params.data.currency}
</Text>
</View>
</View>
</View>
</ScrollView>
<View style={styles.submitButtonContainer}>
<TouchableOpacity
style={[
styles.primaryButtonStyle,
(!showPhoneInput || (showPhoneInput && phoneNumber && (localSelectedCountry || selectedCountry))) && !loading
? {}
: styles.disabledButtonStyle,
]}
onPress={handleSubmit}
disabled={(showPhoneInput && (!phoneNumber || (!localSelectedCountry && !selectedCountry))) || loading}
>
{loading ? (
<ActivityIndicator size="small" color="#ffffff" />
) : (
<Text style={styles.buttonText}>
{t("order.preview.confirm_payment")}
</Text>
)}
</TouchableOpacity>
</View>
</View>
</View>
{/* 国家选择模态框 */}
<Modal
visible={showCountryModal}
animationType="slide"
transparent={true}
onRequestClose={() => setShowCountryModal(false)}
>
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}></Text>
<TouchableOpacity
onPress={() => setShowCountryModal(false)}
style={styles.modalCloseButton}
>
<Text style={styles.modalCloseText}></Text>
</TouchableOpacity>
</View>
<FlatList
data={countryList}
keyExtractor={(item) => item.country.toString()}
renderItem={({ item }) => (
<TouchableOpacity
style={[
styles.countryItem,
selectedCountry?.country === item.country && styles.selectedCountryItem
]}
onPress={() => {
setSelectedCountry(item);
setLocalSelectedCountry(null); // 清除本地存储的选择,使用API的数据
setShowCountryModal(false);
}}
>
<Text style={[
styles.countryItemText,
selectedCountry?.country === item.country && styles.selectedCountryItemText
]}>
{item.name_en} (+{item.country})
</Text>
</TouchableOpacity>
)}
style={styles.countryList}
/>
</View>
</View>
</Modal>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: "#fff",
},
safeAreaContent: {
flex: 1,
paddingTop: Platform.OS === "android" ? 0 : 0,
},
container: {
flex: 1,
backgroundColor: "#fff",
},
scrollContainer: {
flex: 1,
},
mainContent: {
padding: 15,
},
submitButtonContainer: {
paddingHorizontal: 16,
paddingVertical: 20,
backgroundColor: "white",
borderTopWidth: 1,
borderTopColor: "#eaeaea",
},
primaryButtonStyle: {
width: "100%",
height: 50,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#002fa7",
borderRadius: 25,
shadowColor: "#002fa7",
shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.2,
shadowRadius: 5,
elevation: 5,
},
buttonText: {
color: "white",
fontWeight: "600",
fontSize: fontSize(16),
fontFamily: "PingFang SC",
},
disabledButtonStyle: {
backgroundColor: "#c0c0c0",
shadowOpacity: 0,
},
titleContainer: {
width: "100%",
padding: 15,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
position: "relative",
backgroundColor: "#fff",
borderBottomWidth: 1,
borderBottomColor: "#eaeaea",
},
backIconContainer: {
position: "absolute",
left: 15,
backgroundColor: "#fff",
},
titleHeading: {
fontWeight: "600",
fontSize: fontSize(16),
lineHeight: 22,
fontFamily: "PingFang SC",
color: "black",
},
section: {
marginBottom: 20,
backgroundColor: "#fff",
borderRadius: 8,
padding: 15,
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 1,
},
shadowOpacity: 0.22,
shadowRadius: 2.22,
elevation: 3,
},
sectionTitle: {
fontSize: fontSize(18),
fontWeight: "600",
marginBottom: 15,
color: "#000",
},
paymentMethodContainer: {
marginTop: 5,
},
paymentMethodText: {
fontSize: fontSize(17),
color: "#FF6000",
fontWeight: "500",
},
phoneInputContainer: {
marginTop: 15,
},
phoneInputLabel: {
fontSize: fontSize(15),
marginBottom: 5,
color: "#333",
},
phoneInput: {
flex: 1,
paddingHorizontal: 12,
paddingVertical: 15,
fontSize: fontSize(15),
color: "#333",
},
infoRow: {
flexDirection: "row",
justifyContent: "space-between",
marginBottom: 10,
paddingBottom: 10,
borderBottomWidth: 1,
borderBottomColor: "#f0f0f0",
},
infoLabel: {
fontSize: fontSize(15),
color: "#666",
flex: 1,
},
infoValue: {
fontSize: fontSize(15),
color: "#333",
flex: 2,
textAlign: "right",
},
totalRow: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingVertical: 12,
},
totalLabel: {
fontSize: fontSize(16),
fontWeight: "600",
color: "#333",
fontFamily: "PingFang SC",
},
totalValue: {
fontSize: fontSize(20),
fontWeight: "700",
color: "#002fa7",
fontFamily: "PingFang SC",
},
phoneInputRow: {
flexDirection: "row",
alignItems: "center",
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 5,
overflow: "hidden",
},
countryCodeSelector: {
paddingHorizontal: 12,
paddingVertical: 15,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
minWidth: 80,
backgroundColor: "#f8f8f8",
},
countryCodeText: {
fontSize: fontSize(15),
color: "#333",
fontWeight: "600",
marginRight: 5,
},
countryCodeArrow: {
fontSize: fontSize(10),
color: "#666",
},
phoneSeparator: {
width: 1,
height: 30,
backgroundColor: "#ddd",
},
modalOverlay: {
flex: 1,
backgroundColor: "rgba(0, 0, 0, 0.5)",
justifyContent: "center",
alignItems: "center",
},
modalContent: {
backgroundColor: "white",
padding: 20,
borderRadius: 20,
width: "80%",
maxHeight: "80%",
},
modalHeader: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 10,
},
modalTitle: {
fontSize: fontSize(18),
fontWeight: "600",
},
modalCloseButton: {
padding: 5,
},
modalCloseText: {
fontSize: fontSize(16),
fontWeight: "600",
},
countryItem: {
padding: 10,
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 5,
},
selectedCountryItem: {
backgroundColor: "#002fa7",
},
countryItemText: {
fontSize: fontSize(15),
color: "#333",
},
countryList: {
flex: 1,
},
selectedCountryItemText: {
color: "white",
},
});