Compare commits

...

9 Commits

Author SHA1 Message Date
0eb5ca1baf Merge pull request 'Changed region' (#6) from feature/fitlien-signed-url into dev
Reviewed-on: #6
2025-04-05 09:38:31 +00:00
27feed4711 Merge branch 'dev' into feature/fitlien-signed-url 2025-04-05 14:59:51 +05:30
b4a75f4968 Changed region 2025-04-05 14:58:52 +05:30
f00ff4d1d6 Signed url complete (#5)
Reviewed-on: #5
Reviewed-by: Benoy Bose <benoybose@cosq.net>
Co-authored-by: DhanshCOSQ <dhanshas@cosq.net>
Co-committed-by: DhanshCOSQ <dhanshas@cosq.net>
2025-04-05 09:20:27 +00:00
e553bd673a Time changed to one hour 2025-04-05 14:22:47 +05:30
f221f4850e Signed url complete 2025-04-05 12:13:49 +05:30
f9930b143c feature/fitlien-cashfree (#4)
Co-authored-by: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com>
Reviewed-on: #4
Reviewed-by: Dhansh A S <dhanshas@cosq.net>
Co-authored-by: Allen T J <allentj@cosq.net>
Co-committed-by: Allen T J <allentj@cosq.net>
2025-04-03 13:51:57 +00:00
97a13bde56 Merge pull request 'Added function to send email for invoice' (#3) from faeture/fitlien-invoice into dev
Reviewed-on: #3
2025-04-02 07:45:27 +00:00
f3a290cb72 Added function to send email for invoice 2025-04-01 22:23:00 +05:30
9 changed files with 595 additions and 22 deletions

10
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Debug",
"port": 9229
}
]
}

View File

@ -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"
}

View File

@ -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}#

View File

@ -8,15 +8,21 @@
"name": "functions",
"version": "0.0.0",
"dependencies": {
"@types/node-fetch": "^2.6.12",
"axios": "^1.8.4",
"firebase-admin": "^12.6.0",
"firebase-functions": "^6.0.1",
"form-data": "^4.0.1",
"html-to-text": "^9.0.5",
"long": "4.0.0",
"long": "^4.0.0",
"mailgun.js": "^10.4.0",
"node-fetch": "^2.7.0",
"pdfjs-dist": "^5.0.375",
"twilio": "^5.4.0"
},
"devDependencies": {
"@types/long": "^5.0.0",
"@types/node": "^22.13.14",
"firebase-functions-test": "^3.1.0",
"typescript": "^5.8.2"
},
@ -1156,6 +1162,177 @@
"url": "https://opencollective.com/js-sdsl"
}
},
"node_modules/@napi-rs/canvas": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.68.tgz",
"integrity": "sha512-LQESrePLEBLvhuFkXx9jjBXRC2ClYsO5mqQ1m/puth5z9SOuM3N/B3vDuqnC3RJFktDktyK9khGvo7dTkqO9uQ==",
"optional": true,
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@napi-rs/canvas-android-arm64": "0.1.68",
"@napi-rs/canvas-darwin-arm64": "0.1.68",
"@napi-rs/canvas-darwin-x64": "0.1.68",
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.68",
"@napi-rs/canvas-linux-arm64-gnu": "0.1.68",
"@napi-rs/canvas-linux-arm64-musl": "0.1.68",
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.68",
"@napi-rs/canvas-linux-x64-gnu": "0.1.68",
"@napi-rs/canvas-linux-x64-musl": "0.1.68",
"@napi-rs/canvas-win32-x64-msvc": "0.1.68"
}
},
"node_modules/@napi-rs/canvas-android-arm64": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.68.tgz",
"integrity": "sha512-h1KcSR4LKLfRfzeBH65xMxbWOGa1OtMFQbCMVlxPCkN1Zr+2gK+70pXO5ktojIYcUrP6KDcOwoc8clho5ccM/w==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-darwin-arm64": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.68.tgz",
"integrity": "sha512-/VURlrAD4gDoxW1GT/b0nP3fRz/fhxmHI/xznTq2FTwkQLPOlLkDLCvTmQ7v6LtGKdc2Ed6rvYpRan+JXThInQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-darwin-x64": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.68.tgz",
"integrity": "sha512-tEpvGR6vCLTo1Tx9wmDnoOKROpw57wiCWwCpDOuVlj/7rqEJOUYr9ixW4aRJgmeGBrZHgevI0EURys2ER6whmg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.68.tgz",
"integrity": "sha512-U9xbJsumPOiAYeAFZMlHf62b9dGs2HJ6Q5xt7xTB0uEyPeurwhgYBWGgabdsEidyj38YuzI/c3LGBbSQB3vagw==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.68.tgz",
"integrity": "sha512-KFkn8wEm3mPnWD4l8+OUUkxylSJuN5q9PnJRZJgv15RtCA1bgxIwTkBhI/+xuyVMcHqON9sXq7cDkEJtHm35dg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.68.tgz",
"integrity": "sha512-IQzts91rCdOALXBWQxLZRCEDrfFTGDtNRJMNu+2SKZ1uT8cmPQkPwVk5rycvFpvgAcmiFiOSCp1aRrlfU8KPpQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.68.tgz",
"integrity": "sha512-e9AS5UttoIKqXSmBzKZdd3NErSVyOEYzJfNOCGtafGk1//gibTwQXGlSXmAKuErqMp09pyk9aqQRSYzm1AQfBw==",
"cpu": [
"riscv64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.68.tgz",
"integrity": "sha512-Pa/I36VE3j57I3Obhrr+J48KGFfkZk2cJN/2NmW/vCgmoF7kCP6aTVq5n+cGdGWLd/cN9CJ9JvNwEoMRDghu0g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-linux-x64-musl": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.68.tgz",
"integrity": "sha512-9c6rkc5195wNxuUHJdf4/mmnq433OQey9TNvQ9LspJazvHbfSkTij8wtKjASVQsJyPDva4fkWOeV/OQ7cLw0GQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
"version": "0.1.68",
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.68.tgz",
"integrity": "sha512-Fc5Dez23u0FoSATurT6/w1oMytiRnKWEinHivdMvXpge6nG4YvhrASrtqMk8dGJMVQpHr8QJYF45rOrx2YU2Aw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@opentelemetry/api": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
@ -1423,10 +1600,14 @@
"dev": true
},
"node_modules/@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
"optional": true
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/long/-/long-5.0.0.tgz",
"integrity": "sha512-eQs9RsucA/LNjnMoJvWG/nXa7Pot/RbBzilF/QRIU/xRl+0ApxrSUFsV5lmf01SvSlqMzJ7Zwxe440wmz2SJGA==",
"deprecated": "This is a stub types definition. long provides its own type definitions, so you do not need this installed.",
"dev": true,
"dependencies": {
"long": "*"
}
},
"node_modules/@types/mime": {
"version": "1.3.5",
@ -1439,13 +1620,22 @@
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
},
"node_modules/@types/node": {
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
"version": "22.13.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz",
"integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.0"
}
},
"node_modules/@types/qs": {
"version": "6.9.18",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
@ -1659,9 +1849,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
"integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
@ -3085,6 +3275,12 @@
"node": ">=14"
}
},
"node_modules/google-gax/node_modules/@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
"optional": true
},
"node_modules/google-gax/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
@ -4710,7 +4906,6 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"optional": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
@ -4970,6 +5165,17 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
},
"node_modules/pdfjs-dist": {
"version": "5.0.375",
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.0.375.tgz",
"integrity": "sha512-QDRlEVldf/cX88CElGAyRhjqNOO69kmB3HZlalDAfqJ/IvmqJNkipomhBZy4cWATfLVlkQTXE3H4yFvMF2uPsg==",
"engines": {
"node": ">=20"
},
"optionalDependencies": {
"@napi-rs/canvas": "^0.1.67"
}
},
"node_modules/peberminta": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
@ -5824,8 +6030,7 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"optional": true
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-deepmerge": {
"version": "2.0.7",
@ -6058,8 +6263,7 @@
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"optional": true
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/websocket-driver": {
"version": "0.7.4",
@ -6086,7 +6290,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"optional": true,
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"

View File

@ -15,15 +15,21 @@
},
"main": "lib/index.js",
"dependencies": {
"@types/node-fetch": "^2.6.12",
"axios": "^1.8.4",
"firebase-admin": "^12.6.0",
"firebase-functions": "^6.0.1",
"form-data": "^4.0.1",
"html-to-text": "^9.0.5",
"long": "4.0.0",
"long": "^4.0.0",
"mailgun.js": "^10.4.0",
"node-fetch": "^2.7.0",
"pdfjs-dist": "^5.0.375",
"twilio": "^5.4.0"
},
"devDependencies": {
"@types/long": "^5.0.0",
"@types/node": "^22.13.14",
"firebase-functions-test": "^3.1.0",
"typescript": "^5.8.2"
},

View File

@ -4,11 +4,116 @@ import * as admin from 'firebase-admin';
import * as express from "express";
import * as logger from "firebase-functions/logger";
import { onDocumentCreated } from "firebase-functions/firestore";
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs';
import * as https from 'https';
import axios from "axios";
import { getStorage } from 'firebase-admin/storage';
const formData = require('form-data');
const Mailgun = require('mailgun.js');
const { convert } = require('html-to-text');
const twilio = require('twilio')
const twilio = require('twilio');
if (!admin.apps.length) {
admin.initializeApp();
}
export const sendEmailWithAttachment = onRequest({
region: '#{SERVICES_RGN}#'
}, async (request: Request, response: express.Response) => {
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<void>((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 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',
}
};
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;
}
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}#'
@ -135,4 +240,151 @@ export const notifyInvitation = onDocumentCreated({
console.error('Error sending invitation notification:', error);
return null;
}
});
});
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'
}
}
);
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({
region: '#{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: 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
});
}
});

View File

@ -15,4 +15,4 @@
"include": [
"src"
]
}
}

72
package-lock.json generated Normal file
View File

@ -0,0 +1,72 @@
{
"name": "fitlien-services",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@types/busboy": "^1.5.4",
"busboy": "^1.6.0"
},
"devDependencies": {
"@types/long": "^5.0.0"
}
},
"node_modules/@types/busboy": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz",
"integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/long": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/long/-/long-5.0.0.tgz",
"integrity": "sha512-eQs9RsucA/LNjnMoJvWG/nXa7Pot/RbBzilF/QRIU/xRl+0ApxrSUFsV5lmf01SvSlqMzJ7Zwxe440wmz2SJGA==",
"deprecated": "This is a stub types definition. long provides its own type definitions, so you do not need this installed.",
"dev": true,
"dependencies": {
"long": "*"
}
},
"node_modules/@types/node": {
"version": "22.10.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
"integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"streamsearch": "^1.1.0"
},
"engines": {
"node": ">=10.16.0"
}
},
"node_modules/long": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz",
"integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==",
"dev": true
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
}
}
}

9
package.json Normal file
View File

@ -0,0 +1,9 @@
{
"dependencies": {
"@types/busboy": "^1.5.4",
"busboy": "^1.6.0"
},
"devDependencies": {
"@types/long": "^5.0.0"
}
}