diff --git a/.gitea/workflows/deploy-qa.yaml b/.gitea/workflows/deploy-qa.yaml
index 9607ecf..c868dcb 100644
--- a/.gitea/workflows/deploy-qa.yaml
+++ b/.gitea/workflows/deploy-qa.yaml
@@ -1,4 +1,4 @@
-name: Deploy FitLien services to Dev
+name: Deploy FitLien services to QA
on:
push:
diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml
new file mode 100644
index 0000000..dbc0dd5
--- /dev/null
+++ b/.gitea/workflows/deploy.yaml
@@ -0,0 +1,64 @@
+name: Deploy FitLien services
+
+on:
+ push:
+ branches:
+ - main
+jobs:
+ deploy:
+ name: Deploy
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 22
+
+ - name: Clean install
+ run: npm clean-install
+
+ - name: Copy .env.example to .env
+ run: cp functions/.env.example functions/.env
+
+ - name: Replace variables in .env
+ run: |
+ sed -i "s/#{TWILIO_ACCOUNT_SID}#/${{ secrets.TWILIO_ACCOUNT_SID }}/" functions/.env
+ sed -i "s/#{TWILIO_AUTH_TOKEN}#/${{ secrets.TWILIO_AUTH_TOKEN }}/" functions/.env
+ sed -i "s/#{TWILIO_PHONE_NUMBER}#/${{ secrets.TWILIO_PHONE_NUMBER }}/" functions/.env
+ sed -i "s/#{SERVICES_RGN}#/${{ vars.SERVICES_RGN }}/" functions/.env
+ sed -i "s/#{GOOGLE_MAPS_API_KEY}#/${{ secrets.GOOGLE_MAPS_API_KEY }}/" functions/.env
+ sed -i "s/#{SES_FROM_EMAIL}#/${{ vars.SES_FROM_EMAIL }}/" functions/.env
+ sed -i "s/#{SES_REPLY_TO_EMAIL}#/${{ vars.SES_REPLY_TO_EMAIL }}/" functions/.env
+ sed -i "s/#{AWS_ACCESS_KEY_ID}#/${{ secrets.AWS_ACCESS_KEY_ID }}/" functions/.env
+ sed -i "s/#{AWS_SECRET_ACCESS_KEY}#/${{ secrets.AWS_SECRET_ACCESS_KEY }}/" functions/.env
+ sed -i "s/#{AWS_REGION}#/${{ secrets.AWS_REGION }}/" functions/.env
+ sed -i "s/#{PHONEPE_CLIENT_ID}#/${{ secrets.PHONEPE_CLIENT_ID }}/" functions/.env
+ sed -i "s/#{PHONEPE_CLIENT_SECRET}#/${{ secrets.PHONEPE_CLIENT_SECRET }}/" functions/.env
+ sed -i "s/#{PHONEPE_API_URL}#/${{ secrets.PHONEPE_API_URL }}/" functions/.env
+ sed -i "s/#{PHONEPE_WEBHOOK_USERNAME}#/${{ secrets.PHONEPE_WEBHOOK_USERNAME }}/" functions/.env
+ sed -i "s/#{PHONEPE_WEBHOOK_PASSWORD}#/${{ secrets.PHONEPE_WEBHOOK_PASSWORD }}/" functions/.env
+
+ cat functions/.env
+ - name: "Replace #{SERVICES_RGN}# in all .ts files"
+ run: |
+ find . -type f -name "*.ts" -exec sed -i "s/#{SERVICES_RGN}#/${{ vars.SERVICES_RGN }}/g" {} +
+
+ - name: Build
+ run: |
+ npm install -g typescript
+ cd functions
+ npm install
+ npx tsc
+ cd ..
+ ls -la
+
+ - name: Deploy
+ run: |
+ curl -sL firebase.tools | upgrade=true bash
+ firebase use --token ${{ secrets.FIREBASE_TOKEN }} release
+ firebase deploy --token "${{ secrets.FIREBASE_TOKEN }}" --force --non-interactive
diff --git a/firebase.json b/firebase.json
index 21d0f5f..d9a5d56 100644
--- a/firebase.json
+++ b/firebase.json
@@ -1,7 +1,8 @@
{
"firestore": {
"rules": "firestore.rules",
- "indexes": "firestore.indexes.json"
+ "indexes": "firestore.indexes.json",
+ "database": "(default)"
},
"functions": [
{
@@ -43,4 +44,4 @@
"remoteconfig": {
"template": "remoteconfig.template.json"
}
-}
+}
\ No newline at end of file
diff --git a/functions/src/payments/phonepe/webhook.ts b/functions/src/payments/phonepe/webhook.ts
index bf09946..1cb7cff 100644
--- a/functions/src/payments/phonepe/webhook.ts
+++ b/functions/src/payments/phonepe/webhook.ts
@@ -117,343 +117,29 @@ export const phonePeWebhook = onRequest({
try {
logger.info(`Starting payment update process for merchantOrderId: ${payload.merchantOrderId}`);
- const paymentUpdateSuccess = await updatePaymentDataAfterSuccess(
- payload.merchantOrderId,
- payload.orderId,
- payload
- );
+ const orderData = orderDoc.data();
+ const membershipId = orderData.metaInfo?.membershipId;
+ const bookingId = orderData.metaInfo?.bookingId;
- logger.info(`Payment update result for merchantOrderId: ${payload.merchantOrderId}`, {
- success: paymentUpdateSuccess,
- orderId: payload.orderId
- });
+ if (bookingId) {
+ await processDayPassBooking(payload, orderData, bookingId);
+ } else if (membershipId) {
+ const paymentUpdateSuccess = await updatePaymentDataAfterSuccess(
+ payload.merchantOrderId,
+ payload.orderId,
+ payload
+ );
- if (paymentUpdateSuccess) {
- const orderData = orderDoc.data();
- const membershipId = orderData.metaInfo?.membershipId;
-
- logger.info(`Processing invoice for completed payment`, {
- merchantOrderId: payload.merchantOrderId,
- orderId: payload.orderId,
- membershipId: membershipId || 'not-provided'
+ logger.info(`Payment update result for membershipId: ${membershipId}`, {
+ success: paymentUpdateSuccess,
+ orderId: payload.orderId
});
- 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 = `
-
-
- ${isFreeplan ? 'Free Plan Assigned' : 'Thank you for your payment'}
- Dear ${invoiceData.customerName},
- ${isFreeplan ? 'Your free membership has been successfully activated.' : 'Thank you for your payment. Your membership has been successfully activated.'}
- Please find attached your invoice for the ${isFreeplan ? 'membership' : 'payment'}.
- Membership Details:
-
- - Gym: ${gymName}
- ${trainerData ? `- Trainer: ${trainerData.fullName || 'Your Personal Trainer'}
` : ''}
- - Plan: ${invoiceData.planName}
- ${hasDiscount ? `- Original Price: ₹${originalAmount.toFixed(2)}
` : ''}
- ${hasDiscount ? `- Discount: ${discountPercentage.toFixed(1)}%
` : ''}
- ${hasDiscount ? `- You Save: ₹${amountSaved.toFixed(2)}
` : ''}
- - Amount: ₹${orderData.amount.toFixed(2)}${discountText}
- - Transaction ID: ${payload.merchantOrderId}
- - Date: ${formattedDate}
- ${isFreeplan ? '- Payment Method: Online}
' : ''}
-
- If you have any questions, please contact us.
- Regards,
Fitlien Team
-
-
- `;
-
- 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 = `
-
-
- ${isFreeplan ? 'Free Plan Assigned' : `New ${paymentType} Booking Received`}
- Dear Gym Owner,
- ${isFreeplan ? 'A free membership' : 'A new membership'}${paymentType === 'Gym Membership with Personal Training' ? ' with personal training' : ''} has been ${isFreeplan ? 'assigned' : 'received'} for your gym.
- Customer Details:
-
- - Name: ${invoiceData.customerName}
- - Email: ${invoiceData.email}
- - Phone: ${invoiceData.phoneNumber}
-
- Booking Details:
-
- - Type: ${invoiceData.planName}
- ${trainerData ? `- Trainer: ${trainerData.fullName || 'Personal Trainer'}
` : ''}
- ${hasDiscount ? `- Original Price: ₹${originalAmount.toFixed(2)}
` : ''}
- ${hasDiscount ? `- Discount: ${discountPercentage.toFixed(1)}%
` : ''}
- ${hasDiscount ? `- Amount Saved by Customer: ₹${amountSaved.toFixed(2)}
` : ''}
- - Amount: ₹${orderData.amount.toFixed(2)}${discountText}
- - Transaction ID: ${payload.merchantOrderId}
- - Date: ${formattedDate}
-
- Please find the invoice attached.
- Regards,
Fitlien Team
-
-
- `;
-
- 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 = `
-
-
- New Personal Training Client
- Dear ${trainerData.fullName || 'Trainer'},
- A new client has signed up for personal training with you at ${gymName}.
- Client Details:
-
- - Name: ${invoiceData.customerName}
- - Email: ${invoiceData.email}
- - Phone: ${invoiceData.phoneNumber}
-
- Booking Details:
-
- - Type: Personal Training Membership
- ${hasDiscount ? `- Original Price: ₹${originalAmount.toFixed(2)}
` : ''}
- ${hasDiscount ? `- Discount: ${discountPercentage.toFixed(1)}%
` : ''}
- - Amount: ₹${orderData.amount.toFixed(2)}${discountText}
- - Transaction ID: ${payload.merchantOrderId}
- - Date: ${formattedDate}
-
- Please find the invoice attached.
- Regards,
Fitlien Team
-
-
- `;
-
- 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);
- }
+ if (paymentUpdateSuccess) {
+ await processMembershipPayment(payload, orderData, membershipId);
}
+ } else {
+ logger.error(`No membershipId or bookingId found in metaInfo for order: ${payload.merchantOrderId}`);
}
logger.info(`Payment data updated for completed payment: ${payload.merchantOrderId}`);
@@ -473,3 +159,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 = `
+
+
+ New Day Pass Payment Received
+ Dear Gym Owner,
+ A new day pass payment has been received for your gym.
+ Customer Details:
+
+ - Name: ${invoiceData.customerName}
+ - Phone: ${invoiceData.phoneNumber}
+
+ Day Pass Details:
+
+ - Service: Day Pass
+ - Amount: ₹${orderData.amount.toFixed(2)}
+ - Transaction ID: ${payload.merchantOrderId}
+ - Date: ${formattedDate}
+
+ Please find the invoice attached.
+ Regards,
Fitlien Team
+
+
+ `;
+
+ 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 = `
+
+
+ ${isFreeplan ? 'Free Plan Assigned' : 'Thank you for your payment'}
+ Dear ${invoiceData.customerName},
+ ${isFreeplan ? 'Your free membership has been successfully activated.' : 'Thank you for your payment. Your membership has been successfully activated.'}
+ Please find attached your invoice for the ${isFreeplan ? 'membership' : 'payment'}.
+ Membership Details:
+
+ - Gym: ${gymName}
+ ${trainerData ? `- Trainer: ${trainerData.fullName || 'Your Personal Trainer'}
` : ''}
+ - Plan: ${invoiceData.planName}
+ ${hasDiscount ? `- Original Price: ₹${originalAmount.toFixed(2)}
` : ''}
+ ${hasDiscount ? `- Discount: ${discountPercentage.toFixed(1)}%
` : ''}
+ ${hasDiscount ? `- You Save: ₹${amountSaved.toFixed(2)}
` : ''}
+ - Amount: ₹${orderData.amount.toFixed(2)}${discountText}
+ - Transaction ID: ${payload.merchantOrderId}
+ - Date: ${formattedDate}
+ ${isFreeplan ? '- Payment Method: Online}
' : ''}
+
+ If you have any questions, please contact us.
+ Regards,
Fitlien Team
+
+
+ `;
+
+ 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 = `
+
+
+ ${isFreeplan ? 'Free Plan Assigned' : `New ${paymentType} Booking Received`}
+ Dear Gym Owner,
+ ${isFreeplan ? 'A free membership' : 'A new membership'}${paymentType === 'Gym Membership with Personal Training' ? ' with personal training' : ''} has been ${isFreeplan ? 'assigned' : 'received'} for your gym.
+ Customer Details:
+
+ - Name: ${invoiceData.customerName}
+ - Email: ${invoiceData.email}
+ - Phone: ${invoiceData.phoneNumber}
+
+ Booking Details:
+
+ - Type: ${invoiceData.planName}
+ ${trainerData ? `- Trainer: ${trainerData.fullName || 'Personal Trainer'}
` : ''}
+ ${hasDiscount ? `- Original Price: ₹${originalAmount.toFixed(2)}
` : ''}
+ ${hasDiscount ? `- Discount: ${discountPercentage.toFixed(1)}%
` : ''}
+ ${hasDiscount ? `- Amount Saved by Customer: ₹${amountSaved.toFixed(2)}
` : ''}
+ - Amount: ₹${orderData.amount.toFixed(2)}${discountText}
+ - Transaction ID: ${payload.merchantOrderId}
+ - Date: ${formattedDate}
+
+ Please find the invoice attached.
+ Regards,
Fitlien Team
+
+
+ `;
+
+ 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 = `
+
+
+ New Personal Training Client
+ Dear ${trainerData.fullName || 'Trainer'},
+ A new client has signed up for personal training with you at ${gymName}.
+ Client Details:
+
+ - Name: ${invoiceData.customerName}
+ - Email: ${invoiceData.email}
+ - Phone: ${invoiceData.phoneNumber}
+
+ Booking Details:
+
+ - Type: Personal Training Membership
+ ${hasDiscount ? `- Original Price: ₹${originalAmount.toFixed(2)}
` : ''}
+ ${hasDiscount ? `- Discount: ${discountPercentage.toFixed(1)}%
` : ''}
+ - Amount: ₹${orderData.amount.toFixed(2)}${discountText}
+ - Transaction ID: ${payload.merchantOrderId}
+ - Date: ${formattedDate}
+
+ Please find the invoice attached.
+ Regards,
Fitlien Team
+
+
+ `;
+
+ 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);
+ }
+ }
+}