import axios from "axios"; import { onRequest } from "firebase-functions/v2/https"; import { Request} from "firebase-functions/v2/https"; import { getCorsHandler } from "../../shared/middleware"; import { getAdmin, getLogger } from "../../shared/config"; import { updatePaymentDataAfterSuccess } from "./paymentData"; import { InvoiceService } from "./invoice/invoiceService"; const admin = getAdmin(); const logger = getLogger(); const corsHandler = getCorsHandler(); const invoiceService = new InvoiceService(); export const checkPhonePePaymentStatus = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, 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 { await admin.auth().verifyIdToken(idToken); const merchantOrderId = request.query.merchantOrderId as string; if (!merchantOrderId) { response.status(400).json({ error: 'Missing merchant order ID' }); return; } const details = request.query.details === 'true'; const errorContext = request.query.errorContext === 'true'; const clientId = process.env.PHONEPE_CLIENT_ID; const clientSecret = process.env.PHONEPE_CLIENT_SECRET; const apiUrl = process.env.PHONEPE_API_URL; const clientVersion = process.env.PHONEPE_CLIENT_VERSION || '1'; if (!clientId || !clientSecret || !apiUrl) { logger.error('PhonePe credentials not configured'); response.status(500).json({ error: 'Payment gateway configuration error' }); return; } const tokenResponse = await axios.post( `${apiUrl}/v1/oauth/token`, { client_id: clientId, client_version: clientVersion, client_secret: clientSecret, grant_type: 'client_credentials', }, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, } ); const accessToken = tokenResponse.data.access_token; const queryParams = new URLSearchParams(); if (details) queryParams.append('details', 'true'); if (errorContext) queryParams.append('errorContext', 'true'); const queryString = queryParams.toString() ? `?${queryParams.toString()}` : ''; const statusResponse = await axios.get( `${apiUrl}/checkout/v2/order/${merchantOrderId}/status${queryString}`, { headers: { 'Content-Type': 'application/json', 'Authorization': `O-Bearer ${accessToken}`, }, } ); const orderQuery = await admin.firestore() .collection('payment_orders') .where('merchantOrderId', '==', merchantOrderId) .limit(1) .get(); if (orderQuery.empty) { logger.error(`No payment order found with PhonePe orderId: ${merchantOrderId}`); response.status(404).json({ success: false, error: 'Payment order not found', message: `No record found for PhonePe order ID: ${merchantOrderId}` }); return; } const orderDoc = orderQuery.docs[0]; const orderData = orderDoc.data(); await orderDoc.ref.update({ orderStatus: statusResponse.data.state || 'UNKNOWN', lastChecked: new Date(), statusResponse: statusResponse.data }); if (statusResponse.data.state === 'COMPLETED') { try { // Update payment data const paymentUpdateSuccess = await updatePaymentDataAfterSuccess( merchantOrderId, statusResponse.data.orderId, statusResponse.data ); if (paymentUpdateSuccess) { // Extract membership ID from metaInfo const membershipId = orderData.metaInfo?.membershipId; if (membershipId) { try { // Get user data for invoice const membershipDoc = await admin.firestore() .collection('memberships') .doc(membershipId) .get(); if (membershipDoc.exists) { const membershipData = membershipDoc.data(); const userId = membershipData?.userId; // Get user details const userDoc = await admin.firestore() .collection('users') .doc(userId) .get(); if (userDoc.exists) { const userData = userDoc.data(); // Get gym details const gymId = orderData.metaInfo?.gymId || membershipData?.gymId; let gymName = 'Fitlien'; let gymAddress = ''; let gstNumber = ''; if (gymId) { const gymDoc = await admin.firestore() .collection('gyms') .doc(gymId) .get(); if (gymDoc.exists) { const gymData = gymDoc.data(); gymName = gymData?.name || 'Fitlien'; gymAddress = gymData?.address || ''; gstNumber = gymData?.gstNumber || ''; } } // Generate invoice data const invoiceData = { invoiceNumber: `INV-${merchantOrderId.substring(0, 8)}`, businessName: gymName, address: gymAddress, gstNumber: gstNumber, customerName: userData?.displayName || `${membershipData?.['first-name'] || ''} ${membershipData?.['last-name'] || ''}`.trim(), phoneNumber: membershipData?.['phone-number'] || orderData.metaInfo?.phoneNumber || '', email: membershipData?.['email'] || '', planName: orderData.metaInfo?.planName || 'Membership', amount: orderData.amount, transactionId: statusResponse.data.orderId, paymentDate: new Date(), paymentMethod: 'Online' }; const invoicePath = await invoiceService.generateInvoice(invoiceData); // Update payment record with invoice path await admin.firestore() .collection('membership_payments') .doc(membershipId) .get() .then(async (doc) => { if (doc.exists) { const paymentsData = doc.data()?.payments || []; for (let i = 0; i < paymentsData.length; i++) { if (paymentsData[i].referenceNumber === merchantOrderId || paymentsData[i].transactionId === statusResponse.data.orderId) { paymentsData[i].invoicePath = invoicePath; break; } } await doc.ref.update({ 'payments': paymentsData, 'updatedAt': admin.firestore.FieldValue.serverTimestamp(), }); } }); logger.info(`Generated invoice for payment: ${merchantOrderId}, path: ${invoicePath}`); } } } catch (invoiceError) { logger.error('Error generating invoice:', invoiceError); // Continue processing - don't fail the response } } } logger.info(`Payment data updated for completed payment: ${merchantOrderId}`); } catch (paymentUpdateError) { logger.error('Error updating payment data:', paymentUpdateError); // Continue processing - don't fail the response } } logger.info('PhonePe status response data:', JSON.stringify(statusResponse.data)); response.json({ success: true, state: statusResponse.data.state, data: statusResponse.data }); } catch (authError: any) { logger.error('Authentication error:', authError); if (authError.response) { logger.error('API error details:', { status: authError.response.status, data: authError.response.data }); response.status(authError.response.status).json({ success: false, error: 'API error', details: authError.response.data }); } else { response.status(401).json({ success: false, error: 'Invalid authentication token or API error', message: authError.message }); } } } catch (error: any) { logger.error('PhonePe payment status check error:', error); response.status(500).json({ success: false, error: 'Failed to check payment status', details: error.message }); } }); });