diff --git a/functions/src/payments/phonepe/invoice/getInvoiceUrl.ts b/functions/src/payments/phonepe/invoice/getInvoiceUrl.ts index df433f4..e2a5f36 100644 --- a/functions/src/payments/phonepe/invoice/getInvoiceUrl.ts +++ b/functions/src/payments/phonepe/invoice/getInvoiceUrl.ts @@ -1,62 +1,62 @@ -import { onRequest } from "firebase-functions/v2/https"; -import { Request } from "firebase-functions/v2/https"; -import { getCorsHandler } from "../../../shared/middleware"; -import { getAdmin, getLogger } from "../../../shared/config"; -import { InvoiceService } from "./invoiceService"; +// import { onRequest } from "firebase-functions/v2/https"; +// import { Request } from "firebase-functions/v2/https"; +// import { getCorsHandler } from "../../../shared/middleware"; +// import { getAdmin, getLogger } from "../../../shared/config"; +// import { InvoiceService } from "./invoiceService"; -const admin = getAdmin(); -const logger = getLogger(); -const corsHandler = getCorsHandler(); -const invoiceService = new InvoiceService(); +// const admin = getAdmin(); +// const logger = getLogger(); +// const corsHandler = getCorsHandler(); +// const invoiceService = new InvoiceService(); -export const getInvoiceUrl = onRequest({ - region: '#{SERVICES_RGN}#' -}, async (request: Request, response) => { - return corsHandler(request, response, async () => { - try { - const authHeader = request.headers.authorization || ''; - if (!authHeader || !authHeader.startsWith('Bearer ')) { - response.status(401).json({ error: 'Unauthorized' }); - return; - } +// export const getInvoiceUrl = onRequest({ +// region: '#{SERVICES_RGN}#' +// }, async (request: Request, response) => { +// return corsHandler(request, response, async () => { +// try { +// const authHeader = request.headers.authorization || ''; +// if (!authHeader || !authHeader.startsWith('Bearer ')) { +// response.status(401).json({ error: 'Unauthorized' }); +// return; +// } - const idToken = authHeader.split('Bearer ')[1]; +// const idToken = authHeader.split('Bearer ')[1]; - try { - await admin.auth().verifyIdToken(idToken); +// try { +// await admin.auth().verifyIdToken(idToken); - const { invoicePath } = request.query; +// const { invoicePath } = request.query; - if (!invoicePath) { - response.status(400).json({ - success: false, - error: 'Missing invoice path' - }); - return; - } +// if (!invoicePath) { +// response.status(400).json({ +// success: false, +// error: 'Missing invoice path' +// }); +// return; +// } - const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath as string); +// const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath as string); - response.json({ - success: true, - downloadUrl - }); +// response.json({ +// success: true, +// downloadUrl +// }); - } catch (authError: any) { - logger.error('Authentication error:', authError); - response.status(401).json({ - success: false, - error: 'Invalid authentication token', - details: authError.message - }); - } - } catch (error: any) { - logger.error('Error getting invoice URL:', error); - response.status(500).json({ - success: false, - error: 'Failed to get invoice URL', - details: error.message - }); - } - }); -}); +// } catch (authError: any) { +// logger.error('Authentication error:', authError); +// response.status(401).json({ +// success: false, +// error: 'Invalid authentication token', +// details: authError.message +// }); +// } +// } catch (error: any) { +// logger.error('Error getting invoice URL:', error); +// response.status(500).json({ +// success: false, +// error: 'Failed to get invoice URL', +// details: error.message +// }); +// } +// }); +// }); diff --git a/functions/src/payments/phonepe/invoice/index.ts b/functions/src/payments/phonepe/invoice/index.ts index daa2183..393c6c1 100644 --- a/functions/src/payments/phonepe/invoice/index.ts +++ b/functions/src/payments/phonepe/invoice/index.ts @@ -1,13 +1,13 @@ -import { getInvoiceUrl } from './getInvoiceUrl'; +// import { getInvoiceUrl } from './getInvoiceUrl'; import { InvoiceService } from './invoiceService'; -import { processInvoice } from './processInvoice'; -import { sendInvoiceEmail } from './sendInvoiceEmail'; +// import { processInvoice } from './processInvoice'; +// import { sendInvoiceEmail } from './sendInvoiceEmail'; import { directGenerateInvoice } from './directInvoice'; export { - getInvoiceUrl, + // getInvoiceUrl, InvoiceService, - processInvoice, - sendInvoiceEmail, + // processInvoice, + // sendInvoiceEmail, directGenerateInvoice, }; diff --git a/functions/src/payments/phonepe/invoice/processInvoice.ts b/functions/src/payments/phonepe/invoice/processInvoice.ts index 1164d89..490b0a4 100644 --- a/functions/src/payments/phonepe/invoice/processInvoice.ts +++ b/functions/src/payments/phonepe/invoice/processInvoice.ts @@ -1,83 +1,83 @@ -import { onRequest } from "firebase-functions/v2/https"; -import { Request } from "firebase-functions/v2/https"; -import { getCorsHandler } from "../../../shared/middleware"; -import { getAdmin, getLogger } from "../../../shared/config"; -import { InvoiceService } from "./invoiceService"; +// import { onRequest } from "firebase-functions/v2/https"; +// import { Request } from "firebase-functions/v2/https"; +// import { getCorsHandler } from "../../../shared/middleware"; +// import { getAdmin, getLogger } from "../../../shared/config"; +// import { InvoiceService } from "./invoiceService"; -const admin = getAdmin(); -const logger = getLogger(); -const corsHandler = getCorsHandler(); -const invoiceService = new InvoiceService(); +// const admin = getAdmin(); +// const logger = getLogger(); +// const corsHandler = getCorsHandler(); +// const invoiceService = new InvoiceService(); -export const processInvoice = onRequest({ - region: '#{SERVICES_RGN}#' -}, async (request: Request, response) => { - return corsHandler(request, response, async () => { - try { - const authHeader = request.headers.authorization || ''; - if (!authHeader || !authHeader.startsWith('Bearer ')) { - response.status(401).json({ error: 'Unauthorized' }); - return; - } +// export const processInvoice = onRequest({ +// region: '#{SERVICES_RGN}#' +// }, async (request: Request, response) => { +// return corsHandler(request, response, async () => { +// try { +// const authHeader = request.headers.authorization || ''; +// if (!authHeader || !authHeader.startsWith('Bearer ')) { +// response.status(401).json({ error: 'Unauthorized' }); +// return; +// } - const idToken = authHeader.split('Bearer ')[1]; +// const idToken = authHeader.split('Bearer ')[1]; - try { - await admin.auth().verifyIdToken(idToken); +// try { +// await admin.auth().verifyIdToken(idToken); - const { - membershipId, - paymentId, - invoiceData, - emailOptions - } = request.body; +// const { +// membershipId, +// paymentId, +// invoiceData, +// emailOptions +// } = request.body; - if (!membershipId || !paymentId || !invoiceData) { - response.status(400).json({ - success: false, - error: 'Missing required fields' - }); - return; - } +// if (!membershipId || !paymentId || !invoiceData) { +// response.status(400).json({ +// success: false, +// error: 'Missing required fields' +// }); +// return; +// } - const result = await invoiceService.processInvoice( - membershipId, - paymentId, - invoiceData, - emailOptions - ); +// const result = await invoiceService.processInvoice( +// membershipId, +// paymentId, +// invoiceData, +// emailOptions +// ); - if (!result.success) { - response.status(400).json({ - success: false, - error: result.error || 'Failed to process invoice' - }); - return; - } +// if (!result.success) { +// response.status(400).json({ +// success: false, +// error: result.error || 'Failed to process invoice' +// }); +// return; +// } - response.json({ - success: true, - message: 'Invoice processed successfully', - invoicePath: result.invoicePath, - downloadUrl: result.downloadUrl, - emailSent: result.emailSent - }); +// response.json({ +// success: true, +// message: 'Invoice processed successfully', +// invoicePath: result.invoicePath, +// downloadUrl: result.downloadUrl, +// emailSent: result.emailSent +// }); - } catch (authError: any) { - logger.error('Authentication error:', authError); - response.status(401).json({ - success: false, - error: 'Invalid authentication token', - details: authError.message - }); - } - } catch (error: any) { - logger.error('Error processing invoice:', error); - response.status(500).json({ - success: false, - error: 'Failed to process invoice', - details: error.message - }); - } - }); -}); +// } catch (authError: any) { +// logger.error('Authentication error:', authError); +// response.status(401).json({ +// success: false, +// error: 'Invalid authentication token', +// details: authError.message +// }); +// } +// } catch (error: any) { +// logger.error('Error processing invoice:', error); +// response.status(500).json({ +// success: false, +// error: 'Failed to process invoice', +// details: error.message +// }); +// } +// }); +// }); diff --git a/functions/src/payments/phonepe/invoice/sendInvoiceEmail.ts b/functions/src/payments/phonepe/invoice/sendInvoiceEmail.ts index 2af677a..bf51edf 100644 --- a/functions/src/payments/phonepe/invoice/sendInvoiceEmail.ts +++ b/functions/src/payments/phonepe/invoice/sendInvoiceEmail.ts @@ -1,91 +1,91 @@ -import { onRequest } from "firebase-functions/v2/https"; -import { Request } from "firebase-functions/v2/https"; -import { getCorsHandler } from "../../../shared/middleware"; -import { getAdmin, getLogger } from "../../../shared/config"; -import { InvoiceService, EmailOptions } from "./invoiceService"; +// import { onRequest } from "firebase-functions/v2/https"; +// import { Request } from "firebase-functions/v2/https"; +// import { getCorsHandler } from "../../../shared/middleware"; +// import { getAdmin, getLogger } from "../../../shared/config"; +// import { InvoiceService, EmailOptions } from "./invoiceService"; -const admin = getAdmin(); -const logger = getLogger(); -const corsHandler = getCorsHandler(); -const invoiceService = new InvoiceService(); +// const admin = getAdmin(); +// const logger = getLogger(); +// const corsHandler = getCorsHandler(); +// const invoiceService = new InvoiceService(); -export const sendInvoiceEmail = onRequest({ - region: '#{SERVICES_RGN}#' -}, async (request: Request, response) => { - return corsHandler(request, response, async () => { - try { - const authHeader = request.headers.authorization || ''; - if (!authHeader || !authHeader.startsWith('Bearer ')) { - response.status(401).json({ error: 'Unauthorized' }); - return; - } +// export const sendInvoiceEmail = onRequest({ +// region: '#{SERVICES_RGN}#' +// }, async (request: Request, response) => { +// return corsHandler(request, response, async () => { +// try { +// const authHeader = request.headers.authorization || ''; +// if (!authHeader || !authHeader.startsWith('Bearer ')) { +// response.status(401).json({ error: 'Unauthorized' }); +// return; +// } - const idToken = authHeader.split('Bearer ')[1]; +// const idToken = authHeader.split('Bearer ')[1]; - try { - await admin.auth().verifyIdToken(idToken); +// try { +// await admin.auth().verifyIdToken(idToken); - const { - invoicePath, - recipientEmail, - recipientName, - subject, - customHtml, - gymName, - planName, - amount, - transactionId, - paymentDate, - paymentMethod - } = request.body; +// const { +// invoicePath, +// recipientEmail, +// recipientName, +// subject, +// customHtml, +// gymName, +// planName, +// amount, +// transactionId, +// paymentDate, +// paymentMethod +// } = request.body; - if (!invoicePath || !recipientEmail) { - response.status(400).json({ - success: false, - error: 'Missing required fields' - }); - return; - } +// if (!invoicePath || !recipientEmail) { +// response.status(400).json({ +// success: false, +// error: 'Missing required fields' +// }); +// return; +// } - const emailOptions: EmailOptions = { - recipientEmail, - recipientName, - subject, - customHtml, - additionalData: { - gymName, - planName, - amount, - transactionId, - paymentDate: paymentDate ? new Date(paymentDate) : undefined, - paymentMethod - } - }; +// const emailOptions: EmailOptions = { +// recipientEmail, +// recipientName, +// subject, +// customHtml, +// additionalData: { +// gymName, +// planName, +// amount, +// transactionId, +// paymentDate: paymentDate ? new Date(paymentDate) : undefined, +// paymentMethod +// } +// }; - const emailSent = await invoiceService.sendInvoiceEmail(invoicePath, emailOptions); - const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath); +// const emailSent = await invoiceService.sendInvoiceEmail(invoicePath, emailOptions); +// const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath); - response.json({ - success: true, - message: emailSent ? 'Invoice email sent successfully' : 'Failed to send email but URL generated', - downloadUrl - }); +// response.json({ +// success: true, +// message: emailSent ? 'Invoice email sent successfully' : 'Failed to send email but URL generated', +// downloadUrl +// }); - } catch (authError: any) { - logger.error('Authentication error:', authError); - response.status(401).json({ - success: false, - error: 'Invalid authentication token', - details: authError.message - }); - } - } catch (error: any) { - logger.error('Error sending invoice email:', error); - response.status(500).json({ - success: false, - error: 'Failed to send invoice email', - details: error.message - }); - } - }); -}); +// } catch (authError: any) { +// logger.error('Authentication error:', authError); +// response.status(401).json({ +// success: false, +// error: 'Invalid authentication token', +// details: authError.message +// }); +// } +// } catch (error: any) { +// logger.error('Error sending invoice email:', error); +// response.status(500).json({ +// success: false, +// error: 'Failed to send invoice email', +// details: error.message +// }); +// } +// }); +// }); diff --git a/functions/src/payments/phonepe/webhook.ts b/functions/src/payments/phonepe/webhook.ts index 88c9c6f..2e35a3e 100644 --- a/functions/src/payments/phonepe/webhook.ts +++ b/functions/src/payments/phonepe/webhook.ts @@ -142,7 +142,7 @@ export const phonePeWebhook = onRequest({ } else if (paymentId) { await processServicePayment(payload, orderData, paymentId); } else { - logger.error(`No membershipId, bookingId, or serviceId found in metaInfo for order: ${payload.merchantOrderId}`); + logger.error(`No membershipId, bookingId, or paymentId found in metaInfo for order: ${payload.merchantOrderId}`); } logger.info(`Payment data updated for completed payment: ${payload.merchantOrderId}`); @@ -639,6 +639,18 @@ async function processServicePayment(payload: any, orderData: any, paymentId: st return; } + const serviceData = serviceDoc.data(); + + if (serviceData?.status === 'ACCEPTED' && serviceData?.paymentDetails?.merchantOrderId) { + logger.warn(`Service payment already processed for serviceId: ${paymentId}, merchantOrderId: ${serviceData.paymentDetails.merchantOrderId}`); + return; + } + + if (serviceData?.invoicePath && serviceData?.invoiceNumber) { + logger.warn(`Invoice already exists for serviceId: ${paymentId}, invoicePath: ${serviceData.invoicePath}`); + return; + } + await serviceRef.update({ status: 'ACCEPTED', paymentDetails: { @@ -651,9 +663,8 @@ async function processServicePayment(payload: any, orderData: any, paymentId: st updatedAt: admin.firestore.FieldValue.serverTimestamp() }); - logger.info(`Updated service booking status to 'CONFIRMED' for serviceId: ${paymentId}`); + logger.info(`Updated service booking status to 'CONFIRMED' for paymentId: ${paymentId}`); - const serviceData = serviceDoc.data(); const gymId = orderData.metaInfo?.gymId || serviceData?.gymId; if (gymId) { @@ -681,7 +692,7 @@ async function processServicePayment(payload: any, orderData: any, paymentId: st } } - const invoiceNumber = `SRV-${payload.merchantOrderId.substring(0, 8)}`; + const invoiceNumber = `INV-${payload.merchantOrderId.substring(0, 8)}`; logger.info(`Generated invoice number for service: ${invoiceNumber}`); const discountPercentage = orderData.metaInfo?.discount || 0; @@ -703,7 +714,7 @@ async function processServicePayment(payload: any, orderData: any, paymentId: st businessName: gymName, address: gymAddress, gstNumber: orderData.metaInfo?.gstNumber, - customerName: orderData.metaInfo?.customerName || serviceData?.customerName || '', + customerName: orderData.metaInfo?.customerName || serviceData?.normalizedName || '', phoneNumber: orderData.metaInfo?.customerPhone || serviceData?.phoneNumber || '', email: orderData.metaInfo?.customerEmail || serviceData?.email || '', planName: orderData.metaInfo?.serviceName || serviceData?.serviceName || 'Service',