Invoice updates

This commit is contained in:
AllenTJ7 2025-06-25 18:02:34 +05:30
parent 5c886ebe42
commit 9c4cd0fc43
5 changed files with 229 additions and 218 deletions

View File

@ -1,62 +1,62 @@
import { onRequest } from "firebase-functions/v2/https"; // import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https"; // import { Request } from "firebase-functions/v2/https";
import { getCorsHandler } from "../../../shared/middleware"; // import { getCorsHandler } from "../../../shared/middleware";
import { getAdmin, getLogger } from "../../../shared/config"; // import { getAdmin, getLogger } from "../../../shared/config";
import { InvoiceService } from "./invoiceService"; // import { InvoiceService } from "./invoiceService";
const admin = getAdmin(); // const admin = getAdmin();
const logger = getLogger(); // const logger = getLogger();
const corsHandler = getCorsHandler(); // const corsHandler = getCorsHandler();
const invoiceService = new InvoiceService(); // const invoiceService = new InvoiceService();
export const getInvoiceUrl = onRequest({ // export const getInvoiceUrl = onRequest({
region: '#{SERVICES_RGN}#' // region: '#{SERVICES_RGN}#'
}, async (request: Request, response) => { // }, async (request: Request, response) => {
return corsHandler(request, response, async () => { // return corsHandler(request, response, async () => {
try { // try {
const authHeader = request.headers.authorization || ''; // const authHeader = request.headers.authorization || '';
if (!authHeader || !authHeader.startsWith('Bearer ')) { // if (!authHeader || !authHeader.startsWith('Bearer ')) {
response.status(401).json({ error: 'Unauthorized' }); // response.status(401).json({ error: 'Unauthorized' });
return; // return;
} // }
const idToken = authHeader.split('Bearer ')[1]; // const idToken = authHeader.split('Bearer ')[1];
try { // try {
await admin.auth().verifyIdToken(idToken); // await admin.auth().verifyIdToken(idToken);
const { invoicePath } = request.query; // const { invoicePath } = request.query;
if (!invoicePath) { // if (!invoicePath) {
response.status(400).json({ // response.status(400).json({
success: false, // success: false,
error: 'Missing invoice path' // error: 'Missing invoice path'
}); // });
return; // return;
} // }
const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath as string); // const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath as string);
response.json({ // response.json({
success: true, // success: true,
downloadUrl // downloadUrl
}); // });
} catch (authError: any) { // } catch (authError: any) {
logger.error('Authentication error:', authError); // logger.error('Authentication error:', authError);
response.status(401).json({ // response.status(401).json({
success: false, // success: false,
error: 'Invalid authentication token', // error: 'Invalid authentication token',
details: authError.message // details: authError.message
}); // });
} // }
} catch (error: any) { // } catch (error: any) {
logger.error('Error getting invoice URL:', error); // logger.error('Error getting invoice URL:', error);
response.status(500).json({ // response.status(500).json({
success: false, // success: false,
error: 'Failed to get invoice URL', // error: 'Failed to get invoice URL',
details: error.message // details: error.message
}); // });
} // }
}); // });
}); // });

View File

@ -1,13 +1,13 @@
import { getInvoiceUrl } from './getInvoiceUrl'; // import { getInvoiceUrl } from './getInvoiceUrl';
import { InvoiceService } from './invoiceService'; import { InvoiceService } from './invoiceService';
import { processInvoice } from './processInvoice'; // import { processInvoice } from './processInvoice';
import { sendInvoiceEmail } from './sendInvoiceEmail'; // import { sendInvoiceEmail } from './sendInvoiceEmail';
import { directGenerateInvoice } from './directInvoice'; import { directGenerateInvoice } from './directInvoice';
export { export {
getInvoiceUrl, // getInvoiceUrl,
InvoiceService, InvoiceService,
processInvoice, // processInvoice,
sendInvoiceEmail, // sendInvoiceEmail,
directGenerateInvoice, directGenerateInvoice,
}; };

View File

@ -1,83 +1,83 @@
import { onRequest } from "firebase-functions/v2/https"; // import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https"; // import { Request } from "firebase-functions/v2/https";
import { getCorsHandler } from "../../../shared/middleware"; // import { getCorsHandler } from "../../../shared/middleware";
import { getAdmin, getLogger } from "../../../shared/config"; // import { getAdmin, getLogger } from "../../../shared/config";
import { InvoiceService } from "./invoiceService"; // import { InvoiceService } from "./invoiceService";
const admin = getAdmin(); // const admin = getAdmin();
const logger = getLogger(); // const logger = getLogger();
const corsHandler = getCorsHandler(); // const corsHandler = getCorsHandler();
const invoiceService = new InvoiceService(); // const invoiceService = new InvoiceService();
export const processInvoice = onRequest({ // export const processInvoice = onRequest({
region: '#{SERVICES_RGN}#' // region: '#{SERVICES_RGN}#'
}, async (request: Request, response) => { // }, async (request: Request, response) => {
return corsHandler(request, response, async () => { // return corsHandler(request, response, async () => {
try { // try {
const authHeader = request.headers.authorization || ''; // const authHeader = request.headers.authorization || '';
if (!authHeader || !authHeader.startsWith('Bearer ')) { // if (!authHeader || !authHeader.startsWith('Bearer ')) {
response.status(401).json({ error: 'Unauthorized' }); // response.status(401).json({ error: 'Unauthorized' });
return; // return;
} // }
const idToken = authHeader.split('Bearer ')[1]; // const idToken = authHeader.split('Bearer ')[1];
try { // try {
await admin.auth().verifyIdToken(idToken); // await admin.auth().verifyIdToken(idToken);
const { // const {
membershipId, // membershipId,
paymentId, // paymentId,
invoiceData, // invoiceData,
emailOptions // emailOptions
} = request.body; // } = request.body;
if (!membershipId || !paymentId || !invoiceData) { // if (!membershipId || !paymentId || !invoiceData) {
response.status(400).json({ // response.status(400).json({
success: false, // success: false,
error: 'Missing required fields' // error: 'Missing required fields'
}); // });
return; // return;
} // }
const result = await invoiceService.processInvoice( // const result = await invoiceService.processInvoice(
membershipId, // membershipId,
paymentId, // paymentId,
invoiceData, // invoiceData,
emailOptions // emailOptions
); // );
if (!result.success) { // if (!result.success) {
response.status(400).json({ // response.status(400).json({
success: false, // success: false,
error: result.error || 'Failed to process invoice' // error: result.error || 'Failed to process invoice'
}); // });
return; // return;
} // }
response.json({ // response.json({
success: true, // success: true,
message: 'Invoice processed successfully', // message: 'Invoice processed successfully',
invoicePath: result.invoicePath, // invoicePath: result.invoicePath,
downloadUrl: result.downloadUrl, // downloadUrl: result.downloadUrl,
emailSent: result.emailSent // emailSent: result.emailSent
}); // });
} catch (authError: any) { // } catch (authError: any) {
logger.error('Authentication error:', authError); // logger.error('Authentication error:', authError);
response.status(401).json({ // response.status(401).json({
success: false, // success: false,
error: 'Invalid authentication token', // error: 'Invalid authentication token',
details: authError.message // details: authError.message
}); // });
} // }
} catch (error: any) { // } catch (error: any) {
logger.error('Error processing invoice:', error); // logger.error('Error processing invoice:', error);
response.status(500).json({ // response.status(500).json({
success: false, // success: false,
error: 'Failed to process invoice', // error: 'Failed to process invoice',
details: error.message // details: error.message
}); // });
} // }
}); // });
}); // });

View File

@ -1,91 +1,91 @@
import { onRequest } from "firebase-functions/v2/https"; // import { onRequest } from "firebase-functions/v2/https";
import { Request } from "firebase-functions/v2/https"; // import { Request } from "firebase-functions/v2/https";
import { getCorsHandler } from "../../../shared/middleware"; // import { getCorsHandler } from "../../../shared/middleware";
import { getAdmin, getLogger } from "../../../shared/config"; // import { getAdmin, getLogger } from "../../../shared/config";
import { InvoiceService, EmailOptions } from "./invoiceService"; // import { InvoiceService, EmailOptions } from "./invoiceService";
const admin = getAdmin(); // const admin = getAdmin();
const logger = getLogger(); // const logger = getLogger();
const corsHandler = getCorsHandler(); // const corsHandler = getCorsHandler();
const invoiceService = new InvoiceService(); // const invoiceService = new InvoiceService();
export const sendInvoiceEmail = onRequest({ // export const sendInvoiceEmail = onRequest({
region: '#{SERVICES_RGN}#' // region: '#{SERVICES_RGN}#'
}, async (request: Request, response) => { // }, async (request: Request, response) => {
return corsHandler(request, response, async () => { // return corsHandler(request, response, async () => {
try { // try {
const authHeader = request.headers.authorization || ''; // const authHeader = request.headers.authorization || '';
if (!authHeader || !authHeader.startsWith('Bearer ')) { // if (!authHeader || !authHeader.startsWith('Bearer ')) {
response.status(401).json({ error: 'Unauthorized' }); // response.status(401).json({ error: 'Unauthorized' });
return; // return;
} // }
const idToken = authHeader.split('Bearer ')[1]; // const idToken = authHeader.split('Bearer ')[1];
try { // try {
await admin.auth().verifyIdToken(idToken); // await admin.auth().verifyIdToken(idToken);
const { // const {
invoicePath, // invoicePath,
recipientEmail, // recipientEmail,
recipientName, // recipientName,
subject, // subject,
customHtml, // customHtml,
gymName, // gymName,
planName, // planName,
amount, // amount,
transactionId, // transactionId,
paymentDate, // paymentDate,
paymentMethod // paymentMethod
} = request.body; // } = request.body;
if (!invoicePath || !recipientEmail) { // if (!invoicePath || !recipientEmail) {
response.status(400).json({ // response.status(400).json({
success: false, // success: false,
error: 'Missing required fields' // error: 'Missing required fields'
}); // });
return; // return;
} // }
const emailOptions: EmailOptions = { // const emailOptions: EmailOptions = {
recipientEmail, // recipientEmail,
recipientName, // recipientName,
subject, // subject,
customHtml, // customHtml,
additionalData: { // additionalData: {
gymName, // gymName,
planName, // planName,
amount, // amount,
transactionId, // transactionId,
paymentDate: paymentDate ? new Date(paymentDate) : undefined, // paymentDate: paymentDate ? new Date(paymentDate) : undefined,
paymentMethod // paymentMethod
} // }
}; // };
const emailSent = await invoiceService.sendInvoiceEmail(invoicePath, emailOptions); // const emailSent = await invoiceService.sendInvoiceEmail(invoicePath, emailOptions);
const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath); // const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath);
response.json({ // response.json({
success: true, // success: true,
message: emailSent ? 'Invoice email sent successfully' : 'Failed to send email but URL generated', // message: emailSent ? 'Invoice email sent successfully' : 'Failed to send email but URL generated',
downloadUrl // downloadUrl
}); // });
} catch (authError: any) { // } catch (authError: any) {
logger.error('Authentication error:', authError); // logger.error('Authentication error:', authError);
response.status(401).json({ // response.status(401).json({
success: false, // success: false,
error: 'Invalid authentication token', // error: 'Invalid authentication token',
details: authError.message // details: authError.message
}); // });
} // }
} catch (error: any) { // } catch (error: any) {
logger.error('Error sending invoice email:', error); // logger.error('Error sending invoice email:', error);
response.status(500).json({ // response.status(500).json({
success: false, // success: false,
error: 'Failed to send invoice email', // error: 'Failed to send invoice email',
details: error.message // details: error.message
}); // });
} // }
}); // });
}); // });

View File

@ -142,7 +142,7 @@ export const phonePeWebhook = onRequest({
} else if (paymentId) { } else if (paymentId) {
await processServicePayment(payload, orderData, paymentId); await processServicePayment(payload, orderData, paymentId);
} else { } 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}`); logger.info(`Payment data updated for completed payment: ${payload.merchantOrderId}`);
@ -639,6 +639,18 @@ async function processServicePayment(payload: any, orderData: any, paymentId: st
return; 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({ await serviceRef.update({
status: 'ACCEPTED', status: 'ACCEPTED',
paymentDetails: { paymentDetails: {
@ -651,9 +663,8 @@ async function processServicePayment(payload: any, orderData: any, paymentId: st
updatedAt: admin.firestore.FieldValue.serverTimestamp() 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; const gymId = orderData.metaInfo?.gymId || serviceData?.gymId;
if (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}`); logger.info(`Generated invoice number for service: ${invoiceNumber}`);
const discountPercentage = orderData.metaInfo?.discount || 0; const discountPercentage = orderData.metaInfo?.discount || 0;
@ -703,7 +714,7 @@ async function processServicePayment(payload: any, orderData: any, paymentId: st
businessName: gymName, businessName: gymName,
address: gymAddress, address: gymAddress,
gstNumber: orderData.metaInfo?.gstNumber, gstNumber: orderData.metaInfo?.gstNumber,
customerName: orderData.metaInfo?.customerName || serviceData?.customerName || '', customerName: orderData.metaInfo?.customerName || serviceData?.normalizedName || '',
phoneNumber: orderData.metaInfo?.customerPhone || serviceData?.phoneNumber || '', phoneNumber: orderData.metaInfo?.customerPhone || serviceData?.phoneNumber || '',
email: orderData.metaInfo?.customerEmail || serviceData?.email || '', email: orderData.metaInfo?.customerEmail || serviceData?.email || '',
planName: orderData.metaInfo?.serviceName || serviceData?.serviceName || 'Service', planName: orderData.metaInfo?.serviceName || serviceData?.serviceName || 'Service',