BaaS_Driver_Android_App/app/payments/payEmi.tsx

530 lines
14 KiB
TypeScript

import React, { useEffect } from "react";
import {
View,
Text,
TouchableOpacity,
StyleSheet,
SafeAreaView,
Alert,
Share,
Clipboard,
Platform,
Linking,
ActivityIndicator,
} 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";
import { Image } from "expo-image";
import { useSelector, useDispatch } from "react-redux";
import { RootState } from "@/store"; // Adjust path as needed
import { updatePaymentStatus } from "@/store/paymentSlice";
interface UpiPaymentScreenProps {
onBack?: () => void;
onPaymentSuccess?: () => void;
onPaymentFailure?: () => void;
}
const UpiPaymentScreen: React.FC<UpiPaymentScreenProps> = ({
onBack,
onPaymentSuccess,
onPaymentFailure,
}) => {
const dispatch = useDispatch();
const paymentOrder = useSelector(
(state: RootState) => state.payments.paymentOrder
);
useEffect(() => {
// Check if payment order exists
if (!paymentOrder) {
Alert.alert(
"Error",
"No payment order found. Please create a payment order first.",
[{ text: "OK", onPress: onBack }]
);
}
}, [paymentOrder]);
// Show loading if no payment data
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>
);
}
// Format amount with currency symbol
const formatAmount = (amount: number): string => {
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);
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");
};
// Generate UPI payment URL from the API response
const getUpiUrl = (): string => {
// Use the UPI string from the API response
const upiString = paymentOrder.ppayment_link;
// Extract the UPI URL from the deep link
const upiMatch = upiString.match(/upi_string=([^&]+)/);
if (upiMatch) {
return decodeURIComponent(upiMatch[1]);
}
// Fallback: construct UPI URL manually
return `upi://pay?pa=${paymentOrder.upi_handle}&am=${paymentOrder.amount}&cu=INR&tr=${paymentOrder.order_id}`;
};
const payUsingUpiApp = async (): Promise<void> => {
try {
// Check if payment is expired
if (isPaymentExpired()) {
Alert.alert(
"Payment Expired",
"This payment order has expired. Please create a new payment order.",
[{ text: "OK", onPress: onBack }]
);
return;
}
// Check if payment is already completed
if (
paymentOrder.status === "completed" ||
paymentOrder.status === "success"
) {
Alert.alert(
"Payment Already Completed",
"This payment has already been completed.",
[{ text: "OK", onPress: onPaymentSuccess }]
);
return;
}
const upiUrl = getUpiUrl();
console.log("Opening UPI URL:", upiUrl);
// Update payment status to processing
dispatch(updatePaymentStatus("processing"));
// Check if device can handle UPI URLs
const canOpenUrl = await Linking.canOpenURL(upiUrl);
if (canOpenUrl) {
await Linking.openURL(upiUrl);
// Show payment status dialog
Alert.alert(
"Payment Initiated",
"Payment has been initiated. Please complete the payment in your UPI app.",
[
{
text: "Payment Completed",
onPress: () => {
dispatch(updatePaymentStatus("completed"));
onPaymentSuccess?.();
},
},
{
text: "Payment Failed",
style: "cancel",
onPress: () => {
dispatch(updatePaymentStatus("failed"));
onPaymentFailure?.();
},
},
]
);
} else {
// Fallback: Show available UPI apps or general share
Alert.alert(
"UPI App Required",
"Please install a UPI-enabled app like PhonePe, Paytm, Google Pay, or BHIM to make payments.",
[
{
text: "Share Payment Details",
onPress: () => sharePaymentDetails(),
},
{ text: "Cancel", style: "cancel" },
]
);
}
} catch (error) {
console.error("Error opening UPI app:", error);
dispatch(updatePaymentStatus("failed"));
Alert.alert(
"Error",
"Unable to open UPI app. Would you like to share payment details instead?",
[
{
text: "Share Details",
onPress: () => sharePaymentDetails(),
},
{ text: "Cancel", style: "cancel" },
]
);
}
};
const sharePaymentDetails = async (): Promise<void> => {
try {
const shareMessage =
`💰 Payment Request\n\n` +
`Amount: ${formatAmount(paymentOrder.amount)}\n` +
`UPI ID: ${paymentOrder.upi_handle}\n` +
`Order ID: ${paymentOrder.order_id}\n` +
`Transaction ID: ${paymentOrder.transaction_id}\n` +
`Expires: ${getExpiryTime()}\n\n` +
`UPI Link: ${getUpiUrl()}`;
await Share.share({
message: shareMessage,
title: "UPI Payment Details",
});
} catch (error) {
Alert.alert("Error", "Failed to share payment details");
}
};
const copyUpiId = async (): Promise<void> => {
try {
await Clipboard.setString(paymentOrder.upi_handle);
Alert.alert("Copied!", "UPI ID copied to clipboard");
} catch (error) {
Alert.alert("Error", "Failed to copy UPI ID");
}
};
const shareQR = async (): Promise<void> => {
try {
if (await Sharing.isAvailableAsync()) {
await Share.share({
message: `Pay ${formatAmount(
paymentOrder.amount
)} using this QR code`,
url: paymentOrder.qr_code_url,
});
} else {
Alert.alert("Error", "Sharing is not available on this device");
}
} catch (error) {
Alert.alert("Error", "Failed to share QR code");
}
};
const downloadQR = async (): Promise<void> => {
try {
const { status } = await MediaLibrary.requestPermissionsAsync();
if (status !== "granted") {
Alert.alert(
"Permission Required",
"Please grant permission to save images"
);
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);
Alert.alert("Success", "QR code saved to gallery");
} else {
Alert.alert("Error", "Failed to download QR code");
}
} catch (error) {
Alert.alert("Error", "Failed to download QR code");
}
};
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}>
<View style={styles.header}>
<TouchableOpacity onPress={onBack} style={styles.backButton}>
<Ionicons name="chevron-back" size={24} color="#253342" />
</TouchableOpacity>
<Text style={styles.headerTitle}>Pay EMI</Text>
</View>
<View style={styles.content}>
<View style={styles.qrFrame}>
<View style={styles.amountSection}>
<Text style={styles.amountLabel}>Amount to be paid</Text>
<Text style={styles.amount}>
{formatAmount(paymentOrder.amount)}
</Text>
{/* Payment Status */}
<View
style={[
styles.statusBadge,
{ backgroundColor: getStatusColor(paymentOrder.status) },
]}
>
<Text style={styles.statusText}>
{paymentOrder.status.toUpperCase()}
</Text>
</View>
</View>
<View style={styles.qrCodeContainer}>
<Image
source={{ uri: paymentOrder.qr_code_url }}
style={styles.qrCode}
contentFit="contain"
/>
</View>
{/* UPI ID Section */}
<TouchableOpacity onPress={copyUpiId} style={styles.upiIdSection}>
<Text style={styles.upiIdText}>{paymentOrder.upi_handle}</Text>
<Ionicons name="copy-outline" size={16} color="#253342" />
</TouchableOpacity>
{/* Expiry Info */}
<View style={styles.expirySection}>
<Text style={styles.expiryLabel}>Expires:</Text>
<Text style={styles.expiryTime}>{getExpiryTime()}</Text>
</View>
<View style={styles.buttonsContainer}>
<TouchableOpacity onPress={shareQR} style={styles.secondaryButton}>
<Ionicons name="share-outline" size={20} color="#253342" />
<Text style={styles.secondaryButtonText}>Share QR</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={downloadQR}
style={styles.secondaryButton}
>
<Ionicons name="download-outline" size={20} color="#253342" />
<Text style={styles.secondaryButtonText}>Download QR</Text>
</TouchableOpacity>
</View>
</View>
<TouchableOpacity
onPress={payUsingUpiApp}
style={[
styles.primaryButton,
(isPaymentExpired() || paymentOrder.status === "completed") &&
styles.disabledButton,
]}
disabled={isPaymentExpired() || paymentOrder.status === "completed"}
>
<Text
style={[
styles.primaryButtonText,
(isPaymentExpired() || paymentOrder.status === "completed") &&
styles.disabledButtonText,
]}
>
{paymentOrder.status === "completed"
? "Payment Completed"
: isPaymentExpired()
? "Payment Expired"
: "Pay using UPI app"}
</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#F3F5F8",
},
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",
},
content: {
flex: 1,
justifyContent: "space-between",
paddingHorizontal: 16,
paddingBottom: 16,
},
qrFrame: {
backgroundColor: "#FCFCFC",
borderRadius: 8,
padding: 16,
alignItems: "center",
marginTop: 16,
},
amountSection: {
alignItems: "center",
marginBottom: 16,
},
amountLabel: {
fontSize: 14,
color: "#253342",
textAlign: "center",
marginBottom: 4,
},
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: {
width: 248,
height: 248,
justifyContent: "center",
alignItems: "center",
marginBottom: 16,
},
qrCode: {
width: "100%",
height: "100%",
},
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,
alignItems: "center",
},
primaryButtonText: {
fontSize: 14,
fontWeight: "500",
color: "#FCFCFC",
},
disabledButton: {
backgroundColor: "#D8DDE7",
},
disabledButtonText: {
color: "#6C757D",
},
});
export default UpiPaymentScreen;