diff --git a/app/payments/Confirmation.tsx b/app/payments/Confirmation.tsx index 17e9921..3fc5dbf 100644 --- a/app/payments/Confirmation.tsx +++ b/app/payments/Confirmation.tsx @@ -1,23 +1,127 @@ import React from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; import { useRouter } from "expo-router"; +import { useSelector } from "react-redux"; +import { RootState } from "@/store/rootReducer"; +import Header from "@/components/common/Header"; +import CheckCircle from "@/assets/icons/check_circle.svg"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; const PaymentConfirmationScreen = () => { const router = useRouter(); + const { paymentOrder, due_amount } = useSelector( + (state: RootState) => state.payments + ); + + const insets = useSafeAreaInsets(); + + // Format amount with currency + const formatAmount = (amount: number | null) => { + if (!amount) return "₹0"; + return `₹${amount.toLocaleString("en-IN")}`; + }; + + // Format date + const formatDate = (dateString?: string) => { + if (!dateString) + return new Date().toLocaleString("en-IN", { + day: "numeric", + month: "long", + year: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: true, + weekday: "long", + }); + + return new Date(dateString).toLocaleString("en-IN", { + day: "numeric", + month: "long", + year: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: true, + weekday: "long", + }); + }; return ( - Payment Successful! - - Thank you for your payment. Your transaction has been completed. - +
- router.push("/dashboard")} // Or wherever you want to go - > - Go to Dashboard - + + + + + + + + {formatAmount(paymentOrder?.amount || due_amount)} + + + + Payment successful + + {formatDate(paymentOrder?.transaction_date)} + + + + + + + + Transaction Details + + + Payment mode + + {paymentOrder?.payment_mode?.[0] || "UPI"} + + + + Paid to + + {paymentOrder?.upi_handle || "randomupiid@vecpay"} + + + + + Paid by + + {paymentOrder?.paid_by_upi_handle || "amar.kesari@vecpay"} + + + + + Order ID + + {paymentOrder?.order_id || "1000516861984940"} + + + + + Transaction ID + + {paymentOrder?.transaction_id || + paymentOrder?.transaction_order_id || + "1000516861984940"} + + + + RRN + + {paymentOrder?.payment_reference_id || "1000516861984940"} + + + + + router.replace("/(tabs)/payments")} + > + OK + + ); }; @@ -27,32 +131,92 @@ export default PaymentConfirmationScreen; const styles = StyleSheet.create({ container: { flex: 1, + backgroundColor: "#f3f4f6", // Light gray background + }, + contentFrame: { + flex: 1, + paddingHorizontal: 16, + paddingBottom: 16, + justifyContent: "space-between", + }, + qrFrame: { + backgroundColor: "#fcfcfc", + borderRadius: 8, + padding: 16, + paddingVertical: 32, + }, + paymentStatusContainer: { + alignItems: "center", + }, + successIconContainer: { + marginBottom: 16, + alignItems: "center", + }, + successIcon: { + // Icon styling handled by Ionicons + }, + amountText: { + fontSize: 20, + fontWeight: "600", + color: "#252a34", + textAlign: "center", + marginBottom: 16, + }, + statusContainer: { + alignItems: "center", + gap: 4, + }, + statusText: { + fontSize: 14, + color: "#252a34", + textAlign: "center", + }, + dateText: { + fontSize: 14, + color: "#252a34", + textAlign: "center", + }, + divider: { + height: 1, + backgroundColor: "#e5e9f0", + marginVertical: 24, + }, + transactionContainer: { + gap: 8, + }, + sectionHeader: { + fontSize: 14, + fontWeight: "600", + color: "#252a34", + }, + detailRow: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "flex-start", + paddingVertical: 2, + }, + detailLabel: { + fontSize: 14, + color: "#252a34", + flex: 1, + }, + detailValue: { + fontSize: 14, + fontWeight: "600", + color: "#252a34", + textAlign: "left", + flex: 1, + }, + primaryButton: { + backgroundColor: "#008761", + height: 40, + borderRadius: 4, justifyContent: "center", alignItems: "center", - padding: 24, - backgroundColor: "#f0f4f7", - }, - title: { - fontSize: 28, - fontWeight: "bold", - marginBottom: 12, - color: "#2e7d32", - }, - message: { - fontSize: 16, - textAlign: "center", - marginBottom: 30, - color: "#555", - }, - button: { - backgroundColor: "#2e7d32", - paddingVertical: 14, - paddingHorizontal: 30, - borderRadius: 8, }, buttonText: { - color: "white", - fontSize: 16, - fontWeight: "600", + color: "#fcfcfc", + fontSize: 14, + fontWeight: "500", }, }); diff --git a/app/payments/payEmi.tsx b/app/payments/payEmi.tsx index 312cc6e..1d58361 100644 --- a/app/payments/payEmi.tsx +++ b/app/payments/payEmi.tsx @@ -16,7 +16,7 @@ import * as Sharing from "expo-sharing"; import { Image } from "expo-image"; import { useSelector, useDispatch } from "react-redux"; import { RootState } from "@/store"; -import { updatePaymentStatus } from "@/store/paymentSlice"; +import { setPaymentOrder, updatePaymentStatus } from "@/store/paymentSlice"; import Header from "@/components/common/Header"; import ShareIcon from "@/assets/icons/share.svg"; import { useSafeAreaInsets } from "react-native-safe-area-context"; @@ -34,83 +34,74 @@ const UpiPaymentScreen = () => { ); const router = useRouter(); - const { onPaymentConfirmation, offPaymentConfirmation } = useSocket(); - const [isListening, setIsListening] = useState(false); + const { onPaymentConfirmation, offPaymentConfirmation, disconnect } = + useSocket(); const { showSnackbar } = useSnackbar(); const insets = useSafeAreaInsets(); useEffect(() => { + let backPressCount = 0; + let backPressTimer: NodeJS.Timeout | null = null; const handlePaymentConfirmation = (data: any) => { console.log("Payment confirmation received:", data); - Alert.alert( - "Payment Successful!", - "Your payment has been confirmed successfully.", - [ - { - text: "Continue", - onPress: () => { - router.replace("/payments/Confirmation"); - }, - }, - ], - { cancelable: false } + dispatch( + setPaymentOrder({ + ...paymentOrder, + ...data, + }) ); + + offPaymentConfirmation(); + disconnect(); + + router.replace("/payments/Confirmation"); }; onPaymentConfirmation(handlePaymentConfirmation); - setIsListening(true); const backAction = () => { - Alert.alert( - "Cancel Payment?", - "Are you sure you want to cancel this payment?", - [ - { - text: "No", - onPress: () => null, - style: "cancel", - }, - { - text: "Yes", - onPress: () => { - offPaymentConfirmation(); - router.back(); - }, - }, - ] - ); - return true; + if (backPressCount === 0) { + backPressCount++; + console.log("Press back again to cancel payment"); + showSnackbar("Press back again to cancel payment", "success"); + + backPressTimer = setTimeout(() => { + backPressCount = 0; + }, 2000); + + return true; + } else { + if (backPressTimer) clearTimeout(backPressTimer); + offPaymentConfirmation(); + disconnect(); + router.back(); + return true; + } }; const backHandler = BackHandler.addEventListener( "hardwareBackPress", backAction ); - - // Cleanup on unmount return () => { offPaymentConfirmation(); - setIsListening(false); backHandler.remove(); }; }, [onPaymentConfirmation, offPaymentConfirmation, router]); - // Format amount with currency symbol const formatAmount = (amount: number): string => { return `₹${amount.toLocaleString("en-IN")}`; }; - // Check if payment is expired const isPaymentExpired = (): boolean => { const expiryDate = new Date(paymentOrder.expiry_date); const currentDate = new Date(); return currentDate > expiryDate; }; - // Get formatted expiry time const getExpiryTime = (): string => { const expiryDate = new Date(paymentOrder.expiry_date); return expiryDate.toLocaleString("en-IN"); diff --git a/app/payments/selectAmount.tsx b/app/payments/selectAmount.tsx index 442aaba..4f6ab45 100644 --- a/app/payments/selectAmount.tsx +++ b/app/payments/selectAmount.tsx @@ -140,6 +140,8 @@ const SelectAmountScreen = () => { initialValues={initialValues} validationSchema={validationSchema} onSubmit={handleSubmit} + validateOnChange={true} + validateOnBlur={true} > {({ values, @@ -169,7 +171,19 @@ const SelectAmountScreen = () => { const isPayButtonEnabled = () => { if (values.paymentType === "due") return true; - return isValid && dirty && values.customAmount; + + // For custom amount, check if it's valid and meets minimum requirement + if (values.paymentType === "custom") { + const amount = parseFloat(values.customAmount); + return ( + values.customAmount && + !isNaN(amount) && + amount >= payments.MIN_AMOUNT && + !errors.customAmount + ); + } + + return false; }; return ( diff --git a/assets/icons/check_circle.svg b/assets/icons/check_circle.svg new file mode 100644 index 0000000..980c3a2 --- /dev/null +++ b/assets/icons/check_circle.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/store/paymentSlice.ts b/store/paymentSlice.ts index f6b334b..9991d75 100644 --- a/store/paymentSlice.ts +++ b/store/paymentSlice.ts @@ -2,15 +2,20 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { MyPlan } from "@/app/(tabs)/payments"; -// Define the payment order interface based on your API response interface PaymentOrder { - amount: number; - expiry_date: string; + amount: number; // kept as number for math + id?: number; // optional if not always present + expiry_date?: string; order_id: string; - ppayment_link: string; - qr_code_url: string; - status: string; - transaction_id: string; + ppayment_link?: string; + qr_code_url?: string; + status: string; // "confirmed", "pending", etc. + transaction_id?: string; + transaction_order_id?: string; + transaction_date?: string; + payment_mode?: string[]; // e.g. ["UPI"] + payment_reference_id?: string; + paid_by_upi_handle?: string; upi_handle: string; }