paymentSummaryPageComponent.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // component/chargingPage/paymentSummaryPageComponent.tsx
  2. import { View, Text, ScrollView, StyleSheet, Pressable } from 'react-native';
  3. import { SafeAreaView } from 'react-native-safe-area-context';
  4. import { Alert } from 'react-native';
  5. import NormalButton from '../global/normal_button';
  6. import { router, useLocalSearchParams } from 'expo-router';
  7. import { GrayRightArrowIconSvg, RightArrowIconSvg, TickLogoSvg } from '../global/SVG';
  8. import { useEffect, useRef, useState } from 'react';
  9. import useBookingStore from '../../providers/booking_store';
  10. import useCouponStore from '../../providers/coupon_store';
  11. import { walletService } from '../../service/walletService';
  12. import { parseISO, subHours, format } from 'date-fns';
  13. import { chargeStationService } from '../../service/chargeStationService';
  14. import { useTranslation } from '../../util/hooks/useTranslation';
  15. const PaymentSummaryPageComponent = () => {
  16. const { t } = useTranslation();
  17. const selectedCouponName = useCouponStore((state) => state.selectedCouponName);
  18. const selectedCouponRedeemCode = useCouponStore((state) => state.selectedCouponRedeemCode);
  19. const selectedCouponPrice = useCouponStore((state) => state.selectedCouponPrice);
  20. const params = useLocalSearchParams();
  21. const setBookingInfo = useBookingStore((state) => state.setBookingInfo);
  22. const initialSetupDone = useRef(false);
  23. const [selectedCar, setSelectedCar] = useState('');
  24. const [formatOrderId, setFormatOrderId] = useState('');
  25. useEffect(() => {
  26. if (!initialSetupDone.current && Object.keys(params).length > 0) {
  27. const bookingInfo = {
  28. bookTime: params.bookTime as string,
  29. endTime: params.endTime as string,
  30. date: params.date as string,
  31. carID: params.carID as string,
  32. chargingWatt: params.chargingWatt as string,
  33. connectorID: params.connectorID as string,
  34. price: params.price as string,
  35. stationID: params.stationID as string,
  36. user: params.userID as string,
  37. paymentFee: params.paymentFee as string,
  38. carCapacitance: params.carCapacitance as string
  39. };
  40. setBookingInfo(bookingInfo);
  41. console.log('Booking info stored:', bookingInfo);
  42. initialSetupDone.current = true;
  43. }
  44. }, [params, setBookingInfo]);
  45. useEffect(() => {
  46. const fetchDefaultCar = async () => {
  47. try {
  48. const response = await chargeStationService.getUserDefaultCars();
  49. if (response) {
  50. console.log('default car', response.data.id);
  51. setSelectedCar(response.data.id);
  52. }
  53. } catch (error) {
  54. console.log(error);
  55. }
  56. };
  57. fetchDefaultCar();
  58. }, []);
  59. const { bookTime, date, carID, chargingWatt, connectorID, price, stationID, user, paymentFee, carCapacitance } =
  60. useBookingStore();
  61. const calculateFinalFee = (): string => {
  62. console.log('selected coupon price:', selectedCouponPrice);
  63. if (selectedCouponRedeemCode && selectedCouponPrice) {
  64. const difference = parseFloat(paymentFee) - parseFloat(selectedCouponPrice);
  65. return Math.max(0, difference).toFixed(2);
  66. } else {
  67. return paymentFee;
  68. }
  69. };
  70. const finalFee = calculateFinalFee();
  71. const total_power = chargingWatt === '' ? carCapacitance : parseFloat(chargingWatt.split('~')[0]);
  72. const promotion_code = selectedCouponRedeemCode || '';
  73. const total_fee = parseInt(finalFee, 10);
  74. function convertToUTC(date: string, time: string): Date {
  75. const [year, month, day] = date.split('-').map(Number);
  76. if (isNaN(year) || isNaN(month) || isNaN(day)) {
  77. console.error('Invalid date format:', date);
  78. return new Date(NaN); // Return invalid date
  79. }
  80. const [hours, minutes] = time.split(':').map(Number);
  81. if (isNaN(hours) || isNaN(minutes)) {
  82. console.error('Invalid time format:', time);
  83. return new Date(NaN); // Return invalid date
  84. }
  85. // Create a date in local time
  86. const localDate = new Date(year, month - 1, day, hours, minutes);
  87. // Convert to UTC
  88. const utcDate = new Date(localDate.toUTCString());
  89. return utcDate;
  90. }
  91. const handleSubmitPayment = async () => {
  92. console.log('hi');
  93. let type = 'reservation';
  94. let is_ic_call = false;
  95. const utcBookTime = convertToUTC(params.date as string, params.bookTime as string);
  96. const utcEndTime = convertToUTC(params.date as string, params.endTime as string);
  97. if (isNaN(utcBookTime.getTime()) || isNaN(utcEndTime.getTime())) {
  98. console.error('Invalid date or time');
  99. // Handle the error appropriately, maybe show an alert to the user
  100. return;
  101. }
  102. try {
  103. const response = await walletService.submitPayment(
  104. stationID,
  105. connectorID,
  106. user,
  107. utcBookTime,
  108. utcEndTime,
  109. total_power,
  110. total_fee,
  111. promotion_code,
  112. selectedCar,
  113. type,
  114. is_ic_call
  115. );
  116. if (response.status === 200 || response.status === 201) {
  117. router.push({
  118. pathname: '/paymentFinishPage',
  119. params: { formatOrderId: response.data.format_order_id }
  120. });
  121. } else if (response.status === 400) {
  122. console.log('400 error in paymentSummaryPageComponent');
  123. Alert.alert(t('payment_summary.insufficient_balance_title'), t('payment_summary.insufficient_balance_message'));
  124. } else {
  125. console.log('submit payment failed:', response);
  126. }
  127. } catch (error) {
  128. console.log('submit payment error:', error);
  129. }
  130. };
  131. return (
  132. <SafeAreaView className="flex-1 bg-white" edges={['top', 'left', 'right']}>
  133. <ScrollView className="flex-1 mx-[5%]" showsVerticalScrollIndicator={false}>
  134. <View style={{ marginTop: 25 }}>
  135. <Text style={{ fontSize: 45, paddingBottom: 12 }}>{t('payment_summary.title')}</Text>
  136. <View className="flex-column">
  137. <Pressable onPress={() => router.push('selectCouponPage')}>
  138. <Text className="text-lg pb-4">{t('payment_summary.coupon')}</Text>
  139. {selectedCouponName === '' ? (
  140. <View
  141. style={{
  142. borderWidth: 1,
  143. padding: 20,
  144. borderRadius: 12,
  145. borderColor: '#bbbbbb'
  146. }}
  147. className="rounded-xl h-[9vh] items-center flex-row pl-6 justify-between"
  148. >
  149. <View className="flex-row items-center ">
  150. <Text className="color-[#999999] px-4 text-base">{t('payment_summary.select_coupon')}</Text>
  151. </View>
  152. <View className="pr-4">
  153. <GrayRightArrowIconSvg />
  154. </View>
  155. </View>
  156. ) : (
  157. <View className="bg-[#e9f2f7] rounded-xl h-[9vh] items-center flex-row pl-6 justify-between">
  158. <View className="flex-row items-center ">
  159. <TickLogoSvg />
  160. <Text className="color-[#34667c] px-4 text-base">{selectedCouponName}</Text>
  161. </View>
  162. <View className="pr-4">
  163. <RightArrowIconSvg />
  164. </View>
  165. </View>
  166. )}
  167. </Pressable>
  168. </View>
  169. <View>
  170. <Text className="text-xl py-4">{t('payment_summary.fee_summary')}</Text>
  171. <View className="flex-row justify-between">
  172. <Text className="text-base">{t('payment_summary.charging_fee')}</Text>
  173. <Text className="text-base">HK ${finalFee}</Text>
  174. </View>
  175. {chargingWatt === '' ? (
  176. <Text style={styles.grayColor} className="text-base">
  177. {t('payment_summary.estimated_full_charge')}
  178. </Text>
  179. ) : (
  180. <Text style={styles.grayColor} className="text-base">
  181. {t('payment_summary.settled_per_kwh')}{chargingWatt?.split('~')[0]}
  182. </Text>
  183. )}
  184. <View className="h-0.5 my-3 bg-[#f4f4f4]" />
  185. <View className="flex-row justify-between ">
  186. <Text className="text-xl">{t('payment_summary.total')}</Text>
  187. <Text className="text-3xl">
  188. HK$
  189. {finalFee}
  190. </Text>
  191. </View>
  192. <View className="mt-4 ">
  193. <NormalButton
  194. title={
  195. <Text
  196. style={{
  197. color: 'white',
  198. fontSize: 16,
  199. fontWeight: '800'
  200. }}
  201. >
  202. {t('payment_summary.proceed_to_payment')}
  203. </Text>
  204. }
  205. onPress={
  206. // () => router.push('/paymentFinishPage')
  207. handleSubmitPayment
  208. }
  209. extendedStyle={{ padding: 24 }}
  210. />
  211. </View>
  212. <View className="h-8" />
  213. </View>
  214. </View>
  215. </ScrollView>
  216. </SafeAreaView>
  217. );
  218. };
  219. export default PaymentSummaryPageComponent;
  220. const styles = StyleSheet.create({
  221. grayColor: {
  222. color: '#888888'
  223. },
  224. greenColor: {
  225. color: '#02677D'
  226. }
  227. });