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

View File

@ -7,12 +7,9 @@ import {
SafeAreaView,
Alert,
Share,
Clipboard,
Linking,
ActivityIndicator,
BackHandler,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import * as FileSystem from "expo-file-system";
import * as MediaLibrary from "expo-media-library";
import * as Sharing from "expo-sharing";
@ -43,23 +40,11 @@ const UpiPaymentScreen = () => {
const { showSnackbar } = useSnackbar();
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(() => {
// Set up payment confirmation listener
const handlePaymentConfirmation = (data: any) => {
console.log("Payment confirmation received:", data);
// Show success message
Alert.alert(
"Payment Successful!",
"Your payment has been confirmed successfully.",
@ -75,11 +60,9 @@ const UpiPaymentScreen = () => {
);
};
// Start listening for payment confirmation
onPaymentConfirmation(handlePaymentConfirmation);
setIsListening(true);
// Handle Android back button
const backAction = () => {
Alert.alert(
"Cancel Payment?",
@ -120,11 +103,6 @@ const UpiPaymentScreen = () => {
return `${amount.toLocaleString("en-IN")}`;
};
// Extract numeric amount from the amount
const getNumericAmount = (): string => {
return paymentOrder.amount.toString();
};
// Check if payment is expired
const isPaymentExpired = (): boolean => {
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 (
<SafeAreaView style={styles.container}>
<Header title="Pay EMI" showBackButton={true} />
<Header title="Pay EMI" showBackButton={false} />
<View style={styles.content}>
<View style={styles.qrFrame}>

View File

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

View File

@ -183,4 +183,8 @@ export const issueConfig = [
export const payments = {
MIN_AMOUNT: 1,
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 {
isConnected: boolean;
registerTransaction: (transactionId: string) => Promise<void>;
registerTransaction: (
transactionId: string | null | undefined
) => Promise<void>;
onPaymentConfirmation: (callback: (data: any) => void) => void;
offPaymentConfirmation: () => void;
disconnect: () => void;
@ -26,25 +28,6 @@ interface SocketProviderProps {
export const SocketProvider: React.FC<SocketProviderProps> = ({ children }) => {
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 () => {
try {
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 {
if (!transactionId) {
throw new Error("Transaction Id missing in register transaction");

View File

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