import { View, Image, Text, ScrollView, AppState, Pressable, ImageBackground, ActivityIndicator, Modal, Alert, TextInput, Linking, StyleSheet, TouchableOpacity, Dimensions } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { router } from 'expo-router'; import { CrossLogoSvg } from '../global/SVG'; import { useEffect, useRef, useState } from 'react'; import { walletService } from '../../service/walletService'; import NormalButton from '../global/normal_button'; import sha256 from 'crypto-js/sha256'; import { useChargingStore } from '../../providers/scan_qr_payload_store'; const AmountInputModal = ({ visible, onClose, onConfirm }) => { const amounts = [ // { amount: 1, percentage: 0 }, { amount: 200, percentage: 0 }, { amount: 500, percentage: 25 }, { amount: 1000, percentage: 100 }, { amount: 2000, percentage: 300 } ]; const getFontSize = () => { const { width } = Dimensions.get('window'); if (width < 320) return 8; if (width < 350) return 10; //super small phones if (width < 375) return 12; // Smaller phones if (width < 414) return 14; // Average phones return 16; // Larger phones }; return ( 選擇增值金額 {amounts.map((amount) => ( onConfirm(amount.amount)} style={{ backgroundColor: '#02677D', padding: 10, borderRadius: 5, width: '48%', alignItems: 'center', marginBottom: 10 }} > ${amount.amount} {amount.percentage > 0 ? ` (送$${amount.percentage}) ` : ''} ))} *括號為贈款金額 取消 ); }; export const IndividualCouponComponent = ({ title, price, detail, date, setOpacity, redeem_code, onCouponClick, noCircle }: { title: string; price: string; detail: string; onCouponClick?: (clickedCoupon: string, clickedCouponDescription: string) => void; date: string; setOpacity?: boolean; redeem_code?: string; noCircle?: boolean; }) => { const { promotion_code } = useChargingStore(); return ( {/* largest container */} {} : () => onCouponClick(redeem_code)} > {/* price column on the left */} $ {price} {/* this is a hack for good coupon display */} {/* detail column on the right */} {title} {/* if opacity is true=used coupon= no circle */} {noCircle ? ( <> ) : ( {promotion_code?.indexOf(redeem_code as string) + 1} )} {detail} 有效期至 {' '} {date} ); }; const WalletPageComponent = () => { const [walletBalance, setWalletBalance] = useState(null); const [loading, setLoading] = useState(false); const [modalVisible, setModalVisible] = useState(false); const [coupons, setCoupons] = useState([]); const [paymentType, setPaymentType] = useState({}); const [userID, setUserID] = useState(''); const [selectedPaymentType, setSelectedPaymentType] = useState(null); const [amount, setAmount] = useState(0); const [amountModalVisible, setAmountModalVisible] = useState(false); const [outTradeNo, setOutTradeNo] = useState(''); const PAYMENT_CHECK_TIMEOUT = 5 * 60 * 1000; // 5 minutes in milliseconds const [paymentStatus, setPaymentStatus] = useState(null); const [isExpectingPayment, setIsExpectingPayment] = useState(false); const appState = useRef(AppState.currentState); const paymentInitiatedTime = useRef(null); // 优惠券注释 useEffect(() => { const fetchData = async () => { try { setLoading(true); const info = await walletService.getCustomerInfo(); const coupon = await walletService.getCouponForSpecificUser(info.id); const useableConpon = coupon.filter((couponObj: any) => { const today = new Date(); if (couponObj.expire_date === null) { return couponObj.is_consumed === false; } const expireDate = new Date(couponObj.expire_date); return expireDate > today && couponObj.is_consumed === false; }); setCoupons(useableConpon); } catch (error) { } finally { setLoading(false); } }; fetchData(); }, []); //monitor app state useEffect(() => { const subscription = AppState.addEventListener('change', (nextAppState) => { if ( appState.current.match(/inactive|background/) && nextAppState === 'active' && isExpectingPayment && // outTradeNo && paymentInitiatedTime.current ) { const currentTime = new Date().getTime(); if (currentTime - paymentInitiatedTime.current < PAYMENT_CHECK_TIMEOUT) { checkPaymentStatus(); } else { // Payment check timeout reached setIsExpectingPayment(false); setOutTradeNo(''); paymentInitiatedTime.current = null; Alert.alert( 'Payment Timeout', 'The payment status check has timed out. Please check your payment history.' ); } } appState.current = nextAppState; }); return () => { subscription.remove(); }; }, [outTradeNo, isExpectingPayment]); //check payment status const checkPaymentStatus = async () => { try { const result = await walletService.checkPaymentStatus(outTradeNo); setPaymentStatus(result); if (result && !result.some((item: any) => item.errmsg?.includes('處理中'))) { // Payment successful Alert.alert('Success', 'Payment was successful!', [ { text: '成功', onPress: async () => { const wallet = await walletService.getWalletBalance(); setWalletBalance(wallet); } } ]); } else { Alert.alert('Payment Failed', 'Payment was not successful. Please try again.'); } setIsExpectingPayment(false); setOutTradeNo(''); paymentInitiatedTime.current = null; } catch (error) { console.error('Failed to check payment status:', error); Alert.alert('Error', 'Failed to check payment status. Please check your payment history.'); } }; //fetch customer wallet balance useEffect(() => { const fetchData = async () => { try { setLoading(true); const info = await walletService.getCustomerInfo(); const wallet = await walletService.getWalletBalance(); setUserID(info.id); setWalletBalance(wallet); // setCoupons(coupon); } catch (error) { } finally { setLoading(false); } }; fetchData(); }, []); const formatMoney = (amount: any) => { if (amount === null || amount === undefined || isNaN(Number(amount))) { return 'LOADING'; } if (typeof amount !== 'number') { amount = Number(amount); } // Check if the number is a whole number if (Number.isInteger(amount)) { return amount.toLocaleString('en-US'); } // For decimal numbers, show one decimal place return Number(amount) .toFixed(1) .replace(/\B(?=(\d{3})+(?!\d))/g, ','); }; const filterPaymentOptions = (options, allowedKeys) => { return Object.fromEntries(Object.entries(options).filter(([key]) => allowedKeys.includes(key))); }; function formatTime(utcTimeString) { // Parse the UTC time string const date = new Date(utcTimeString); // Add 8 hours date.setHours(date.getHours()); // Format the date const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); // Return the formatted string return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } useEffect(() => { const fetchPaymentType = async () => { const response = await walletService.selectPaymentType(); // console.log('response', response); const filteredPaymentTypes = filterPaymentOptions(response, ['union_pay_wap_payment', 'payme_wap_payment']); setPaymentType(filteredPaymentTypes); }; fetchPaymentType(); }, []); const handleAmountConfirm = async (inputAmount: number) => { setAmountModalVisible(false); try { const response = await walletService.getOutTradeNo(); if (response) { setOutTradeNo(response); setIsExpectingPayment(true); paymentInitiatedTime.current = new Date().getTime(); const now = new Date(); const formattedTime = formatTime(now); const out_trade_no = response; let amount = inputAmount * 100; const origin = 'https://openapi-hk.qfapi.com/checkstand/#/?'; const obj = { // appcode: '6937EF25DF6D4FA78BB2285441BC05E9', appcode: '636E234FB30D43598FC8F0140A1A7282', goods_name: 'Crazy Charge 錢包增值', out_trade_no: response, paysource: 'crazycharge_checkout', return_url: 'https://www.google.com', failed_url: 'https://www.google.com', notify_url: 'https://api.crazycharge.com.hk/api/v1/clients/qfpay/webhook', sign_type: 'sha256', txamt: amount, txcurrcd: 'HKD', txdtm: formattedTime }; const paramStringify = (json, flag?) => { let str = ''; let keysArr = Object.keys(json); keysArr.sort().forEach((val) => { if (!json[val]) return; str += `${val}=${flag ? encodeURIComponent(json[val]) : json[val]}&`; }); return str.slice(0, -1); }; // const api_key = '8F59E31F6ADF4D2894365F2BB6D2FF2C'; const api_key = '3E2727FBA2DA403EA325E73F36B07824'; const params = paramStringify(obj); const sign = sha256(`${params}${api_key}`).toString(); const url = `${origin}${paramStringify(obj, true)}&sign=${sign}`; try { const supported = await Linking.canOpenURL(url); if (supported) { await Linking.openURL(url); } else { Alert.alert('錯誤', '請稍後再試'); } } catch (error) { console.error('Top-up failed:', error); Alert.alert('Error', 'Failed to process top-up. Please try again.'); } } else { } } catch (error) {} }; const handleCouponClick = async (couponName: string, couponDescription: string) => { router.push({ pathname: '/couponDetailPage', params: { couponName: couponName, couponDescription: couponDescription } }); }; const formattedAmount = formatMoney(walletBalance); return ( { router.replace('/accountMainPage'); }} > 錢包 餘額 (HKD) {loading ? ( ) : ( <> $ {formattedAmount === 'LOADING' || amount == null ? ( ) : ( `${formattedAmount}` )} )} { setAmountModalVisible(true); }} > + 增值 { router.push({ pathname: '/paymentRecord', params: { walletBalance: formatMoney(walletBalance) } }); }} > 訂單紀錄 {/* 優惠券 router.push('couponPage')}> 顯示所有 */} router.push('couponPage')} title={ 查看所有優惠券 } extendedStyle={{ padding: 15 }} /> setAmountModalVisible(false)} onConfirm={handleAmountConfirm} /> ); }; const styles = StyleSheet.create({ button: { maxWidth: '100%', fontSize: 16, backgroundColor: '#025c72', justifyContent: 'center', alignItems: 'center', borderRadius: 12, padding: 20 }, buttonPressed: { backgroundColor: '#28495c' } }); export default WalletPageComponent;