From 05d2b918b4133de30748a80d19695d9da67cbc54 Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Thu, 3 Apr 2025 17:09:23 +0530 Subject: [PATCH 1/2] cashfree service added --- firebase.json | 18 +++++ functions/.env.example | 3 + functions/src/index.ts | 152 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 170 insertions(+), 3 deletions(-) diff --git a/firebase.json b/firebase.json index da6f4cb..076f09a 100644 --- a/firebase.json +++ b/firebase.json @@ -21,6 +21,24 @@ "storage": { "rules": "storage.rules" }, + "emulators": { + "functions": { + "port": 5001 + }, + "firestore": { + "port": 8084 + }, + "storage": { + "port": 9199 + }, + "ui": { + "enabled": true, + "port": 4000 + }, + "auth": { + "port": 9099 + } + }, "remoteconfig": { "template": "remoteconfig.template.json" } diff --git a/functions/.env.example b/functions/.env.example index 7c2adec..381bfad 100644 --- a/functions/.env.example +++ b/functions/.env.example @@ -5,3 +5,6 @@ TWILIO_ACCOUNT_SID=#{TWILIO_ACCOUNT_SID}# TWILIO_AUTH_TOKEN=#{TWILIO_AUTH_TOKEN}# TWILIO_PHONE_NUMBER=#{TWILIO_PHONE_NUMBER}# SERVICES_RGN=#{SERVICES_RGN}# +CASHFREE_CLIENT_ID=#{CASHFREE_CLIENT_ID}# +CASHFREE_CLIENT_SECRET=#{CASHFREE_CLIENT_SECRET}# + diff --git a/functions/src/index.ts b/functions/src/index.ts index 33a2aa5..f8c6544 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -8,6 +8,8 @@ import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs'; import * as https from 'https'; +import axios from "axios"; + const formData = require('form-data'); const Mailgun = require('mailgun.js'); const { convert } = require('html-to-text'); @@ -78,7 +80,7 @@ export const sendEmailWithAttachment = onRequest({ } }); export const sendEmailMessage = onRequest({ - region: '#{SERVICES_RGN}#' + region: process.env.SERVICES_RGN }, (request: Request, response: express.Response) => { const mailgun = new Mailgun(formData); const mailGunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY }); @@ -107,7 +109,7 @@ export const sendEmailMessage = onRequest({ }); export const sendSMSMessage = onRequest({ - region: '#{SERVICES_RGN}#' + region: process.env.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; @@ -136,7 +138,7 @@ interface Invitation { export const notifyInvitation = onDocumentCreated({ document: 'notifications/{notificationId}', - region: '#{SERVICES_RGN}#' + region: process.env.SERVICES_RGN }, async (event: any) => { const invitation = event.data?.data() as Invitation; const invitationId = event.params.invitationId; @@ -204,5 +206,149 @@ export const notifyInvitation = onDocumentCreated({ } }); +export const createCashfreeOrder = onRequest({ + region: process.env.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' + } + } + ); + + await admin.firestore().collection('payment_orders').doc(orderId).set({ + userId: uid, + amount: amount, + customerEmail: customerEmail, + customerPhone: customerPhone, + orderStatus: 'CREATED', + paymentGateway: 'Cashfree', + createdAt: admin.firestore.FieldValue.serverTimestamp(), + ...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({ + region: process.env.SERVICES_RGN +}, async (request: Request, response: express.Response) => { + try { + const orderId = request.body.order_id || request.query.order_id; + + if (!orderId) { + response.status(400).json({ error: 'Order ID is required' }); + return; + } + + 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 + } + } + ); + + await admin.firestore().collection('payment_orders').doc(orderId).update({ + orderStatus: cashfreeResponse.data.order_status, + paymentDetails: cashfreeResponse.data, + updatedAt: admin.firestore.FieldValue.serverTimestamp() + }); + + 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 + }); + } +}); -- 2.43.0 From c32e5b1d63f9978bf6c5004813ab93d61ad401eb Mon Sep 17 00:00:00 2001 From: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com> Date: Thu, 3 Apr 2025 19:18:47 +0530 Subject: [PATCH 2/2] Update index.ts --- functions/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index f8c6544..c5f1c80 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -278,7 +278,7 @@ export const createCashfreeOrder = onRequest({ customerPhone: customerPhone, orderStatus: 'CREATED', paymentGateway: 'Cashfree', - createdAt: admin.firestore.FieldValue.serverTimestamp(), + createdAt: new Date(), ...cashfreeResponse.data }); @@ -330,7 +330,7 @@ export const verifyCashfreePayment = onRequest({ await admin.firestore().collection('payment_orders').doc(orderId).update({ orderStatus: cashfreeResponse.data.order_status, paymentDetails: cashfreeResponse.data, - updatedAt: admin.firestore.FieldValue.serverTimestamp() + updatedAt: new Date() }); if (request.headers['x-webhook-source'] === 'cashfree') { -- 2.43.0