Handle Payment Confirmation

feature/app-setup
vinay kumar 2025-08-13 15:20:30 +05:30
parent 5b89bebe0b
commit 1ef44669e1
6 changed files with 82 additions and 102 deletions

View File

@ -72,7 +72,6 @@ interface PaymentHistoryResponse {
} }
const formatPaymentDate = (dateString: string) => { const formatPaymentDate = (dateString: string) => {
console.log(dateString.split(",")[0], "datestring");
try { try {
return dateString.split(",")[0]; return dateString.split(",")[0];
} catch { } catch {
@ -82,7 +81,6 @@ const formatPaymentDate = (dateString: string) => {
// Format time for payment history // Format time for payment history
const formatPaymentTime = (dateString: string) => { const formatPaymentTime = (dateString: string) => {
console.log(dateString, "formattime");
try { try {
// Expected: "12 Aug 2025, 12:38:23 pm" // Expected: "12 Aug 2025, 12:38:23 pm"
const [datePart, timePart] = dateString.split(","); const [datePart, timePart] = dateString.split(",");
@ -452,7 +450,6 @@ export default function PaymentsTabScreen() {
)} )}
</View> </View>
</View> </View>
<Overlay isUploading={isLoading} />
</ScrollView> </ScrollView>
); );
} }

View File

@ -7,12 +7,9 @@ import {
SafeAreaView, SafeAreaView,
Alert, Alert,
Share, Share,
Clipboard,
Linking, Linking,
ActivityIndicator,
BackHandler, BackHandler,
} from "react-native"; } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import * as FileSystem from "expo-file-system"; import * as FileSystem from "expo-file-system";
import * as MediaLibrary from "expo-media-library"; import * as MediaLibrary from "expo-media-library";
import * as Sharing from "expo-sharing"; import * as Sharing from "expo-sharing";
@ -43,23 +40,11 @@ const UpiPaymentScreen = () => {
const { showSnackbar } = useSnackbar(); const { showSnackbar } = useSnackbar();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
if (!paymentOrder) {
return (
<SafeAreaView style={styles.container}>
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#00876F" />
<Text style={styles.loadingText}>Loading payment details...</Text>
</View>
</SafeAreaView>
);
}
useEffect(() => { useEffect(() => {
// Set up payment confirmation listener
const handlePaymentConfirmation = (data: any) => { const handlePaymentConfirmation = (data: any) => {
console.log("Payment confirmation received:", data); console.log("Payment confirmation received:", data);
// Show success message
Alert.alert( Alert.alert(
"Payment Successful!", "Payment Successful!",
"Your payment has been confirmed successfully.", "Your payment has been confirmed successfully.",
@ -75,11 +60,9 @@ const UpiPaymentScreen = () => {
); );
}; };
// Start listening for payment confirmation
onPaymentConfirmation(handlePaymentConfirmation); onPaymentConfirmation(handlePaymentConfirmation);
setIsListening(true); setIsListening(true);
// Handle Android back button
const backAction = () => { const backAction = () => {
Alert.alert( Alert.alert(
"Cancel Payment?", "Cancel Payment?",
@ -120,11 +103,6 @@ const UpiPaymentScreen = () => {
return `${amount.toLocaleString("en-IN")}`; return `${amount.toLocaleString("en-IN")}`;
}; };
// Extract numeric amount from the amount
const getNumericAmount = (): string => {
return paymentOrder.amount.toString();
};
// Check if payment is expired // Check if payment is expired
const isPaymentExpired = (): boolean => { const isPaymentExpired = (): boolean => {
const expiryDate = new Date(paymentOrder.expiry_date); const expiryDate = new Date(paymentOrder.expiry_date);
@ -261,23 +239,9 @@ const UpiPaymentScreen = () => {
} }
}; };
const getStatusColor = (status: string): string => {
switch (status) {
case "completed":
case "success":
return "#00876F";
case "failed":
return "#E74C3C";
case "processing":
return "#F39C12";
default:
return "#6C757D";
}
};
return ( return (
<SafeAreaView style={styles.container}> <SafeAreaView style={styles.container}>
<Header title="Pay EMI" showBackButton={true} /> <Header title="Pay EMI" showBackButton={false} />
<View style={styles.content}> <View style={styles.content}>
<View style={styles.qrFrame}> <View style={styles.qrFrame}>

View File

@ -19,10 +19,10 @@ import { BASE_URL, payments } from "@/constants/config";
import api from "@/services/axiosClient"; import api from "@/services/axiosClient";
import { setPaymentOrder } from "@/store/paymentSlice"; import { setPaymentOrder } from "@/store/paymentSlice";
import { Overlay } from "@/components/common/Overlay"; import { Overlay } from "@/components/common/Overlay";
import { useRouter } from "expo-router"; import { useFocusEffect, useRouter } from "expo-router";
import { useSocket } from "@/contexts/SocketContext"; import { useSocket } from "@/contexts/SocketContext";
import { useSnackbar } from "@/contexts/Snackbar";
// Validation schema
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
paymentType: Yup.string().required("Please select a payment option"), paymentType: Yup.string().required("Please select a payment option"),
customAmount: Yup.string().when("paymentType", { customAmount: Yup.string().when("paymentType", {
@ -47,11 +47,12 @@ const validationSchema = Yup.object().shape({
}); });
const SelectAmountScreen = () => { const SelectAmountScreen = () => {
// Fetch due amount from Redux
const dueAmount = useSelector( const dueAmount = useSelector(
(state: RootState) => state.payments?.due_amount || 0 (state: RootState) => state.payments?.due_amount || 0
); );
const { showSnackbar } = useSnackbar();
const router = useRouter(); const router = useRouter();
const [isFetching, setIsFetching] = useState<boolean>(false); const [isFetching, setIsFetching] = useState<boolean>(false);
@ -64,41 +65,72 @@ const SelectAmountScreen = () => {
customAmount: "", customAmount: "",
}; };
const existingPaymentOrder = useSelector(
(state: RootState) => state.payments?.paymentOrder
);
const dispatch = useDispatch(); const dispatch = useDispatch();
useFocusEffect(
React.useCallback(() => {
console.log(
"SelectAmountScreen focused - clearing paymentOrder and remounting"
);
// Clear the payment order
dispatch(setPaymentOrder(null));
setIsFetching(false);
}, [dispatch])
);
const handleSubmit = async (values: any) => { const handleSubmit = async (values: any) => {
setIsFetching(true); setIsFetching(true);
const paymentAmount = const paymentAmount =
values.paymentType === "due" values.paymentType === "due"
? dueAmount ? dueAmount
: parseFloat(values.customAmount); : parseFloat(values.customAmount);
console.log(values, "values");
try { try {
const res = await api.post(`/api/v1/create-order`, { let orderData = existingPaymentOrder;
amount: paymentAmount,
});
console.log(res.data, "response from select amount"); if (
if (res.data && res.data.success) { existingPaymentOrder &&
dispatch(setPaymentOrder(res.data.data)); existingPaymentOrder.amount === paymentAmount
try { ) {
await registerTransaction(res.data.data.transaction_id); console.log(
console.log("Transaction registered successfully"); "Order for current amount already exists, using existing order"
);
// Navigate to payment screen orderData = existingPaymentOrder;
router.push("/payments/payEmi");
} catch (socketError) {
throw socketError;
}
} else { } else {
throw new Error("Failed to create order"); console.log("Creating new order for amount:", paymentAmount);
const res = await api.post(`/api/v1/create-order`, {
amount: paymentAmount,
});
console.log(res.data, "response from select amount");
if (res.data && res.data.success) {
orderData = res.data.data;
dispatch(setPaymentOrder(orderData));
} else {
throw new Error("Failed to create order");
}
}
try {
await registerTransaction(orderData?.transaction_id);
console.log("Transaction registered successfully");
router.push("/payments/payEmi");
} catch (socketError) {
console.error("Socket connection failed:", socketError);
throw socketError;
} }
} catch (err) { } catch (err) {
console.error(err, "Error in creating order."); console.error(err, "Error in creating order.");
showSnackbar("Something went wrong.", "error");
} finally { } finally {
setIsFetching(false); setIsFetching(false);
} }
console.log("Payment Amount:", paymentAmount); console.log("Payment Amount:", paymentAmount);
}; };
@ -263,7 +295,6 @@ const SelectAmountScreen = () => {
</View> </View>
</ScrollView> </ScrollView>
{/* Pay Button */}
<View style={styles.buttonContainer}> <View style={styles.buttonContainer}>
<TouchableOpacity <TouchableOpacity
style={[ style={[

View File

@ -183,4 +183,8 @@ export const issueConfig = [
export const payments = { export const payments = {
MIN_AMOUNT: 1, MIN_AMOUNT: 1,
LINK_EXPIRED: "Payment link expired", LINK_EXPIRED: "Payment link expired",
SOCKET_CONNECTION_TIMEOUT_IN_SECS: 5,
REGISTER_TRANSACTION_EMIT_EVENT_NAME: "register-transaction",
REGISTER_TRANSACTION_LISTEN_EVENT_NAME: "registration-ack",
PAYMENT_CONFIRMATION_EVENT_NAME: "register-transaction",
}; };

View File

@ -10,7 +10,9 @@ import SocketService from "../services/paymentSocket";
interface SocketContextType { interface SocketContextType {
isConnected: boolean; isConnected: boolean;
registerTransaction: (transactionId: string) => Promise<void>; registerTransaction: (
transactionId: string | null | undefined
) => Promise<void>;
onPaymentConfirmation: (callback: (data: any) => void) => void; onPaymentConfirmation: (callback: (data: any) => void) => void;
offPaymentConfirmation: () => void; offPaymentConfirmation: () => void;
disconnect: () => void; disconnect: () => void;
@ -26,25 +28,6 @@ interface SocketProviderProps {
export const SocketProvider: React.FC<SocketProviderProps> = ({ children }) => { export const SocketProvider: React.FC<SocketProviderProps> = ({ children }) => {
const [isConnected, setIsConnected] = useState(false); const [isConnected, setIsConnected] = useState(false);
// useEffect(() => {
// const initSocket = async () => {
// try {
// await SocketService.connect();
// setIsConnected(true);
// } catch (error) {
// console.error("Failed to connect socket:", error);
// setIsConnected(false);
// }
// };
// initSocket();
// return () => {
// SocketService.disconnect();
// setIsConnected(false);
// };
// }, []);
const connectSocket = async () => { const connectSocket = async () => {
try { try {
await SocketService.connect(); await SocketService.connect();
@ -56,7 +39,9 @@ export const SocketProvider: React.FC<SocketProviderProps> = ({ children }) => {
} }
}; };
const registerTransaction = async (transactionId: string) => { const registerTransaction = async (
transactionId: string | null | undefined
) => {
try { try {
if (!transactionId) { if (!transactionId) {
throw new Error("Transaction Id missing in register transaction"); throw new Error("Transaction Id missing in register transaction");

View File

@ -1,5 +1,9 @@
// services/socketService.ts // services/socketService.ts
import { PAYMENT_SOCKET_BASE_URL, STORAGE_KEYS } from "@/constants/config"; import {
PAYMENT_SOCKET_BASE_URL,
payments,
STORAGE_KEYS,
} from "@/constants/config";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import io, { Socket } from "socket.io-client"; import io, { Socket } from "socket.io-client";
@ -66,19 +70,17 @@ class SocketService {
return; return;
} }
const timeoutMs = 5000; const timeoutSecs = payments.SOCKET_CONNECTION_TIMEOUT_IN_SECS * 1000;
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
socket.off("registration-ack", onRegistrationAck); socket.off("registration-ack", onRegistrationAck);
reject(new Error("Timeout: No registration-ack received")); reject(new Error("Timeout: No registration-ack received"));
}, timeoutMs); }, timeoutSecs);
const onRegistrationAck = (data: any) => { const onRegistrationAck = (data: any) => {
console.log("inside onRegisterAck");
if (data.transactionId === transactionId) { if (data.transactionId === transactionId) {
clearTimeout(timeoutId); clearTimeout(timeoutId);
// socket.off("registration-ack", onRegistrationAck); socket.off("registration-ack", onRegistrationAck);
if (data.success) { if (data.success) {
resolve(); resolve();
} else { } else {
@ -87,33 +89,30 @@ class SocketService {
} }
}; };
socket.on("registration-ack", onRegistrationAck); //register the transaction with the specific 'transactionId'
socket.emit(payments.REGISTER_TRANSACTION_EMIT_EVENT_NAME, {
socket.on("register-transaction", this.onPaymentConfirm); transactionId,
socket.onAny((eventName, ...args) => {
console.log(eventName, "eventname", JSON.stringify(args), "✅✅✅✅✅");
}); });
socket.on(
socket.emit("register-transaction", { transactionId }); payments.REGISTER_TRANSACTION_LISTEN_EVENT_NAME,
onRegistrationAck
);
}); });
} }
private onPaymentConfirm(eventname: string, ...args: any[]): void { //payment confirmation is received on 'register-transaction' eventName
console.log(eventname, "eventName", JSON.stringify(args));
}
public onPaymentConfirmation(callback: (data: any) => void): void { public onPaymentConfirmation(callback: (data: any) => void): void {
if (!this.socket) { if (!this.socket) {
console.error("Socket not connected"); console.error("Socket not connected");
return; return;
} }
this.socket.on("register-transaction", callback); this.socket.on(payments.PAYMENT_CONFIRMATION_EVENT_NAME, callback);
} }
public offPaymentConfirmation(): void { public offPaymentConfirmation(): void {
if (this.socket) { if (this.socket) {
this.socket.off("payment-confirmation"); this.socket.off(payments.PAYMENT_CONFIRMATION_EVENT_NAME);
} }
} }