walletPageComponent.tsx 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. import {
  2. View,
  3. Image,
  4. Text,
  5. ScrollView,
  6. AppState,
  7. Pressable,
  8. ImageBackground,
  9. ActivityIndicator,
  10. Modal,
  11. Alert,
  12. TextInput,
  13. Linking
  14. } from 'react-native';
  15. import { SafeAreaView } from 'react-native-safe-area-context';
  16. import { router } from 'expo-router';
  17. import { CrossLogoSvg } from '../global/SVG';
  18. import { useEffect, useRef, useState } from 'react';
  19. import { walletService } from '../../service/walletService';
  20. import UnionPayImage from '../../assets/unionpay.png';
  21. import PayMeImage from '../../assets/payme.png';
  22. import { formatCouponDate, formatDate } from '../../util/lib';
  23. import { set } from 'date-fns';
  24. import { reloadAppAsync } from 'expo';
  25. const TopUpModal = ({ visible, onClose, onSelect, paymentOptions }) => {
  26. const getPaymentImage = (key) => {
  27. switch (key) {
  28. case 'union_pay_wap_payment':
  29. return UnionPayImage;
  30. case 'payme_wap_payment':
  31. return PayMeImage;
  32. default:
  33. return null;
  34. }
  35. };
  36. return (
  37. <Modal animationType="fade" transparent={true} visible={visible} onRequestClose={onClose}>
  38. <View
  39. style={{
  40. flex: 1,
  41. justifyContent: 'center',
  42. alignItems: 'center',
  43. backgroundColor: 'rgba(0,0,0,0.5)'
  44. }}
  45. >
  46. <View
  47. style={{
  48. backgroundColor: 'white',
  49. padding: 20,
  50. borderRadius: 10,
  51. width: '80%',
  52. maxHeight: '80%'
  53. }}
  54. >
  55. <Text style={{ fontSize: 20, marginBottom: 20 }}>選擇支付方式</Text>
  56. <ScrollView>
  57. {Object.entries(paymentOptions).map(([key, value]) => (
  58. <Pressable
  59. key={key}
  60. onPress={() => onSelect(value)}
  61. style={{
  62. padding: 10,
  63. marginBottom: 10,
  64. borderBottomWidth: 1,
  65. borderBottomColor: '#eee'
  66. }}
  67. >
  68. <View className="flex flex-row items-center space-x-2">
  69. <Image
  70. source={getPaymentImage(key)}
  71. style={{ width: 40, height: 40, marginRight: 10 }}
  72. resizeMode="contain"
  73. />
  74. <Text className="tracking-wider">
  75. {key === 'union_pay_wap_payment' ? 'UnionPay' : 'PayMe'}
  76. </Text>
  77. </View>
  78. </Pressable>
  79. ))}
  80. </ScrollView>
  81. <Pressable onPress={onClose} style={{ padding: 10, alignItems: 'center', marginTop: 10 }}>
  82. <Text style={{ color: 'red' }}>關閉</Text>
  83. </Pressable>
  84. </View>
  85. </View>
  86. </Modal>
  87. );
  88. };
  89. const AmountInputModal = ({ visible, onClose, onConfirm }) => {
  90. const [inputAmount, setInputAmount] = useState('');
  91. return (
  92. <Modal animationType="fade" transparent={true} visible={visible} onRequestClose={onClose}>
  93. <View
  94. style={{
  95. flex: 1,
  96. justifyContent: 'center',
  97. alignItems: 'center',
  98. backgroundColor: 'rgba(0,0,0,0.5)'
  99. }}
  100. >
  101. <View
  102. style={{
  103. backgroundColor: 'white',
  104. padding: 20,
  105. borderRadius: 10,
  106. width: '80%'
  107. }}
  108. >
  109. <Text style={{ fontSize: 20, marginBottom: 20 }}>輸入增值金額</Text>
  110. <TextInput
  111. style={{
  112. borderWidth: 1,
  113. borderColor: '#ccc',
  114. borderRadius: 5,
  115. padding: 10,
  116. marginBottom: 20,
  117. fontSize: 18
  118. }}
  119. keyboardType="numeric"
  120. placeholder="輸入金額"
  121. value={inputAmount}
  122. onChangeText={setInputAmount}
  123. />
  124. <Pressable
  125. onPress={() => onConfirm(inputAmount)}
  126. style={{
  127. backgroundColor: '#02677D',
  128. padding: 10,
  129. borderRadius: 5,
  130. alignItems: 'center'
  131. }}
  132. >
  133. <Text style={{ color: 'white', fontSize: 18 }}>確認</Text>
  134. </Pressable>
  135. <Pressable onPress={onClose} style={{ padding: 10, alignItems: 'center', marginTop: 10 }}>
  136. <Text style={{ color: 'red' }}>取消</Text>
  137. </Pressable>
  138. </View>
  139. </View>
  140. </Modal>
  141. );
  142. };
  143. export const IndividualCouponComponent = ({
  144. title,
  145. price,
  146. detail,
  147. date
  148. }: {
  149. title: string;
  150. price: string;
  151. detail: string;
  152. date: string;
  153. }) => {
  154. return (
  155. <Pressable onPress={() => console.log('abc')}>
  156. <View className="bg-[#e7f2f8] h-[124px] rounded-xl flex-row mb-3">
  157. <View className="bg-white mx-3 my-3 w-[28%] rounded-xl">
  158. <View className="flex-row justify-center items-center pr-4 pt-4 ">
  159. <Text className="color-[#02677d] text-2xl pl-2 pr-1">$</Text>
  160. <Text className="color-[#02677d] text-3xl font-bold" adjustsFontSizeToFit={true}>
  161. {price}
  162. </Text>
  163. </View>
  164. <View className="items-center justify-center">
  165. <Text className="text-base mt-1">{title}</Text>
  166. </View>
  167. </View>
  168. {/* //dash line */}
  169. <View style={{ overflow: 'hidden' }}>
  170. <View
  171. style={{
  172. borderStyle: 'dashed',
  173. borderWidth: 1,
  174. borderColor: '#CCCCCC',
  175. margin: -1,
  176. width: 0,
  177. marginRight: 0,
  178. height: '100%'
  179. }}
  180. >
  181. <View style={{ height: 60 }}></View>
  182. </View>
  183. </View>
  184. <View className="flex-col flex-1 m-[5%] justify-center ">
  185. <Text className="text-lg">{title}</Text>
  186. <Text className="color-[#888888] text-sm">{detail}</Text>
  187. <View className="flex-row items-center ">
  188. <Text className="text-base">有效期 </Text>
  189. <Text className="text-base font-bold text-[#02677d]">{date}</Text>
  190. </View>
  191. </View>
  192. </View>
  193. </Pressable>
  194. );
  195. };
  196. const WalletPageComponent = () => {
  197. const [walletBalance, setWalletBalance] = useState<string | null>(null);
  198. const [loading, setLoading] = useState<boolean>(false);
  199. const [modalVisible, setModalVisible] = useState(false);
  200. const [coupons, setCoupons] = useState([]);
  201. const [paymentType, setPaymentType] = useState({});
  202. const [userID, setUserID] = useState('');
  203. const [selectedPaymentType, setSelectedPaymentType] = useState<string | null>(null);
  204. const [amount, setAmount] = useState<number>(0);
  205. const [amountModalVisible, setAmountModalVisible] = useState(false);
  206. const [outTradeNo, setOutTradeNo] = useState('');
  207. const PAYMENT_CHECK_TIMEOUT = 5 * 60 * 1000; // 5 minutes in milliseconds
  208. const [paymentStatus, setPaymentStatus] = useState(null);
  209. const [isExpectingPayment, setIsExpectingPayment] = useState(false);
  210. const appState = useRef(AppState.currentState);
  211. const paymentInitiatedTime = useRef(null);
  212. useEffect(() => {
  213. const subscription = AppState.addEventListener('change', (nextAppState) => {
  214. if (
  215. appState.current.match(/inactive|background/) &&
  216. nextAppState === 'active' &&
  217. isExpectingPayment &&
  218. outTradeNo &&
  219. paymentInitiatedTime.current
  220. ) {
  221. const currentTime = new Date().getTime();
  222. if (currentTime - paymentInitiatedTime.current < PAYMENT_CHECK_TIMEOUT) {
  223. checkPaymentStatus();
  224. } else {
  225. // Payment check timeout reached
  226. setIsExpectingPayment(false);
  227. setOutTradeNo('');
  228. paymentInitiatedTime.current = null;
  229. Alert.alert(
  230. 'Payment Timeout',
  231. 'The payment status check has timed out. Please check your payment history.'
  232. );
  233. }
  234. }
  235. appState.current = nextAppState;
  236. });
  237. return () => {
  238. subscription.remove();
  239. };
  240. }, [outTradeNo, isExpectingPayment]);
  241. const checkPaymentStatus = async () => {
  242. try {
  243. const result = await walletService.checkPaymentStatus(outTradeNo);
  244. setPaymentStatus(result);
  245. console.log('checkPaymentStatus from walletPageComponent', result);
  246. if (result[0].respcd === '0000') {
  247. console.log(result);
  248. // Payment successful
  249. Alert.alert('Success', 'Payment was successful!', [
  250. {
  251. text: 'OK',
  252. onPress: async () => {
  253. const wallet = await walletService.getWalletBalance();
  254. setWalletBalance(wallet);
  255. console.log('new wallet:', wallet);
  256. }
  257. }
  258. ]);
  259. } else {
  260. Alert.alert('Payment Failed', 'Payment was not successful. Please try again.');
  261. }
  262. setIsExpectingPayment(false);
  263. setOutTradeNo('');
  264. paymentInitiatedTime.current = null;
  265. } catch (error) {
  266. console.error('Failed to check payment status:', error);
  267. Alert.alert('Error', 'Failed to check payment status. Please check your payment history.');
  268. }
  269. };
  270. // useEffect(() => {
  271. // const handleAppStateChange = (nextAppState) => {
  272. // if (appState.match(/inactive|background/) && nextAppState === 'active') {
  273. // console.log('App has come to the foreground!');
  274. // // Check payment status or update UI here
  275. // console.log('outTradeNo', outTradeNo);
  276. // }
  277. // setAppState(nextAppState);
  278. // };
  279. // AppState.addEventListener('change', handleAppStateChange);
  280. // }, [appState]);
  281. useEffect(() => {
  282. const fetchData = async () => {
  283. try {
  284. setLoading(true);
  285. const info = await walletService.getCustomerInfo();
  286. // const coupon = await walletService.getCouponForSpecificUser(info.id);
  287. const wallet = await walletService.getWalletBalance();
  288. console.log(wallet);
  289. setUserID(info.id);
  290. setWalletBalance(wallet);
  291. setCoupons(coupon);
  292. } catch (error) {
  293. console.log(error);
  294. } finally {
  295. setLoading(false);
  296. }
  297. };
  298. fetchData();
  299. }, []);
  300. const formatMoney = (amount: any) => {
  301. if (typeof amount !== 'number') {
  302. amount = Number(amount);
  303. }
  304. return amount.toLocaleString('en-US');
  305. };
  306. const filterPaymentOptions = (options, allowedKeys) => {
  307. return Object.fromEntries(Object.entries(options).filter(([key]) => allowedKeys.includes(key)));
  308. };
  309. useEffect(() => {
  310. const fetchPaymentType = async () => {
  311. const response = await walletService.selectPaymentType();
  312. console.log('response', response);
  313. const filteredPaymentTypes = filterPaymentOptions(response, ['union_pay_wap_payment', 'payme_wap_payment']);
  314. setPaymentType(filteredPaymentTypes);
  315. };
  316. fetchPaymentType();
  317. }, []);
  318. const handleTopUp = (selectedValue) => {
  319. setSelectedPaymentType(selectedValue);
  320. setModalVisible(false);
  321. setAmountModalVisible(true);
  322. };
  323. const handleAmountConfirm = async (inputAmount) => {
  324. setAmountModalVisible(false);
  325. try {
  326. const numericAmount = parseFloat(inputAmount);
  327. if (isNaN(numericAmount) || numericAmount <= 0) {
  328. throw new Error('Invalid amount');
  329. }
  330. const response = await walletService.submitPaymentAfterSelectingType(
  331. numericAmount,
  332. selectedPaymentType,
  333. 'test'
  334. );
  335. setOutTradeNo(response.out_trade_no);
  336. console.log('handleAmountConfirm outtradeno here,', response.out_trade_no);
  337. setIsExpectingPayment(true);
  338. paymentInitiatedTime.current = new Date().getTime();
  339. const payUrl = response.pay_url;
  340. const supported = await Linking.canOpenURL(payUrl);
  341. if (supported) {
  342. await Linking.openURL(payUrl);
  343. } else {
  344. throw new Error("Can't open payment URL");
  345. }
  346. } catch (error) {
  347. console.error('Top-up failed:', error);
  348. Alert.alert('Error', 'Failed to process top-up. Please try again.');
  349. }
  350. };
  351. return (
  352. <SafeAreaView className="flex-1 bg-white" edges={['top', 'right', 'left']}>
  353. <ScrollView className="flex-1 ">
  354. <View className="flex-1 mx-[5%]">
  355. <View style={{ marginTop: 25 }}>
  356. <Pressable
  357. onPress={() => {
  358. if (router.canGoBack()) {
  359. router.back();
  360. } else {
  361. router.replace('/accountMainPage');
  362. }
  363. }}
  364. >
  365. <CrossLogoSvg />
  366. </Pressable>
  367. <Text style={{ fontSize: 45, marginVertical: 25 }}>錢包</Text>
  368. </View>
  369. <View>
  370. <ImageBackground
  371. className="flex-col-reverse shadow-lg"
  372. style={{ height: 200 }}
  373. source={require('../../assets/walletCard1.png')}
  374. resizeMode="contain"
  375. >
  376. <View className="mx-[5%] pb-6">
  377. <Text className="text-white text-xl">餘額 (HKD)</Text>
  378. <View className="flex-row items-center justify-between ">
  379. <Text style={{ fontSize: 52 }} className=" text-white font-bold">
  380. {loading ? (
  381. <View className="items-center justify-center">
  382. <ActivityIndicator />
  383. </View>
  384. ) : (
  385. <>
  386. <Text>$</Text>
  387. {formatMoney(walletBalance)}
  388. </>
  389. )}
  390. </Text>
  391. <Pressable
  392. className="rounded-2xl items-center justify-center p-3 px-5 pr-6 "
  393. style={{
  394. backgroundColor: 'rgba(231, 242, 248, 0.2)'
  395. }}
  396. onPress={() => {
  397. console.log('增值');
  398. setModalVisible(true);
  399. }}
  400. >
  401. <Text className="text-white font-bold">+ 增值</Text>
  402. </Pressable>
  403. </View>
  404. </View>
  405. </ImageBackground>
  406. </View>
  407. <View className="flex-row-reverse mt-2 mb-6">
  408. <Pressable
  409. onPress={() => {
  410. router.push({
  411. pathname: '/paymentRecord',
  412. params: { walletBalance: formatMoney(walletBalance) }
  413. });
  414. }}
  415. >
  416. <Text className="text-[#02677D] text-lg underline">付款記錄</Text>
  417. </Pressable>
  418. </View>
  419. </View>
  420. <View className="w-full h-1 bg-[#DBE4E8]" />
  421. <View className="flex-row justify-between mx-[5%] pt-6 pb-3">
  422. <Text className="text-xl">優惠券</Text>
  423. <Pressable onPress={() => router.push('couponPage')}>
  424. <Text className="text-xl text-[#888888]">顯示所有</Text>
  425. </Pressable>
  426. </View>
  427. <View className="flex-1 flex-col mx-[5%]">
  428. {loading ? (
  429. <View className="items-center justify-center">
  430. <ActivityIndicator />
  431. </View>
  432. ) : (
  433. <View>
  434. {coupons
  435. .filter(
  436. (coupon) =>
  437. coupon.is_consumed === false && new Date(coupon.expire_date) > new Date()
  438. )
  439. .slice(0, 2)
  440. .map((coupon, index) => (
  441. <IndividualCouponComponent
  442. key={index}
  443. title={coupon.name}
  444. price={coupon.amount}
  445. detail={coupon.description}
  446. date={formatCouponDate(coupon.expire_date)}
  447. />
  448. ))}
  449. </View>
  450. )}
  451. </View>
  452. </ScrollView>
  453. <TopUpModal
  454. visible={modalVisible}
  455. onClose={() => setModalVisible(false)}
  456. onSelect={handleTopUp}
  457. paymentOptions={paymentType}
  458. />
  459. <AmountInputModal
  460. visible={amountModalVisible}
  461. onClose={() => setAmountModalVisible(false)}
  462. onConfirm={handleAmountConfirm}
  463. />
  464. </SafeAreaView>
  465. );
  466. };
  467. export default WalletPageComponent;
  468. //////BELOW uses QFPay 的托管收银台页面 to 增值
  469. //////BELOW uses QFPay 的托管收银台页面 to 增值
  470. //////BELOW uses QFPay 的托管收银台页面 to 增值
  471. //////BELOW uses QFPay 的托管收银台页面 to 增值
  472. //////BELOW uses QFPay 的托管收银台页面 to 增值
  473. // import {
  474. // View,
  475. // Image,
  476. // Text,
  477. // ScrollView,
  478. // AppState,
  479. // Pressable,
  480. // ImageBackground,
  481. // ActivityIndicator,
  482. // Modal,
  483. // Alert,
  484. // TextInput,
  485. // Linking
  486. // } from 'react-native';
  487. // import { SafeAreaView } from 'react-native-safe-area-context';
  488. // import { router } from 'expo-router';
  489. // import { CrossLogoSvg } from '../global/SVG';
  490. // import { useEffect, useRef, useState } from 'react';
  491. // import { walletService } from '../../service/walletService';
  492. // import UnionPayImage from '../../assets/unionpay.png';
  493. // import PayMeImage from '../../assets/payme.png';
  494. // import { formatCouponDate, formatDate } from '../../util/lib';
  495. // import { set } from 'date-fns';
  496. // import { reloadAppAsync } from 'expo';
  497. // import sha256 from 'crypto-js/sha256';
  498. // const AmountInputModal = ({ visible, onClose, onConfirm }) => {
  499. // const [inputAmount, setInputAmount] = useState('');
  500. // return (
  501. // <Modal animationType="fade" transparent={true} visible={visible} onRequestClose={onClose}>
  502. // <View
  503. // style={{
  504. // flex: 1,
  505. // justifyContent: 'center',
  506. // alignItems: 'center',
  507. // backgroundColor: 'rgba(0,0,0,0.5)'
  508. // }}
  509. // >
  510. // <View
  511. // style={{
  512. // backgroundColor: 'white',
  513. // padding: 20,
  514. // borderRadius: 10,
  515. // width: '80%'
  516. // }}
  517. // >
  518. // <Text style={{ fontSize: 20, marginBottom: 20 }}>輸入增值金額</Text>
  519. // <TextInput
  520. // style={{
  521. // borderWidth: 1,
  522. // borderColor: '#ccc',
  523. // borderRadius: 5,
  524. // padding: 10,
  525. // marginBottom: 20,
  526. // fontSize: 18
  527. // }}
  528. // keyboardType="numeric"
  529. // placeholder="輸入金額"
  530. // value={inputAmount}
  531. // onChangeText={setInputAmount}
  532. // />
  533. // <Pressable
  534. // onPress={() => onConfirm(inputAmount)}
  535. // style={{
  536. // backgroundColor: '#02677D',
  537. // padding: 10,
  538. // borderRadius: 5,
  539. // alignItems: 'center'
  540. // }}
  541. // >
  542. // <Text style={{ color: 'white', fontSize: 18 }}>確認</Text>
  543. // </Pressable>
  544. // <Pressable onPress={onClose} style={{ padding: 10, alignItems: 'center', marginTop: 10 }}>
  545. // <Text style={{ color: 'red' }}>取消</Text>
  546. // </Pressable>
  547. // </View>
  548. // </View>
  549. // </Modal>
  550. // );
  551. // };
  552. // export const IndividualCouponComponent = ({
  553. // title,
  554. // price,
  555. // detail,
  556. // date
  557. // }: {
  558. // title: string;
  559. // price: string;
  560. // detail: string;
  561. // date: string;
  562. // }) => {
  563. // return (
  564. // <Pressable onPress={() => console.log('abc')}>
  565. // <View className="bg-[#e7f2f8] h-[124px] rounded-xl flex-row mb-3">
  566. // <View className="bg-white mx-3 my-3 w-[28%] rounded-xl">
  567. // <View className="flex-row justify-center items-center pr-4 pt-4 ">
  568. // <Text className="color-[#02677d] text-2xl pl-2 pr-1">$</Text>
  569. // <Text className="color-[#02677d] text-3xl font-bold" adjustsFontSizeToFit={true}>
  570. // {price}
  571. // </Text>
  572. // </View>
  573. // <View className="items-center justify-center">
  574. // <Text className="text-base mt-1">{title}</Text>
  575. // </View>
  576. // </View>
  577. // {/* //dash line */}
  578. // <View style={{ overflow: 'hidden' }}>
  579. // <View
  580. // style={{
  581. // borderStyle: 'dashed',
  582. // borderWidth: 1,
  583. // borderColor: '#CCCCCC',
  584. // margin: -1,
  585. // width: 0,
  586. // marginRight: 0,
  587. // height: '100%'
  588. // }}
  589. // >
  590. // <View style={{ height: 60 }}></View>
  591. // </View>
  592. // </View>
  593. // <View className="flex-col flex-1 m-[5%] justify-center ">
  594. // <Text className="text-lg">{title}</Text>
  595. // <Text className="color-[#888888] text-sm">{detail}</Text>
  596. // <View className="flex-row items-center ">
  597. // <Text className="text-base">有效期 </Text>
  598. // <Text className="text-base font-bold text-[#02677d]">{date}</Text>
  599. // </View>
  600. // </View>
  601. // </View>
  602. // </Pressable>
  603. // );
  604. // };
  605. // const WalletPageComponent = () => {
  606. // const [walletBalance, setWalletBalance] = useState<string | null>(null);
  607. // const [loading, setLoading] = useState<boolean>(false);
  608. // const [modalVisible, setModalVisible] = useState(false);
  609. // const [coupons, setCoupons] = useState([]);
  610. // const [paymentType, setPaymentType] = useState({});
  611. // const [userID, setUserID] = useState('');
  612. // const [selectedPaymentType, setSelectedPaymentType] = useState<string | null>(null);
  613. // const [amount, setAmount] = useState<number>(0);
  614. // const [amountModalVisible, setAmountModalVisible] = useState(false);
  615. // const [outTradeNo, setOutTradeNo] = useState('');
  616. // const PAYMENT_CHECK_TIMEOUT = 5 * 60 * 1000; // 5 minutes in milliseconds
  617. // const [paymentStatus, setPaymentStatus] = useState(null);
  618. // const [isExpectingPayment, setIsExpectingPayment] = useState(false);
  619. // const appState = useRef(AppState.currentState);
  620. // const paymentInitiatedTime = useRef(null);
  621. // useEffect(() => {
  622. // const subscription = AppState.addEventListener('change', (nextAppState) => {
  623. // if (
  624. // appState.current.match(/inactive|background/) &&
  625. // nextAppState === 'active' &&
  626. // isExpectingPayment &&
  627. // // outTradeNo &&
  628. // paymentInitiatedTime.current
  629. // ) {
  630. // const currentTime = new Date().getTime();
  631. // if (currentTime - paymentInitiatedTime.current < PAYMENT_CHECK_TIMEOUT) {
  632. // checkPaymentStatus();
  633. // } else {
  634. // // Payment check timeout reached
  635. // setIsExpectingPayment(false);
  636. // setOutTradeNo('');
  637. // paymentInitiatedTime.current = null;
  638. // Alert.alert(
  639. // 'Payment Timeout',
  640. // 'The payment status check has timed out. Please check your payment history.'
  641. // );
  642. // }
  643. // }
  644. // appState.current = nextAppState;
  645. // });
  646. // return () => {
  647. // subscription.remove();
  648. // };
  649. // }, [outTradeNo, isExpectingPayment]);
  650. // const checkPaymentStatus = async () => {
  651. // try {
  652. // console.log('what is the outTradeNo?? ', outTradeNo);
  653. // const result = await walletService.checkPaymentStatus(outTradeNo);
  654. // setPaymentStatus(result);
  655. // console.log('checkPaymentStatus from walletPageComponent', result);
  656. // if (result) {
  657. // // Payment successful
  658. // Alert.alert('Success', 'Payment was successful!', [
  659. // {
  660. // text: '成功',
  661. // onPress: async () => {
  662. // const wallet = await walletService.getWalletBalance();
  663. // setWalletBalance(wallet);
  664. // console.log('new wallet:', wallet);
  665. // }
  666. // }
  667. // ]);
  668. // } else {
  669. // Alert.alert('Payment Failed', 'Payment was not successful. Please try again.');
  670. // }
  671. // setIsExpectingPayment(false);
  672. // setOutTradeNo('');
  673. // paymentInitiatedTime.current = null;
  674. // } catch (error) {
  675. // console.error('Failed to check payment status:', error);
  676. // Alert.alert('Error', 'Failed to check payment status. Please check your payment history.');
  677. // }
  678. // };
  679. // useEffect(() => {
  680. // const fetchData = async () => {
  681. // try {
  682. // setLoading(true);
  683. // const info = await walletService.getCustomerInfo();
  684. // const wallet = await walletService.getWalletBalance();
  685. // console.log(wallet);
  686. // setUserID(info.id);
  687. // setWalletBalance(wallet);
  688. // setCoupons(coupon);
  689. // } catch (error) {
  690. // console.log(error);
  691. // } finally {
  692. // setLoading(false);
  693. // }
  694. // };
  695. // fetchData();
  696. // }, []);
  697. // const formatMoney = (amount: any) => {
  698. // if (typeof amount !== 'number') {
  699. // amount = Number(amount);
  700. // }
  701. // return amount.toLocaleString('en-US');
  702. // };
  703. // const filterPaymentOptions = (options, allowedKeys) => {
  704. // return Object.fromEntries(Object.entries(options).filter(([key]) => allowedKeys.includes(key)));
  705. // };
  706. // function formatTime(utcTimeString) {
  707. // // Parse the UTC time string
  708. // const date = new Date(utcTimeString);
  709. // // Add 8 hours
  710. // date.setHours(date.getHours());
  711. // // Format the date
  712. // const year = date.getFullYear();
  713. // const month = String(date.getMonth() + 1).padStart(2, '0');
  714. // const day = String(date.getDate()).padStart(2, '0');
  715. // const hours = String(date.getHours()).padStart(2, '0');
  716. // const minutes = String(date.getMinutes()).padStart(2, '0');
  717. // const seconds = String(date.getSeconds()).padStart(2, '0');
  718. // // Return the formatted string
  719. // return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  720. // }
  721. // useEffect(() => {
  722. // const fetchPaymentType = async () => {
  723. // const response = await walletService.selectPaymentType();
  724. // // console.log('response', response);
  725. // const filteredPaymentTypes = filterPaymentOptions(response, ['union_pay_wap_payment', 'payme_wap_payment']);
  726. // setPaymentType(filteredPaymentTypes);
  727. // };
  728. // fetchPaymentType();
  729. // }, []);
  730. // const handleAmountConfirm = async (inputAmount) => {
  731. // setAmountModalVisible(false);
  732. // try {
  733. // const response = await walletService.getOutTradeNo();
  734. // console.log('do i have outtrade no??', response);
  735. // if (response) {
  736. // setOutTradeNo(response);
  737. // setIsExpectingPayment(true);
  738. // paymentInitiatedTime.current = new Date().getTime();
  739. // const now = new Date();
  740. // const formattedTime = formatTime(now);
  741. // console.log(formattedTime);
  742. // const out_trade_no = response;
  743. // let amount = parseInt(inputAmount, 10) * 100;
  744. // const origin = 'https://openapi-hk.qfapi.com/checkstand/#/?';
  745. // const obj = {
  746. // appcode: 'F8A0AC83C61A40D3840CA9FA930C5D66',
  747. // goods_name: 'Crazy Charge 錢包增值',
  748. // out_trade_no: response,
  749. // paysource: 'crazycharge_checkout',
  750. // return_url: 'https://www.google.com',
  751. // failed_url: 'https://www.google.com',
  752. // notify_url: 'https://api.crazycharge.com.hk/api/v1/clients/qfpay/webhook',
  753. // sign_type: 'sha256',
  754. // txamt: amount,
  755. // txcurrcd: 'HKD',
  756. // txdtm: formattedTime
  757. // };
  758. // const paramStringify = (json, flag?) => {
  759. // let str = '';
  760. // let keysArr = Object.keys(json);
  761. // keysArr.sort().forEach((val) => {
  762. // if (!json[val]) return;
  763. // str += `${val}=${flag ? encodeURIComponent(json[val]) : json[val]}&`;
  764. // });
  765. // return str.slice(0, -1);
  766. // };
  767. // const api_key = '5BA7EC74416D4146B9C4E9D71A84D930';
  768. // const params = paramStringify(obj);
  769. // const sign = sha256(`${params}${api_key}`).toString();
  770. // const url = `${origin}${paramStringify(obj, true)}&sign=${sign}`;
  771. // try {
  772. // console.log(url);
  773. // const supported = await Linking.canOpenURL(url);
  774. // if (supported) {
  775. // await Linking.openURL(url);
  776. // } else {
  777. // Alert.alert('錯誤', '請稍後再試');
  778. // }
  779. // } catch (error) {
  780. // console.error('Top-up failed:', error);
  781. // Alert.alert('Error', 'Failed to process top-up. Please try again.');
  782. // }
  783. // } else {
  784. // console.log('no');
  785. // }
  786. // } catch (error) {
  787. // console.log(error);
  788. // }
  789. // };
  790. // return (
  791. // <SafeAreaView className="flex-1 bg-white" edges={['top', 'right', 'left']}>
  792. // <ScrollView className="flex-1 ">
  793. // <View className="flex-1 mx-[5%]">
  794. // <View style={{ marginTop: 25 }}>
  795. // <Pressable
  796. // onPress={() => {
  797. // if (router.canGoBack()) {
  798. // router.back();
  799. // } else {
  800. // router.replace('/accountMainPage');
  801. // }
  802. // }}
  803. // >
  804. // <CrossLogoSvg />
  805. // </Pressable>
  806. // <Text style={{ fontSize: 45, marginVertical: 25 }}>錢包</Text>
  807. // </View>
  808. // <View>
  809. // <ImageBackground
  810. // className="flex-col-reverse shadow-lg"
  811. // style={{ height: 200 }}
  812. // source={require('../../assets/walletCard1.png')}
  813. // resizeMode="contain"
  814. // >
  815. // <View className="mx-[5%] pb-6">
  816. // <Text className="text-white text-xl">餘額 (HKD)</Text>
  817. // <View className="flex-row items-center justify-between ">
  818. // <Text style={{ fontSize: 52 }} className=" text-white font-bold">
  819. // {loading ? (
  820. // <View className="items-center justify-center">
  821. // <ActivityIndicator />
  822. // </View>
  823. // ) : (
  824. // <>
  825. // <Text>$</Text>
  826. // {formatMoney(walletBalance)}
  827. // </>
  828. // )}
  829. // </Text>
  830. // <Pressable
  831. // className="rounded-2xl items-center justify-center p-3 px-5 pr-6 "
  832. // style={{
  833. // backgroundColor: 'rgba(231, 242, 248, 0.2)'
  834. // }}
  835. // onPress={() => {
  836. // console.log('增值');
  837. // setAmountModalVisible(true);
  838. // }}
  839. // >
  840. // <Text className="text-white font-bold">+ 增值</Text>
  841. // </Pressable>
  842. // </View>
  843. // </View>
  844. // </ImageBackground>
  845. // </View>
  846. // <View className="flex-row-reverse mt-2 mb-6">
  847. // <Pressable
  848. // onPress={() => {
  849. // router.push({
  850. // pathname: '/paymentRecord',
  851. // params: { walletBalance: formatMoney(walletBalance) }
  852. // });
  853. // }}
  854. // >
  855. // <Text className="text-[#02677D] text-lg underline">付款記錄</Text>
  856. // </Pressable>
  857. // </View>
  858. // </View>
  859. // <View className="w-full h-1 bg-[#DBE4E8]" />
  860. // <View className="flex-row justify-between mx-[5%] pt-6 pb-3">
  861. // <Text className="text-xl">優惠券</Text>
  862. // <Pressable onPress={() => router.push('couponPage')}>
  863. // <Text className="text-xl text-[#888888]">顯示所有</Text>
  864. // </Pressable>
  865. // </View>
  866. // <View className="flex-1 flex-col mx-[5%]">
  867. // {loading ? (
  868. // <View className="items-center justify-center">
  869. // <ActivityIndicator />
  870. // </View>
  871. // ) : (
  872. // <View>
  873. // {coupons
  874. // .filter(
  875. // (coupon) =>
  876. // coupon.is_consumed === false && new Date(coupon.expire_date) > new Date()
  877. // )
  878. // .slice(0, 2)
  879. // .map((coupon, index) => (
  880. // <IndividualCouponComponent
  881. // key={index}
  882. // title={coupon.name}
  883. // price={coupon.amount}
  884. // detail={coupon.description}
  885. // date={formatCouponDate(coupon.expire_date)}
  886. // />
  887. // ))}
  888. // </View>
  889. // )}
  890. // </View>
  891. // </ScrollView>
  892. // {/* <TopUpModal
  893. // visible={modalVisible}
  894. // onClose={() => setModalVisible(false)}
  895. // onSelect={handleTopUp}
  896. // paymentOptions={paymentType}
  897. // /> */}
  898. // <AmountInputModal
  899. // visible={amountModalVisible}
  900. // onClose={() => setAmountModalVisible(false)}
  901. // onConfirm={handleAmountConfirm}
  902. // />
  903. // </SafeAreaView>
  904. // );
  905. // };
  906. // export default WalletPageComponent;