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;