From fc01b038b3529936fffc944d5c4dc70fd2963a16 Mon Sep 17 00:00:00 2001 From: DhanshCOSQ Date: Mon, 7 Apr 2025 20:03:20 +0530 Subject: [PATCH 1/8] Added cors createCashfreeOrder --- functions/package-lock.json | 1 + functions/package.json | 1 + functions/src/index.ts | 169 +++++++++++++++++++----------------- 3 files changed, 90 insertions(+), 81 deletions(-) diff --git a/functions/package-lock.json b/functions/package-lock.json index ac67f4d..8a295b3 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@types/node-fetch": "^2.6.12", "axios": "^1.8.4", + "cors": "^2.8.5", "firebase-admin": "^12.6.0", "firebase-functions": "^6.0.1", "form-data": "^4.0.1", diff --git a/functions/package.json b/functions/package.json index 5c20815..939628c 100644 --- a/functions/package.json +++ b/functions/package.json @@ -17,6 +17,7 @@ "dependencies": { "@types/node-fetch": "^2.6.12", "axios": "^1.8.4", + "cors": "^2.8.5", "firebase-admin": "^12.6.0", "firebase-functions": "^6.0.1", "form-data": "^4.0.1", diff --git a/functions/src/index.ts b/functions/src/index.ts index a26adc6..b250098 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -8,6 +8,7 @@ import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs'; import * as https from 'https'; +import cors from 'cors'; import axios from "axios"; import { getStorage } from 'firebase-admin/storage'; const formData = require('form-data'); @@ -19,6 +20,9 @@ const twilio = require('twilio'); if (!admin.apps.length) { admin.initializeApp(); } + +const corsHandler = cors({ origin: true }); + export const sendEmailWithAttachment = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: express.Response) => { @@ -245,92 +249,95 @@ export const notifyInvitation = onDocumentCreated({ export const createCashfreeOrder = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: express.Response) => { - 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 decodedToken = await admin.auth().verifyIdToken(idToken); - const uid = decodedToken.uid; - - const { - amount, - customerName, - customerEmail, - customerPhone, - productInfo - } = request.body; - - if (!amount || !customerEmail || !customerPhone) { - response.status(400).json({ error: 'Missing required fields' }); - return; - } - - const clientId = process.env.CASHFREE_CLIENT_ID; - const clientSecret = process.env.CASHFREE_CLIENT_SECRET; - const isTest = true; - - const apiUrl = isTest - ? 'https://sandbox.cashfree.com/pg/orders' - : 'https://api.cashfree.com/pg/orders'; - - const orderId = `order_${Date.now()}_${uid.substring(0, 6)}`; - - const cashfreeResponse = await axios.post( - apiUrl, - { - order_id: orderId, - order_amount: amount, - order_currency: 'INR', - customer_details: { - customer_id: uid, - customer_name: customerName || 'Fitlien User', - customer_email: customerEmail, - customer_phone: customerPhone - }, - order_meta: { - return_url: `https://fitlien.com/payment/status?order_id={order_id}`, - // notify_url: `https://$filien.web.app/verifyCashfreePayment` - }, - order_note: productInfo || 'Fitlien Membership' - }, - { - headers: { - 'x-api-version': '2022-09-01', - 'x-client-id': clientId, - 'x-client-secret': clientSecret, - 'Content-Type': 'application/json' - } + return corsHandler(request, response, async () => { + try { + const authHeader = request.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + response.status(401).json({ error: 'Unauthorized' }); + return; } - ); - await admin.firestore().collection('payment_orders').doc(orderId).set({ - userId: uid, - amount: amount, - customerEmail: customerEmail, - customerPhone: customerPhone, - orderStatus: 'CREATED', - paymentGateway: 'Cashfree', - createdAt: new Date(), - ...cashfreeResponse.data - }); + const idToken = authHeader.split('Bearer ')[1]; + const decodedToken = await admin.auth().verifyIdToken(idToken); + const uid = decodedToken.uid; - response.json({ - order_id: cashfreeResponse.data.order_id, - payment_session_id: cashfreeResponse.data.payment_session_id - }); + const { + amount, + customerName, + customerEmail, + customerPhone, + productInfo + } = request.body; - logger.info(`Cashfree order created: ${orderId}`); - } catch (error: any) { - logger.error('Cashfree order creation error:', error); - response.status(500).json({ - error: 'Failed to create payment order', - details: error.response?.data || error.message - }); - } + if (!amount || !customerEmail || !customerPhone) { + response.status(400).json({ error: 'Missing required fields' }); + return; + } + + const clientId = process.env.CASHFREE_CLIENT_ID; + const clientSecret = process.env.CASHFREE_CLIENT_SECRET; + const isTest = true; + + const apiUrl = isTest + ? 'https://sandbox.cashfree.com/pg/orders' + : 'https://api.cashfree.com/pg/orders'; + + const orderId = `order_${Date.now()}_${uid.substring(0, 6)}`; + + const cashfreeResponse = await axios.post( + apiUrl, + { + order_id: orderId, + order_amount: amount, + order_currency: 'INR', + customer_details: { + customer_id: uid, + customer_name: customerName || 'Fitlien User', + customer_email: customerEmail, + customer_phone: customerPhone + }, + order_meta: { + return_url: `https://fitlien.com/payment/status?order_id={order_id}`, + // notify_url: `https://$filien.web.app/verifyCashfreePayment` + }, + order_note: productInfo || 'Fitlien Membership' + }, + { + headers: { + 'x-api-version': '2022-09-01', + 'x-client-id': clientId, + 'x-client-secret': clientSecret, + 'Content-Type': 'application/json' + } + } + ); + + await admin.firestore().collection('payment_orders').doc(orderId).set({ + userId: uid, + amount: amount, + customerEmail: customerEmail, + customerPhone: customerPhone, + orderStatus: 'CREATED', + paymentGateway: 'Cashfree', + createdAt: new Date(), + ...cashfreeResponse.data + }); + + response.json({ + order_id: cashfreeResponse.data.order_id, + payment_session_id: cashfreeResponse.data.payment_session_id + }); + + logger.info(`Cashfree order created: ${orderId}`); + } catch (error: any) { + logger.error('Cashfree order creation error:', error); + response.status(500).json({ + error: 'Failed to create payment order', + details: error.response?.data || error.message + }); + } + }); }); export const verifyCashfreePayment = onRequest({ -- 2.43.0 From ad532bd53d9fa3c58fb523a49a8044250b9e03ed Mon Sep 17 00:00:00 2001 From: DhanshCOSQ Date: Mon, 7 Apr 2025 20:37:12 +0530 Subject: [PATCH 2/8] added cors to every function --- functions/src/index.ts | 339 +++++++++++++++++++++-------------------- 1 file changed, 177 insertions(+), 162 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index b250098..4c98cab 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -26,147 +26,158 @@ const corsHandler = cors({ origin: true }); export const sendEmailWithAttachment = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: express.Response) => { - try { - const { toAddress, subject, message, fileUrl, fileName } = request.body; + return corsHandler(request, response, async () => { + try { + const { toAddress, subject, message, fileUrl, fileName } = request.body; - if (!toAddress || !subject || !message || !fileUrl) { - response.status(400).json({ - error: 'Missing required fields (toAddress, subject, message, fileUrl)' - }); - return; - } - 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(); + if (!toAddress || !subject || !message || !fileUrl) { + response.status(400).json({ + error: 'Missing required fields (toAddress, subject, message, fileUrl)' }); - }).on('error', (err) => { - fs.unlink(tempFilePath, () => { }); - reject(err); - }); - }); - - const mailgun = new Mailgun(formData); - 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', + return; } - }; + 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(); + }); + }).on('error', (err) => { + fs.unlink(tempFilePath, () => { }); + reject(err); + }); + }); - const result = await client.messages.create(process.env.MAILGUN_SERVER, data); - fs.unlinkSync(tempFilePath); + const mailgun = new Mailgun(formData); + const client = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY }); - logger.info('Email with attachment from URL sent successfully'); - response.json({ success: true, result }); - } catch (error) { - logger.error('Error sending email with attachment from URL:', error); - response.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error) }); - } + 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'); + response.json({ success: true, result }); + } catch (error) { + logger.error('Error sending email with attachment from URL:', error); + response.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error) }); + } + }); }); export const accessFile = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: express.Response) => { - try { - const filePath = request.query.path as string; - if (!filePath) { - response.status(400).send('File path is required'); - return; + return corsHandler(request, response, async () => { + + try { + const filePath = request.query.path as string; + if (!filePath) { + response.status(400).send('File path is required'); + return; + } + + const expirationMs = 60 * 60 * 1000; + + const bucket = getStorage().bucket(); + const file = bucket.file(filePath); + + const [exists] = await file.exists(); + if (!exists) { + response.status(404).send('File not found'); + return; + } + + const [signedUrl] = await file.getSignedUrl({ + action: 'read', + expires: Date.now() + expirationMs, + responseDisposition: `attachment; filename="${path.basename(filePath)}"`, + }); + + response.redirect(signedUrl); + logger.info(`File access redirect for ${filePath}`); + } catch (error) { + logger.error('Error accessing file:', error); + response.status(500).send('Error accessing file'); } - - const expirationMs = 60 * 60 * 1000; - - const bucket = getStorage().bucket(); - const file = bucket.file(filePath); - - const [exists] = await file.exists(); - if (!exists) { - response.status(404).send('File not found'); - return; - } - - const [signedUrl] = await file.getSignedUrl({ - action: 'read', - expires: Date.now() + expirationMs, - responseDisposition: `attachment; filename="${path.basename(filePath)}"`, - }); - - response.redirect(signedUrl); - logger.info(`File access redirect for ${filePath}`); - } catch (error) { - logger.error('Error accessing file:', error); - response.status(500).send('Error accessing file'); - } + }); }); export const sendEmailMessage = onRequest({ region: '#{SERVICES_RGN}#' }, (request: Request, response: express.Response) => { - const mailgun = new Mailgun(formData); - const mailGunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY }); + return corsHandler(request, response, async () => { - const toAddress = request.body.toAddress; - const subject = request.body.subject; - const message = request.body.message; - const options = { - wordwrap: 130, - }; + const mailgun = new Mailgun(formData); + const mailGunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY }); - const textMessage = convert(message, options); - mailGunClient.messages.create(process.env.MAILGUN_SERVER, { - from: process.env.MAILGUN_FROM_ADDRESS, - to: toAddress, - subject: subject, - text: textMessage, - html: message - }).then((res: any) => { - logger.info(res); - response.send(res); - }).catch((err: any) => { - logger.error(err); - response.send(err); + const toAddress = request.body.toAddress; + const subject = request.body.subject; + const message = request.body.message; + const options = { + wordwrap: 130, + }; + + const textMessage = convert(message, options); + mailGunClient.messages.create(process.env.MAILGUN_SERVER, { + from: process.env.MAILGUN_FROM_ADDRESS, + to: toAddress, + subject: subject, + text: textMessage, + html: message + }).then((res: any) => { + logger.info(res); + response.send(res); + }).catch((err: any) => { + logger.error(err); + response.send(err); + }); }); }); export const sendSMSMessage = onRequest({ region: '#{SERVICES_RGN}#' }, (request: Request, response: express.Response) => { - const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); - const { to, body } = request.body; - client.messages - .create({ - body: body, - from: process.env.TWILIO_PHONE_NUMBER, - to: to - }) - .then((message: any) => { - logger.info('SMS sent successfully:', message.sid); - response.json({ success: true, messageId: message.sid }); - }) - .catch((error: any) => { - logger.error('Error sending SMS:', error); - response.status(500).json({ success: false, error: error.message }); - }); + return corsHandler(request, response, async () => { + + const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); + const { to, body } = request.body; + client.messages + .create({ + body: body, + from: process.env.TWILIO_PHONE_NUMBER, + to: to + }) + .then((message: any) => { + logger.info('SMS sent successfully:', message.sid); + response.json({ success: true, messageId: message.sid }); + }) + .catch((error: any) => { + logger.error('Error sending SMS:', error); + response.status(500).json({ success: false, error: error.message }); + }); + }); }); interface Invitation { @@ -180,6 +191,7 @@ export const notifyInvitation = onDocumentCreated({ document: 'notifications/{notificationId}', region: '#{SERVICES_RGN}#' }, async (event: any) => { + const invitation = event.data?.data() as Invitation; const invitationId = event.params.invitationId; @@ -343,55 +355,58 @@ export const createCashfreeOrder = onRequest({ export const verifyCashfreePayment = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: express.Response) => { - try { - const orderId = request.body.order_id || request.query.order_id; + return corsHandler(request, response, async () => { - if (!orderId) { - response.status(400).json({ error: 'Order ID is required' }); - return; - } + try { + const orderId = request.body.order_id || request.query.order_id; - const clientId = process.env.CASHFREE_CLIENT_ID; - const clientSecret = process.env.CASHFREE_CLIENT_SECRET; - const isTest = process.env.CASHFREE_ENVIRONMENT !== 'production'; - - const apiUrl = isTest - ? `https://sandbox.cashfree.com/pg/orders/${orderId}` - : `https://api.cashfree.com/pg/orders/${orderId}`; - - const cashfreeResponse = await axios.get( - apiUrl, - { - headers: { - 'x-api-version': '2022-09-01', - 'x-client-id': clientId, - 'x-client-secret': clientSecret - } + if (!orderId) { + response.status(400).json({ error: 'Order ID is required' }); + return; } - ); - await admin.firestore().collection('payment_orders').doc(orderId).update({ - orderStatus: cashfreeResponse.data.order_status, - paymentDetails: cashfreeResponse.data, - updatedAt: new Date() - }); + const clientId = process.env.CASHFREE_CLIENT_ID; + const clientSecret = process.env.CASHFREE_CLIENT_SECRET; + const isTest = process.env.CASHFREE_ENVIRONMENT !== 'production'; - if (request.headers['x-webhook-source'] === 'cashfree') { - response.status(200).send('OK'); - return; + const apiUrl = isTest + ? `https://sandbox.cashfree.com/pg/orders/${orderId}` + : `https://api.cashfree.com/pg/orders/${orderId}`; + + const cashfreeResponse = await axios.get( + apiUrl, + { + headers: { + 'x-api-version': '2022-09-01', + 'x-client-id': clientId, + 'x-client-secret': clientSecret + } + } + ); + + await admin.firestore().collection('payment_orders').doc(orderId).update({ + orderStatus: cashfreeResponse.data.order_status, + paymentDetails: cashfreeResponse.data, + updatedAt: new Date() + }); + + if (request.headers['x-webhook-source'] === 'cashfree') { + response.status(200).send('OK'); + return; + } + + response.json({ + status: cashfreeResponse.data.order_status, + paymentDetails: cashfreeResponse.data + }); + + logger.info(`Cashfree payment verified: ${orderId}`); + } catch (error: any) { + logger.error('Cashfree payment verification error:', error); + response.status(500).json({ + error: 'Failed to verify payment status', + details: error.response?.data || error.message + }); } - - response.json({ - status: cashfreeResponse.data.order_status, - paymentDetails: cashfreeResponse.data - }); - - logger.info(`Cashfree payment verified: ${orderId}`); - } catch (error: any) { - logger.error('Cashfree payment verification error:', error); - response.status(500).json({ - error: 'Failed to verify payment status', - details: error.response?.data || error.message - }); - } + }); }); -- 2.43.0 From ce1c28d3e1c529abdb48648a87b214f7f3ef29df Mon Sep 17 00:00:00 2001 From: DhanshCOSQ Date: Tue, 8 Apr 2025 16:01:36 +0530 Subject: [PATCH 3/8] added google api. --- functions/.env.example | 1 + functions/src/index.ts | 58 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/functions/.env.example b/functions/.env.example index 381bfad..7f1bf60 100644 --- a/functions/.env.example +++ b/functions/.env.example @@ -7,4 +7,5 @@ TWILIO_PHONE_NUMBER=#{TWILIO_PHONE_NUMBER}# SERVICES_RGN=#{SERVICES_RGN}# CASHFREE_CLIENT_ID=#{CASHFREE_CLIENT_ID}# CASHFREE_CLIENT_SECRET=#{CASHFREE_CLIENT_SECRET}# +GOOGLE_PLACES_API_KEY=#{GOOGLE_PLACES_API_KEY}# diff --git a/functions/src/index.ts b/functions/src/index.ts index 4c98cab..b369f00 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -410,3 +410,61 @@ export const verifyCashfreePayment = onRequest({ } }); }); + +export const getPlacesAutocomplete = onRequest({ + region: '#{SERVICES_RGN}#' +}, async (request: Request, response: express.Response) => { + return corsHandler(request, response, async () => { + try { + const { input, location, radius, types, components, sessiontoken } = request.query; + + if (!input) { + response.status(400).json({ + error: 'Input parameter is required for autocomplete' + }); + return; + } + + const apiKey = process.env.GOOGLE_PLACES_API_KEY; + if (!apiKey) { + logger.error('Google Places API key is not configured'); + response.status(500).json({ error: 'Server configuration error' }); + return; + } + + const url = 'https://maps.googleapis.com/maps/api/place/autocomplete/json'; + const params: any = { + key: apiKey, + input: input + }; + + if (location && radius) { + params.location = location; + params.radius = radius; + } + + if (types) { + params.types = types; + } + + if (components) { + params.components = components; + } + + if (sessiontoken) { + params.sessiontoken = sessiontoken; + } + + const result = await axios.get(url, { params }); + + logger.info('Google Places Autocomplete API request completed successfully'); + response.json(result.data); + } catch (error) { + logger.error('Error fetching place autocomplete suggestions:', error); + response.status(500).json({ + success: false, + error: error instanceof Error ? error.message : String(error) + }); + } + }); +}); -- 2.43.0 From 24cac6f1e766038ab45486dec25f2b51202168ec Mon Sep 17 00:00:00 2001 From: DhanshCOSQ Date: Tue, 8 Apr 2025 18:15:51 +0530 Subject: [PATCH 4/8] Changed variable name from GOOGLE_PLACES_API_KEY to GOOGLE_MAPS_API_KEY --- functions/.env.example | 2 +- functions/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/.env.example b/functions/.env.example index 7f1bf60..8f998fb 100644 --- a/functions/.env.example +++ b/functions/.env.example @@ -7,5 +7,5 @@ TWILIO_PHONE_NUMBER=#{TWILIO_PHONE_NUMBER}# SERVICES_RGN=#{SERVICES_RGN}# CASHFREE_CLIENT_ID=#{CASHFREE_CLIENT_ID}# CASHFREE_CLIENT_SECRET=#{CASHFREE_CLIENT_SECRET}# -GOOGLE_PLACES_API_KEY=#{GOOGLE_PLACES_API_KEY}# +GOOGLE_MAPS_API_KEY=#{GOOGLE_MAPS_API_KEY}# diff --git a/functions/src/index.ts b/functions/src/index.ts index b369f00..c1daa19 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -425,7 +425,7 @@ export const getPlacesAutocomplete = onRequest({ return; } - const apiKey = process.env.GOOGLE_PLACES_API_KEY; + const apiKey = process.env.GOOGLE_MAPS_API_KEY; if (!apiKey) { logger.error('Google Places API key is not configured'); response.status(500).json({ error: 'Server configuration error' }); -- 2.43.0 From 96e44fcb86aea12ab046a41e9134c9b26d3c4a37 Mon Sep 17 00:00:00 2001 From: DhanshCOSQ Date: Wed, 9 Apr 2025 15:40:18 +0530 Subject: [PATCH 5/8] Service region changed --- functions/src/index.ts | 50 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index d464648..67c4ab5 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -24,7 +24,7 @@ if (!admin.apps.length) { const corsHandler = cors({ origin: true }); export const sendEmailWithAttachment = onRequest({ - region: process.env.SERVICES_RGN + region: '#{SERVICES_RGN}#' }, async (request: Request, response: express.Response) => { return corsHandler(request, response, async () => { try { @@ -157,7 +157,7 @@ export const sendEmailMessage = onRequest({ }); export const sendSMSMessage = onRequest({ - region: process.env.SERVICES_RGN + region: '#{SERVICES_RGN}#' }, (request: Request, response: express.Response) => { return corsHandler(request, response, async () => { @@ -181,26 +181,26 @@ export const sendSMSMessage = onRequest({ }); export const processNotificationOnCreate = onDocumentCreated({ - region: process.env.SERVICES_RGN, + region: '#{SERVICES_RGN}#', document: 'notifications/{notificationId}' }, async (event) => { try { const notification = event.data?.data(); const notificationId = event.params.notificationId; - + if (!notification) { logger.error(`No data found for notification ${notificationId}`); return; } - + if (notification.notificationSent === true) { logger.info(`Notification ${notificationId} already sent, skipping.`); return; } - + let userId = null; let fcmToken = null; - + if (notification.userId) { userId = notification.userId; const userDoc = await admin.firestore().collection('users').doc(userId).get(); @@ -226,14 +226,14 @@ export const processNotificationOnCreate = onDocumentCreated({ .where('phoneNumber', '==', notification.phoneNumber) .limit(1) .get(); - + if (!userQuery.empty) { const userDoc = userQuery.docs[0]; userId = userDoc.id; fcmToken = userDoc.data()?.fcmToken; } } - + if (!fcmToken) { logger.error(`FCM token not found for notification ${notificationId}`); await admin.firestore().collection('notifications').doc(notificationId).update({ @@ -242,35 +242,35 @@ export const processNotificationOnCreate = onDocumentCreated({ }); return; } - + let title = 'New Notification'; let body = notification.message || 'You have a new notification'; let data: Record = { type: notification.type, }; - + switch (notification.type) { case 'day_pass_entry': const isAccepted = notification.status === 'ACCEPTED'; title = isAccepted ? 'Day Pass Approved' : 'Day Pass Denied'; - body = notification.message || (isAccepted ? - `Your day pass has been approved` : + body = notification.message || (isAccepted ? + `Your day pass has been approved` : `Your day pass has been denied`); data.gymName = notification.gymName || ''; break; - + case 'trainer_assigned_to_client': title = 'Trainer Assigned'; body = notification.message || `${notification.trainerName} has been assigned as your trainer`; data.trainerName = notification.trainerName || ''; data.membershipId = notification.membershipId || ''; break; - + case 'client_invitations': if (notification.userId || notification.invitorId) { const isAccept = notification.status === 'ACCEPTED'; title = isAccept ? 'Invitation Accepted' : 'Invitation Rejected'; - body = notification.message || (isAccept ? + body = notification.message || (isAccept ? `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been accepted` : `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been rejected`); } else if (notification.phoneNumber) { @@ -278,17 +278,17 @@ export const processNotificationOnCreate = onDocumentCreated({ if (notification.status === 'ACCEPTED') { invitationStatus = 'accepted'; title = 'Invitation Accepted'; - body = notification.message || + body = notification.message || `You have accepted the invitation from ${notification.name}`; } else if (notification.status === 'REJECTED') { invitationStatus = 'rejected'; title = 'Invitation Rejected'; - body = notification.message || + body = notification.message || `You have rejected the invitation from ${notification.name}`; } else if (notification.status === 'PENDING') { invitationStatus = 'pending'; title = 'New Invitation'; - body = notification.message || + body = notification.message || `You have a new invitation pending from ${notification.name}`; } else { invitationStatus = 'unknown'; @@ -297,19 +297,19 @@ export const processNotificationOnCreate = onDocumentCreated({ } data.status = invitationStatus; } - + data.gymName = notification.gymName || ''; data.clientEmail = notification.clientEmail || ''; data.clientName = notification.name || ''; data.invitationId = notification.invitationId || ''; data.subscriptionName = notification.subscriptionName || ''; break; - + default: logger.info(`Using default handling for notification type: ${notification.type}`); break; } - + const message: admin.messaging.Message = { notification: { title: title, @@ -337,18 +337,18 @@ export const processNotificationOnCreate = onDocumentCreated({ }, token: fcmToken, }; - + try { const fcmResponse = await admin.messaging().send(message); logger.info(`FCM notification sent successfully: ${fcmResponse}`); - + await admin.firestore().collection('notifications').doc(notificationId).update({ notificationSent: true, sentAt: admin.firestore.FieldValue?.serverTimestamp?.() || new Date() }); } catch (error) { logger.error(`Error sending notification ${notificationId}:`, error); - + await admin.firestore().collection('notifications').doc(notificationId).update({ notificationError: error instanceof Error ? error.message : String(error), updatedAt: admin.firestore.FieldValue?.serverTimestamp?.() || new Date() -- 2.43.0 From dca28b4417add0f45986e2c7c270b58c18eca289 Mon Sep 17 00:00:00 2001 From: DhanshCOSQ Date: Wed, 9 Apr 2025 17:13:01 +0530 Subject: [PATCH 6/8] Added new function to get the lat and lng --- functions/src/index.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/functions/src/index.ts b/functions/src/index.ts index 67c4ab5..eb4913a 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -569,3 +569,44 @@ export const getPlacesAutocomplete = onRequest({ } }); }); + +export const getPlaceDetails = onRequest({ + region: '#{SERVICES_RGN}#' +}, async (request: Request, response: express.Response) => { + return corsHandler(request, response, async () => { + try { + const { place_id, fields } = request.query; + + if (!place_id) { + response.status(400).json({ + error: 'place_id parameter is required' + }); + return; + } + + const apiKey = process.env.GOOGLE_MAPS_API_KEY; + if (!apiKey) { + logger.error('Google Places API key is not configured'); + response.status(500).json({ error: 'Server configuration error' }); + return; + } + + const url = 'https://maps.googleapis.com/maps/api/place/details/json'; + const params: any = { + key: apiKey, + place_id: place_id, + fields: fields || 'geometry' + }; + + const result = await axios.get(url, { params }); + logger.info('Google Places Details API request completed successfully'); + response.json(result.data); + } catch (error) { + logger.error('Error fetching place details:', error); + response.status(500).json({ + success: false, + error: error instanceof Error ? error.message : String(error) + }); + } + }); +}); -- 2.43.0 From 897a65abdd0482c4a297fa45cd0859f8fb508326 Mon Sep 17 00:00:00 2001 From: DhanshCOSQ Date: Fri, 11 Apr 2025 18:57:54 +0530 Subject: [PATCH 7/8] Upated the function createCashfreeOrder --- functions/src/index.ts | 170 +++++++++++++++++++++++++---------------- 1 file changed, 104 insertions(+), 66 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index eb4913a..d13051d 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -372,87 +372,125 @@ export const createCashfreeOrder = onRequest({ } const idToken = authHeader.split('Bearer ')[1]; - const decodedToken = await admin.auth().verifyIdToken(idToken); - const uid = decodedToken.uid; + try { + const decodedToken = await admin.auth().verifyIdToken(idToken); + const uid = decodedToken.uid; - const { - amount, - customerName, - customerEmail, - customerPhone, - productInfo - } = request.body; + const { + amount, + customerName, + customerEmail, + customerPhone, + productInfo, + userId, + gymId, + orderId + } = request.body; - if (!amount || !customerEmail || !customerPhone) { - response.status(400).json({ error: 'Missing required fields' }); - return; - } - - const clientId = process.env.CASHFREE_CLIENT_ID; - const clientSecret = process.env.CASHFREE_CLIENT_SECRET; - const isTest = true; - - const apiUrl = isTest - ? 'https://sandbox.cashfree.com/pg/orders' - : 'https://api.cashfree.com/pg/orders'; - - const orderId = `order_${Date.now()}_${uid.substring(0, 6)}`; - - const cashfreeResponse = await axios.post( - apiUrl, - { - order_id: orderId, - order_amount: amount, - order_currency: 'INR', - customer_details: { - customer_id: uid, - customer_name: customerName || 'Fitlien User', - customer_email: customerEmail, - customer_phone: customerPhone - }, - order_meta: { - return_url: `https://fitlien.com/payment/status?order_id={order_id}`, - // notify_url: `https://$filien.web.app/verifyCashfreePayment` - }, - order_note: productInfo || 'Fitlien Membership' - }, - { - headers: { - 'x-api-version': '2022-09-01', - 'x-client-id': clientId, - 'x-client-secret': clientSecret, - 'Content-Type': 'application/json' - } + if (!amount || !customerEmail || !customerPhone) { + response.status(400).json({ error: 'Missing required fields' }); + return; } - ); - await admin.firestore().collection('payment_orders').doc(orderId).set({ - userId: uid, - amount: amount, - customerEmail: customerEmail, - customerPhone: customerPhone, - orderStatus: 'CREATED', - paymentGateway: 'Cashfree', - createdAt: new Date(), - ...cashfreeResponse.data - }); + const clientId = process.env.CASHFREE_CLIENT_ID; + const clientSecret = process.env.CASHFREE_CLIENT_SECRET; - response.json({ - order_id: cashfreeResponse.data.order_id, - payment_session_id: cashfreeResponse.data.payment_session_id - }); + if (!clientId || !clientSecret) { + logger.error('Cashfree credentials not configured'); + response.status(500).json({ error: 'Payment gateway configuration error' }); + return; + } - logger.info(`Cashfree order created: ${orderId}`); + const isTest = true; + const hashKey = `hash_${Date.now()}_${uid.substring(0, 1)}_${orderId}`; + const apiUrl = isTest + ? 'https://sandbox.cashfree.com/pg/orders' + : 'https://api.cashfree.com/pg/orders'; + + try { + const cashfreeResponse = await axios.post( + apiUrl, + { + order_id: orderId, + hash_key: hashKey, + order_amount: amount, + order_currency: 'INR', + customer_details: { + customer_id: uid, + customer_name: customerName || 'Fitlien User', + customer_email: customerEmail, + customer_phone: customerPhone + }, + order_meta: { + return_url: `https://fitlien.com/payment-bridge?order_id=${orderId}&hash_key=${hashKey}&user_id=${userId}&gym_id=${gymId}`, + // notify_url: `https://$filien.web.app/verifyCashfreePayment` + }, + order_note: productInfo || 'Fitlien Membership' + }, + { + headers: { + 'x-api-version': '2022-09-01', + 'x-client-id': clientId, + 'x-client-secret': clientSecret, + 'Content-Type': 'application/json' + } + } + ); + + try { + await admin.firestore().collection('payment_orders').doc(orderId).set({ + userId: uid, + amount: amount, + customerEmail: customerEmail, + customerPhone: customerPhone, + orderStatus: 'CREATED', + paymentGateway: 'Cashfree', + createdAt: new Date(), + hashKey: hashKey, + clientId: userId, + gymId: gymId, + orderId: orderId, + ...cashfreeResponse.data + }); + } catch (firestoreError) { + logger.error('Error storing order in Firestore:', firestoreError); + } + + response.json({ + success: true, + order_id: cashfreeResponse.data.order_id, + payment_session_id: cashfreeResponse.data.payment_session_id + }); + + logger.info(`Cashfree order created: ${orderId}`); + } catch (axiosError: any) { + logger.error('Cashfree API error:', axiosError); + response.status(axiosError.response?.status || 500).json({ + success: false, + error: 'Payment gateway error', + details: axiosError.response?.data || axiosError.message, + code: axiosError.code + }); + } + } catch (authError) { + logger.error('Authentication error:', authError); + response.status(401).json({ + success: false, + error: 'Invalid authentication token' + }); + } } catch (error: any) { logger.error('Cashfree order creation error:', error); response.status(500).json({ + success: false, error: 'Failed to create payment order', - details: error.response?.data || error.message + details: error.message }); } }); }); + export const verifyCashfreePayment = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: express.Response) => { -- 2.43.0 From 2d5ac9dfbb53111bc5bac15f47dce1c42c18ca5f Mon Sep 17 00:00:00 2001 From: DhanshCOSQ Date: Sat, 12 Apr 2025 08:46:54 +0530 Subject: [PATCH 8/8] Updated to get the payment url --- functions/src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index d13051d..045a927 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -384,7 +384,8 @@ export const createCashfreeOrder = onRequest({ productInfo, userId, gymId, - orderId + orderId, + webHostUrl, } = request.body; if (!amount || !customerEmail || !customerPhone) { @@ -422,7 +423,7 @@ export const createCashfreeOrder = onRequest({ customer_phone: customerPhone }, order_meta: { - return_url: `https://fitlien.com/payment-bridge?order_id=${orderId}&hash_key=${hashKey}&user_id=${userId}&gym_id=${gymId}`, + return_url: `https://fitlien.com/payment-bridge?order_id=${orderId}&hash_key=${hashKey}&user_id=${userId}&gym_id=${gymId}&web_host_url=${webHostUrl}`, // notify_url: `https://$filien.web.app/verifyCashfreePayment` }, order_note: productInfo || 'Fitlien Membership' -- 2.43.0