Handle Payment Confirmation
parent
5b89bebe0b
commit
1ef44669e1
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}>
|
||||||
|
|
|
||||||
|
|
@ -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={[
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue