diff --git a/app/(tabs)/payments.tsx b/app/(tabs)/payments.tsx
index c97a5ba..3b06388 100644
--- a/app/(tabs)/payments.tsx
+++ b/app/(tabs)/payments.tsx
@@ -38,6 +38,7 @@ import { displayValue } from "@/utils/Common";
import RefreshIcon from "@/assets/icons/refresh.svg";
import CustomerSupport from "@/components/home/CustomerSupportModal";
import { useTranslation } from "react-i18next";
+import { Image } from "expo-image";
export interface MyPlan {
no_of_emi: number;
@@ -231,8 +232,16 @@ export default function PaymentsTabScreen() {
},
headerTitle: () => (
- {model}
- {chasisNumber}
+
+
+
+
+ {model}
+ {chasisNumber}
+
),
headerRight: () => (
@@ -270,7 +279,6 @@ export default function PaymentsTabScreen() {
// Format currency
const formatCurrency = (amount: number) => {
- console.log(amount, "amount format current");
return `₹${amount.toLocaleString()}`;
};
@@ -590,6 +598,22 @@ const StatusBadge = ({
};
const styles = StyleSheet.create({
+ logo: {
+ width: "100%",
+ height: "100%",
+ resizeMode: "contain",
+ },
+ logoContainer: {
+ padding: 8,
+ borderRadius: 44 / 2, // make it perfectly round
+ borderWidth: 2,
+ borderColor: "#E5E9F0",
+ width: 44,
+ height: 44,
+ alignItems: "center",
+ justifyContent: "center",
+ overflow: "hidden", // ensures image doesn't overflow
+ },
loadingContainer: {
padding: 16,
alignItems: "center",
@@ -611,8 +635,9 @@ const styles = StyleSheet.create({
width: "80%",
},
headerTitleContainer: {
- flexDirection: "column",
+ flexDirection: "row",
backgroundColor: "#F3F5F8",
+ gap: 8,
},
subtitle: {
fontSize: 18,
diff --git a/app/payments/Confirmation.tsx b/app/payments/Confirmation.tsx
index a7a680f..6e38dff 100644
--- a/app/payments/Confirmation.tsx
+++ b/app/payments/Confirmation.tsx
@@ -9,6 +9,8 @@ import Pending from "@/assets/icons/pending.svg";
import Failed from "@/assets/icons/cancel.svg";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTranslation } from "react-i18next";
+import { payments } from "@/constants/config";
+import { formatDate } from "@/utils/Payments";
const PaymentConfirmationScreen = () => {
const router = useRouter();
@@ -22,35 +24,11 @@ const PaymentConfirmationScreen = () => {
const paymentStatus = paymentOrder?.status || "confirmed";
// Format amount with currency
- const formatAmount = (amount: number | null) => {
+ const formatAmount = (amount: number | null | undefined) => {
if (!amount) return "₹0";
return `₹${amount.toLocaleString("en-IN")}`;
};
- // Format date
- const formatDate = (dateString?: string) => {
- if (!dateString)
- return new Date().toLocaleString("en-IN", {
- day: "numeric",
- month: "long",
- year: "numeric",
- hour: "numeric",
- minute: "2-digit",
- hour12: true,
- weekday: "long",
- });
-
- return new Date(dateString).toLocaleString("en-IN", {
- day: "numeric",
- month: "long",
- year: "numeric",
- hour: "numeric",
- minute: "2-digit",
- hour12: true,
- weekday: "long",
- });
- };
-
// Get icon and text based on status
const getStatusDisplay = () => {
switch (paymentStatus) {
@@ -95,15 +73,15 @@ const PaymentConfirmationScreen = () => {
+ {statusDisplay.text}
{statusDisplay.icon}
- {formatAmount(paymentOrder?.amount || due_amount)}
+ {formatAmount(paymentOrder?.amount)}
-
+ Payment to {payments.AMOUNT_PAID_TO}
- {statusDisplay.text}
{formatDate(paymentOrder?.transaction_date)}
@@ -200,7 +178,6 @@ const styles = StyleSheet.create({
backgroundColor: "#fcfcfc",
borderRadius: 8,
padding: 16,
- paddingVertical: 32,
},
paymentStatusContainer: {
alignItems: "center",
@@ -220,6 +197,13 @@ const styles = StyleSheet.create({
successIcon: {
// Icon styling handled by Ionicons
},
+ paidTo: {
+ fontSize: 12,
+ fontWeight: "700",
+ color: "#253342",
+ textAlign: "center",
+ lineHeight: 20,
+ },
amountText: {
fontSize: 20,
fontWeight: "600",
diff --git a/app/payments/TransactionDetails.tsx b/app/payments/TransactionDetails.tsx
index 021c600..81c302b 100644
--- a/app/payments/TransactionDetails.tsx
+++ b/app/payments/TransactionDetails.tsx
@@ -9,16 +9,15 @@ import {
import { useNavigation, useRoute } from "@react-navigation/native";
import { useRouter } from "expo-router";
import api from "@/services/axiosClient";
-import { BASE_URL } from "@/constants/config";
+import { BASE_URL, payments } from "@/constants/config";
import { useSnackbar } from "@/contexts/Snackbar";
-import { SafeAreaView } from "react-native-safe-area-context";
-// Import your success/failure icons
import SuccessIcon from "@/assets/icons/check_circle.svg";
import FailureIcon from "@/assets/icons/cancel.svg";
import PendingIcon from "@/assets/icons/pending.svg";
import Header from "@/components/common/Header";
import { useTranslation } from "react-i18next";
+import { formatDate } from "@/utils/Payments";
interface TransactionDetailData {
id: number;
@@ -152,18 +151,18 @@ export default function TransactionDetailScreen() {
) : (
+
+ {getStatusText(transactionData.status)}
+
{getStatusIcon(transactionData.status)}
{formatCurrency(transactionData.amount)}
-
-
- {getStatusText(transactionData.status)}
-
+ Payment to {payments.AMOUNT_PAID_TO}
- {formatDateTime(transactionData.transaction_date)}
+ {formatDate(transactionData?.transaction_date)}
@@ -305,7 +304,6 @@ const styles = StyleSheet.create({
fontSize: 14,
fontWeight: "400",
color: "#252A34",
- marginBottom: 4,
},
dateTime: {
fontSize: 14,
diff --git a/app/payments/payEmi.tsx b/app/payments/payEmi.tsx
index c5c7c9f..f55e0fe 100644
--- a/app/payments/payEmi.tsx
+++ b/app/payments/payEmi.tsx
@@ -27,6 +27,7 @@ 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");
@@ -34,9 +35,6 @@ const UpiPaymentScreen = () => {
const dispatch = useDispatch();
//paymentorder.amount is undefined
console.log("inside payemi ✅✅");
- useEffect(() => {
- console.log("inside pay emi useeffect 🔥🔥🔥🔥🔥");
- }, []);
const paymentOrder = useSelector(
(state: RootState) => state.payments.paymentOrder
);
@@ -46,36 +44,36 @@ const UpiPaymentScreen = () => {
const { showSnackbar } = useSnackbar();
const insets = useSafeAreaInsets();
+ const [isCancelModalVisible, setIsCancelModalVisible] =
+ useState(false);
+
+ function toggleCancelModal() {
+ setIsCancelModalVisible(!isCancelModalVisible);
+ }
+
+ function handleCancelPayment() {
+ offPaymentConfirmation();
+ disconnect();
+ router.back();
+ }
+
useFocusEffect(
React.useCallback(() => {
- let backPressCount = 0;
let backPressTimer: NodeJS.Timeout | null = null;
const backAction = () => {
- if (backPressCount === 0) {
- backPressCount++;
- showSnackbar("Press back again to cancel payment", "info");
- backPressTimer = setTimeout(() => {
- backPressCount = 0;
- }, 2000);
- return true;
- } else {
- if (backPressTimer) clearTimeout(backPressTimer);
- offPaymentConfirmation();
- disconnect();
- router.back();
- return true;
- }
+ setIsCancelModalVisible(true);
+ return true;
};
const backHandler = BackHandler.addEventListener(
"hardwareBackPress",
backAction
);
- // ✅ Cleanup when screen loses focus
+
return () => {
if (backPressTimer) clearTimeout(backPressTimer);
backHandler.remove();
};
- }, [offPaymentConfirmation, disconnect, router])
+ }, [])
);
useFocusEffect(
@@ -99,7 +97,7 @@ const UpiPaymentScreen = () => {
])
);
- const formatAmount = (amount: number): string => {
+ const formatAmount = (amount: number | null | undefined): string => {
if (amount == null || amount == undefined) return `₹${0}`;
return `₹${amount.toLocaleString("en-IN")}`;
};
@@ -143,18 +141,28 @@ const UpiPaymentScreen = () => {
const shareQR = async (): Promise => {
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 Share.share({
- message: `Pay ${formatAmount(
- paymentOrder.amount
- )} using this QR code`,
- url: paymentOrder.qr_code_url,
+ await Sharing.shareAsync(downloadResult.uri, {
+ mimeType: "image/png",
+ dialogTitle: "Share QR Code",
+ UTI: "public.png",
});
} else {
- Alert.alert("Error", "Sharing is not available on this device");
+ showSnackbar("Sharing is not available on this device.", "error");
}
} catch (error) {
- Alert.alert("Error", "Failed to share QR code");
+ console.error("Error sharing QR code:", error);
+ showSnackbar("Failed to share QR code", "error");
}
};
@@ -190,74 +198,82 @@ const UpiPaymentScreen = () => {
const { t } = useTranslation();
return (
-
-
-
-
-
-
-
- {t("payment.amount-to-be-paid")}
-
-
- {formatAmount(paymentOrder?.amount)}
-
-
-
-
-
-
-
-
-
- {t("payment.share-qr")}
+ <>
+
+
+
+
+
+
+
+ {t("payment.amount-to-be-paid")}
-
+ {payments.AMOUNT_PAID_TO}
+
+ {formatAmount(paymentOrder?.amount)}
+
+
+
+
+
+
+
+
+
+ {t("payment.share-qr")}
+
+
+
+
+
+ {t("payment.download-qr")}
+
+
+
-
-
- {t("payment.download-qr")}
+
+ {t("payment.pay-using-upi")}
-
-
- {t("payment.pay-using-upi")}
-
-
-
-
-
- {t("payment.confirm-payment")}
-
- handlePaymentDone()}
- style={styles.paymentDone}
- >
- {t("payment.payment-done")}
-
+
+
+ {t("payment.confirm-payment")}
+
+ handlePaymentDone()}
+ style={styles.paymentDone}
+ >
+ {t("payment.payment-done")}
+
+
-
-
-
+
+
+
+ >
);
};
@@ -305,6 +321,7 @@ const styles = StyleSheet.create({
borderRadius: 4,
},
confirmTitle: {
+ padding: 2,
fontWeight: "400",
fontSize: 14,
lineHeight: 14,
@@ -346,6 +363,13 @@ const styles = StyleSheet.create({
textAlign: "center",
marginBottom: 4,
},
+ paidTo: {
+ fontSize: 12,
+ fontWeight: "700",
+ color: "#253342",
+ textAlign: "center",
+ lineHeight: 20,
+ },
amount: {
fontSize: 20,
fontWeight: "600",
diff --git a/app/payments/selectAmount.tsx b/app/payments/selectAmount.tsx
index 9e5be81..80cb552 100644
--- a/app/payments/selectAmount.tsx
+++ b/app/payments/selectAmount.tsx
@@ -23,6 +23,7 @@ import { useSocket } from "@/contexts/SocketContext";
import { useSnackbar } from "@/contexts/Snackbar";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTranslation } from "react-i18next";
+import { displayValue, formatCurrency } from "@/utils/Common";
const validationSchema = Yup.object().shape({
paymentType: Yup.string().required("Please select a payment option"),
@@ -169,34 +170,41 @@ const SelectAmountScreen = () => {
values,
errors,
touched,
- handleChange,
handleBlur,
handleSubmit,
setFieldValue,
- isValid,
- validateField,
- dirty,
}) => {
const handleQuickAmountPress = (amount: number) => {
- const currentAmount = values.customAmount
- ? parseFloat(values.customAmount)
- : 0;
- const newAmount = currentAmount + amount;
+ let newAmount = values.customAmount
+ ? parseFloat(values.customAmount) + amount
+ : amount;
+
+ if (newAmount > payments.MAX_AMOUNT) {
+ newAmount = payments.MAX_AMOUNT;
+ }
+
+ setFieldValue("paymentType", "custom");
setFieldValue("customAmount", newAmount.toString());
- validateField("customAmount");
};
const handleCustomAmountChange = (text: string) => {
- const numericText = text.replace(/[^0-9.]/g, "");
+ let numericText = text.replace(/[^0-9.]/g, "");
const parts = numericText.split(".");
- const formattedText =
- parts.length > 2
- ? parts[0] + "." + parts.slice(1).join("")
- : numericText;
- setFieldValue("customAmount", formattedText, true);
+ if (parts.length > 2) {
+ numericText = parts[0] + "." + parts[1];
+ }
+ if (parts[1]?.length > 2) {
+ numericText = parts[0] + "." + parts[1].slice(0, 2);
+ }
+
+ const numValue = parseFloat(numericText);
+ if (!isNaN(numValue) && numValue > payments.MAX_AMOUNT) {
+ numericText = payments.MAX_AMOUNT.toString();
+ }
+
+ setFieldValue("customAmount", numericText, true);
setFieldValue("paymentType", "custom");
- validateField("customAmount"); // Trigger validation after change
};
const getPaymentAmount = () => {
@@ -214,34 +222,15 @@ const SelectAmountScreen = () => {
!isNaN(paymentAmount) &&
paymentAmount >= payments.MIN_AMOUNT);
- // Button text logic
- let buttonText = "";
- if (values.paymentType === "due") {
- buttonText = `Pay ₹${paymentAmount.toFixed(2)}`;
- } else {
- buttonText =
- paymentAmount >= payments.MIN_AMOUNT
- ? `Pay ₹${paymentAmount.toFixed(2)}`
- : "Select Amount";
- }
-
return (
-
+
-
@@ -269,7 +258,7 @@ const SelectAmountScreen = () => {
- ₹{dueAmount?.toFixed(2)}
+ {displayValue(dueAmount, formatCurrency)}
@@ -317,7 +306,6 @@ const SelectAmountScreen = () => {
placeholderTextColor="#94A3B8"
keyboardType="numeric"
onFocus={() => setFieldValue("paymentType", "custom")}
- // Add return key handling
returnKeyType="done"
/>
@@ -358,34 +346,28 @@ const SelectAmountScreen = () => {
)}
+
+ handleSubmit()}
+ disabled={!isButtonEnabled}
+ >
+ {getPaymentAmount() < payments.MIN_AMOUNT ? (
+
+ {t("payment.select-amount")}
+
+ ) : (
+
+ Pay {displayValue(getPaymentAmount(), formatCurrency)}
+
+ )}
+
+
-
-
- handleSubmit()}
- disabled={!isButtonEnabled}
- >
- {getPaymentAmount() < payments.MIN_AMOUNT ? (
-
- {t("payment.select-amount")}
-
- ) : (
-
- Pay ₹{getPaymentAmount().toFixed(2)}
-
- )}
-
-
-
+
);
}}
diff --git a/app/user/edit_name.tsx b/app/user/edit_name.tsx
index 02e75f6..171b881 100644
--- a/app/user/edit_name.tsx
+++ b/app/user/edit_name.tsx
@@ -148,7 +148,6 @@ const styles = StyleSheet.create({
},
formContainer: {
flex: 1,
- justifyContent: "space-between",
},
inputContainer: {
marginBottom: 24,
diff --git a/components/Payments/CancelPaymentModal.tsx b/components/Payments/CancelPaymentModal.tsx
new file mode 100644
index 0000000..f09d25b
--- /dev/null
+++ b/components/Payments/CancelPaymentModal.tsx
@@ -0,0 +1,91 @@
+import BottomSheetModal from "@/components/common/BottomSheetModal";
+import { useTranslation } from "react-i18next";
+import { StyleSheet, Text, TouchableOpacity, View } from "react-native";
+
+interface CancelPaymentProps {
+ visible: boolean;
+ onClose: () => void;
+ onCancel: () => void;
+}
+
+export default function CancelPayment({
+ visible,
+ onClose,
+ onCancel,
+}: CancelPaymentProps) {
+ const { t } = useTranslation();
+ return (
+
+
+ {t("payment.do-you-want-to-cancel-payment")}
+
+
+
+
+ {t("payment.cancel-payment")}
+
+
+
+
+ {t("payment.continue-payment")}
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ buttonsContainer: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ paddingVertical: 8,
+ borderTopWidth: 1,
+ borderBottomWidth: 1,
+ borderColor: "#D8DDE7",
+ },
+ bottomButtonText: {
+ textAlign: "center",
+ },
+ button: {
+ width: "45%",
+ height: 44,
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ borderWidth: 1,
+ borderColor: "#E5E9F0",
+ borderRadius: 4,
+ backgroundColor: "#F3F5F8",
+ },
+ row: {
+ flexDirection: "row",
+ gap: 16,
+ justifyContent: "space-between",
+ },
+ secondaryButton: {
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "center",
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ height: 40,
+ borderRadius: 4,
+ backgroundColor: "#F3F5F8",
+ borderWidth: 1,
+ borderColor: "#D8DDE7",
+ width: 156,
+ gap: 8,
+ },
+ fullButton: {
+ width: "100%",
+ },
+ buttonText: {
+ fontSize: 14,
+ fontWeight: "500",
+ color: "#252A34",
+ },
+});
diff --git a/components/Payments/PaymentHistoryCard.tsx b/components/Payments/PaymentHistoryCard.tsx
index 354999d..5b8a5d0 100644
--- a/components/Payments/PaymentHistoryCard.tsx
+++ b/components/Payments/PaymentHistoryCard.tsx
@@ -124,7 +124,6 @@ const styles = StyleSheet.create({
textAlign: "left",
},
paymentAmount: {
- width: 69,
height: 20,
fontFamily: "Inter-SemiBold",
fontSize: 14,
@@ -140,7 +139,6 @@ const styles = StyleSheet.create({
alignItems: "center",
},
paymentDetails: {
- width: 237,
height: 16,
fontFamily: "Inter-Regular",
fontSize: 12,
@@ -156,7 +154,6 @@ const styles = StyleSheet.create({
alignItems: "center",
paddingHorizontal: 8,
paddingVertical: 2,
- marginLeft: 4, // simulate gap
},
statusText: {
fontFamily: "Inter-Medium",
diff --git a/constants/config.ts b/constants/config.ts
index e7a65cc..89f1b3d 100644
--- a/constants/config.ts
+++ b/constants/config.ts
@@ -185,11 +185,13 @@ export const issueConfig = [
];
export const payments = {
- MIN_AMOUNT: 200,
+ MIN_AMOUNT: 1,
+ MAX_AMOUNT: 500000,
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",
EMI_WARNING_DAYS_THRESHOLD: 14,
+ AMOUNT_PAID_TO: "Nav Shakti Lithium Pvt. Ltd.",
};
diff --git a/services/i18n/locals/en.json b/services/i18n/locals/en.json
index 70122f8..475bc75 100644
--- a/services/i18n/locals/en.json
+++ b/services/i18n/locals/en.json
@@ -113,7 +113,10 @@
"pay-using-upi": "Pay using UPI App",
"confirm-payment": "Confirm once your payment is completed",
"payment-done": "Payment Done",
- "view-plan": "View Plan"
+ "view-plan": "View Plan",
+ "cancel-payment": "Cancel Payment",
+ "continue-payment": "Continue Payment",
+ "do-you-want-to-cancel-payment": "Are you sure you want to cancel the payment?"
},
"service": {
"schedule-maintenance": "Schedule Maintenance",
diff --git a/services/i18n/locals/hi.json b/services/i18n/locals/hi.json
index fd6c4a6..478c81a 100644
--- a/services/i18n/locals/hi.json
+++ b/services/i18n/locals/hi.json
@@ -113,7 +113,10 @@
"amount-to-be-paid": "भुगतान की जाने वाली राशि",
"pay-using-upi": "UPI ऐप का उपयोग करके भुगतान करें",
"confirm-payment": "भुगतान पूरा होने पर पुष्टि करें।",
- "payment-done": "भुगतान हो गया"
+ "payment-done": "भुगतान हो गया",
+ "cancel-payment": "भुगतान रद्द करें",
+ "continue-payment": "भुगतान जारी रखें",
+ "do-you-want-to-cancel-payment": "क्या आप वाकई भुगतान रद्द करना चाहते हैं?"
},
"service": {
"schedule-maintenance": "शेड्यूल मेंटेनेंस",
diff --git a/utils/Common.ts b/utils/Common.ts
index cd4bc31..54a2bfd 100644
--- a/utils/Common.ts
+++ b/utils/Common.ts
@@ -4,3 +4,7 @@ export const displayValue = (value: any, formatter?: (val: any) => string) => {
}
return formatter ? formatter(value) : value;
};
+
+export const formatCurrency = (amount: number) => {
+ return `₹${amount.toLocaleString()}`;
+};
diff --git a/utils/Payments.ts b/utils/Payments.ts
index e4e9503..ac7ea8c 100644
--- a/utils/Payments.ts
+++ b/utils/Payments.ts
@@ -10,3 +10,28 @@ export const toCamel = (obj: any): any => {
}
return obj;
};
+
+export const formatDate = (dateString?: string | null) => {
+ if (!dateString) {
+ return "--";
+ }
+ const date = dateString ? new Date(dateString) : new Date();
+
+ const optionsDate = {
+ day: "numeric",
+ month: "long",
+ year: "numeric",
+ } as const;
+ const optionsTime = {
+ hour: "numeric",
+ minute: "2-digit",
+ hour12: true,
+ } as const;
+ const optionsWeekday = { weekday: "long" } as const;
+
+ const formattedDate = date.toLocaleDateString("en-IN", optionsDate);
+ const formattedTime = date.toLocaleTimeString("en-IN", optionsTime);
+ const formattedWeekday = date.toLocaleDateString("en-IN", optionsWeekday);
+
+ return `${formattedDate}, ${formattedTime}, ${formattedWeekday}`;
+};