From de9ea73dcdc6ff37d08a2c8d35a6184ba133bff6 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Tue, 20 May 2025 15:45:20 +0530 Subject: [PATCH 01/22] Update firebase.json --- firebase.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase.json b/firebase.json index ecc3d3c..6920866 100644 --- a/firebase.json +++ b/firebase.json @@ -7,7 +7,7 @@ { "source": "functions", "codebase": "default", - "timeoutSeconds": 300, + "timeoutSeconds": 360, "ignore": [ "node_modules", ".git", -- 2.43.0 From 95492d304fc8faadf993758ac30816f85ac55121 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Tue, 20 May 2025 16:08:33 +0530 Subject: [PATCH 02/22] Update webhook.ts --- functions/src/payments/phonepe/webhook.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/functions/src/payments/phonepe/webhook.ts b/functions/src/payments/phonepe/webhook.ts index 1694817..90a0a43 100644 --- a/functions/src/payments/phonepe/webhook.ts +++ b/functions/src/payments/phonepe/webhook.ts @@ -105,7 +105,15 @@ export const phonePeWebhook = onRequest({ logger.info(`Updated order status via webhook for orderId: ${payload.orderId} to ${payload.state}`); } - if (payload.state === 'COMPLETED') { + logger.info(`Checking payment state`, { + state: payload.state, + stateType: typeof payload.state, + stateLength: payload.state ? payload.state.length : 0, + stateUpperCase: payload.state ? payload.state.toUpperCase() : null, + stateComparison: payload.state === 'COMPLETED' + }); + + if (payload.state && payload.state.trim().toUpperCase() === 'COMPLETED') { try { logger.info(`Starting payment update process for merchantOrderId: ${payload.merchantOrderId}`); -- 2.43.0 From 4ce107c4aff6b1c0bd81fd813055ac370350b9e9 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Tue, 20 May 2025 16:16:18 +0530 Subject: [PATCH 03/22] Update index.ts --- functions/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/functions/src/index.ts b/functions/src/index.ts index 19fa4e0..8bc48e9 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -6,3 +6,6 @@ export { processNotificationOnCreate } from './notifications'; export * from './payments'; export { getPlaceDetails, getPlacesAutocomplete } from './places'; export { registerClient } from './clientRegistration'; +import { setGlobalOptions } from "firebase-functions/v2"; + +setGlobalOptions({ timeoutSeconds: 300 }); \ No newline at end of file -- 2.43.0 From 66a65aed386efe885680bf7a0269196b6fee0743 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Tue, 20 May 2025 16:16:21 +0530 Subject: [PATCH 04/22] Update firebase.json --- firebase.json | 1 - 1 file changed, 1 deletion(-) diff --git a/firebase.json b/firebase.json index 6920866..dfc4226 100644 --- a/firebase.json +++ b/firebase.json @@ -7,7 +7,6 @@ { "source": "functions", "codebase": "default", - "timeoutSeconds": 360, "ignore": [ "node_modules", ".git", -- 2.43.0 From 5d138e5937160633fd5702f18445f10367ec595e Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Tue, 20 May 2025 16:21:38 +0530 Subject: [PATCH 05/22] Update index.ts --- functions/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index 8bc48e9..57a12f6 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -8,4 +8,4 @@ export { getPlaceDetails, getPlacesAutocomplete } from './places'; export { registerClient } from './clientRegistration'; import { setGlobalOptions } from "firebase-functions/v2"; -setGlobalOptions({ timeoutSeconds: 300 }); \ No newline at end of file +setGlobalOptions({ timeoutSeconds: 400 }); \ No newline at end of file -- 2.43.0 From c252ba8620cc20e50d0b9a8db179d5de0a5f76d1 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Tue, 20 May 2025 16:27:05 +0530 Subject: [PATCH 06/22] timeout changes --- firebase.json | 1 + functions/src/index.ts | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/firebase.json b/firebase.json index dfc4226..608886c 100644 --- a/firebase.json +++ b/firebase.json @@ -7,6 +7,7 @@ { "source": "functions", "codebase": "default", + "timeoutSeconds": 540, "ignore": [ "node_modules", ".git", diff --git a/functions/src/index.ts b/functions/src/index.ts index 1b8cb5b..19fa4e0 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -6,6 +6,3 @@ export { processNotificationOnCreate } from './notifications'; export * from './payments'; export { getPlaceDetails, getPlacesAutocomplete } from './places'; export { registerClient } from './clientRegistration'; -import { setGlobalOptions } from "firebase-functions/v2"; - -setGlobalOptions({ timeoutSeconds: 400 }); -- 2.43.0 From 3d64e11a056ad2343b51824b3052a6f17805194c Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Tue, 20 May 2025 17:05:30 +0530 Subject: [PATCH 07/22] changes for deploying --- firebase.json | 1 + functions/src/index.ts | 11 +++++++++++ package.json | 6 +----- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/firebase.json b/firebase.json index 608886c..21d0f5f 100644 --- a/firebase.json +++ b/firebase.json @@ -8,6 +8,7 @@ "source": "functions", "codebase": "default", "timeoutSeconds": 540, + "memory": "1GiB", "ignore": [ "node_modules", ".git", diff --git a/functions/src/index.ts b/functions/src/index.ts index 19fa4e0..660ddc7 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -1,3 +1,14 @@ +import { setGlobalOptions } from "firebase-functions/v2"; + +setGlobalOptions({ + region: "#{SERVICES_RGN}#", + memory: "1GiB", + timeoutSeconds: 540, + minInstances: 0, + maxInstances: 10, + concurrency: 80 +}); + export * from './shared/config'; export { sendEmailMessage, sendEmailWithAttachment, sendEmailSES } from './email'; export { sendSMSMessage } from './sms'; diff --git a/package.json b/package.json index 073e12e..da0eb6c 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,7 @@ { "dependencies": { "@types/busboy": "^1.5.4", - "@types/nodemailer": "^6.4.17", - "@types/pdfkit": "^0.13.9", "busboy": "^1.6.0", - "date-fns": "^4.1.0", - "nodemailer": "^7.0.3", - "pdfkit": "^0.17.1" + "date-fns": "^4.1.0" } } -- 2.43.0 From 623ebc84dffd69bbd8c5ef33db4c1d2c3cd7bc73 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Wed, 21 May 2025 14:12:33 +0530 Subject: [PATCH 08/22] added dependency --- functions/package-lock.json | 10 ++++++++++ functions/package.json | 1 + 2 files changed, 11 insertions(+) diff --git a/functions/package-lock.json b/functions/package-lock.json index e5b23b4..149bfc8 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -13,6 +13,7 @@ "aws-sdk": "^2.1692.0", "axios": "^1.9.0", "cors": "^2.8.5", + "date-fns": "^4.1.0", "firebase-admin": "^12.6.0", "firebase-functions": "^6.0.1", "form-data": "^4.0.1", @@ -3805,6 +3806,15 @@ "utrie": "^1.0.2" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", diff --git a/functions/package.json b/functions/package.json index b9f1c89..0e77a8f 100644 --- a/functions/package.json +++ b/functions/package.json @@ -20,6 +20,7 @@ "aws-sdk": "^2.1692.0", "axios": "^1.9.0", "cors": "^2.8.5", + "date-fns": "^4.1.0", "firebase-admin": "^12.6.0", "firebase-functions": "^6.0.1", "form-data": "^4.0.1", -- 2.43.0 From dd060fb963855bd09f24ea58f68039db2ea678b3 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Wed, 21 May 2025 15:37:57 +0530 Subject: [PATCH 09/22] changed import and collection name --- functions/src/payments/phonepe/invoice/invoiceService.ts | 4 ++-- functions/src/payments/phonepe/webhook.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/functions/src/payments/phonepe/invoice/invoiceService.ts b/functions/src/payments/phonepe/invoice/invoiceService.ts index 95ffa7d..8c3d491 100644 --- a/functions/src/payments/phonepe/invoice/invoiceService.ts +++ b/functions/src/payments/phonepe/invoice/invoiceService.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import { format } from 'date-fns'; import { sendEmailWithAttachmentUtil } from "../../../utils/emailService"; import { jsPDF } from "jspdf"; -import 'jspdf-autotable'; +import autoTable from 'jspdf-autotable'; const admin = getAdmin(); const logger = getLogger(); @@ -83,7 +83,7 @@ export class InvoiceService { doc.text(`Phone: ${data.phoneNumber}`, 20, 80); doc.text(`Email: ${data.email}`, 20, 90); - (doc as any).autoTable({ + autoTable(doc,{ startY: 110, head: [['No.', 'Description', 'HSN/SAC', 'Amount (INR)']], body: [ diff --git a/functions/src/payments/phonepe/webhook.ts b/functions/src/payments/phonepe/webhook.ts index 90a0a43..42aa29f 100644 --- a/functions/src/payments/phonepe/webhook.ts +++ b/functions/src/payments/phonepe/webhook.ts @@ -154,7 +154,7 @@ export const phonePeWebhook = onRequest({ logger.info(`Fetching user data for userId: ${userId}`); const userDoc = await admin.firestore() - .collection('users') + .collection('client_profiles') .doc(userId) .get(); if (userDoc.exists) { -- 2.43.0 From d73f02726ee2d9d09487e7d358fa33c179a7a7ec Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Wed, 21 May 2025 16:55:54 +0530 Subject: [PATCH 10/22] changed pdf layout --- .../phonepe/invoice/invoiceService.ts | 165 ++++++++++++------ 1 file changed, 110 insertions(+), 55 deletions(-) diff --git a/functions/src/payments/phonepe/invoice/invoiceService.ts b/functions/src/payments/phonepe/invoice/invoiceService.ts index 8c3d491..bcc78a4 100644 --- a/functions/src/payments/phonepe/invoice/invoiceService.ts +++ b/functions/src/payments/phonepe/invoice/invoiceService.ts @@ -43,7 +43,7 @@ export class InvoiceService { async generateInvoice(data: InvoiceData): Promise { try { const tempFilePath = path.join(os.tmpdir(), `invoice_${data.invoiceNumber}.pdf`); - + const hasGst = data.gstNumber && data.gstNumber.length > 0; const baseAmount = hasGst ? data.amount / 1.18 : data.amount; const sgst = hasGst ? baseAmount * 0.09 : 0; @@ -51,66 +51,110 @@ export class InvoiceService { const formattedDate = format(data.paymentDate, 'dd/MM/yyyy'); const doc = new jsPDF(); - + doc.setFontSize(20); doc.setFont('helvetica', 'bold'); doc.text(data.businessName, 20, 20); - + doc.setFontSize(12); doc.setFont('helvetica', 'normal'); - doc.text(data.address, 20, 30); - - if (hasGst) { - doc.text(`GSTIN: ${data.gstNumber}`, 20, 40); + + const maxWidth = 170; + const lineHeight = 5; + + const addressLines = doc.splitTextToSize(data.address, maxWidth); + + if (addressLines.length <= 2) { + for (let i = 0; i < addressLines.length; i++) { + doc.text(addressLines[i], 20, 30 + (i * lineHeight)); + } + } else { + doc.text(addressLines[0], 20, 30); + + let secondLine = addressLines[1]; + if (secondLine.length > 3) { + secondLine = secondLine.substring(0, secondLine.length - 3) + '...'; + } else { + secondLine += '...'; + } + doc.text(secondLine, 20, 35); } - + + const gstYPosition = 40; + + if (hasGst) { + doc.text(`GSTIN: ${data.gstNumber}`, 20, gstYPosition); + } + doc.setFontSize(24); doc.setFont('helvetica', 'bold'); doc.text('RECEIPT', 190, 20, { align: 'right' }); - + doc.setFontSize(12); doc.text(`Receipt #: ${data.invoiceNumber}`, 190, 30, { align: 'right' }); doc.text(`Date: ${formattedDate}`, 190, 40, { align: 'right' }); - + doc.line(20, 45, 190, 45); - + doc.setFontSize(12); + const receiptToBoxX = 15; + const receiptToBoxY = 55; + const receiptToBoxWidth = 175; + const receiptToBoxHeight = 45; + + doc.setDrawColor(0, 0, 0); + doc.setLineWidth(0.5); + doc.rect(receiptToBoxX, receiptToBoxY, receiptToBoxWidth, receiptToBoxHeight); doc.setFont('helvetica', 'bold'); doc.text('Receipt To:', 20, 60); - + doc.setFont('helvetica', 'normal'); doc.text(data.customerName, 20, 70); doc.text(`Phone: ${data.phoneNumber}`, 20, 80); doc.text(`Email: ${data.email}`, 20, 90); - - autoTable(doc,{ + + autoTable(doc, { startY: 110, head: [['No.', 'Description', 'HSN/SAC', 'Amount (INR)']], body: [ ['1', `${data.planName} Subscription`, '999723', baseAmount.toFixed(2)] ], - headStyles: { fillColor: [220, 220, 220], textColor: [0, 0, 0], fontStyle: 'bold' }, - styles: { halign: 'center' }, + headStyles: { + fillColor: [220, 220, 220], + textColor: [0, 0, 0], + fontStyle: 'bold', + lineWidth: 0.5, + lineColor: [0, 0, 0] + }, + styles: { + halign: 'center', + lineWidth: 0.5, + lineColor: [0, 0, 0] + }, columnStyles: { 0: { halign: 'center', cellWidth: 20 }, 1: { halign: 'left' }, 2: { halign: 'center', cellWidth: 40 }, 3: { halign: 'right', cellWidth: 40 } - } + }, + theme: 'grid', + tableLineWidth: 0.5, + tableLineColor: [0, 0, 0], + }); - + const finalY = (doc as any).lastAutoTable.finalY + 20; - + if (hasGst) { doc.text('Taxable Amount:', 150, finalY, { align: 'right' }); doc.text(`${baseAmount.toFixed(2)} INR`, 190, finalY, { align: 'right' }); - + doc.text('SGST (9%):', 150, finalY + 10, { align: 'right' }); doc.text(`${sgst.toFixed(2)} INR`, 190, finalY + 10, { align: 'right' }); - + doc.text('CGST (9%):', 150, finalY + 20, { align: 'right' }); doc.text(`${cgst.toFixed(2)} INR`, 190, finalY + 20, { align: 'right' }); - + doc.setFont('helvetica', 'bold'); doc.text('Total Amount:', 150, finalY + 30, { align: 'right' }); doc.text(`${data.amount.toFixed(2)} INR`, 190, finalY + 30, { align: 'right' }); @@ -119,27 +163,38 @@ export class InvoiceService { doc.text('Total Amount:', 150, finalY, { align: 'right' }); doc.text(`${data.amount.toFixed(2)} INR`, 190, finalY, { align: 'right' }); } - + const paymentY = hasGst ? finalY + 50 : finalY + 20; - + + doc.line(20, 45, 190, 45); + + const boxX = 15; + const boxY = paymentY - 5; + const boxWidth = 175; + const boxHeight = 45; + + doc.setDrawColor(0, 0, 0); + doc.setLineWidth(0.5); + doc.rect(boxX, boxY, boxWidth, boxHeight); + doc.setFont('helvetica', 'bold'); doc.text('Payment Information:', 20, paymentY); - + doc.setFont('helvetica', 'normal'); doc.text(`Transaction ID: ${data.transactionId}`, 20, paymentY + 10); doc.text(`Payment Method: ${data.paymentMethod}`, 20, paymentY + 20); doc.text(`Payment Date: ${formattedDate}`, 20, paymentY + 30); - + doc.setFontSize(12); doc.setFont('helvetica', 'italic'); doc.text('Thank you for your business!', 105, 270, { align: 'center' }); - + doc.setFontSize(10); doc.setFont('helvetica', 'normal'); doc.text('This is a computer-generated receipt and does not require a signature.', 105, 280, { align: 'center' }); - + fs.writeFileSync(tempFilePath, Buffer.from(doc.output('arraybuffer'))); - + const invoicePath = `invoices/${data.invoiceNumber}.pdf`; const bucket = admin.storage().bucket(); await bucket.upload(tempFilePath, { @@ -148,9 +203,9 @@ export class InvoiceService { contentType: 'application/pdf', }, }); - + fs.unlinkSync(tempFilePath); - + return invoicePath; } catch (error: any) { logger.error('Error generating invoice:', error); @@ -162,15 +217,15 @@ export class InvoiceService { try { const bucket = admin.storage().bucket(); const file = bucket.file(invoicePath); - - const expirationMs = 7 * 24 * 60 * 60 * 1000; - + + const expirationMs = 7 * 24 * 60 * 60 * 1000; + const [signedUrl] = await file.getSignedUrl({ action: 'read', expires: Date.now() + expirationMs, responseDisposition: `attachment; filename="${path.basename(invoicePath)}"`, }); - + return signedUrl; } catch (error: any) { logger.error('Error getting invoice download URL:', error); @@ -183,37 +238,37 @@ export class InvoiceService { const membershipPaymentsRef = admin.firestore() .collection('membership_payments') .doc(membershipId); - + const docSnapshot = await membershipPaymentsRef.get(); - + if (!docSnapshot.exists) { logger.error(`No membership payments found for membershipId: ${membershipId}`); return false; } - + const data = docSnapshot.data(); const paymentsData = data?.payments || []; - + let found = false; for (let i = 0; i < paymentsData.length; i++) { - if (paymentsData[i].referenceNumber === paymentId || - paymentsData[i].transactionId === paymentId) { + if (paymentsData[i].referenceNumber === paymentId || + paymentsData[i].transactionId === paymentId) { paymentsData[i].invoicePath = invoicePath; found = true; break; } } - + if (!found) { logger.error(`No payment found with ID: ${paymentId} in membership: ${membershipId}`); return false; } - + await membershipPaymentsRef.update({ 'payments': paymentsData, 'updatedAt': admin.firestore.FieldValue.serverTimestamp(), }); - + logger.info(`Successfully updated invoice path for payment: ${paymentId}`); return true; } catch (error: any) { @@ -225,11 +280,11 @@ export class InvoiceService { async sendInvoiceEmail(invoicePath: string, emailOptions: EmailOptions): Promise { try { const downloadUrl = await this.getInvoiceDownloadUrl(invoicePath); - - const formattedDate = emailOptions.additionalData?.paymentDate + + const formattedDate = emailOptions.additionalData?.paymentDate ? new Date(emailOptions.additionalData.paymentDate).toLocaleDateString('en-GB') : new Date().toLocaleDateString('en-GB'); - + const emailHtml = emailOptions.customHtml || ` @@ -251,7 +306,7 @@ export class InvoiceService { `; - + await sendEmailWithAttachmentUtil( emailOptions.recipientEmail, emailOptions.subject || 'Your Fitlien Membership Invoice', @@ -259,7 +314,7 @@ export class InvoiceService { downloadUrl, `Invoice_${path.basename(invoicePath)}` ); - + logger.info(`Invoice email sent to ${emailOptions.recipientEmail}`); return true; } catch (error: any) { @@ -269,7 +324,7 @@ export class InvoiceService { } async processInvoice( - membershipId: string, + membershipId: string, paymentId: string, invoiceData: InvoiceData, emailOptions?: EmailOptions @@ -282,9 +337,9 @@ export class InvoiceService { }> { try { const invoicePath = await this.generateInvoice(invoiceData); - + const updateSuccess = await this.updateInvoicePath(membershipId, paymentId, invoicePath); - + if (!updateSuccess) { return { success: false, @@ -293,14 +348,14 @@ export class InvoiceService { error: 'Failed to update payment with invoice path' }; } - + const downloadUrl = await this.getInvoiceDownloadUrl(invoicePath); - + let emailSent = false; if (emailOptions && emailOptions.recipientEmail) { emailSent = await this.sendInvoiceEmail(invoicePath, emailOptions); } - + return { success: true, invoicePath, -- 2.43.0 From 18afada52ae6fac65652d564717e0a000d529b5d Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Wed, 21 May 2025 17:00:19 +0530 Subject: [PATCH 11/22] changes --- .../payments/phonepe/invoice/directInvoice.ts | 2 -- .../payments/phonepe/invoice/getInvoiceUrl.ts | 1 - functions/src/payments/phonepe/paymentData.ts | 16 ++++------------ 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/functions/src/payments/phonepe/invoice/directInvoice.ts b/functions/src/payments/phonepe/invoice/directInvoice.ts index 93969f5..5df728d 100644 --- a/functions/src/payments/phonepe/invoice/directInvoice.ts +++ b/functions/src/payments/phonepe/invoice/directInvoice.ts @@ -65,11 +65,9 @@ export const directGenerateInvoice = onRequest({ paymentMethod: paymentMethod || 'Online' }; - // Generate the invoice without updating any payment records const invoicePath = await invoiceService.generateInvoice(invoiceData); const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath); - // Send email if requested let emailSent = false; if (sendEmail && email) { emailSent = await invoiceService.sendInvoiceEmail(invoicePath, { diff --git a/functions/src/payments/phonepe/invoice/getInvoiceUrl.ts b/functions/src/payments/phonepe/invoice/getInvoiceUrl.ts index 038a14c..df433f4 100644 --- a/functions/src/payments/phonepe/invoice/getInvoiceUrl.ts +++ b/functions/src/payments/phonepe/invoice/getInvoiceUrl.ts @@ -35,7 +35,6 @@ export const getInvoiceUrl = onRequest({ return; } - // Get a download URL for the invoice const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath as string); response.json({ diff --git a/functions/src/payments/phonepe/paymentData.ts b/functions/src/payments/phonepe/paymentData.ts index b6eca48..13d7536 100644 --- a/functions/src/payments/phonepe/paymentData.ts +++ b/functions/src/payments/phonepe/paymentData.ts @@ -3,7 +3,6 @@ import { getAdmin, getLogger } from "../../shared/config"; const admin = getAdmin(); const logger = getLogger(); -// Define an interface for the payment data to avoid type errors interface PaymentData { id: string; date: string; @@ -14,7 +13,7 @@ interface PaymentData { discount: any; transactionId: string; createdAt: Date; - invoicePath?: string; // Make this optional + invoicePath?: string; } export async function updatePaymentDataAfterSuccess( @@ -24,7 +23,6 @@ export async function updatePaymentDataAfterSuccess( invoicePath?: string ): Promise { try { - // Get the payment order from Firestore const orderQuery = await admin.firestore() .collection('payment_orders') .where('merchantOrderId', '==', merchantOrderId) @@ -39,19 +37,17 @@ export async function updatePaymentDataAfterSuccess( const orderDoc = orderQuery.docs[0]; const orderData = orderDoc.data(); - // Extract membership ID from metaInfo const membershipId = orderData.metaInfo?.membershipId; if (!membershipId) { logger.error(`No membershipId found in metaInfo for order: ${merchantOrderId}`); return false; } - const isoDate = new Date().toLocaleDateString('en-GB').split('/').join('-'); // DD-MM-YYYY format + const isoDate = new Date().toLocaleDateString('en-GB').split('/').join('-'); const dateTimestamp = admin.firestore.Timestamp.now(); - // Create payment data object with proper typing const paymentData: PaymentData = { - id: admin.firestore().collection('_').doc().id, // Generate a UUID + id: admin.firestore().collection('_').doc().id, date: isoDate, dateTimestamp: dateTimestamp, amount: orderData.amount, @@ -62,19 +58,16 @@ export async function updatePaymentDataAfterSuccess( createdAt: new Date() }; - // Add invoice path if provided if (invoicePath) { paymentData.invoicePath = invoicePath; } - // Get reference to membership payments document const membershipPaymentsRef = admin.firestore() .collection('membership_payments') .doc(membershipId); const docSnapshot = await membershipPaymentsRef.get(); - // Update or create the membership payments document if (docSnapshot.exists) { await membershipPaymentsRef.update({ 'payments': admin.firestore.FieldValue.arrayUnion(paymentData), @@ -92,7 +85,6 @@ export async function updatePaymentDataAfterSuccess( }); } - // Update membership status await updateMembershipStatus(membershipId, orderData.userId); logger.info(`Successfully updated payment data for membership: ${membershipId}`); @@ -118,7 +110,7 @@ async function updateMembershipStatus(membershipId: string, userId: string): Pro .collection('memberships') .doc(membershipId) .update({ - 'status': 'ACTIVE', // Assuming this matches your InvitationStatus.active + 'status': 'ACTIVE', 'updatedAt': admin.firestore.FieldValue.serverTimestamp(), }); -- 2.43.0 From 89b9ba897a938bdd5673bfda022441949d3bede6 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Wed, 21 May 2025 23:23:22 +0530 Subject: [PATCH 12/22] changes to invoice layout --- .../phonepe/invoice/invoiceService.ts | 26 +++++++++---------- functions/src/payments/phonepe/webhook.ts | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/functions/src/payments/phonepe/invoice/invoiceService.ts b/functions/src/payments/phonepe/invoice/invoiceService.ts index bcc78a4..741b900 100644 --- a/functions/src/payments/phonepe/invoice/invoiceService.ts +++ b/functions/src/payments/phonepe/invoice/invoiceService.ts @@ -54,7 +54,7 @@ export class InvoiceService { doc.setFontSize(20); doc.setFont('helvetica', 'bold'); - doc.text(data.businessName, 20, 20); + doc.text(data.businessName, 13, 20); doc.setFontSize(12); doc.setFont('helvetica', 'normal'); @@ -66,10 +66,10 @@ export class InvoiceService { if (addressLines.length <= 2) { for (let i = 0; i < addressLines.length; i++) { - doc.text(addressLines[i], 20, 30 + (i * lineHeight)); + doc.text(addressLines[i], 13, 30 + (i * lineHeight)); } } else { - doc.text(addressLines[0], 20, 30); + doc.text(addressLines[0], 13, 30); let secondLine = addressLines[1]; if (secondLine.length > 3) { @@ -77,7 +77,7 @@ export class InvoiceService { } else { secondLine += '...'; } - doc.text(secondLine, 20, 35); + doc.text(secondLine, 13, 35); } const gstYPosition = 40; @@ -97,10 +97,10 @@ export class InvoiceService { doc.line(20, 45, 190, 45); doc.setFontSize(12); - const receiptToBoxX = 15; + const receiptToBoxX = 13; const receiptToBoxY = 55; - const receiptToBoxWidth = 175; - const receiptToBoxHeight = 45; + const receiptToBoxWidth = 140; + const receiptToBoxHeight = 40; doc.setDrawColor(0, 0, 0); doc.setLineWidth(0.5); @@ -166,12 +166,12 @@ export class InvoiceService { const paymentY = hasGst ? finalY + 50 : finalY + 20; - doc.line(20, 45, 190, 45); + doc.line(13, 45, 190, 45); - const boxX = 15; + const boxX = 13; const boxY = paymentY - 5; - const boxWidth = 175; - const boxHeight = 45; + const boxWidth = 140; + const boxHeight = 40; doc.setDrawColor(0, 0, 0); doc.setLineWidth(0.5); @@ -181,8 +181,8 @@ export class InvoiceService { doc.text('Payment Information:', 20, paymentY); doc.setFont('helvetica', 'normal'); - doc.text(`Transaction ID: ${data.transactionId}`, 20, paymentY + 10); - doc.text(`Payment Method: ${data.paymentMethod}`, 20, paymentY + 20); + doc.text(`Transaction ID: ${data.transactionId}`, 18, paymentY + 10); + doc.text(`Payment Method: ${data.paymentMethod}`, 18, paymentY + 20); doc.text(`Payment Date: ${formattedDate}`, 20, paymentY + 30); doc.setFontSize(12); diff --git a/functions/src/payments/phonepe/webhook.ts b/functions/src/payments/phonepe/webhook.ts index 42aa29f..9c1f6cc 100644 --- a/functions/src/payments/phonepe/webhook.ts +++ b/functions/src/payments/phonepe/webhook.ts @@ -154,7 +154,7 @@ export const phonePeWebhook = onRequest({ logger.info(`Fetching user data for userId: ${userId}`); const userDoc = await admin.firestore() - .collection('client_profiles') + .collection('users') .doc(userId) .get(); if (userDoc.exists) { @@ -245,7 +245,7 @@ export const phonePeWebhook = onRequest({ customerName: userData?.displayName || `${membershipData?.fields?.['first-name'] || ''} ${membershipData?.fields?.['last-name'] || ''}`.trim(), phoneNumber: membershipData?.fields?.['phone-number'] || orderData.metaInfo?.phoneNumber || '', email: membershipData?.fields?.['email'] || '', - planName: orderData.metaInfo?.planName || subscriptionName || paymentType, + planName: orderData.metaInfo?.planName || subscriptionName, amount: orderData.amount, transactionId: payload.orderId, paymentDate: new Date(), -- 2.43.0 From df404e405ccdafc06cc19dc03b52b5c7271c35ba Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Fri, 23 May 2025 13:04:45 +0530 Subject: [PATCH 13/22] Update webhook.ts --- functions/src/payments/phonepe/webhook.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/src/payments/phonepe/webhook.ts b/functions/src/payments/phonepe/webhook.ts index 9c1f6cc..537d431 100644 --- a/functions/src/payments/phonepe/webhook.ts +++ b/functions/src/payments/phonepe/webhook.ts @@ -154,11 +154,11 @@ export const phonePeWebhook = onRequest({ logger.info(`Fetching user data for userId: ${userId}`); const userDoc = await admin.firestore() - .collection('users') + .collection('client_profiles') .doc(userId) .get(); if (userDoc.exists) { - logger.info(`User data retrieved successfully for userId: ${userId}`); + logger.info(`User data retrieved successfully for userId(Client): ${userId}`); logger.info(`Starting invoice generation process for payment: ${payload.merchantOrderId}`); -- 2.43.0 From 354881d5bf82f7f1fbec859685fc4eeffe94bad2 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Fri, 23 May 2025 13:19:15 +0530 Subject: [PATCH 14/22] Update webhook.ts --- functions/src/payments/phonepe/webhook.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/functions/src/payments/phonepe/webhook.ts b/functions/src/payments/phonepe/webhook.ts index 537d431..f12eb58 100644 --- a/functions/src/payments/phonepe/webhook.ts +++ b/functions/src/payments/phonepe/webhook.ts @@ -150,15 +150,15 @@ export const phonePeWebhook = onRequest({ logger.info(`Membership data retrieved successfully for membershipId: ${membershipId}`); const membershipData = membershipDoc.data(); - const userId = membershipData?.userId; + const uid = membershipData?.userId; - logger.info(`Fetching user data for userId: ${userId}`); + logger.info(`Fetching user data for uid(Client): ${uid}`); const userDoc = await admin.firestore() .collection('client_profiles') - .doc(userId) + .doc(uid) .get(); if (userDoc.exists) { - logger.info(`User data retrieved successfully for userId(Client): ${userId}`); + logger.info(`User data retrieved successfully for uid(Client): ${uid}`); logger.info(`Starting invoice generation process for payment: ${payload.merchantOrderId}`); -- 2.43.0 From ed33fe3c46800b598a4e81dadafc126686a5ef7c Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Fri, 23 May 2025 13:43:08 +0530 Subject: [PATCH 15/22] changed layout --- .../phonepe/invoice/invoiceService.ts | 21 ++++++++++--------- functions/src/payments/phonepe/webhook.ts | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/functions/src/payments/phonepe/invoice/invoiceService.ts b/functions/src/payments/phonepe/invoice/invoiceService.ts index 741b900..df7afcf 100644 --- a/functions/src/payments/phonepe/invoice/invoiceService.ts +++ b/functions/src/payments/phonepe/invoice/invoiceService.ts @@ -99,19 +99,19 @@ export class InvoiceService { doc.setFontSize(12); const receiptToBoxX = 13; const receiptToBoxY = 55; - const receiptToBoxWidth = 140; + const receiptToBoxWidth = 100; const receiptToBoxHeight = 40; doc.setDrawColor(0, 0, 0); doc.setLineWidth(0.5); doc.rect(receiptToBoxX, receiptToBoxY, receiptToBoxWidth, receiptToBoxHeight); doc.setFont('helvetica', 'bold'); - doc.text('Receipt To:', 20, 60); + doc.text('Receipt To:', 18, 60); doc.setFont('helvetica', 'normal'); - doc.text(data.customerName, 20, 70); - doc.text(`Phone: ${data.phoneNumber}`, 20, 80); - doc.text(`Email: ${data.email}`, 20, 90); + doc.text(data.customerName, 18, 70); + doc.text(`Phone: ${data.phoneNumber}`, 18, 80); + doc.text(`Email: ${data.email}`, 18, 90); autoTable(doc, { startY: 110, @@ -155,10 +155,13 @@ export class InvoiceService { doc.text('CGST (9%):', 150, finalY + 20, { align: 'right' }); doc.text(`${cgst.toFixed(2)} INR`, 190, finalY + 20, { align: 'right' }); + doc.line(120, finalY + 25, 190, finalY + 25); + doc.setFont('helvetica', 'bold'); doc.text('Total Amount:', 150, finalY + 30, { align: 'right' }); doc.text(`${data.amount.toFixed(2)} INR`, 190, finalY + 30, { align: 'right' }); } else { + doc.line(120, finalY - 5, 190, finalY - 5); doc.setFont('helvetica', 'bold'); doc.text('Total Amount:', 150, finalY, { align: 'right' }); doc.text(`${data.amount.toFixed(2)} INR`, 190, finalY, { align: 'right' }); @@ -166,11 +169,9 @@ export class InvoiceService { const paymentY = hasGst ? finalY + 50 : finalY + 20; - doc.line(13, 45, 190, 45); - const boxX = 13; const boxY = paymentY - 5; - const boxWidth = 140; + const boxWidth = 100; const boxHeight = 40; doc.setDrawColor(0, 0, 0); @@ -178,12 +179,12 @@ export class InvoiceService { doc.rect(boxX, boxY, boxWidth, boxHeight); doc.setFont('helvetica', 'bold'); - doc.text('Payment Information:', 20, paymentY); + doc.text('Payment Information:', 18, paymentY); doc.setFont('helvetica', 'normal'); doc.text(`Transaction ID: ${data.transactionId}`, 18, paymentY + 10); doc.text(`Payment Method: ${data.paymentMethod}`, 18, paymentY + 20); - doc.text(`Payment Date: ${formattedDate}`, 20, paymentY + 30); + doc.text(`Payment Date: ${formattedDate}`, 18, paymentY + 30); doc.setFontSize(12); doc.setFont('helvetica', 'italic'); diff --git a/functions/src/payments/phonepe/webhook.ts b/functions/src/payments/phonepe/webhook.ts index f12eb58..2ba5136 100644 --- a/functions/src/payments/phonepe/webhook.ts +++ b/functions/src/payments/phonepe/webhook.ts @@ -197,7 +197,7 @@ export const phonePeWebhook = onRequest({ const gymData = gymDoc.data(); gymName = gymData?.name || 'Fitlien'; gymAddress = gymData?.address || ''; - subscriptionName = gymData?.subscriptions?.name || ''; + subscriptionName = membershipData?.subscription?.normalizedName || ''; if (gymData?.userId) { const gymOwnerDoc = await admin.firestore() -- 2.43.0 From 1a40513cc4d3ff351d28270492428c3f34ae92c2 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Fri, 23 May 2025 14:27:02 +0530 Subject: [PATCH 16/22] Update invoiceService.ts --- .../phonepe/invoice/invoiceService.ts | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/functions/src/payments/phonepe/invoice/invoiceService.ts b/functions/src/payments/phonepe/invoice/invoiceService.ts index df7afcf..fcf9cec 100644 --- a/functions/src/payments/phonepe/invoice/invoiceService.ts +++ b/functions/src/payments/phonepe/invoice/invoiceService.ts @@ -54,7 +54,7 @@ export class InvoiceService { doc.setFontSize(20); doc.setFont('helvetica', 'bold'); - doc.text(data.businessName, 13, 20); + doc.text(data.businessName, 15, 20); doc.setFontSize(12); doc.setFont('helvetica', 'normal'); @@ -66,10 +66,10 @@ export class InvoiceService { if (addressLines.length <= 2) { for (let i = 0; i < addressLines.length; i++) { - doc.text(addressLines[i], 13, 30 + (i * lineHeight)); + doc.text(addressLines[i], 15, 30 + (i * lineHeight)); } } else { - doc.text(addressLines[0], 13, 30); + doc.text(addressLines[0], 15, 30); let secondLine = addressLines[1]; if (secondLine.length > 3) { @@ -77,7 +77,7 @@ export class InvoiceService { } else { secondLine += '...'; } - doc.text(secondLine, 13, 35); + doc.text(secondLine, 15, 35); } const gstYPosition = 40; @@ -88,17 +88,17 @@ export class InvoiceService { doc.setFontSize(24); doc.setFont('helvetica', 'bold'); - doc.text('RECEIPT', 190, 20, { align: 'right' }); + doc.text('RECEIPT', 195, 20, { align: 'right' }); doc.setFontSize(12); - doc.text(`Receipt #: ${data.invoiceNumber}`, 190, 30, { align: 'right' }); - doc.text(`Date: ${formattedDate}`, 190, 40, { align: 'right' }); + doc.text(`Receipt #: ${data.invoiceNumber}`, 195, 30, { align: 'right' }); + doc.text(`Date: ${formattedDate}`, 195, 40, { align: 'right' }); - doc.line(20, 45, 190, 45); + doc.line(15, 45, 195, 45); doc.setFontSize(12); - const receiptToBoxX = 13; - const receiptToBoxY = 55; + const receiptToBoxX = 15; + const receiptToBoxY = 50; const receiptToBoxWidth = 100; const receiptToBoxHeight = 40; @@ -110,12 +110,20 @@ export class InvoiceService { doc.setFont('helvetica', 'normal'); doc.text(data.customerName, 18, 70); - doc.text(`Phone: ${data.phoneNumber}`, 18, 80); - doc.text(`Email: ${data.email}`, 18, 90); + doc.text(`Phone: ${data.phoneNumber}`, 18, 75); + doc.text(`Email: ${data.email}`, 18, 80); autoTable(doc, { startY: 110, - head: [['No.', 'Description', 'HSN/SAC', 'Amount (INR)']], + margin: {left: 15, right: 15}, + head: [ + [ + {content: 'No.', styles: {halign: 'center'}}, + {content: 'Description', styles: {halign: 'left'}}, + {content: 'HSN/SAC', styles: {halign: 'center'}}, + {content: 'Amount (INR)', styles: {halign: 'right'}} + ] + ], body: [ ['1', `${data.planName} Subscription`, '999723', baseAmount.toFixed(2)] ], @@ -147,30 +155,30 @@ export class InvoiceService { if (hasGst) { doc.text('Taxable Amount:', 150, finalY, { align: 'right' }); - doc.text(`${baseAmount.toFixed(2)} INR`, 190, finalY, { align: 'right' }); + doc.text(`${baseAmount.toFixed(2)} INR`, 195, finalY, { align: 'right' }); doc.text('SGST (9%):', 150, finalY + 10, { align: 'right' }); - doc.text(`${sgst.toFixed(2)} INR`, 190, finalY + 10, { align: 'right' }); + doc.text(`${sgst.toFixed(2)} INR`, 195, finalY + 10, { align: 'right' }); doc.text('CGST (9%):', 150, finalY + 20, { align: 'right' }); - doc.text(`${cgst.toFixed(2)} INR`, 190, finalY + 20, { align: 'right' }); + doc.text(`${cgst.toFixed(2)} INR`, 195, finalY + 20, { align: 'right' }); - doc.line(120, finalY + 25, 190, finalY + 25); + doc.line(120, finalY + 25, 195, finalY + 25); doc.setFont('helvetica', 'bold'); doc.text('Total Amount:', 150, finalY + 30, { align: 'right' }); - doc.text(`${data.amount.toFixed(2)} INR`, 190, finalY + 30, { align: 'right' }); + doc.text(`${data.amount.toFixed(2)} INR`, 195, finalY + 30, { align: 'right' }); } else { - doc.line(120, finalY - 5, 190, finalY - 5); + doc.line(120, finalY - 5, 195, finalY - 5); doc.setFont('helvetica', 'bold'); doc.text('Total Amount:', 150, finalY, { align: 'right' }); - doc.text(`${data.amount.toFixed(2)} INR`, 190, finalY, { align: 'right' }); + doc.text(`${data.amount.toFixed(2)} INR`, 195, finalY, { align: 'right' }); } const paymentY = hasGst ? finalY + 50 : finalY + 20; - const boxX = 13; - const boxY = paymentY - 5; + const boxX = 15; + const boxY = paymentY - 10; const boxWidth = 100; const boxHeight = 40; @@ -183,8 +191,8 @@ export class InvoiceService { doc.setFont('helvetica', 'normal'); doc.text(`Transaction ID: ${data.transactionId}`, 18, paymentY + 10); - doc.text(`Payment Method: ${data.paymentMethod}`, 18, paymentY + 20); - doc.text(`Payment Date: ${formattedDate}`, 18, paymentY + 30); + doc.text(`Payment Method: ${data.paymentMethod}`, 18, paymentY + 15); + doc.text(`Payment Date: ${formattedDate}`, 18, paymentY + 20); doc.setFontSize(12); doc.setFont('helvetica', 'italic'); -- 2.43.0 From 083485d7de58d2e7b1884ef41a0b86c617d20e43 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Fri, 23 May 2025 14:37:01 +0530 Subject: [PATCH 17/22] Update invoiceService.ts --- functions/src/payments/phonepe/invoice/invoiceService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/src/payments/phonepe/invoice/invoiceService.ts b/functions/src/payments/phonepe/invoice/invoiceService.ts index fcf9cec..9e1530f 100644 --- a/functions/src/payments/phonepe/invoice/invoiceService.ts +++ b/functions/src/payments/phonepe/invoice/invoiceService.ts @@ -100,7 +100,7 @@ export class InvoiceService { const receiptToBoxX = 15; const receiptToBoxY = 50; const receiptToBoxWidth = 100; - const receiptToBoxHeight = 40; + const receiptToBoxHeight = 36; doc.setDrawColor(0, 0, 0); doc.setLineWidth(0.5); @@ -180,7 +180,7 @@ export class InvoiceService { const boxX = 15; const boxY = paymentY - 10; const boxWidth = 100; - const boxHeight = 40; + const boxHeight = 36; doc.setDrawColor(0, 0, 0); doc.setLineWidth(0.5); -- 2.43.0 From 8ad307f3a984a45d99bbb48b8a1dc90d9be635f1 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Fri, 23 May 2025 14:40:04 +0530 Subject: [PATCH 18/22] Update invoiceService.ts --- functions/src/payments/phonepe/invoice/invoiceService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/src/payments/phonepe/invoice/invoiceService.ts b/functions/src/payments/phonepe/invoice/invoiceService.ts index 9e1530f..60b88a0 100644 --- a/functions/src/payments/phonepe/invoice/invoiceService.ts +++ b/functions/src/payments/phonepe/invoice/invoiceService.ts @@ -163,7 +163,7 @@ export class InvoiceService { doc.text('CGST (9%):', 150, finalY + 20, { align: 'right' }); doc.text(`${cgst.toFixed(2)} INR`, 195, finalY + 20, { align: 'right' }); - doc.line(120, finalY + 25, 195, finalY + 25); + doc.line(15, finalY + 25, 15, finalY + 25); doc.setFont('helvetica', 'bold'); doc.text('Total Amount:', 150, finalY + 30, { align: 'right' }); -- 2.43.0 From 5fe4d305026765961531f265eca7eef40b04f1b7 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Fri, 23 May 2025 15:00:25 +0530 Subject: [PATCH 19/22] Update invoiceService.ts --- functions/src/payments/phonepe/invoice/invoiceService.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/functions/src/payments/phonepe/invoice/invoiceService.ts b/functions/src/payments/phonepe/invoice/invoiceService.ts index 60b88a0..df4fbe4 100644 --- a/functions/src/payments/phonepe/invoice/invoiceService.ts +++ b/functions/src/payments/phonepe/invoice/invoiceService.ts @@ -94,6 +94,7 @@ export class InvoiceService { doc.text(`Receipt #: ${data.invoiceNumber}`, 195, 30, { align: 'right' }); doc.text(`Date: ${formattedDate}`, 195, 40, { align: 'right' }); + doc.setLineWidth(0.5); doc.line(15, 45, 195, 45); doc.setFontSize(12); @@ -163,13 +164,15 @@ export class InvoiceService { doc.text('CGST (9%):', 150, finalY + 20, { align: 'right' }); doc.text(`${cgst.toFixed(2)} INR`, 195, finalY + 20, { align: 'right' }); - doc.line(15, finalY + 25, 15, finalY + 25); + doc.setLineWidth(0.5); + doc.line(15, finalY + 25, 195, finalY + 25); doc.setFont('helvetica', 'bold'); doc.text('Total Amount:', 150, finalY + 30, { align: 'right' }); doc.text(`${data.amount.toFixed(2)} INR`, 195, finalY + 30, { align: 'right' }); } else { - doc.line(120, finalY - 5, 195, finalY - 5); + doc.setLineWidth(0.5); + doc.line(15, finalY - 5, 195, finalY - 5); doc.setFont('helvetica', 'bold'); doc.text('Total Amount:', 150, finalY, { align: 'right' }); doc.text(`${data.amount.toFixed(2)} INR`, 195, finalY, { align: 'right' }); -- 2.43.0 From 2fb0280e877b65841f6adaf4f5295140cf88bba6 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Tue, 27 May 2025 23:36:48 +0530 Subject: [PATCH 20/22] Update emailService.ts --- functions/src/utils/emailService.ts | 259 ++++++++++++++++++++++------ 1 file changed, 206 insertions(+), 53 deletions(-) diff --git a/functions/src/utils/emailService.ts b/functions/src/utils/emailService.ts index b7f70a8..787669a 100644 --- a/functions/src/utils/emailService.ts +++ b/functions/src/utils/emailService.ts @@ -1,15 +1,135 @@ -import * as os from 'os'; -import * as path from 'path'; -import * as fs from 'fs'; -import * as https from 'https'; -import { getLogger } from "../shared/config"; -import formData from 'form-data'; -import Mailgun from 'mailgun.js'; -const { convert } = require('html-to-text'); -const mailgun = new Mailgun(formData); +import { getLogger } from "../shared/config"; +import { SESClient } from "@aws-sdk/client-ses"; +import { SendEmailCommand, SendRawEmailCommand } from "@aws-sdk/client-ses"; +import * as mime from 'mime-types'; +import axios from 'axios'; + const logger = getLogger(); +interface EmailRequest { + to: string | string[]; + subject: string; + html: string; + text?: string; + from: string; + replyTo?: string; + attachments?: Attachment[]; + fileUrl?: string; + fileName?: string; +} + +interface Attachment { + filename: string; + content: string | Buffer; // Base64 encoded string or Buffer + contentType?: string; +} + +const stripHtml = (html: string): string => { + if (!html) return ''; + return html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim(); +} + +async function sendSimpleEmail(data: EmailRequest, recipients: string[]) { + const ses = new SESClient({ + region: 'ap-south-1', + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '' + } + }); + + const command = new SendEmailCommand({ + Source: data.from, + Destination: { ToAddresses: recipients }, + Message: { + Subject: { Data: data.subject }, + Body: { + Html: { Data: data.html }, + Text: { Data: data.text || stripHtml(data.html) } + } + }, + ReplyToAddresses: data.replyTo ? [data.replyTo] : undefined, + }); + + const result = await ses.send(command); + return { messageId: result.MessageId }; +} + +async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]) { + const ses = new SESClient({ + region: 'ap-south-1', + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '' + } + }); + + const boundary = `boundary_${Math.random().toString(16).substr(2)}`; + let rawMessage = `From: ${data.from}\n`; + rawMessage += `To: ${recipients.join(', ')}\n`; + rawMessage += `Subject: ${data.subject}\n`; + rawMessage += `MIME-Version: 1.0\n`; + rawMessage += `Content-Type: multipart/mixed; boundary="${boundary}"\n\n`; + + // Add email body (multipart/alternative) + rawMessage += `--${boundary}\n`; + rawMessage += `Content-Type: multipart/alternative; boundary="alt_${boundary}"\n\n`; + + // Text part + if (data.text) { + rawMessage += `--alt_${boundary}\n`; + rawMessage += `Content-Type: text/plain; charset=UTF-8\n\n`; + rawMessage += `${data.text}\n\n`; + } + + // HTML part + rawMessage += `--alt_${boundary}\n`; + rawMessage += `Content-Type: text/html; charset=UTF-8\n\n`; + rawMessage += `${data.html}\n\n`; + + // Close alternative part + rawMessage += `--alt_${boundary}--\n\n`; + + // Add attachments + for (const attachment of data.attachments || []) { + const contentType = attachment.contentType || + mime.lookup(attachment.filename) || + 'application/octet-stream'; + + rawMessage += `--${boundary}\n`; + rawMessage += `Content-Type: ${contentType}; name="${attachment.filename}"\n`; + rawMessage += `Content-Disposition: attachment; filename="${attachment.filename}"\n`; + rawMessage += `Content-Transfer-Encoding: base64\n\n`; + + const contentBuffer = typeof attachment.content === 'string' + ? Buffer.from(attachment.content, 'base64') + : attachment.content; + + rawMessage += contentBuffer.toString('base64') + '\n\n'; + } + + // Close message + rawMessage += `--${boundary}--`; + + const command = new SendRawEmailCommand({ + RawMessage: { Data: Buffer.from(rawMessage) } + }); + + const result = await ses.send(command); + return { messageId: result.MessageId }; +} + +async function downloadFileFromUrl(url: string): Promise { + try { + const response = await axios.get(url, { responseType: 'arraybuffer' }); + return Buffer.from(response.data); + } catch (error) { + logger.error(`Error downloading file from URL: ${error}`); + throw new Error(`Failed to download file: ${error}`); + } +} + export async function sendEmailWithAttachmentUtil( toAddress: string, subject: string, @@ -18,53 +138,86 @@ export async function sendEmailWithAttachmentUtil( fileName?: string ): Promise { try { - const tempFilePath = path.join(os.tmpdir(), fileName || 'attachment.pdf'); - await new Promise((resolve, reject) => { - const file = fs.createWriteStream(tempFilePath); - https.get(fileUrl, (res) => { - res.pipe(file); - file.on('finish', () => { - file.close(); - resolve(); + logger.info(`Sending email with attachment to: ${toAddress}`); + + // Initialize data with basic fields + const data: EmailRequest = { + to: toAddress, + html: message, + subject: subject, + text: stripHtml(message), + from: process.env.SES_FROM_EMAIL || 'support@fitlien.com', + replyTo: process.env.SES_REPLY_TO_EMAIL || 'support@fitlien.com', + attachments: [] + }; + + // Handle file URL if provided + if (fileUrl && fileName) { + logger.info(`Downloading attachment from URL: ${fileUrl}`); + try { + const fileContent = await downloadFileFromUrl(fileUrl); + + // Add the downloaded file as an attachment + data.attachments!.push({ + filename: fileName, + content: fileContent, + contentType: mime.lookup(fileName) || 'application/octet-stream' }); - }).on('error', (err) => { - fs.unlink(tempFilePath, () => {}); - reject(err); - }); - }); - - try { - const client = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY! }); - const options = { - wordwrap: 130, - }; - const textMessage = convert(message, options); - const fileBuffer = fs.readFileSync(tempFilePath); - const attachmentFilename = fileName || path.basename(fileUrl.split('?')[0]); - - const data = { - from: process.env.MAILGUN_FROM_ADDRESS, - to: toAddress, - subject: subject, - text: textMessage, - html: message, - attachment: { - data: fileBuffer, - filename: attachmentFilename, - contentType: 'application/pdf', - } - }; - - const result = await client.messages.create(process.env.MAILGUN_SERVER!, data); - fs.unlinkSync(tempFilePath); - logger.info('Email with attachment from URL sent successfully'); - return { success: true, result }; - } catch (e) { - logger.error(`Error while sending E-mail. Error: ${e}`); - throw e; + + logger.info(`Successfully downloaded attachment: ${fileName}`); + } catch (downloadError) { + logger.error(`Failed to download attachment: ${downloadError}`); + throw new Error(`Failed to process attachment: ${downloadError}`); + } } + + if (!data.to || !data.subject || !data.html || !data.from) { + throw new Error('Missing required email fields'); + } + + logger.info(`Sending Email '${data.subject}' to '${data.to}' from '${data.from}'`); + const recipients = Array.isArray(data.to) ? data.to : [data.to]; + + let result; + if (data.attachments && data.attachments.length > 0) { + result = await sendEmailWithAttachments(data, recipients); + } else { + result = await sendSimpleEmail(data, recipients); + } + + logger.info('Email sent successfully via SES'); + return { success: true, result }; + } catch (error) { - logger.error('Error sending email with attachment from URL:', error); + logger.error('Error sending email with attachment via SES:', error); + throw error; + } +} + +// Additional utility function for sending simple emails without attachments +export async function sendSimpleEmailUtil( + toAddress: string, + subject: string, + message: string +): Promise { + try { + const data: EmailRequest = { + to: toAddress, + html: message, + subject: subject, + text: stripHtml(message), + from: process.env.SES_FROM_EMAIL || 'support@fitlien.com', + replyTo: process.env.SES_REPLY_TO_EMAIL || 'support@fitlien.com' + }; + + const recipients = Array.isArray(data.to) ? data.to : [data.to]; + const result = await sendSimpleEmail(data, recipients); + + logger.info('Simple email sent successfully via SES'); + return { success: true, result }; + + } catch (error) { + logger.error('Error sending simple email via SES:', error); throw error; } } -- 2.43.0 From 4817424c710640fdb52147edf70b0241793a71b2 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Tue, 27 May 2025 23:47:57 +0530 Subject: [PATCH 21/22] Update emailService.ts --- functions/src/utils/emailService.ts | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/functions/src/utils/emailService.ts b/functions/src/utils/emailService.ts index 787669a..1bb4e06 100644 --- a/functions/src/utils/emailService.ts +++ b/functions/src/utils/emailService.ts @@ -193,31 +193,3 @@ export async function sendEmailWithAttachmentUtil( throw error; } } - -// Additional utility function for sending simple emails without attachments -export async function sendSimpleEmailUtil( - toAddress: string, - subject: string, - message: string -): Promise { - try { - const data: EmailRequest = { - to: toAddress, - html: message, - subject: subject, - text: stripHtml(message), - from: process.env.SES_FROM_EMAIL || 'support@fitlien.com', - replyTo: process.env.SES_REPLY_TO_EMAIL || 'support@fitlien.com' - }; - - const recipients = Array.isArray(data.to) ? data.to : [data.to]; - const result = await sendSimpleEmail(data, recipients); - - logger.info('Simple email sent successfully via SES'); - return { success: true, result }; - - } catch (error) { - logger.error('Error sending simple email via SES:', error); - throw error; - } -} -- 2.43.0 From f0f2cd3411a78c9e0c779b30807be51d5ecf6206 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Wed, 28 May 2025 00:14:17 +0530 Subject: [PATCH 22/22] Update webhook.ts --- functions/src/payments/phonepe/webhook.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/functions/src/payments/phonepe/webhook.ts b/functions/src/payments/phonepe/webhook.ts index 2ba5136..8f797d7 100644 --- a/functions/src/payments/phonepe/webhook.ts +++ b/functions/src/payments/phonepe/webhook.ts @@ -172,6 +172,7 @@ export const phonePeWebhook = onRequest({ 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; @@ -308,7 +309,7 @@ export const phonePeWebhook = onRequest({ const formattedDate = format(new Date(), 'dd/MM/yyyy'); - if (membershipData?.fields?.['email']) { + if (emailCustomer) { logger.info(`Preparing to send invoice email to customer: ${membershipData?.fields?.['email']}`); try { const emailSubject = isFreeplan -- 2.43.0