phonepe #55
@ -131,328 +131,11 @@ export const phonePeWebhook = onRequest({
|
|||||||
if (paymentUpdateSuccess) {
|
if (paymentUpdateSuccess) {
|
||||||
const orderData = orderDoc.data();
|
const orderData = orderDoc.data();
|
||||||
const membershipId = orderData.metaInfo?.membershipId;
|
const membershipId = orderData.metaInfo?.membershipId;
|
||||||
|
const bookingId = orderData.metaInfo?.bookingId;
|
||||||
logger.info(`Processing invoice for completed payment`, {
|
if (bookingId) {
|
||||||
merchantOrderId: payload.merchantOrderId,
|
await processDayPassBooking(payload, orderData, bookingId);
|
||||||
orderId: payload.orderId,
|
} else if (membershipId) {
|
||||||
membershipId: membershipId || 'not-provided'
|
await processMembershipPayment(payload, orderData, membershipId);
|
||||||
});
|
|
||||||
|
|
||||||
if (membershipId) {
|
|
||||||
try {
|
|
||||||
logger.info(`Fetching membership data for membershipId: ${membershipId}`);
|
|
||||||
const membershipDoc = await admin.firestore()
|
|
||||||
.collection('memberships')
|
|
||||||
.doc(membershipId)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (membershipDoc.exists) {
|
|
||||||
logger.info(`Membership data retrieved successfully for membershipId: ${membershipId}`);
|
|
||||||
|
|
||||||
const membershipData = membershipDoc.data();
|
|
||||||
const uid = membershipData?.userId;
|
|
||||||
|
|
||||||
logger.info(`Fetching user data for uid(Client): ${uid}`);
|
|
||||||
const userDoc = await admin.firestore()
|
|
||||||
.collection('client_profiles')
|
|
||||||
.doc(uid)
|
|
||||||
.get();
|
|
||||||
if (userDoc.exists) {
|
|
||||||
logger.info(`User data retrieved successfully for uid(Client): ${uid}`);
|
|
||||||
|
|
||||||
logger.info(`Starting invoice generation process for payment: ${payload.merchantOrderId}`);
|
|
||||||
|
|
||||||
const userData = userDoc.data();
|
|
||||||
|
|
||||||
const gymId = orderData.metaInfo?.gymId || membershipData?.gymId;
|
|
||||||
let gymName = 'Fitlien';
|
|
||||||
let gymAddress = '';
|
|
||||||
let subscriptionName = '';
|
|
||||||
let gymOwnerEmail = '';
|
|
||||||
let paymentType = orderData.metaInfo?.paymentType || 'Gym Membership';
|
|
||||||
let trainerId = orderData.metaInfo?.trainerId;
|
|
||||||
let trainerData = null;
|
|
||||||
let emailCustomer = membershipData?.fields?.['email'] || membershipData?.fields?.['Email Address'];
|
|
||||||
|
|
||||||
const discountPercentage = orderData.metaInfo?.discount || 0;
|
|
||||||
const hasDiscount = discountPercentage > 0;
|
|
||||||
const isFreeplan = discountPercentage === 100;
|
|
||||||
const originalAmount = hasDiscount ?
|
|
||||||
orderData.amount / (1 - discountPercentage / 100) :
|
|
||||||
orderData.amount;
|
|
||||||
const discountText = isFreeplan ?
|
|
||||||
" (Free Plan)" :
|
|
||||||
hasDiscount ? ` (${discountPercentage.toFixed(0)}% discount applied)` :
|
|
||||||
'';
|
|
||||||
const amountSaved = hasDiscount ?
|
|
||||||
originalAmount - orderData.amount :
|
|
||||||
0;
|
|
||||||
|
|
||||||
if (gymId) {
|
|
||||||
const gymDoc = await admin.firestore()
|
|
||||||
.collection('gyms')
|
|
||||||
.doc(gymId)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (gymDoc.exists) {
|
|
||||||
const gymData = gymDoc.data();
|
|
||||||
gymName = gymData?.name || 'Fitlien';
|
|
||||||
gymAddress = gymData?.address || '';
|
|
||||||
subscriptionName = membershipData?.subscription?.normalizedName || '';
|
|
||||||
|
|
||||||
if (gymData?.userId) {
|
|
||||||
const gymOwnerDoc = await admin.firestore()
|
|
||||||
.collection('users')
|
|
||||||
.doc(gymData.userId)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (gymOwnerDoc.exists) {
|
|
||||||
const gymOwnerData = gymOwnerDoc.data();
|
|
||||||
gymOwnerEmail = gymOwnerData?.email || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paymentType === 'Gym Membership with Personal Training' && trainerId) {
|
|
||||||
try {
|
|
||||||
const trainerDoc = await admin.firestore()
|
|
||||||
.collection('trainer_profiles')
|
|
||||||
.doc(trainerId)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (trainerDoc.exists) {
|
|
||||||
trainerData = trainerDoc.data();
|
|
||||||
}
|
|
||||||
} catch (trainerError) {
|
|
||||||
logger.error('Error fetching trainer data:', trainerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const invoiceNumber = `INV-${payload.merchantOrderId.substring(0, 8)}`;
|
|
||||||
|
|
||||||
logger.info(`Generated invoice number: ${invoiceNumber}`);
|
|
||||||
|
|
||||||
logger.info(`Preparing invoice data for generation`, {
|
|
||||||
invoiceNumber,
|
|
||||||
merchantOrderId: payload.merchantOrderId,
|
|
||||||
gymName: gymName
|
|
||||||
});
|
|
||||||
const invoiceData = {
|
|
||||||
invoiceNumber,
|
|
||||||
businessName: gymName,
|
|
||||||
address: gymAddress,
|
|
||||||
gstNumber: userData?.gstNumber,
|
|
||||||
customerName: userData?.displayName || `${membershipData?.fields?.['first-name'] || ''} ${membershipData?.fields?.['last-name'] || ''}`.trim() || membershipData?.fields?.['First Name'] || '',
|
|
||||||
phoneNumber: membershipData?.fields?.['phone-number'] || membershipData?.fields?.['Phone Number'] || orderData.metaInfo?.phoneNumber || '',
|
|
||||||
email: membershipData?.fields?.['email'] || membershipData?.fields?.['Email Address'] || '',
|
|
||||||
planName: orderData.metaInfo?.planName || subscriptionName,
|
|
||||||
amount: orderData.amount,
|
|
||||||
transactionId: payload.orderId,
|
|
||||||
paymentDate: new Date(),
|
|
||||||
paymentMethod: 'Online'
|
|
||||||
};
|
|
||||||
|
|
||||||
const invoicePath = await invoiceService.generateInvoice(invoiceData);
|
|
||||||
logger.info(`Invoice generated successfully at path: ${invoicePath}`);
|
|
||||||
|
|
||||||
logger.info(`Updating membership payment with invoice path`, {
|
|
||||||
membershipId,
|
|
||||||
invoicePath
|
|
||||||
});
|
|
||||||
|
|
||||||
await admin.firestore()
|
|
||||||
.collection('membership_payments')
|
|
||||||
.doc(membershipId)
|
|
||||||
.get()
|
|
||||||
.then(async (doc) => {
|
|
||||||
if (doc.exists) {
|
|
||||||
logger.info(`Found membership payment document for membershipId: ${membershipId}`);
|
|
||||||
|
|
||||||
const paymentsData = doc.data()?.payments || [];
|
|
||||||
let paymentFound = false;
|
|
||||||
|
|
||||||
for (let i = 0; i < paymentsData.length; i++) {
|
|
||||||
if (paymentsData[i].referenceNumber === payload.merchantOrderId ||
|
|
||||||
paymentsData[i].transactionId === payload.orderId) {
|
|
||||||
paymentsData[i].invoicePath = invoicePath;
|
|
||||||
paymentFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Payment record ${paymentFound ? 'found' : 'not found'} in membership payments`, {
|
|
||||||
membershipId,
|
|
||||||
merchantOrderId: payload.merchantOrderId,
|
|
||||||
orderId: payload.orderId
|
|
||||||
});
|
|
||||||
|
|
||||||
await doc.ref.update({
|
|
||||||
'payments': paymentsData,
|
|
||||||
'updatedAt': admin.firestore.FieldValue.serverTimestamp(),
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(`Successfully updated membership payment with invoice path`, {
|
|
||||||
membershipId,
|
|
||||||
invoicePath
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logger.warn(`No membership payment document found for membershipId: ${membershipId}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(`Generated invoice for payment: ${payload.merchantOrderId}, path: ${invoicePath}`);
|
|
||||||
|
|
||||||
logger.info(`Getting download URL for invoice: ${invoicePath}`);
|
|
||||||
const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath);
|
|
||||||
logger.info(`Generated download URL for invoice: ${invoicePath}`);
|
|
||||||
|
|
||||||
const formattedDate = format(new Date(), 'dd/MM/yyyy');
|
|
||||||
|
|
||||||
if (emailCustomer) {
|
|
||||||
logger.info(`Preparing to send invoice email to customer: ${membershipData?.fields?.['email']}`);
|
|
||||||
try {
|
|
||||||
const emailSubject = isFreeplan
|
|
||||||
? `Free Plan Assigned - ${gymName}`
|
|
||||||
: `New Membership - ${gymName}`;
|
|
||||||
|
|
||||||
const customerEmailHtml = `
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h2>${isFreeplan ? 'Free Plan Assigned' : 'Thank you for your payment'}</h2>
|
|
||||||
<p>Dear ${invoiceData.customerName},</p>
|
|
||||||
<p>${isFreeplan ? 'Your free membership has been successfully activated.' : 'Thank you for your payment. Your membership has been successfully activated.'}</p>
|
|
||||||
<p>Please find attached your invoice for the ${isFreeplan ? 'membership' : 'payment'}.</p>
|
|
||||||
<p>Membership Details:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Gym: ${gymName}</li>
|
|
||||||
${trainerData ? `<li>Trainer: ${trainerData.fullName || 'Your Personal Trainer'}</li>` : ''}
|
|
||||||
<li>Plan: ${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>
|
|
||||||
${isFreeplan ? '<li>Payment Method: Online}</li>' : ''}
|
|
||||||
</ul>
|
|
||||||
<p>If you have any questions, please contact us.</p>
|
|
||||||
<p>Regards,<br>Fitlien Team</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
|
|
||||||
await sendEmailWithAttachmentUtil(
|
|
||||||
emailCustomer,
|
|
||||||
emailSubject,
|
|
||||||
customerEmailHtml,
|
|
||||||
downloadUrl,
|
|
||||||
`Invoice_${path.basename(invoicePath)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info(`Invoice email sent to ${membershipData?.fields?.['email']} for payment: ${payload.merchantOrderId}`);
|
|
||||||
} catch (emailError) {
|
|
||||||
logger.error('Error sending customer invoice email:', emailError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gymOwnerEmail) {
|
|
||||||
logger.info(`Preparing to send invoice email to gym owner: ${gymOwnerEmail}`);
|
|
||||||
try {
|
|
||||||
const ownerEmailSubject = isFreeplan
|
|
||||||
? `Free Plan Assigned${paymentType === 'Gym Membership with Personal Training' ? ' with Personal Training' : ''} - ${gymName}`
|
|
||||||
: `New Membership${paymentType === 'Gym Membership with Personal Training' ? ' with Personal Training' : ''} - ${gymName}`;
|
|
||||||
|
|
||||||
const gymOwnerEmailHtml = `
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h2>${isFreeplan ? 'Free Plan Assigned' : `New ${paymentType} Booking Received`}</h2>
|
|
||||||
<p>Dear Gym Owner,</p>
|
|
||||||
<p>${isFreeplan ? 'A free membership' : 'A new membership'}${paymentType === 'Gym Membership with Personal Training' ? ' with personal training' : ''} has been ${isFreeplan ? 'assigned' : 'received'} for your gym.</p>
|
|
||||||
<p>Customer Details:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Name: ${invoiceData.customerName}</li>
|
|
||||||
<li>Email: ${invoiceData.email}</li>
|
|
||||||
<li>Phone: ${invoiceData.phoneNumber}</li>
|
|
||||||
</ul>
|
|
||||||
<p>Booking Details:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Type: ${invoiceData.planName}</li>
|
|
||||||
${trainerData ? `<li>Trainer: ${trainerData.fullName || 'Personal Trainer'}</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(`Invoice email sent to gym owner (${gymOwnerEmail}) for payment: ${payload.merchantOrderId}`);
|
|
||||||
} catch (ownerEmailError) {
|
|
||||||
logger.error('Error sending gym owner invoice email:', ownerEmailError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paymentType === 'Gym Membership with Personal Training' && trainerData && trainerData.email) {
|
|
||||||
try {
|
|
||||||
const trainerEmailHtml = `
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h2>New Personal Training Client</h2>
|
|
||||||
<p>Dear ${trainerData.fullName || 'Trainer'},</p>
|
|
||||||
<p>A new client has signed up for personal training with you at ${gymName}.</p>
|
|
||||||
<p>Client Details:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Name: ${invoiceData.customerName}</li>
|
|
||||||
<li>Email: ${invoiceData.email}</li>
|
|
||||||
<li>Phone: ${invoiceData.phoneNumber}</li>
|
|
||||||
</ul>
|
|
||||||
<p>Booking Details:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Type: Personal Training Membership</li>
|
|
||||||
${hasDiscount ? `<li>Original Price: ₹${originalAmount.toFixed(2)}</li>` : ''}
|
|
||||||
${hasDiscount ? `<li>Discount: ${discountPercentage.toFixed(1)}%</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(
|
|
||||||
trainerData.email,
|
|
||||||
`New Personal Training Client - ${gymName}`,
|
|
||||||
trainerEmailHtml,
|
|
||||||
downloadUrl,
|
|
||||||
`Invoice_${path.basename(invoicePath)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info(`Invoice email sent to trainer (${trainerData.email}) for payment: ${payload.merchantOrderId}`);
|
|
||||||
} catch (trainerEmailError) {
|
|
||||||
logger.error('Error sending trainer invoice email:', trainerEmailError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (invoiceError) {
|
|
||||||
logger.error('Error generating invoice:', invoiceError);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,3 +156,467 @@ export const phonePeWebhook = onRequest({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function processDayPassBooking(payload: any, orderData: any, bookingId: string) {
|
||||||
|
try {
|
||||||
|
logger.info(`Processing day pass booking for bookingId: ${bookingId}`);
|
||||||
|
|
||||||
|
const bookingRef = admin.firestore().collection('day_pass_bookings').doc(bookingId);
|
||||||
|
const bookingDoc = await bookingRef.get();
|
||||||
|
|
||||||
|
if (!bookingDoc.exists) {
|
||||||
|
logger.error(`Day pass booking not found for bookingId: ${bookingId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await bookingRef.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 day pass booking status to 'Accepted' for bookingId: ${bookingId}`);
|
||||||
|
|
||||||
|
const bookingData = bookingDoc.data();
|
||||||
|
const gymId = orderData.metaInfo?.gymId || bookingData?.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 = `INV-${payload.merchantOrderId.substring(0, 8)}`;
|
||||||
|
|
||||||
|
logger.info(`Generated invoice number for day pass: ${invoiceNumber}`);
|
||||||
|
|
||||||
|
const invoiceData = {
|
||||||
|
invoiceNumber,
|
||||||
|
businessName: gymName,
|
||||||
|
address: gymAddress,
|
||||||
|
gstNumber: orderData.metaInfo?.gstNumber,
|
||||||
|
customerName: orderData.metaInfo?.customerName || bookingData?.customerName || '',
|
||||||
|
phoneNumber: orderData.metaInfo?.customerPhone || bookingData?.phoneNumber || '',
|
||||||
|
email: orderData.metaInfo?.customerEmail || bookingData?.email || '',
|
||||||
|
planName: 'Day Pass',
|
||||||
|
amount: orderData.amount,
|
||||||
|
transactionId: payload.orderId,
|
||||||
|
paymentDate: new Date(),
|
||||||
|
paymentMethod: 'Online'
|
||||||
|
};
|
||||||
|
|
||||||
|
const invoicePath = await invoiceService.generateInvoice(invoiceData);
|
||||||
|
logger.info(`Day pass invoice generated successfully at path: ${invoicePath}`);
|
||||||
|
|
||||||
|
await bookingRef.update({
|
||||||
|
invoicePath: invoicePath,
|
||||||
|
invoiceNumber: invoiceNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Updated day pass booking with invoice path: ${invoicePath}`);
|
||||||
|
|
||||||
|
const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath);
|
||||||
|
const formattedDate = format(new Date(), 'dd/MM/yyyy');
|
||||||
|
|
||||||
|
if (gymOwnerEmail) {
|
||||||
|
logger.info(`Preparing to send day pass invoice email to gym owner: ${gymOwnerEmail}`);
|
||||||
|
try {
|
||||||
|
const ownerEmailSubject = `New Day Pass Payment - ${gymName}`;
|
||||||
|
|
||||||
|
const gymOwnerEmailHtml = `
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h2>New Day Pass Payment Received</h2>
|
||||||
|
<p>Dear Gym Owner,</p>
|
||||||
|
<p>A new day pass payment has been received for your gym.</p>
|
||||||
|
<p>Customer Details:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Name: ${invoiceData.customerName}</li>
|
||||||
|
<li>Phone: ${invoiceData.phoneNumber}</li>
|
||||||
|
</ul>
|
||||||
|
<p>Day Pass Details:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Service: Day Pass</li>
|
||||||
|
<li>Amount: ₹${orderData.amount.toFixed(2)}</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(`Day pass invoice email sent to gym owner (${gymOwnerEmail}) for payment: ${payload.merchantOrderId}`);
|
||||||
|
} catch (ownerEmailError) {
|
||||||
|
logger.error('Error sending gym owner day pass invoice email:', ownerEmailError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (invoiceError) {
|
||||||
|
logger.error('Error generating day pass invoice:', invoiceError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error processing day pass booking:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processMembershipPayment(payload: any, orderData: any, membershipId: string) {
|
||||||
|
logger.info(`Processing membership for completed payment`, {
|
||||||
|
merchantOrderId: payload.merchantOrderId,
|
||||||
|
orderId: payload.orderId,
|
||||||
|
membershipId: membershipId || 'not-provided'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (membershipId) {
|
||||||
|
try {
|
||||||
|
logger.info(`Fetching membership data for membershipId: ${membershipId}`);
|
||||||
|
const membershipDoc = await admin.firestore()
|
||||||
|
.collection('memberships')
|
||||||
|
.doc(membershipId)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (membershipDoc.exists) {
|
||||||
|
logger.info(`Membership data retrieved successfully for membershipId: ${membershipId}`);
|
||||||
|
|
||||||
|
const membershipData = membershipDoc.data();
|
||||||
|
const uid = membershipData?.userId;
|
||||||
|
|
||||||
|
logger.info(`Fetching user data for uid(Client): ${uid}`);
|
||||||
|
const userDoc = await admin.firestore()
|
||||||
|
.collection('client_profiles')
|
||||||
|
.doc(uid)
|
||||||
|
.get();
|
||||||
|
if (userDoc.exists) {
|
||||||
|
logger.info(`User data retrieved successfully for uid(Client): ${uid}`);
|
||||||
|
|
||||||
|
logger.info(`Starting invoice generation process for payment: ${payload.merchantOrderId}`);
|
||||||
|
|
||||||
|
const userData = userDoc.data();
|
||||||
|
|
||||||
|
const gymId = orderData.metaInfo?.gymId || membershipData?.gymId;
|
||||||
|
let gymName = 'Fitlien';
|
||||||
|
let gymAddress = '';
|
||||||
|
let subscriptionName = '';
|
||||||
|
let gymOwnerEmail = '';
|
||||||
|
let paymentType = orderData.metaInfo?.paymentType || 'Gym Membership';
|
||||||
|
let trainerId = orderData.metaInfo?.trainerId;
|
||||||
|
let trainerData = null;
|
||||||
|
let emailCustomer = membershipData?.fields?.['email'] || membershipData?.fields?.['Email Address'];
|
||||||
|
|
||||||
|
const discountPercentage = orderData.metaInfo?.discount || 0;
|
||||||
|
const hasDiscount = discountPercentage > 0;
|
||||||
|
const isFreeplan = discountPercentage === 100;
|
||||||
|
const originalAmount = hasDiscount ?
|
||||||
|
orderData.amount / (1 - discountPercentage / 100) :
|
||||||
|
orderData.amount;
|
||||||
|
const discountText = isFreeplan ?
|
||||||
|
" (Free Plan)" :
|
||||||
|
hasDiscount ? ` (${discountPercentage.toFixed(0)}% discount applied)` :
|
||||||
|
'';
|
||||||
|
const amountSaved = hasDiscount ?
|
||||||
|
originalAmount - orderData.amount :
|
||||||
|
0;
|
||||||
|
|
||||||
|
if (gymId) {
|
||||||
|
const gymDoc = await admin.firestore()
|
||||||
|
.collection('gyms')
|
||||||
|
.doc(gymId)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (gymDoc.exists) {
|
||||||
|
const gymData = gymDoc.data();
|
||||||
|
gymName = gymData?.name || 'Fitlien';
|
||||||
|
gymAddress = gymData?.address || '';
|
||||||
|
subscriptionName = membershipData?.subscription?.normalizedName || '';
|
||||||
|
|
||||||
|
if (gymData?.userId) {
|
||||||
|
const gymOwnerDoc = await admin.firestore()
|
||||||
|
.collection('users')
|
||||||
|
.doc(gymData.userId)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (gymOwnerDoc.exists) {
|
||||||
|
const gymOwnerData = gymOwnerDoc.data();
|
||||||
|
gymOwnerEmail = gymOwnerData?.email || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paymentType === 'Gym Membership with Personal Training' && trainerId) {
|
||||||
|
try {
|
||||||
|
const trainerDoc = await admin.firestore()
|
||||||
|
.collection('trainer_profiles')
|
||||||
|
.doc(trainerId)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (trainerDoc.exists) {
|
||||||
|
trainerData = trainerDoc.data();
|
||||||
|
}
|
||||||
|
} catch (trainerError) {
|
||||||
|
logger.error('Error fetching trainer data:', trainerError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoiceNumber = `INV-${payload.merchantOrderId.substring(0, 8)}`;
|
||||||
|
|
||||||
|
logger.info(`Generated invoice number: ${invoiceNumber}`);
|
||||||
|
|
||||||
|
logger.info(`Preparing invoice data for generation`, {
|
||||||
|
invoiceNumber,
|
||||||
|
merchantOrderId: payload.merchantOrderId,
|
||||||
|
gymName: gymName
|
||||||
|
});
|
||||||
|
const invoiceData = {
|
||||||
|
invoiceNumber,
|
||||||
|
businessName: gymName,
|
||||||
|
address: gymAddress,
|
||||||
|
gstNumber: userData?.gstNumber,
|
||||||
|
customerName: userData?.displayName || `${membershipData?.fields?.['first-name'] || ''} ${membershipData?.fields?.['last-name'] || ''}`.trim() || membershipData?.fields?.['First Name'] || '',
|
||||||
|
phoneNumber: membershipData?.fields?.['phone-number'] || membershipData?.fields?.['Phone Number'] || orderData.metaInfo?.phoneNumber || '',
|
||||||
|
email: membershipData?.fields?.['email'] || membershipData?.fields?.['Email Address'] || '',
|
||||||
|
planName: orderData.metaInfo?.planName || subscriptionName,
|
||||||
|
amount: orderData.amount,
|
||||||
|
transactionId: payload.orderId,
|
||||||
|
paymentDate: new Date(),
|
||||||
|
paymentMethod: 'Online'
|
||||||
|
};
|
||||||
|
|
||||||
|
const invoicePath = await invoiceService.generateInvoice(invoiceData);
|
||||||
|
logger.info(`Invoice generated successfully at path: ${invoicePath}`);
|
||||||
|
|
||||||
|
logger.info(`Updating membership payment with invoice path`, {
|
||||||
|
membershipId,
|
||||||
|
invoicePath
|
||||||
|
});
|
||||||
|
|
||||||
|
await admin.firestore()
|
||||||
|
.collection('membership_payments')
|
||||||
|
.doc(membershipId)
|
||||||
|
.get()
|
||||||
|
.then(async (doc) => {
|
||||||
|
if (doc.exists) {
|
||||||
|
logger.info(`Found membership payment document for membershipId: ${membershipId}`);
|
||||||
|
|
||||||
|
const paymentsData = doc.data()?.payments || [];
|
||||||
|
let paymentFound = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < paymentsData.length; i++) {
|
||||||
|
if (paymentsData[i].referenceNumber === payload.merchantOrderId ||
|
||||||
|
paymentsData[i].transactionId === payload.orderId) {
|
||||||
|
paymentsData[i].invoicePath = invoicePath;
|
||||||
|
paymentFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Payment record ${paymentFound ? 'found' : 'not found'} in membership payments`, {
|
||||||
|
membershipId,
|
||||||
|
merchantOrderId: payload.merchantOrderId,
|
||||||
|
orderId: payload.orderId
|
||||||
|
});
|
||||||
|
|
||||||
|
await doc.ref.update({
|
||||||
|
'payments': paymentsData,
|
||||||
|
'updatedAt': admin.firestore.FieldValue.serverTimestamp(),
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Successfully updated membership payment with invoice path`, {
|
||||||
|
membershipId,
|
||||||
|
invoicePath
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.warn(`No membership payment document found for membershipId: ${membershipId}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Generated invoice for payment: ${payload.merchantOrderId}, path: ${invoicePath}`);
|
||||||
|
|
||||||
|
logger.info(`Getting download URL for invoice: ${invoicePath}`);
|
||||||
|
const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath);
|
||||||
|
logger.info(`Generated download URL for invoice: ${invoicePath}`);
|
||||||
|
|
||||||
|
const formattedDate = format(new Date(), 'dd/MM/yyyy');
|
||||||
|
|
||||||
|
if (emailCustomer) {
|
||||||
|
logger.info(`Preparing to send invoice email to customer: ${membershipData?.fields?.['email']}`);
|
||||||
|
try {
|
||||||
|
const emailSubject = isFreeplan
|
||||||
|
? `Free Plan Assigned - ${gymName}`
|
||||||
|
: `New Membership - ${gymName}`;
|
||||||
|
|
||||||
|
const customerEmailHtml = `
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h2>${isFreeplan ? 'Free Plan Assigned' : 'Thank you for your payment'}</h2>
|
||||||
|
<p>Dear ${invoiceData.customerName},</p>
|
||||||
|
<p>${isFreeplan ? 'Your free membership has been successfully activated.' : 'Thank you for your payment. Your membership has been successfully activated.'}</p>
|
||||||
|
<p>Please find attached your invoice for the ${isFreeplan ? 'membership' : 'payment'}.</p>
|
||||||
|
<p>Membership Details:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Gym: ${gymName}</li>
|
||||||
|
${trainerData ? `<li>Trainer: ${trainerData.fullName || 'Your Personal Trainer'}</li>` : ''}
|
||||||
|
<li>Plan: ${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>
|
||||||
|
${isFreeplan ? '<li>Payment Method: Online}</li>' : ''}
|
||||||
|
</ul>
|
||||||
|
<p>If you have any questions, please contact us.</p>
|
||||||
|
<p>Regards,<br>Fitlien Team</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
await sendEmailWithAttachmentUtil(
|
||||||
|
emailCustomer,
|
||||||
|
emailSubject,
|
||||||
|
customerEmailHtml,
|
||||||
|
downloadUrl,
|
||||||
|
`Invoice_${path.basename(invoicePath)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(`Invoice email sent to ${membershipData?.fields?.['email']} for payment: ${payload.merchantOrderId}`);
|
||||||
|
} catch (emailError) {
|
||||||
|
logger.error('Error sending customer invoice email:', emailError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gymOwnerEmail) {
|
||||||
|
logger.info(`Preparing to send invoice email to gym owner: ${gymOwnerEmail}`);
|
||||||
|
try {
|
||||||
|
const ownerEmailSubject = isFreeplan
|
||||||
|
? `Free Plan Assigned${paymentType === 'Gym Membership with Personal Training' ? ' with Personal Training' : ''} - ${gymName}`
|
||||||
|
: `New Membership${paymentType === 'Gym Membership with Personal Training' ? ' with Personal Training' : ''} - ${gymName}`;
|
||||||
|
|
||||||
|
const gymOwnerEmailHtml = `
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h2>${isFreeplan ? 'Free Plan Assigned' : `New ${paymentType} Booking Received`}</h2>
|
||||||
|
<p>Dear Gym Owner,</p>
|
||||||
|
<p>${isFreeplan ? 'A free membership' : 'A new membership'}${paymentType === 'Gym Membership with Personal Training' ? ' with personal training' : ''} has been ${isFreeplan ? 'assigned' : 'received'} for your gym.</p>
|
||||||
|
<p>Customer Details:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Name: ${invoiceData.customerName}</li>
|
||||||
|
<li>Email: ${invoiceData.email}</li>
|
||||||
|
<li>Phone: ${invoiceData.phoneNumber}</li>
|
||||||
|
</ul>
|
||||||
|
<p>Booking Details:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Type: ${invoiceData.planName}</li>
|
||||||
|
${trainerData ? `<li>Trainer: ${trainerData.fullName || 'Personal Trainer'}</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(`Invoice email sent to gym owner (${gymOwnerEmail}) for payment: ${payload.merchantOrderId}`);
|
||||||
|
} catch (ownerEmailError) {
|
||||||
|
logger.error('Error sending gym owner invoice email:', ownerEmailError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paymentType === 'Gym Membership with Personal Training' && trainerData && trainerData.email) {
|
||||||
|
try {
|
||||||
|
const trainerEmailHtml = `
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h2>New Personal Training Client</h2>
|
||||||
|
<p>Dear ${trainerData.fullName || 'Trainer'},</p>
|
||||||
|
<p>A new client has signed up for personal training with you at ${gymName}.</p>
|
||||||
|
<p>Client Details:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Name: ${invoiceData.customerName}</li>
|
||||||
|
<li>Email: ${invoiceData.email}</li>
|
||||||
|
<li>Phone: ${invoiceData.phoneNumber}</li>
|
||||||
|
</ul>
|
||||||
|
<p>Booking Details:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Type: Personal Training Membership</li>
|
||||||
|
${hasDiscount ? `<li>Original Price: ₹${originalAmount.toFixed(2)}</li>` : ''}
|
||||||
|
${hasDiscount ? `<li>Discount: ${discountPercentage.toFixed(1)}%</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(
|
||||||
|
trainerData.email,
|
||||||
|
`New Personal Training Client - ${gymName}`,
|
||||||
|
trainerEmailHtml,
|
||||||
|
downloadUrl,
|
||||||
|
`Invoice_${path.basename(invoicePath)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(`Invoice email sent to trainer (${trainerData.email}) for payment: ${payload.merchantOrderId}`);
|
||||||
|
} catch (trainerEmailError) {
|
||||||
|
logger.error('Error sending trainer invoice email:', trainerEmailError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (invoiceError) {
|
||||||
|
logger.error('Error generating invoice:', invoiceError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user