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.

1043 lines
28 KiB

import React, { useState, useEffect } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ScrollView,
Image,
Alert,
ActivityIndicator,
} from "react-native";
import { payApi, PaymentMethodsResponse } from "../../services/api/payApi";
import fontSize from "../../utils/fontsizeUtils";
import BackIcon from "../../components/BackIcon";
import { useNavigation, NavigationProp } from "@react-navigation/native";
import widthUtils from "../../utils/widthUtils";
import useOrderStore from "../../store/order";
import useCreateOrderStore from "../../store/createOrder";
import { useRoute, RouteProp } from "@react-navigation/native";
import useUserStore from "../../store/user";
import { createOrderDataType } from "../../types/createOrder";
import {
ordersApi,
OrderData, CreateOrderRequest,
} from "../../services/api/orders";
// Define route params type
type PaymentMethodRouteParams = {
freight_forwarder_address_id?: number;
};
// Define the root navigation params
type RootStackParamList = {
PreviewOrder: undefined;
Pay: { order_id: string };
ShippingFee: { freight_forwarder_address_id?: number };
PaymentMethod: { freight_forwarder_address_id?: number };
PreviewAddress: undefined;
AddressList: undefined;
// Add other routes as needed
};
interface PaymentOption {
id: string;
label: string;
icon: string;
value?: string | string[];
}
interface PaymentTab {
id: string;
label: string;
options: PaymentOption[];
}
const PaymentMethodItem = ({
option,
isSelected,
onSelect,
selectedCurrency,
onSelectCurrency,
exchangeRates,
totalAmount,
convertedAmount,
isConverting,
}: {
option: PaymentOption;
isSelected: boolean;
onSelect: () => void;
selectedCurrency?: string;
onSelectCurrency?: (currency: string) => void;
exchangeRates?: {
usd: number;
eur: number;
};
totalAmount?: number;
convertedAmount?: number;
isConverting?: boolean;
}) => (
<View>
<TouchableOpacity
style={[styles.paymentOption, isSelected && styles.paymentSelected]}
onPress={onSelect}
>
<View style={styles.paymentContent}>
<View style={styles.defaultPaymentContainer}>
<Text style={styles.paymentIcon}>{option.icon}</Text>
<Text style={styles.paymentLabel}>{option.label}</Text>
</View>
{Array.isArray(option.value) && option.value.length > 0 && (
<View style={styles.operatorContainer}>
{option.value.map((op: string) => (
<View key={op} style={styles.operatorBox}>
<Text style={styles.operatorText}>{op}</Text>
</View>
))}
</View>
)}
</View>
<View style={styles.radioButton}>
<View
style={[styles.radioInner, isSelected && styles.radioInnerSelected]}
/>
</View>
</TouchableOpacity>
{/* Show currency selector directly under PayPal when selected */}
{isSelected && option.label === "Paypal" && selectedCurrency && onSelectCurrency && exchangeRates && totalAmount && (
<CurrencySelector
selectedCurrency={selectedCurrency}
onSelectCurrency={onSelectCurrency}
exchangeRates={exchangeRates}
totalAmount={totalAmount}
convertedAmount={convertedAmount}
isConverting={isConverting}
/>
)}
</View>
);
// Currency selector component
interface CurrencySelectorProps {
selectedCurrency: string;
onSelectCurrency: (currency: string) => void;
exchangeRates: {
usd: number;
eur: number;
};
totalAmount: number;
convertedAmount?: number;
isConverting?: boolean;
}
const CurrencySelector = ({
selectedCurrency,
onSelectCurrency,
exchangeRates,
totalAmount,
convertedAmount = 0,
isConverting = false,
}: CurrencySelectorProps) => (
<View style={styles.currencySelectorContainer}>
<Text style={styles.currencySelectorTitle}>Select Currency</Text>
<View style={styles.currencyOptions}>
<TouchableOpacity
style={[
styles.currencyOption,
selectedCurrency === "USD" && styles.selectedCurrencyOption,
]}
onPress={() => onSelectCurrency("USD")}
>
<Text style={styles.currencyText}>USD</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.currencyOption,
selectedCurrency === "EUR" && styles.selectedCurrencyOption,
]}
onPress={() => onSelectCurrency("EUR")}
>
<Text style={styles.currencyText}>EUR</Text>
</TouchableOpacity>
</View>
<View style={styles.totalContainer}>
{!isConverting && (
<Text style={styles.totalText}>
{selectedCurrency === "USD" ? "$" : "€"}{convertedAmount}
</Text>
)}
{isConverting && (
<ActivityIndicator size="small" color="#ff6000" style={styles.loadingIndicator} />
)}
</View>
</View>
);
export const PaymentMethod = () => {
const [tabs, setTabs] = useState<PaymentTab[]>([
{
id: "online",
label: "Online Payment",
options: [],
},
{
id: "offline",
label: "Offline Payment",
options: [],
},
]);
const [currentTab, setCurrentTab] = useState("online");
const [paymentMethods, setPaymentMethods] =
useState<PaymentMethodsResponse>();
const [selectedPayment, setSelectedPayment] = useState<string | null>(null);
const navigation = useNavigation<NavigationProp<RootStackParamList>>();
const route =
useRoute<RouteProp<Record<string, PaymentMethodRouteParams>, string>>();
const [expanded, setExpanded] = useState(false);
const order = useOrderStore((state) => state.order);
const [previewOrder, setPreviewOrder] = useState<OrderData>();
const [loading, setLoading] = useState(false);
const { user } = useUserStore();
const [createOrderData, setCreateOrderData] = useState<createOrderDataType>();
const { items, orderData,setOrderData } = useCreateOrderStore();
const [selectedCurrency, setSelectedCurrency] = useState("USD");
const [convertedAmount, setConvertedAmount] = useState(0);
const [isConverting, setIsConverting] = useState(false);
const [exchangeRates] = useState({
usd: 580.00,
eur: 655.96
});
const [totalAmount, setTotalAmount] = useState(121.97);
const toggleExpanded = () => {
setExpanded(!expanded);
};
const onSelectPayment = (paymentId: string) => {
if(paymentId === "Paypal"){
setIsConverting(true);
payApi.convertCurrency({
from_currency: user.currency,
to_currency: selectedCurrency,
amount: (
(previewOrder?.total_amount || 0) +
(createOrderData?.domestic_shipping_fee || 0) +
(createOrderData?.shipping_fee || 0)
),
}).then((res) => {
setConvertedAmount(res.converted_amount);
setIsConverting(false);
}).catch(error => {
console.error("Currency conversion failed:", error);
setIsConverting(false);
});
}
setSelectedPayment(paymentId);
};
const onSelectCurrency = (currency: string) => {
setSelectedCurrency(currency);
// Call the API to convert the currency
setIsConverting(true);
payApi.convertCurrency({
from_currency: user.currency,
to_currency: currency,
amount: (
(previewOrder?.total_amount || 0) +
(createOrderData?.domestic_shipping_fee || 0) +
(createOrderData?.shipping_fee || 0)
),
}).then((res) => {
setConvertedAmount(res.converted_amount);
setIsConverting(false);
}).catch(error => {
console.error("Currency conversion failed:", error);
setIsConverting(false);
});
};
const getPaymentMethods = async () => {
try {
const response = await payApi.getCountryPaymentMethods();
setPaymentMethods(response);
// 设置默认支付方式选项
setTabs([
{
id: "online",
label: "Online Payment",
options: response.current_country_methods.map((method) => ({
id: method.key,
label: method.key,
icon: getPaymentIcon(method.key),
value: method.value,
})),
},
{
id: "offline",
label: "Offline Payment",
options: [],
},
]);
} catch (error) {
console.error("获取支付方式失败:", error);
}
};
const getPaymentIcon = (key: string): string => {
switch (key) {
case "Brainnel Pay(Mobile Money)":
return "💳";
case "Wave":
return "💸";
case "Paypal":
return "🅿";
case "Bank Card Payment":
return "💳";
default:
return "💰";
}
};
useEffect(() => {
getPaymentMethods();
}, []);
useEffect(() => {
setLoading(true);
if (route.params?.freight_forwarder_address_id) {
const data = {
country_code: route.params.freight_forwarder_address_id,
items: items,
};
ordersApi
.getOrders(data)
.then((res) => {
setPreviewOrder(res);
setLoading(false);
})
.catch(() => {
setLoading(false);
Alert.alert("Error", "Failed to get preview order");
});
}
}, [route.params?.freight_forwarder_address_id]);
useEffect(() => {
setCreateOrderData({
...orderData,
address_id: orderData.address_id,
domestic_shipping_fee: orderData.domestic_shipping_fee,
shipping_fee: orderData.shipping_fee,
transport_type: orderData.transport_type,
currency: user.currency,
});
console.log("orderData", orderData);
}, [orderData]);
const handleSubmit = async () => {
if (!selectedPayment) {
Alert.alert("请选择支付方式");
return;
}
const items =
previewOrder?.items.map((item) => ({
offer_id: item.offer_id,
cart_item_id: item.cart_item_id,
sku_id: item.sku_id,
product_name: item.product_name,
product_name_en: item.product_name_en,
product_name_ar: item.product_name_ar,
product_name_fr: item.product_name_fr,
sku_attributes: item.attributes.map((attr) => ({
attribute_name: attr.attribute_name,
attribute_value: attr.value,
})),
sku_image:item.sku_image_url,
quantity: item.quantity,
unit_price: item.unit_price,
total_price: item.total_price,
})) || [];
if (createOrderData) {
createOrderData.items = items;
createOrderData.payment_method = selectedPayment;
createOrderData.total_amount = selectedPayment === 'Paypal' ? convertedAmount :
Number(((previewOrder?.total_amount || 0) + (orderData?.domestic_shipping_fee || 0)
+ (orderData?.shipping_fee || 0)).toFixed(2));
createOrderData.actual_amount = Number((previewOrder?.total_amount || 0).toFixed(2));
createOrderData.currency = selectedPayment === 'Paypal' ? selectedCurrency : user.currency;
}
setOrderData(createOrderData || {});
const res = await ordersApi.createOrder(createOrderData as CreateOrderRequest)
console.log(res)
navigation.navigate("PreviewOrder");
};
return (
<View style={{ flex: 1, backgroundColor: "#fff" }}>
<View style={styles.titleContainer}>
<View style={styles.backIconContainer}>
<TouchableOpacity onPress={() => navigation.goBack()}>
<BackIcon size={20} />
</TouchableOpacity>
</View>
<Text style={styles.titleHeading}>Payment Method</Text>
</View>
{loading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#f77f3a" />
</View>
) : (
<ScrollView
style={[styles.container, { backgroundColor: "#fff" }]}
showsVerticalScrollIndicator={false}
bounces={false}
overScrollMode="never"
>
<View style={styles.sectionHeader}>
<Text style={styles.sectionIcon}>💳</Text>
<Text style={styles.sectionTitle}>Payment Method</Text>
</View>
{/* 选项卡 */}
<View style={styles.tabContainer}>
{tabs.map((tab) => (
<TouchableOpacity
key={tab.id}
style={[
styles.tabButton,
currentTab === tab.id && styles.tabButtonActive,
]}
onPress={() => {
console.log(tab.id);
setCurrentTab(tab.id)
}}
>
<Text
style={[
styles.tabText,
currentTab === tab.id && styles.tabTextActive,
]}
>
{tab.label}
</Text>
</TouchableOpacity>
))}
</View>
{/* 支付选项 */}
<View style={styles.paymentOptions}>
{tabs
.find((tab) => tab.id === currentTab)
?.options.map((option) => (
<PaymentMethodItem
key={option.id}
option={option}
isSelected={selectedPayment === option.id}
onSelect={() => onSelectPayment(option.id)}
selectedCurrency={selectedCurrency}
onSelectCurrency={onSelectCurrency}
exchangeRates={exchangeRates}
totalAmount={totalAmount}
convertedAmount={convertedAmount}
isConverting={isConverting}
/>
))}
</View>
{/* Order Summary Section */}
<View style={styles.section}>
<View style={styles.section1}>
<View style={styles.sectionHeader1}>
<Text style={styles.sectionIcon1}>📦</Text>
<Text style={styles.sectionTitle1}>Order Summary</Text>
</View>
<View style={styles.setOrderContent}>
<Text style={styles.noCouponsMessage}>
Products({previewOrder?.items?.length || 0} items)
</Text>
<TouchableOpacity onPress={toggleExpanded}>
<Text style={styles.sectionAction}>
{expanded ? "Hide Details" : "View Details"}
</Text>
</TouchableOpacity>
</View>
<View
style={[
styles.orderItems,
expanded && styles.orderItemsExpanded,
]}
>
{previewOrder?.items?.map((item) => (
<View key={item.sku_id} style={styles.orderItem}>
{item.sku_image_url ? (
<Image
source={{ uri: item.sku_image_url }}
style={styles.itemImage}
/>
) : (
<View style={styles.itemImagePlaceholder} />
)}
<View style={styles.itemDetails}>
<Text style={styles.itemName} numberOfLines={2}>
{item.product_name}
</Text>
{item.sku_attributes?.map((attribute) => (
<Text
style={styles.itemVariant}
key={attribute?.attribute_value}
numberOfLines={1}
>
{attribute?.attribute_name}:{" "}
{attribute?.attribute_value}
</Text>
))}
<Text style={styles.itemQuantity}>
Qty: {item.quantity}
</Text>
</View>
<View style={styles.itemPrices}>
<Text style={styles.itemPrice}>${item?.total_price}</Text>
</View>
</View>
))}
</View>
</View>
</View>
<View style={styles.priceBox}>
<View style={styles.priceBox1}>
<Text></Text>
<View>
<Text>
{previewOrder?.total_amount || 0} {previewOrder?.currency}
</Text>
</View>
</View>
<View style={styles.priceBox1}>
<Text></Text>
<View>
<Text>
{createOrderData?.domestic_shipping_fee || 0}{" "}
{previewOrder?.currency}
</Text>
</View>
</View>
<View style={styles.priceBox1}>
<Text></Text>
<View>
<Text>
{createOrderData?.shipping_fee || 0} {previewOrder?.currency}
</Text>
</View>
</View>
</View>
{/* 实际支付金额 */}
<View style={styles.actualPaymentBox}>
<View style={styles.actualPaymentBox1}>
<Text
style={{
fontSize: fontSize(18),
fontWeight: "600",
color: "#000",
}}
>
Total
</Text>
{
selectedPayment === 'Paypal' && selectedCurrency !== user.currency && (
<View style={{flexDirection:'row'}}>
<Text
style={{
fontSize: fontSize(18),
fontWeight: "600",
color: "#151515",
textDecorationLine: 'line-through'
}}
>
{(
(previewOrder?.total_amount || 0) +
(createOrderData?.domestic_shipping_fee || 0) +
(createOrderData?.shipping_fee || 0)
).toFixed(2)}{" "}
{previewOrder?.currency}
</Text>
<Text
style={{
fontSize: fontSize(18),
fontWeight: "600",
color: "#ff6000",
marginLeft:10
}}
>
{convertedAmount}{selectedCurrency === "USD" ? "USD" : "EUR"}
</Text>
</View>
)
}
{
selectedPayment === 'Paypal' && selectedCurrency === user.currency && (
<View style={{flexDirection:'row'}}>
<Text
style={{
fontSize: fontSize(18),
fontWeight: "600",
color: "#ff6000",
}}
>
{(
(previewOrder?.total_amount || 0) +
(createOrderData?.domestic_shipping_fee || 0) +
(createOrderData?.shipping_fee || 0)
).toFixed(2)}{" "}
{previewOrder?.currency}
</Text>
</View>
)
}
{
selectedPayment !== 'Paypal'&& (
<Text
style={{
fontSize: fontSize(18),
fontWeight: "600",
color: "#ff6000",
}}
>
{(
(previewOrder?.total_amount || 0) +
(createOrderData?.domestic_shipping_fee || 0) +
(createOrderData?.shipping_fee || 0)
).toFixed(2)}{" "}
{previewOrder?.currency}
</Text>
)
}
</View>
</View>
<View style={styles.submitButtonContainer}>
<TouchableOpacity
style={[
styles.primaryButtonStyle,
// (isDomesticShippingLoading || domesticShippingFeeData?.total_shipping_fee == null)
// ? styles.disabledButtonStyle : {}
]}
onPress={handleSubmit}
// disabled={isDomesticShippingLoading || domesticShippingFeeData?.total_shipping_fee == null}
>
<Text style={styles.buttonText}></Text>
</TouchableOpacity>
</View>
</ScrollView>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
marginTop: 10,
},
sectionHeader: {
flexDirection: "row",
alignItems: "center",
marginBottom: 15,
paddingHorizontal: 15,
},
sectionIcon: {
fontSize: fontSize(20),
marginRight: 8,
},
sectionTitle: {
fontSize: fontSize(18),
fontWeight: "600",
color: "#000",
},
tabContainer: {
flexDirection: "row",
marginBottom: 15,
borderBottomWidth: 1,
borderBottomColor: "#EEEEEE",
paddingHorizontal: 15,
},
tabButton: {
paddingVertical: 10,
paddingHorizontal: 15,
marginRight: 10,
},
tabButtonActive: {
borderBottomWidth: 2,
borderBottomColor: "#FF5100",
},
tabText: {
fontSize: fontSize(16),
color: "#666",
},
tabTextActive: {
color: "#FF5100",
fontWeight: "500",
},
paymentOptions: {
marginBottom: 15,
paddingHorizontal: 15,
},
paymentOption: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
padding: 15,
backgroundColor: "#F8F8F8",
borderRadius: 8,
marginBottom: 10,
},
paymentSelected: {
backgroundColor: "#FFF0E8",
borderWidth: 1,
borderColor: "#FF5100",
},
paymentContent: {
flex: 1,
},
defaultPaymentContainer: {
flexDirection: "row",
alignItems: "center",
},
paymentIcon: {
fontSize: fontSize(24),
marginRight: 8,
},
paymentLabel: {
fontSize: fontSize(16),
fontWeight: "500",
},
operatorContainer: {
flexDirection: "row",
flexWrap: "wrap",
marginTop: 8,
},
operatorBox: {
backgroundColor: "#fff",
paddingVertical: 4,
paddingHorizontal: 8,
borderRadius: 4,
marginRight: 8,
marginBottom: 4,
},
operatorText: {
fontSize: fontSize(12),
color: "#666",
},
radioButton: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 1,
borderColor: "#CCCCCC",
justifyContent: "center",
alignItems: "center",
},
radioInner: {
width: 12,
height: 12,
borderRadius: 6,
backgroundColor: "transparent",
},
radioInnerSelected: {
backgroundColor: "#FF5100",
},
titleContainer: {
width: "100%",
padding: 15,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
position: "relative",
backgroundColor: "#fff",
},
backIconContainer: {
position: "absolute",
left: 15,
backgroundColor: "#fff",
},
titleHeading: {
fontWeight: "600",
fontSize: 20,
lineHeight: 22,
fontFamily: "PingFang SC",
color: "black",
},
// Order Summary Styles
section: {
backgroundColor: "#fff",
borderRadius: 8,
paddingHorizontal: 15,
marginTop: 15,
},
section1: {
backgroundColor: "#fff",
borderRadius: 8,
overflow: "hidden",
},
sectionHeader1: {
flexDirection: "row",
alignItems: "center",
paddingTop: 12,
paddingBottom: 12,
borderBottomWidth: 1,
borderBottomColor: "#f5f5f5",
},
sectionIcon1: {
fontSize: fontSize(18),
marginRight: 10,
color: "#666",
},
sectionTitle1: {
fontSize: fontSize(15),
fontWeight: "500",
flex: 1,
},
setOrderContent: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingTop: 12,
paddingBottom: 12,
paddingHorizontal: 12,
},
sectionAction: {
color: "#ff6000",
fontSize: fontSize(13),
fontWeight: "500",
},
noCouponsMessage: {
color: "#888",
fontSize: fontSize(13),
},
orderItems: {
maxHeight: 0,
overflow: "hidden",
},
orderItemsExpanded: {
maxHeight: 1000, // Arbitrary large number to accommodate all items
},
orderItem: {
flexDirection: "row",
padding: 16,
borderBottomWidth: 1,
borderBottomColor: "#f5f5f5",
},
itemImage: {
width: widthUtils(70, 70).width,
height: widthUtils(70, 70).height,
borderRadius: 6,
marginRight: 12,
borderWidth: 1,
borderColor: "#eee",
},
itemImagePlaceholder: {
width: widthUtils(70, 70).width,
height: widthUtils(70, 70).height,
borderRadius: 6,
marginRight: 12,
borderWidth: 1,
borderColor: "#eee",
backgroundColor: "#f1f1f1",
},
itemDetails: {
flex: 1,
},
itemName: {
fontSize: fontSize(14),
lineHeight: 18,
},
itemVariant: {
fontSize: fontSize(12),
color: "#666",
backgroundColor: "#f7f7f7",
paddingVertical: 3,
paddingHorizontal: 6,
borderRadius: 4,
marginTop: 6,
alignSelf: "flex-start",
},
itemQuantity: {
fontSize: fontSize(12),
color: "#666",
marginTop: 4,
},
itemPrices: {
alignItems: "flex-end",
fontSize: fontSize(13),
color: "#555",
},
itemPrice: {
fontWeight: "600",
color: "#ff6000",
fontSize: fontSize(15),
marginBottom: 5,
},
priceBox: {
borderRadius: 10,
marginTop: 15,
paddingHorizontal: 15,
},
priceBox1: {
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
padding: 12,
borderBottomWidth: 1,
borderBottomColor: "#f5f5f5",
},
actualPaymentBox: {
padding: 12,
borderRadius: 6,
backgroundColor: "#fff8f4",
marginTop: 15,
marginHorizontal: 15,
marginBottom: 20,
},
actualPaymentBox1: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
loadingContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#fff",
},
submitButtonContainer: {
paddingRight: 11,
paddingLeft: 11,
marginTop: 20,
marginBottom: 20,
},
primaryButtonStyle: {
width: "100%",
height: 50,
justifyContent: "center",
alignItems: "center",
fontWeight: "600",
fontSize: 16,
lineHeight: 22,
fontFamily: "PingFang SC",
color: "white",
backgroundColor: "#002fa7",
borderWidth: 0,
borderRadius: 25,
},
buttonText: {
color: "white",
fontWeight: "600",
fontSize: 16,
lineHeight: 22,
fontFamily: "PingFang SC",
},
selectedCountryText: {
padding: 0,
margin: 0,
fontWeight: "500",
fontSize: 16,
lineHeight: 22,
fontFamily: "PingFang SC",
color: "#646472",
},
disabledButtonStyle: {
backgroundColor: "#ccc",
},
currencySelectorContainer: {
padding: 15,
backgroundColor: "#f9f9f9",
borderRadius: 8,
marginTop: 5,
marginBottom: 15,
},
currencySelectorTitle: {
fontSize: fontSize(16),
fontWeight: "600",
color: "#000",
marginBottom: 15,
},
currencyOptions: {
flexDirection: "row",
marginBottom: 15,
},
currencyOption: {
flex: 1,
padding: 15,
borderWidth: 1,
borderColor: "#DDDDDD",
borderRadius: 4,
marginRight: 10,
alignItems: "center",
justifyContent: "center",
},
selectedCurrencyOption: {
borderColor: "#002fa7",
backgroundColor: "#fff",
},
currencyText: {
fontSize: fontSize(16),
fontWeight: "500",
color: "#000",
},
exchangeRateContainer: {
marginBottom: 15,
},
exchangeRateText: {
fontSize: fontSize(14),
color: "#666",
marginBottom: 5,
},
totalContainer: {
marginTop: 10,
borderTopWidth: 1,
borderTopColor: "#EEEEEE",
paddingTop: 10,
flexDirection: 'row',
alignItems: 'center',
},
totalText: {
fontSize: fontSize(16),
fontWeight: "600",
color: "#ff6000",
},
loadingIndicator: {
marginLeft: 10,
},
});