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:

- -

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:

- -

Booking Details:

- -

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:

- -

Booking Details:

- -

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:

+ +

Day Pass Details:

+ +

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:

+ +

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:

+ +

Booking Details:

+ +

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:

+ +

Booking Details:

+ +

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); + } + } +}