Compare commits

..

No commits in common. "cc5b6d69875a2ee2022c577ce2dcdc4d371e95f9" and "f19e3b012ddeda88a3f8e49a2dd363b3e8e5bc97" have entirely different histories.

8 changed files with 235 additions and 283 deletions

View File

@ -166,52 +166,6 @@
} }
] ]
}, },
{
"collectionGroup": "workout_logs",
"queryScope": "COLLECTION",
"fields": [
{
"fieldPath": "user_id",
"order": "ASCENDING"
},
{
"fieldPath": "date",
"order": "DESCENDING"
}
]
},
{
"collectionGroup": "workout_logs",
"queryScope": "COLLECTION",
"fields": [
{
"fieldPath": "user_id",
"order": "ASCENDING"
},
{
"fieldPath": "date",
"order": "ASCENDING"
}
]
},
{
"collectionGroup": "workout_logs",
"queryScope": "COLLECTION",
"fields": [
{
"fieldPath": "user_id",
"order": "ASCENDING"
},
{
"fieldPath": "start_time",
"order": "ASCENDING"
},
{
"fieldPath": "date",
"order": "ASCENDING"
}
]
},
{ {
"collectionGroup": "terms_and_conditions", "collectionGroup": "terms_and_conditions",
"queryScope": "COLLECTION", "queryScope": "COLLECTION",

View File

@ -37,7 +37,7 @@ const stripHtml = (html: string): string => {
async function sendSimpleEmail(data: EmailRequest, recipients: string[]) { async function sendSimpleEmail(data: EmailRequest, recipients: string[]) {
const ses = new SESClient({ const ses = new SESClient({
region: '#{AWS_REGION}#', region: 'ap-south-1',
credentials: { credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '' secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''
@ -63,7 +63,7 @@ async function sendSimpleEmail(data: EmailRequest, recipients: string[]) {
async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]) { async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]) {
const ses = new SESClient({ const ses = new SESClient({
region: 'ap-south-1', region: '#{SERVICES_RGN}#',
credentials: { credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '' secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''

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

@ -120,7 +120,7 @@ export const phonePeWebhook = onRequest({
const orderData = orderDoc.data(); const orderData = orderDoc.data();
const membershipId = orderData.metaInfo?.membershipId; const membershipId = orderData.metaInfo?.membershipId;
const bookingId = orderData.metaInfo?.bookingId; const bookingId = orderData.metaInfo?.bookingId;
const paymentId = orderData.metaInfo?.paymentId; const serviceId = orderData.metaInfo?.serviceId;
if (bookingId) { if (bookingId) {
await processDayPassBooking(payload, orderData, bookingId); await processDayPassBooking(payload, orderData, bookingId);
@ -139,10 +139,10 @@ export const phonePeWebhook = onRequest({
if (paymentUpdateSuccess) { if (paymentUpdateSuccess) {
await processMembershipPayment(payload, orderData, membershipId); await processMembershipPayment(payload, orderData, membershipId);
} }
} else if (paymentId) { } else if (serviceId) {
await processServicePayment(payload, orderData, paymentId); await processServicePayment(payload, orderData, serviceId);
} else { } else {
logger.error(`No membershipId, bookingId, or paymentId found in metaInfo for order: ${payload.merchantOrderId}`); logger.error(`No membershipId, bookingId, or serviceId 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,18 +639,6 @@ 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: {
@ -663,8 +651,9 @@ 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 paymentId: ${paymentId}`); logger.info(`Updated service booking status to 'CONFIRMED' for serviceId: ${paymentId}`);
const serviceData = serviceDoc.data();
const gymId = orderData.metaInfo?.gymId || serviceData?.gymId; const gymId = orderData.metaInfo?.gymId || serviceData?.gymId;
if (gymId) { if (gymId) {
@ -692,7 +681,7 @@ async function processServicePayment(payload: any, orderData: any, paymentId: st
} }
} }
const invoiceNumber = `INV-${payload.merchantOrderId.substring(0, 8)}`; const invoiceNumber = `SRV-${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;
@ -714,7 +703,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?.normalizedName || '', customerName: orderData.metaInfo?.customerName || serviceData?.customerName || '',
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',

View File

@ -21,7 +21,7 @@ interface EmailRequest {
interface Attachment { interface Attachment {
filename: string; filename: string;
content: string | Buffer; content: string | Buffer; // Base64 encoded string or Buffer
contentType?: string; contentType?: string;
} }
@ -32,7 +32,7 @@ const stripHtml = (html: string): string => {
async function sendSimpleEmail(data: EmailRequest, recipients: string[]) { async function sendSimpleEmail(data: EmailRequest, recipients: string[]) {
const ses = new SESClient({ const ses = new SESClient({
region: process.env.AWS_REGION, region: 'ap-south-1',
credentials: { credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '' secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''
@ -58,7 +58,7 @@ async function sendSimpleEmail(data: EmailRequest, recipients: string[]) {
async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]) { async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]) {
const ses = new SESClient({ const ses = new SESClient({
region: process.env.AWS_REGION, region: 'ap-south-1',
credentials: { credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '' secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''
@ -72,21 +72,26 @@ async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]
rawMessage += `MIME-Version: 1.0\n`; rawMessage += `MIME-Version: 1.0\n`;
rawMessage += `Content-Type: multipart/mixed; boundary="${boundary}"\n\n`; rawMessage += `Content-Type: multipart/mixed; boundary="${boundary}"\n\n`;
// Add email body (multipart/alternative)
rawMessage += `--${boundary}\n`; rawMessage += `--${boundary}\n`;
rawMessage += `Content-Type: multipart/alternative; boundary="alt_${boundary}"\n\n`; rawMessage += `Content-Type: multipart/alternative; boundary="alt_${boundary}"\n\n`;
// Text part
if (data.text) { if (data.text) {
rawMessage += `--alt_${boundary}\n`; rawMessage += `--alt_${boundary}\n`;
rawMessage += `Content-Type: text/plain; charset=UTF-8\n\n`; rawMessage += `Content-Type: text/plain; charset=UTF-8\n\n`;
rawMessage += `${data.text}\n\n`; rawMessage += `${data.text}\n\n`;
} }
// HTML part
rawMessage += `--alt_${boundary}\n`; rawMessage += `--alt_${boundary}\n`;
rawMessage += `Content-Type: text/html; charset=UTF-8\n\n`; rawMessage += `Content-Type: text/html; charset=UTF-8\n\n`;
rawMessage += `${data.html}\n\n`; rawMessage += `${data.html}\n\n`;
// Close alternative part
rawMessage += `--alt_${boundary}--\n\n`; rawMessage += `--alt_${boundary}--\n\n`;
// Add attachments
for (const attachment of data.attachments || []) { for (const attachment of data.attachments || []) {
const contentType = attachment.contentType || const contentType = attachment.contentType ||
mime.lookup(attachment.filename) || mime.lookup(attachment.filename) ||
@ -104,6 +109,7 @@ async function sendEmailWithAttachments(data: EmailRequest, recipients: string[]
rawMessage += contentBuffer.toString('base64') + '\n\n'; rawMessage += contentBuffer.toString('base64') + '\n\n';
} }
// Close message
rawMessage += `--${boundary}--`; rawMessage += `--${boundary}--`;
const command = new SendRawEmailCommand({ const command = new SendRawEmailCommand({
@ -134,6 +140,7 @@ export async function sendEmailWithAttachmentUtil(
try { try {
logger.info(`Sending email with attachment to: ${toAddress}`); logger.info(`Sending email with attachment to: ${toAddress}`);
// Initialize data with basic fields
const data: EmailRequest = { const data: EmailRequest = {
to: toAddress, to: toAddress,
html: message, html: message,
@ -144,11 +151,13 @@ export async function sendEmailWithAttachmentUtil(
attachments: [] attachments: []
}; };
// Handle file URL if provided
if (fileUrl && fileName) { if (fileUrl && fileName) {
logger.info(`Downloading attachment from URL: ${fileUrl}`); logger.info(`Downloading attachment from URL: ${fileUrl}`);
try { try {
const fileContent = await downloadFileFromUrl(fileUrl); const fileContent = await downloadFileFromUrl(fileUrl);
// Add the downloaded file as an attachment
data.attachments!.push({ data.attachments!.push({
filename: fileName, filename: fileName,
content: fileContent, content: fileContent,