Merge branch 'dev' into qa
All checks were successful
Deploy FitLien services to QA / Deploy to QA (push) Successful in 3m53s

This commit is contained in:
Benoy Bose 2025-06-24 15:19:20 +05:30
commit f19e3b012d
3 changed files with 2419 additions and 1484 deletions

File diff suppressed because it is too large Load Diff

View File

@ -111,8 +111,8 @@ export class InvoiceService {
doc.setFont('helvetica', 'normal'); doc.setFont('helvetica', 'normal');
doc.text(data.customerName, 18, 70); doc.text(data.customerName, 18, 70);
doc.text(`Phone: ${data.phoneNumber}`, 18, 75); doc.text(`${data.phoneNumber}`, 18, 75);
doc.text(`Email: ${data.email}`, 18, 80); doc.text(`${data.email}`, 18, 80);
autoTable(doc, { autoTable(doc, {
startY: 110, startY: 110,

View File

@ -120,6 +120,7 @@ export const phonePeWebhook = onRequest({
const orderData = orderDoc.data(); const orderData = orderDoc.data();
const membershipId = orderData.metaInfo?.membershipId; const membershipId = orderData.metaInfo?.membershipId;
const bookingId = orderData.metaInfo?.bookingId; const bookingId = orderData.metaInfo?.bookingId;
const serviceId = orderData.metaInfo?.serviceId;
if (bookingId) { if (bookingId) {
await processDayPassBooking(payload, orderData, bookingId); await processDayPassBooking(payload, orderData, bookingId);
@ -138,8 +139,10 @@ export const phonePeWebhook = onRequest({
if (paymentUpdateSuccess) { if (paymentUpdateSuccess) {
await processMembershipPayment(payload, orderData, membershipId); await processMembershipPayment(payload, orderData, membershipId);
} }
} else if (serviceId) {
await processServicePayment(payload, orderData, serviceId);
} else { } else {
logger.error(`No membershipId or bookingId found in metaInfo for order: ${payload.merchantOrderId}`); logger.error(`No membershipId, bookingId, or serviceId found in metaInfo for order: ${payload.merchantOrderId}`);
} }
logger.info(`Payment data updated for completed payment: ${payload.merchantOrderId}`); logger.info(`Payment data updated for completed payment: ${payload.merchantOrderId}`);
@ -623,3 +626,206 @@ async function processMembershipPayment(payload: any, orderData: any, membership
} }
} }
} }
async function processServicePayment(payload: any, orderData: any, paymentId: string) {
try {
logger.info(`Processing service payment for serviceId: ${paymentId}`);
const serviceRef = admin.firestore().collection('service_payments').doc(paymentId);
const serviceDoc = await serviceRef.get();
if (!serviceDoc.exists) {
logger.error(`Service booking not found for serviceId: ${paymentId}`);
return;
}
await serviceRef.update({
status: 'ACCEPTED',
paymentDetails: {
transactionId: payload.orderId,
merchantOrderId: payload.merchantOrderId,
amount: orderData.amount,
paymentDate: new Date(),
paymentMethod: 'PhonePe'
},
updatedAt: admin.firestore.FieldValue.serverTimestamp()
});
logger.info(`Updated service booking status to 'CONFIRMED' for serviceId: ${paymentId}`);
const serviceData = serviceDoc.data();
const gymId = orderData.metaInfo?.gymId || serviceData?.gymId;
if (gymId) {
try {
const gymDoc = await admin.firestore().collection('gyms').doc(gymId).get();
let gymName = 'Fitlien';
let gymAddress = '';
let gymOwnerEmail = '';
if (gymDoc.exists) {
const gymData = gymDoc.data();
gymName = gymData?.name || 'Fitlien';
gymAddress = gymData?.address || '';
if (gymData?.userId) {
const gymOwnerDoc = await admin.firestore()
.collection('users')
.doc(gymData.userId)
.get();
if (gymOwnerDoc.exists) {
const gymOwnerData = gymOwnerDoc.data();
gymOwnerEmail = gymOwnerData?.email || '';
}
}
}
const invoiceNumber = `SRV-${payload.merchantOrderId.substring(0, 8)}`;
logger.info(`Generated invoice number for service: ${invoiceNumber}`);
const discountPercentage = orderData.metaInfo?.discount || 0;
const hasDiscount = discountPercentage > 0;
const isFreeService = discountPercentage === 100;
const originalAmount = hasDiscount ?
orderData.amount / (1 - discountPercentage / 100) :
orderData.amount;
const discountText = isFreeService ?
" (Free Service)" :
hasDiscount ? ` (${discountPercentage.toFixed(0)}% discount applied)` :
'';
const amountSaved = hasDiscount ?
originalAmount - orderData.amount :
0;
const invoiceData = {
invoiceNumber,
businessName: gymName,
address: gymAddress,
gstNumber: orderData.metaInfo?.gstNumber,
customerName: orderData.metaInfo?.customerName || serviceData?.customerName || '',
phoneNumber: orderData.metaInfo?.customerPhone || serviceData?.phoneNumber || '',
email: orderData.metaInfo?.customerEmail || serviceData?.email || '',
planName: orderData.metaInfo?.serviceName || serviceData?.serviceName || 'Service',
amount: orderData.amount,
transactionId: payload.orderId,
paymentDate: new Date(),
paymentMethod: 'Online'
};
const invoicePath = await invoiceService.generateInvoice(invoiceData);
logger.info(`Service invoice generated successfully at path: ${invoicePath}`);
await serviceRef.update({
invoicePath: invoicePath,
invoiceNumber: invoiceNumber
});
logger.info(`Updated service booking with invoice path: ${invoicePath}`);
const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath);
const formattedDate = format(new Date(), 'dd/MM/yyyy');
if (invoiceData.email) {
logger.info(`Preparing to send service invoice email to customer: ${invoiceData.email}`);
try {
const emailSubject = isFreeService
? `Free Service Confirmed - ${gymName}`
: `Service Booking Confirmed - ${gymName}`;
const customerEmailHtml = `
<html>
<body>
<h2>${isFreeService ? 'Free Service Confirmed' : 'Service Booking Confirmed'}</h2>
<p>Dear ${invoiceData.customerName},</p>
<p>${isFreeService ? 'Your free service has been successfully confirmed.' : 'Thank you for your payment. Your service booking has been confirmed.'}</p>
<p>Please find attached your invoice for the service.</p>
<p>Service Details:</p>
<ul>
<li>Gym: ${gymName}</li>
<li>Service: ${invoiceData.planName}</li>
${hasDiscount ? `<li>Original Price: ₹${originalAmount.toFixed(2)}</li>` : ''}
${hasDiscount ? `<li>Discount: ${discountPercentage.toFixed(1)}%</li>` : ''}
${hasDiscount ? `<li>You Save: ₹${amountSaved.toFixed(2)}</li>` : ''}
<li>Amount: ${orderData.amount.toFixed(2)}${discountText}</li>
<li>Transaction ID: ${payload.merchantOrderId}</li>
<li>Date: ${formattedDate}</li>
</ul>
<p>If you have any questions, please contact us.</p>
<p>Regards,<br>Fitlien Team</p>
</body>
</html>
`;
await sendEmailWithAttachmentUtil(
invoiceData.email,
emailSubject,
customerEmailHtml,
downloadUrl,
`Invoice_${path.basename(invoicePath)}`
);
logger.info(`Service invoice email sent to customer (${invoiceData.email}) for payment: ${payload.merchantOrderId}`);
} catch (emailError) {
logger.error('Error sending customer service invoice email:', emailError);
}
}
if (gymOwnerEmail) {
logger.info(`Preparing to send service invoice email to gym owner: ${gymOwnerEmail}`);
try {
const ownerEmailSubject = isFreeService
? `Free Service Assigned - ${gymName}`
: `New Service Booking - ${gymName}`;
const gymOwnerEmailHtml = `
<html>
<body>
<h2>${isFreeService ? 'Free Service Assigned' : 'New Service Booking Received'}</h2>
<p>Dear Gym Owner,</p>
<p>${isFreeService ? 'A free service has been assigned' : 'A new service booking has been received'} for your gym.</p>
<p>Customer Details:</p>
<ul>
<li>Name: ${invoiceData.customerName}</li>
<li>Phone: ${invoiceData.phoneNumber}</li>
<li>Email: ${invoiceData.email}</li>
</ul>
<p>Service Details:</p>
<ul>
<li>Service: ${invoiceData.planName}</li>
${hasDiscount ? `<li>Original Price: ₹${originalAmount.toFixed(2)}</li>` : ''}
${hasDiscount ? `<li>Discount: ${discountPercentage.toFixed(1)}%</li>` : ''}
${hasDiscount ? `<li>Amount Saved by Customer: ₹${amountSaved.toFixed(2)}</li>` : ''}
<li>Amount: ${orderData.amount.toFixed(2)}${discountText}</li>
<li>Transaction ID: ${payload.merchantOrderId}</li>
<li>Date: ${formattedDate}</li>
</ul>
<p>Please find the invoice attached.</p>
<p>Regards,<br>Fitlien Team</p>
</body>
</html>
`;
await sendEmailWithAttachmentUtil(
gymOwnerEmail,
ownerEmailSubject,
gymOwnerEmailHtml,
downloadUrl,
`Invoice_${path.basename(invoicePath)}`
);
logger.info(`Service invoice email sent to gym owner (${gymOwnerEmail}) for payment: ${payload.merchantOrderId}`);
} catch (ownerEmailError) {
logger.error('Error sending gym owner service invoice email:', ownerEmailError);
}
}
} catch (invoiceError) {
logger.error('Error generating service invoice:', invoiceError);
}
}
} catch (error) {
logger.error('Error processing service payment:', error);
}
}