diff --git a/functions/.env.example b/functions/.env.example index 481d163..d78480a 100644 --- a/functions/.env.example +++ b/functions/.env.example @@ -10,3 +10,4 @@ CASHFREE_CLIENT_SECRET=#{CASHFREE_CLIENT_SECRET}# GOOGLE_MAPS_API_KEY=#{GOOGLE_MAPS_API_KEY}# FITLIENHOST=#{FITLIENHOST}# CASHFREE_URL=#{CASHFREE_URL}# +CASHFREE_LINK_URL=#{CASHFREE_LINK_URL}# diff --git a/functions/src/index.ts b/functions/src/index.ts index fae6bef..21f92ae 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -15,7 +15,7 @@ const formData = require('form-data'); const Mailgun = require('mailgun.js'); const { convert } = require('html-to-text'); const twilio = require('twilio'); - +const { v4: uuidv4 } = require('uuid'); if (!admin.apps.length) { admin.initializeApp(); @@ -486,6 +486,144 @@ export const createCashfreeOrder = onRequest({ }); }); +export const createCashfreeLink = onRequest({ + region: '#{SERVICES_RGN}#' +}, async (request: Request, response: express.Response) => { + + return corsHandler(request, response, async () => { + try { + const authHeader = request.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + response.status(401).json({ error: 'Unauthorized' }); + return; + } + + const idToken = authHeader.split('Bearer ')[1]; + try { + const decodedToken = await admin.auth().verifyIdToken(idToken); + const uid = decodedToken.uid; + + 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 expirationDate = new Date(Date.now() + 24 * 60 * 60 * 1000); + const expirationString = expirationDate.toISOString(); + const clientId = process.env.CASHFREE_CLIENT_ID; + const clientSecret = process.env.CASHFREE_CLIENT_SECRET; + let apiUrl = process.env.CASHFREE_LINK_URL; + + if (!clientId || !clientSecret) { + logger.error('Cashfree credentials not configured'); + response.status(500).json({ error: 'Payment gateway configuration error' }); + return; + } + + const linkId = uuidv4(); + try { + const options = { + method: 'POST', + headers: { + 'x-api-version': '2025-01-01', + 'x-client-id': `${process.env.CASHFREE_CLIENT_ID}`, + 'x-client-secret': `${process.env.CASHFREE_CLIENT_SECRET}`, + 'Content-Type': 'application/json' + }, + body: `{ + "customer_details": { + "customer_email": "${customerEmail}", + "customer_name": "${customerName}", + "customer_phone": "${customerPhone}" + }, + "link_amount": ${amount}, + "link_auto_reminders": true, + "link_currency": "INR", + "link_expiry_time": "${expirationString}", + "link_id": "${linkId}", + "link_meta": { + "notify_url": "https://ee08e626ecd88c61c85f5c69c0418cb5.m.pipedream.net", + "return_url": "https://www.cashfree.com/devstudio/thankyou", + "upi_intent": false + }, + "link_notes": { + "userId": "${userId}", + "gymId": "${gymId}", + "orderId": "${orderId}", + "requestUserId": "${uid}" + }, + "link_notify": { + "send_email": true, + "send_sms": true + }, + "link_partial_payments": false, + "link_purpose": "${productInfo}", + "order_splits": [] + }` + }; + const cashfreeResponse = await axios.post(apiUrl!, options); + try { + await admin.firestore().collection('payment_links').doc(orderId).set({ + requestUserId: uid, + amount: amount, + customerEmail: customerEmail, + customerPhone: customerPhone, + userId: userId, + gymId: gymId, + orderId: orderId, + ...cashfreeResponse.data, + createdAt: new Date(), + }); + } catch (firestoreError) { + logger.error('Error storing order in Firestore:', firestoreError); + } + + response.json({ + success: true, + linkId: linkId, + linkUrl: cashfreeResponse.data.link_url, + linkExpiryTime: cashfreeResponse.data.link_expiry_time, + linkStatus: cashfreeResponse.data.link_status + }); + + } 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.message + }); + } + }); +}); + export const verifyCashfreePayment = onRequest({ region: '#{SERVICES_RGN}#'