fitlien-services/functions/src/payments/phonepe/createPhonepeOrder.ts
Allen T J 9efa31b6cc
All checks were successful
Deploy FitLien services to Dev / Deploy to Dev (push) Successful in 4m15s
phonepe (#23)
Co-authored-by: AllenTJ7 <163137620+AllenTJ7@users.noreply.github.com>
Reviewed-on: #23
Co-authored-by: Allen T J <allentj@cosq.net>
Co-committed-by: Allen T J <allentj@cosq.net>
2025-05-05 14:09:08 +00:00

134 lines
4.7 KiB
TypeScript

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 axios from "axios";
const admin = getAdmin();
const logger = getLogger();
const corsHandler = getCorsHandler();
export const createPhonePeOrder = 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 {
const decodedToken = await admin.auth().verifyIdToken(idToken);
const uid = decodedToken.uid;
const {
merchantOrderId,
amount,
expireAfter,
metaInfo,
paymentFlow
} = request.body;
if (!merchantOrderId || !amount || !paymentFlow || !expireAfter) {
response.status(400).json({ error: 'Missing required fields' });
return;
}
if (!paymentFlow.type || !paymentFlow.merchantUrls || !paymentFlow.merchantUrls.redirectUrl) {
response.status(400).json({ error: 'Invalid payment flow configuration' });
return;
}
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;
}
try {
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 paymentResponse = await axios.post(
`${apiUrl}/checkout/v2/pay`,
request.body,
{
headers: {
'Content-Type': 'application/json',
'Authorization': `O-Bearer ${accessToken}`,
},
}
);
try {
await admin.firestore().collection('payment_orders').doc(merchantOrderId).set({
userId: uid,
amount: amount / 100,
orderStatus: paymentResponse.data.state || 'PENDING',
paymentGateway: 'PhonePe',
createdAt: new Date(),
merchantOrderId: merchantOrderId,
paymentUrl: paymentResponse.data.redirectUrl,
orderId: paymentResponse.data.orderId,
expireAt: new Date(paymentResponse.data.expireAt),
// rawResponse: paymentResponse.data,
metaInfo: metaInfo || {}
});
} catch (firestoreError) {
logger.error('Error storing order in Firestore:', firestoreError);
}
response.json({
...paymentResponse.data,
merchantOrderId: merchantOrderId
});
logger.info(`PhonePe order created: ${merchantOrderId}`);
} catch (apiError: any) {
logger.error('PhonePe API error:', apiError);
response.status(apiError.response?.status || 500).json({
success: false,
error: 'Payment gateway error',
details: apiError.response?.data || apiError.message,
code: apiError.code
});
}
} catch (authError) {
logger.error('Authentication error:', authError);
response.status(401).json({
success: false,
error: 'Invalid authentication token'
});
}
} catch (error: any) {
logger.error('PhonePe order creation error:', error);
response.status(500).json({
success: false,
error: 'Failed to create payment order',
details: error.message
});
}
});
});