BaaS_Driver_Android_App/app/payments/payEmi.tsx

479 lines
12 KiB
TypeScript

import React, { useEffect, useState } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
Alert,
Share,
Linking,
BackHandler,
ScrollView,
Dimensions,
} from "react-native";
import * as FileSystem from "expo-file-system";
import * as MediaLibrary from "expo-media-library";
import * as Sharing from "expo-sharing";
import { Image } from "expo-image";
import { useSelector, useDispatch } from "react-redux";
import { RootState } from "@/store";
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";
import { useSnackbar } from "@/contexts/Snackbar";
import DownloadIcon from "@/assets/icons/download.svg";
import { payments } from "@/constants/config";
import { useFocusEffect, useRouter } from "expo-router";
import { useSocket } from "@/contexts/SocketContext";
import { useTranslation } from "react-i18next";
import CancelPayment from "@/components/Payments/CancelPaymentModal";
const { height: screenHeight } = Dimensions.get("window");
const UpiPaymentScreen = () => {
const dispatch = useDispatch();
//paymentorder.amount is undefined
console.log("inside payemi ✅✅");
const paymentOrder = useSelector(
(state: RootState) => state.payments.paymentOrder
);
const router = useRouter();
const { onPaymentConfirmation, offPaymentConfirmation, disconnect } =
useSocket();
const { showSnackbar } = useSnackbar();
const insets = useSafeAreaInsets();
const [isCancelModalVisible, setIsCancelModalVisible] =
useState<boolean>(false);
function toggleCancelModal() {
setIsCancelModalVisible(!isCancelModalVisible);
}
function handleCancelPayment() {
offPaymentConfirmation();
disconnect();
router.back();
}
useFocusEffect(
React.useCallback(() => {
let backPressTimer: NodeJS.Timeout | null = null;
const backAction = () => {
setIsCancelModalVisible(true);
return true;
};
const backHandler = BackHandler.addEventListener(
"hardwareBackPress",
backAction
);
return () => {
if (backPressTimer) clearTimeout(backPressTimer);
backHandler.remove();
};
}, [])
);
useFocusEffect(
React.useCallback(() => {
const handlePaymentConfirmation = (data: any) => {
dispatch(setPaymentOrder({ ...paymentOrder, ...data }));
offPaymentConfirmation();
disconnect();
router.replace("/payments/Confirmation");
};
onPaymentConfirmation(handlePaymentConfirmation);
return () => {
offPaymentConfirmation();
};
}, [
paymentOrder,
onPaymentConfirmation,
offPaymentConfirmation,
disconnect,
router,
])
);
const formatAmount = (amount: number | null | undefined): string => {
if (amount == null || amount == undefined) return `${0}`;
return `${amount.toLocaleString("en-IN")}`;
};
const isPaymentExpired = (): boolean => {
const expiryDate = new Date(paymentOrder.expiry_date);
const currentDate = new Date();
return currentDate > expiryDate;
};
const getUpiUrl = (): string => {
const upiString = paymentOrder.payment_link;
const upiMatch = upiString.match(/upi_string=([^&]+)/);
if (upiMatch) {
return decodeURIComponent(upiMatch[1]);
}
return `upi://pay?pa=${paymentOrder.upi_handle}&am=${paymentOrder.amount}&cu=INR&tr=${paymentOrder.order_id}`;
};
const payUsingUpiApp = async (): Promise<void> => {
try {
if (isPaymentExpired()) {
showSnackbar(payments.LINK_EXPIRED, "error");
return;
}
const upiUrl = getUpiUrl();
console.log("Opening UPI URL:", upiUrl);
dispatch(updatePaymentStatus("processing"));
const canOpenUrl = await Linking.canOpenURL(upiUrl);
if (canOpenUrl) {
await Linking.openURL(upiUrl);
} else {
showSnackbar("UPI App Required.", "error");
}
} catch (error) {
console.error("Error opening UPI app:", error);
dispatch(updatePaymentStatus("failed"));
showSnackbar("Unable to open UPI App", "error");
}
};
const shareQR = async (): Promise<void> => {
try {
const fileUri =
FileSystem.documentDirectory + `qr-${paymentOrder?.order_id}.png`;
// Download QR code image to local file
const downloadResult = await FileSystem.downloadAsync(
paymentOrder?.qr_code_url,
fileUri
);
// Share using expo-sharing
if (await Sharing.isAvailableAsync()) {
await Sharing.shareAsync(downloadResult.uri, {
mimeType: "image/png",
dialogTitle: "Share QR Code",
UTI: "public.png",
});
} else {
showSnackbar("Sharing is not available on this device.", "error");
}
} catch (error) {
console.error("Error sharing QR code:", error);
showSnackbar("Failed to share QR code", "error");
}
};
const downloadQR = async (): Promise<void> => {
try {
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status !== "granted") {
showSnackbar("Please grant permission to save images", "error");
return;
}
const fileUri =
FileSystem.documentDirectory + `qr-${paymentOrder.order_id}.png`;
const downloadResult = await FileSystem.downloadAsync(
paymentOrder.qr_code_url,
fileUri
);
if (downloadResult.status === 200) {
const asset = await MediaLibrary.createAssetAsync(downloadResult.uri);
await MediaLibrary.createAlbumAsync("Payment QR Codes", asset, false);
showSnackbar("QR code saved to gallery", "success");
} else {
showSnackbar("Failed to Download QR code", "error");
}
} catch (error) {
showSnackbar("Failed to Download QR code", "error");
}
};
function handlePaymentDone() {
router.navigate("/(tabs)/payments");
}
const { t } = useTranslation();
return (
<>
<View style={styles.container}>
<Header title={t("payment.pay-emi")} showBackButton={false} />
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
>
<View style={styles.content}>
<View style={styles.qrFrame}>
<View style={styles.amountSection}>
<Text style={styles.amountLabel}>
{t("payment.amount-to-be-paid")}
</Text>
<Text style={styles.paidTo}>{payments.AMOUNT_PAID_TO}</Text>
<Text style={styles.amount}>
{formatAmount(paymentOrder?.amount)}
</Text>
</View>
<View style={styles.qrCodeContainer}>
<Image
source={{ uri: paymentOrder?.qr_code_url }}
style={styles.qrCode}
contentFit="contain"
/>
</View>
<View style={styles.buttonsContainer}>
<TouchableOpacity
onPress={shareQR}
style={styles.secondaryButton}
>
<ShareIcon />
<Text style={styles.secondaryButtonText}>
{t("payment.share-qr")}
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={downloadQR}
style={styles.secondaryButton}
>
<DownloadIcon />
<Text style={styles.secondaryButtonText}>
{t("payment.download-qr")}
</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
onPress={payUsingUpiApp}
style={[styles.primaryButton]}
>
<Text style={[styles.primaryButtonText]}>
{t("payment.pay-using-upi")}
</Text>
</TouchableOpacity>
</View>
<View style={[styles.confirm, { marginBottom: insets.bottom }]}>
<Text style={styles.confirmTitle}>
{t("payment.confirm-payment")}
</Text>
<TouchableOpacity
onPress={() => handlePaymentDone()}
style={styles.paymentDone}
>
<Text style={styles.doneText}>{t("payment.payment-done")}</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
</View>
<CancelPayment
visible={isCancelModalVisible}
onClose={toggleCancelModal}
onCancel={handleCancelPayment}
/>
</>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#F3F5F8",
},
scrollView: {
flex: 1,
},
scrollContent: {
flexGrow: 1,
},
content: {
flex: 1,
paddingHorizontal: 16,
paddingVertical: 16,
justifyContent: "space-between",
minHeight: screenHeight * 0.8, // Ensures minimum height for space-between to work
},
qrFrame: {
backgroundColor: "#FCFCFC",
borderRadius: 8,
padding: 16,
alignItems: "center",
},
confirm: {
padding: 16,
flexDirection: "column",
backgroundColor: "#FCFCFC",
gap: 16,
borderRadius: 8,
},
doneText: {
textAlign: "center",
fontWeight: "500",
fontSize: 14,
lineHeight: 20,
},
paymentDone: {
padding: 16,
borderWidth: 1,
borderColor: "#DCE1E9",
borderRadius: 4,
},
confirmTitle: {
padding: 2,
fontWeight: "400",
fontSize: 14,
lineHeight: 14,
color: "#252A34",
},
loadingContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
loadingText: {
marginTop: 16,
fontSize: 16,
color: "#253342",
},
header: {
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 16,
paddingVertical: 12,
backgroundColor: "#FFFFFF",
},
backButton: {
padding: 8,
marginRight: 8,
},
headerTitle: {
fontSize: 18,
fontWeight: "600",
color: "#253342",
},
amountSection: {
alignItems: "center",
marginBottom: 16,
},
amountLabel: {
fontSize: 14,
color: "#253342",
textAlign: "center",
marginBottom: 4,
},
paidTo: {
fontSize: 12,
fontWeight: "700",
color: "#253342",
textAlign: "center",
lineHeight: 20,
},
amount: {
fontSize: 20,
fontWeight: "600",
color: "#253342",
textAlign: "center",
marginBottom: 8,
},
statusBadge: {
paddingHorizontal: 12,
paddingVertical: 4,
borderRadius: 12,
},
statusText: {
fontSize: 12,
fontWeight: "600",
color: "#FFFFFF",
},
qrCodeContainer: {
marginBottom: 16,
borderWidth: 2,
borderColor: "#E5E9F0",
padding: 16,
borderRadius: 8,
width: "100%",
height: "auto",
},
qrCode: {
width: "100%",
aspectRatio: 1,
alignSelf: "center",
},
upiIdSection: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
marginBottom: 12,
padding: 8,
backgroundColor: "#F3F5F8",
borderRadius: 4,
},
upiIdText: {
fontSize: 14,
color: "#253342",
marginRight: 8,
fontWeight: "500",
},
expirySection: {
alignItems: "center",
marginBottom: 16,
},
expiryLabel: {
fontSize: 12,
color: "#6C757D",
marginBottom: 2,
},
expiryTime: {
fontSize: 14,
color: "#253342",
fontWeight: "500",
},
buttonsContainer: {
flexDirection: "row",
gap: 8,
width: "100%",
},
secondaryButton: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
paddingVertical: 10,
paddingHorizontal: 16,
backgroundColor: "#F3F5F8",
borderColor: "#D8DDE7",
borderWidth: 1,
borderRadius: 4,
gap: 4,
},
secondaryButtonText: {
fontSize: 14,
fontWeight: "500",
color: "#253342",
},
primaryButton: {
backgroundColor: "#00876F",
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 4,
marginTop: 16,
width: "100%",
alignItems: "center",
},
primaryButtonText: {
fontSize: 14,
fontWeight: "500",
color: "#FCFCFC",
},
disabledButton: {
backgroundColor: "#D8DDE7",
},
disabledButtonText: {
color: "#6C757D",
},
});
export default UpiPaymentScreen;