| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086 |
- // import {
- // View,
- // Image,
- // Text,
- // ScrollView,
- // AppState,
- // Pressable,
- // ImageBackground,
- // ActivityIndicator,
- // Modal,
- // Alert,
- // TextInput,
- // Linking
- // } 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 UnionPayImage from '../../assets/unionpay.png';
- // import PayMeImage from '../../assets/payme.png';
- // import { formatCouponDate, formatDate } from '../../util/lib';
- // import { set } from 'date-fns';
- // import { reloadAppAsync } from 'expo';
- // const TopUpModal = ({ visible, onClose, onSelect, paymentOptions }) => {
- // const getPaymentImage = (key) => {
- // switch (key) {
- // case 'union_pay_wap_payment':
- // return UnionPayImage;
- // case 'payme_wap_payment':
- // return PayMeImage;
- // default:
- // return null;
- // }
- // };
- // return (
- // <Modal animationType="fade" transparent={true} visible={visible} onRequestClose={onClose}>
- // <View
- // style={{
- // flex: 1,
- // justifyContent: 'center',
- // alignItems: 'center',
- // backgroundColor: 'rgba(0,0,0,0.5)'
- // }}
- // >
- // <View
- // style={{
- // backgroundColor: 'white',
- // padding: 20,
- // borderRadius: 10,
- // width: '80%',
- // maxHeight: '80%'
- // }}
- // >
- // <Text style={{ fontSize: 20, marginBottom: 20 }}>選擇支付方式</Text>
- // <ScrollView>
- // {Object.entries(paymentOptions).map(([key, value]) => (
- // <Pressable
- // key={key}
- // onPress={() => onSelect(value)}
- // style={{
- // padding: 10,
- // marginBottom: 10,
- // borderBottomWidth: 1,
- // borderBottomColor: '#eee'
- // }}
- // >
- // <View className="flex flex-row items-center space-x-2">
- // <Image
- // source={getPaymentImage(key)}
- // style={{ width: 40, height: 40, marginRight: 10 }}
- // resizeMode="contain"
- // />
- // <Text className="tracking-wider">
- // {key === 'union_pay_wap_payment' ? 'UnionPay' : 'PayMe'}
- // </Text>
- // </View>
- // </Pressable>
- // ))}
- // </ScrollView>
- // <Pressable onPress={onClose} style={{ padding: 10, alignItems: 'center', marginTop: 10 }}>
- // <Text style={{ color: 'red' }}>關閉</Text>
- // </Pressable>
- // </View>
- // </View>
- // </Modal>
- // );
- // };
- // const AmountInputModal = ({ visible, onClose, onConfirm }) => {
- // const [inputAmount, setInputAmount] = useState('');
- // return (
- // <Modal animationType="fade" transparent={true} visible={visible} onRequestClose={onClose}>
- // <View
- // style={{
- // flex: 1,
- // justifyContent: 'center',
- // alignItems: 'center',
- // backgroundColor: 'rgba(0,0,0,0.5)'
- // }}
- // >
- // <View
- // style={{
- // backgroundColor: 'white',
- // padding: 20,
- // borderRadius: 10,
- // width: '80%'
- // }}
- // >
- // <Text style={{ fontSize: 20, marginBottom: 20 }}>輸入增值金額</Text>
- // <TextInput
- // style={{
- // borderWidth: 1,
- // borderColor: '#ccc',
- // borderRadius: 5,
- // padding: 10,
- // marginBottom: 20,
- // fontSize: 18
- // }}
- // keyboardType="numeric"
- // placeholder="輸入金額"
- // value={inputAmount}
- // onChangeText={setInputAmount}
- // />
- // <Pressable
- // onPress={() => onConfirm(inputAmount)}
- // style={{
- // backgroundColor: '#02677D',
- // padding: 10,
- // borderRadius: 5,
- // alignItems: 'center'
- // }}
- // >
- // <Text style={{ color: 'white', fontSize: 18 }}>確認</Text>
- // </Pressable>
- // <Pressable onPress={onClose} style={{ padding: 10, alignItems: 'center', marginTop: 10 }}>
- // <Text style={{ color: 'red' }}>取消</Text>
- // </Pressable>
- // </View>
- // </View>
- // </Modal>
- // );
- // };
- // export const CouponComponent = ({
- // title,
- // price,
- // detail,
- // date
- // }: {
- // title: string;
- // price: string;
- // detail: string;
- // date: string;
- // }) => {
- // return (
- // <Pressable onPress={() => console.log('abc')}>
- // <View className="bg-[#e7f2f8] h-[124px] rounded-xl flex-row mb-3">
- // <View className="bg-white mx-3 my-3 w-[28%] rounded-xl">
- // <View className="flex-row justify-center items-center pr-4 pt-4 ">
- // <Text className="color-[#02677d] text-2xl pl-2 pr-1">$</Text>
- // <Text className="color-[#02677d] text-3xl font-bold" adjustsFontSizeToFit={true}>
- // {price}
- // </Text>
- // </View>
- // <View className="items-center justify-center">
- // <Text className="text-base mt-1">{title}</Text>
- // </View>
- // </View>
- // {/* //dash line */}
- // <View style={{ overflow: 'hidden' }}>
- // <View
- // style={{
- // borderStyle: 'dashed',
- // borderWidth: 1,
- // borderColor: '#CCCCCC',
- // margin: -1,
- // width: 0,
- // marginRight: 0,
- // height: '100%'
- // }}
- // >
- // <View style={{ height: 60 }}></View>
- // </View>
- // </View>
- // <View className="flex-col flex-1 m-[5%] justify-center ">
- // <Text className="text-lg">{title}</Text>
- // <Text className="color-[#888888] text-sm">{detail}</Text>
- // <View className="flex-row items-center ">
- // <Text className="text-base">有效期 </Text>
- // <Text className="text-base font-bold text-[#02677d]">{date}</Text>
- // </View>
- // </View>
- // </View>
- // </Pressable>
- // );
- // };
- // const WalletPageComponent = () => {
- // const [walletBalance, setWalletBalance] = useState<string | null>(null);
- // const [loading, setLoading] = useState<boolean>(false);
- // const [modalVisible, setModalVisible] = useState(false);
- // const [coupons, setCoupons] = useState([]);
- // const [paymentType, setPaymentType] = useState({});
- // const [userID, setUserID] = useState('');
- // const [selectedPaymentType, setSelectedPaymentType] = useState<string | null>(null);
- // const [amount, setAmount] = useState<number>(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 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]);
- // const checkPaymentStatus = async () => {
- // try {
- // const result = await walletService.checkPaymentStatus(outTradeNo);
- // setPaymentStatus(result);
- // console.log('checkPaymentStatus from walletPageComponent', result);
- // if (result[0].respcd === '0000') {
- // console.log(result);
- // // Payment successful
- // Alert.alert('Success', 'Payment was successful!', [
- // {
- // text: 'OK',
- // onPress: async () => {
- // const wallet = await walletService.getWalletBalance();
- // setWalletBalance(wallet);
- // console.log('new wallet:', 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.');
- // }
- // };
- // // useEffect(() => {
- // // const handleAppStateChange = (nextAppState) => {
- // // if (appState.match(/inactive|background/) && nextAppState === 'active') {
- // // console.log('App has come to the foreground!');
- // // // Check payment status or update UI here
- // // console.log('outTradeNo', outTradeNo);
- // // }
- // // setAppState(nextAppState);
- // // };
- // // AppState.addEventListener('change', handleAppStateChange);
- // // }, [appState]);
- // useEffect(() => {
- // const fetchData = async () => {
- // try {
- // setLoading(true);
- // const info = await walletService.getCustomerInfo();
- // // const coupon = await walletService.getCouponForSpecificUser(info.id);
- // const wallet = await walletService.getWalletBalance();
- // console.log(wallet);
- // setUserID(info.id);
- // setWalletBalance(wallet);
- // setCoupons(coupon);
- // } catch (error) {
- // console.log(error);
- // } finally {
- // setLoading(false);
- // }
- // };
- // fetchData();
- // }, []);
- // const formatMoney = (amount: any) => {
- // if (typeof amount !== 'number') {
- // amount = Number(amount);
- // }
- // return amount.toLocaleString('en-US');
- // };
- // const filterPaymentOptions = (options, allowedKeys) => {
- // return Object.fromEntries(Object.entries(options).filter(([key]) => allowedKeys.includes(key)));
- // };
- // 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 handleTopUp = (selectedValue) => {
- // setSelectedPaymentType(selectedValue);
- // setModalVisible(false);
- // setAmountModalVisible(true);
- // };
- // const handleAmountConfirm = async (inputAmount) => {
- // setAmountModalVisible(false);
- // try {
- // const numericAmount = parseFloat(inputAmount);
- // if (isNaN(numericAmount) || numericAmount <= 0) {
- // throw new Error('Invalid amount');
- // }
- // const response = await walletService.submitPaymentAfterSelectingType(
- // numericAmount,
- // selectedPaymentType,
- // 'test'
- // );
- // setOutTradeNo(response.out_trade_no);
- // console.log('handleAmountConfirm outtradeno here,', response.out_trade_no);
- // setIsExpectingPayment(true);
- // paymentInitiatedTime.current = new Date().getTime();
- // const payUrl = response.pay_url;
- // const supported = await Linking.canOpenURL(payUrl);
- // if (supported) {
- // await Linking.openURL(payUrl);
- // } else {
- // throw new Error("Can't open payment URL");
- // }
- // } catch (error) {
- // console.error('Top-up failed:', error);
- // Alert.alert('Error', 'Failed to process top-up. Please try again.');
- // }
- // };
- // return (
- // <SafeAreaView className="flex-1 bg-white" edges={['top', 'right', 'left']}>
- // <ScrollView className="flex-1 ">
- // <View className="flex-1 mx-[5%]">
- // <View style={{ marginTop: 25 }}>
- // <Pressable
- // onPress={() => {
- // if (router.canGoBack()) {
- // router.back();
- // } else {
- // router.replace('/accountMainPage');
- // }
- // }}
- // >
- // <CrossLogoSvg />
- // </Pressable>
- // <Text style={{ fontSize: 45, marginVertical: 25 }}>錢包</Text>
- // </View>
- // <View>
- // <ImageBackground
- // className="flex-col-reverse shadow-lg"
- // style={{ height: 200 }}
- // source={require('../../assets/walletCard1.png')}
- // resizeMode="contain"
- // >
- // <View className="mx-[5%] pb-6">
- // <Text className="text-white text-xl">餘額 (HKD)</Text>
- // <View className="flex-row items-center justify-between ">
- // <Text style={{ fontSize: 52 }} className=" text-white font-bold">
- // {loading ? (
- // <View className="items-center justify-center">
- // <ActivityIndicator />
- // </View>
- // ) : (
- // <>
- // <Text>$</Text>
- // {formatMoney(walletBalance)}
- // </>
- // )}
- // </Text>
- // <Pressable
- // className="rounded-2xl items-center justify-center p-3 px-5 pr-6 "
- // style={{
- // backgroundColor: 'rgba(231, 242, 248, 0.2)'
- // }}
- // onPress={() => {
- // console.log('增值');
- // setModalVisible(true);
- // }}
- // >
- // <Text className="text-white font-bold">+ 增值</Text>
- // </Pressable>
- // </View>
- // </View>
- // </ImageBackground>
- // </View>
- // <View className="flex-row-reverse mt-2 mb-6">
- // <Pressable
- // onPress={() => {
- // router.push({
- // pathname: '/paymentRecord',
- // params: { walletBalance: formatMoney(walletBalance) }
- // });
- // }}
- // >
- // <Text className="text-[#02677D] text-lg underline">付款記錄</Text>
- // </Pressable>
- // </View>
- // </View>
- // <View className="w-full h-1 bg-[#DBE4E8]" />
- // <View className="flex-row justify-between mx-[5%] pt-6 pb-3">
- // <Text className="text-xl">優惠券</Text>
- // <Pressable onPress={() => router.push('couponPage')}>
- // <Text className="text-xl text-[#888888]">顯示所有</Text>
- // </Pressable>
- // </View>
- // <View className="flex-1 flex-col mx-[5%]">
- // {loading ? (
- // <View className="items-center justify-center">
- // <ActivityIndicator />
- // </View>
- // ) : (
- // <View>
- // {coupons
- // .filter(
- // (coupon) =>
- // coupon.is_consumed === false && new Date(coupon.expire_date) > new Date()
- // )
- // .slice(0, 2)
- // .map((coupon, index) => (
- // <CouponComponent
- // key={index}
- // title={coupon.name}
- // price={coupon.amount}
- // detail={coupon.description}
- // date={formatCouponDate(coupon.expire_date)}
- // />
- // ))}
- // </View>
- // )}
- // </View>
- // </ScrollView>
- // <TopUpModal
- // visible={modalVisible}
- // onClose={() => setModalVisible(false)}
- // onSelect={handleTopUp}
- // paymentOptions={paymentType}
- // />
- // <AmountInputModal
- // visible={amountModalVisible}
- // onClose={() => setAmountModalVisible(false)}
- // onConfirm={handleAmountConfirm}
- // />
- // </SafeAreaView>
- // );
- // };
- // export default WalletPageComponent;
- //////BELOW uses QFPay 的托管收银台页面 to 增值
- //////BELOW uses QFPay 的托管收银台页面 to 增值
- //////BELOW uses QFPay 的托管收银台页面 to 增值
- //////BELOW uses QFPay 的托管收银台页面 to 增值
- //////BELOW uses QFPay 的托管收银台页面 to 增值
- import {
- View,
- Image,
- Text,
- ScrollView,
- AppState,
- Pressable,
- ImageBackground,
- ActivityIndicator,
- Modal,
- Alert,
- TextInput,
- Linking,
- 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 UnionPayImage from '../../assets/unionpay.png';
- // import PayMeImage from '../../assets/payme.png';
- import { formatCouponDate, formatDate } from '../../util/lib';
- import { set } from 'date-fns';
- import { reloadAppAsync } from 'expo';
- import sha256 from 'crypto-js/sha256';
- import { useCallback } from 'react';
- 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: 5 },
- { amount: 1000, percentage: 10 },
- { amount: 2000, percentage: 15 }
- ];
- 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 (
- <Modal animationType="fade" transparent={true} visible={visible} onRequestClose={onClose}>
- <View
- style={{
- flex: 1,
- justifyContent: 'center',
- alignItems: 'center',
- backgroundColor: 'rgba(0,0,0,0.5)'
- }}
- >
- <View
- style={{
- backgroundColor: 'white',
- padding: 20,
- borderRadius: 10,
- width: '80%'
- }}
- >
- <Text style={{ fontSize: 20, marginBottom: 20 }}>選擇增值金額</Text>
- <View
- style={{
- flexDirection: 'row',
- flexWrap: 'wrap',
- justifyContent: 'space-between',
- marginBottom: 20
- }}
- >
- {amounts.map((amount) => (
- <Pressable
- key={amount.amount}
- onPress={() => onConfirm(amount.amount)}
- style={{
- backgroundColor: '#02677D',
- padding: 10,
- borderRadius: 5,
- width: '48%',
- alignItems: 'center',
- marginBottom: 10
- }}
- >
- <Text style={{ color: 'white', fontSize: getFontSize() }}>
- ${amount.amount}
- {amount.percentage > 0 ? ` (+${amount.percentage}%) ` : ''}
- </Text>
- </Pressable>
- ))}
- </View>
- <Text>*括號為回贈比例</Text>
- <Pressable onPress={onClose} style={{ padding: 10, alignItems: 'center', marginTop: 10 }}>
- <Text style={{ color: 'red' }}>取消</Text>
- </Pressable>
- </View>
- </View>
- </Modal>
- );
- };
- 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 (
- <ImageBackground
- source={require('../../assets/empty_coupon.png')}
- resizeMode="contain"
- style={{ width: '100%', aspectRatio: 16 / 5, justifyContent: 'center' }}
- className={`mb-3 lg:mb-4
- ${setOpacity ? 'opacity-50' : ''}`}
- >
- {/* largest container */}
- <Pressable
- className="flex-row w-full h-full "
- onPress={setOpacity ? () => {} : () => onCouponClick(redeem_code)}
- >
- {/* price column on the left */}
- <View className="flex-row items-center w-[31%] items-center justify-center">
- <Text className="pl-1 lg:pl-2 text-[#02677D] text-base md:text-lg lg:text-xl">$</Text>
- <Text className="text-3xl lg:text-4xl text-[#02677D] font-[600]">{price}</Text>
- </View>
- {/* this is a hack for good coupon display */}
- <View className="w-[7%] " />
- {/* detail column on the right */}
- <View className=" w-[62%] flex flex-col justify-evenly">
- <View className="flex flex-row justify-between items-center w-[90%]">
- <Text className="text-base lg:text-lg xl:text-xl">{title}</Text>
- {/* if opacity is true=used coupon= no circle */}
- {noCircle ? (
- <></>
- ) : (
- <View
- style={{
- width: 24,
- height: 24,
- borderRadius: 12,
- borderWidth: 2,
- borderColor: '#02677D',
- justifyContent: 'center',
- alignItems: 'center'
- }}
- className={`${promotion_code?.includes(redeem_code) ? 'bg-[#02677D]' : 'bg-white'}`}
- >
- <Text className="text-white">{promotion_code?.indexOf(redeem_code) + 1}</Text>
- </View>
- )}
- </View>
- <Text numberOfLines={2} ellipsizeMode="tail" className="text-xs w-[90%]">
- {detail}
- </Text>
- <View className="flex flex-row">
- <Text className="text-sm lg:text-base xl:text-lg">有效期至 {' '}</Text>
- <Text className="text-sm lg:text-base xl:text-lg font-bold text-[#02677D]">{date}</Text>
- </View>
- </View>
- </Pressable>
- </ImageBackground>
- );
- };
- const WalletPageComponent = () => {
- const [walletBalance, setWalletBalance] = useState<string | null>(null);
- const [loading, setLoading] = useState<boolean>(false);
- const [modalVisible, setModalVisible] = useState(false);
- const [coupons, setCoupons] = useState([]);
- const [paymentType, setPaymentType] = useState({});
- const [userID, setUserID] = useState('');
- const [selectedPaymentType, setSelectedPaymentType] = useState<string | null>(null);
- const [amount, setAmount] = useState<number>(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) {
- console.log(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 {
- console.log('what is the outTradeNo?? ', outTradeNo);
- const result = await walletService.checkPaymentStatus(outTradeNo);
- setPaymentStatus(result);
- console.log('checkPaymentStatus from walletPageComponent', result);
- if (result && !result.some((item) => item.errmsg?.includes('處理中'))) {
- // Payment successful
- Alert.alert('Success', 'Payment was successful!', [
- {
- text: '成功',
- onPress: async () => {
- const wallet = await walletService.getWalletBalance();
- setWalletBalance(wallet);
- console.log('new wallet:', 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();
- console.log('wallet', wallet);
- console.log('type of wallet', typeof wallet);
- setUserID(info.id);
- setWalletBalance(wallet);
- setCoupons(coupon);
- } catch (error) {
- console.log(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) => {
- setAmountModalVisible(false);
- try {
- const response = await walletService.getOutTradeNo();
- console.log('do i have outtrade no??', response);
- if (response) {
- setOutTradeNo(response);
- setIsExpectingPayment(true);
- paymentInitiatedTime.current = new Date().getTime();
- const now = new Date();
- const formattedTime = formatTime(now);
- console.log('formattedTime in walletPageComponent', formattedTime);
- const out_trade_no = response;
- console.log('inputAmount in walletPageComponent', inputAmount);
- let amount = inputAmount * 100;
- console.log('amount in walletPageComponent', amount);
- 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 {
- console.log(url);
- 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 {
- console.log('no');
- }
- } catch (error) {
- console.log(error);
- }
- };
- // const handleCouponClick = async (clickedCoupon: string) => {
- // Alert.alert(
- // '立即使用優惠券', // Title
- // '按確認打開相機,掃描充電站上的二維碼以使用優惠券', // Message
- // [
- // {
- // text: '取消',
- // style: 'cancel'
- // },
- // {
- // text: '確認',
- // onPress: () => router.push('scanQrPage')
- // }
- // ]
- // );
- // };
- const handleCouponClick = async (couponName: string, couponDescription: string) => {
- router.push({
- pathname: '/couponDetailPage',
- params: {
- couponName: couponName,
- couponDescription: couponDescription
- }
- });
- };
- const formattedAmount = formatMoney(walletBalance);
- return (
- <SafeAreaView className="flex-1 bg-white" edges={['top', 'right', 'left']}>
- <ScrollView className="flex-1 ">
- <View className="flex-1 mx-[5%]">
- <View style={{ marginTop: 25 }}>
- <Pressable
- onPress={() => {
- if (router.canGoBack()) {
- router.back();
- } else {
- router.replace('/accountMainPage');
- }
- }}
- >
- <CrossLogoSvg />
- </Pressable>
- <Text style={{ fontSize: 45, marginVertical: 25 }}>錢包</Text>
- </View>
- <View>
- <ImageBackground
- className="flex-col-reverse shadow-lg"
- style={{ height: 200 }}
- source={require('../../assets/walletCard1.png')}
- resizeMode="contain"
- >
- <View className="mx-[5%] pb-6">
- <Text className="text-white text-xl">餘額 (HKD)</Text>
- <View className="flex-row items-center justify-between ">
- <Text style={{ fontSize: 52 }} className=" text-white font-bold">
- {loading ? (
- <View className="items-center justify-center">
- <ActivityIndicator />
- </View>
- ) : (
- <>
- <Text>$</Text>
- {formattedAmount === 'LOADING' || amount == null ? (
- <ActivityIndicator />
- ) : (
- `${formattedAmount}`
- )}
- </>
- )}
- </Text>
- <Pressable
- className="rounded-2xl items-center justify-center p-3 px-5 pr-6 "
- style={{
- backgroundColor: 'rgba(231, 242, 248, 0.2)'
- }}
- onPress={() => {
- setAmountModalVisible(true);
- }}
- >
- <Text className="text-white font-bold">+ 增值</Text>
- </Pressable>
- </View>
- </View>
- </ImageBackground>
- </View>
- <View className="flex-row-reverse mt-2 mb-6">
- <Pressable
- onPress={() => {
- router.push({
- pathname: '/paymentRecord',
- params: { walletBalance: formatMoney(walletBalance) }
- });
- }}
- >
- <Text className="text-[#02677D] text-lg underline">訂單紀錄</Text>
- </Pressable>
- </View>
- </View>
- <View className="w-full h-1 bg-[#DBE4E8]" />
- <View className="flex-row justify-between mx-[5%] pt-6 pb-3">
- <Text className="text-xl">優惠券</Text>
- <Pressable onPress={() => router.push('couponPage')}>
- <Text className="text-xl text-[#888888]">顯示所有</Text>
- </Pressable>
- </View>
- <View className="flex-1 flex-col mx-[5%]">
- {loading ? (
- <View className="items-center justify-center">
- <ActivityIndicator />
- </View>
- ) : (
- <View>
- {coupons
- .filter(
- (coupon) =>
- coupon.is_consumed === false && new Date(coupon.expire_date) > new Date()
- )
- .slice(0, 2)
- .map((coupon, index) => (
- <IndividualCouponComponent
- key={index}
- title={coupon.coupon.name}
- price={coupon.coupon.amount}
- detail={coupon.coupon.description}
- date={formatCouponDate(coupon.expire_date)}
- noCircle={true}
- onCouponClick={() => handleCouponClick(coupon.coupon.name, coupon.coupon.description)}
- />
- ))}
- </View>
- )}
- </View>
- </ScrollView>
- {/* <TopUpModal
- visible={modalVisible}
- onClose={() => setModalVisible(false)}
- onSelect={handleTopUp}
- paymentOptions={paymentType}
- /> */}
- <AmountInputModal
- visible={amountModalVisible}
- onClose={() => setAmountModalVisible(false)}
- onConfirm={handleAmountConfirm}
- />
- </SafeAreaView>
- );
- };
- export default WalletPageComponent;
|