Compare commits
No commits in common. "ecbe9d184bd96b2f3688eaa6f4f27712923da930" and "7a796243b0a4fa53d53311bf06503819fb284180" have entirely different histories.
ecbe9d184b
...
7a796243b0
@ -1,4 +1,4 @@
|
|||||||
name: Deploy FitLien services to QA
|
name: Deploy FitLien services to Dev
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
name: Deploy FitLien services
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: Deploy
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Clean install
|
|
||||||
run: npm clean-install
|
|
||||||
|
|
||||||
- name: Copy .env.example to .env
|
|
||||||
run: cp functions/.env.example functions/.env
|
|
||||||
|
|
||||||
- name: Replace variables in .env
|
|
||||||
run: |
|
|
||||||
sed -i "s/#{TWILIO_ACCOUNT_SID}#/${{ secrets.TWILIO_ACCOUNT_SID }}/" functions/.env
|
|
||||||
sed -i "s/#{TWILIO_AUTH_TOKEN}#/${{ secrets.TWILIO_AUTH_TOKEN }}/" functions/.env
|
|
||||||
sed -i "s/#{TWILIO_PHONE_NUMBER}#/${{ secrets.TWILIO_PHONE_NUMBER }}/" functions/.env
|
|
||||||
sed -i "s/#{SERVICES_RGN}#/${{ vars.SERVICES_RGN }}/" functions/.env
|
|
||||||
sed -i "s/#{GOOGLE_MAPS_API_KEY}#/${{ secrets.GOOGLE_MAPS_API_KEY }}/" functions/.env
|
|
||||||
sed -i "s/#{SES_FROM_EMAIL}#/${{ vars.SES_FROM_EMAIL }}/" functions/.env
|
|
||||||
sed -i "s/#{SES_REPLY_TO_EMAIL}#/${{ vars.SES_REPLY_TO_EMAIL }}/" functions/.env
|
|
||||||
sed -i "s/#{AWS_ACCESS_KEY_ID}#/${{ secrets.AWS_ACCESS_KEY_ID }}/" functions/.env
|
|
||||||
sed -i "s/#{AWS_SECRET_ACCESS_KEY}#/${{ secrets.AWS_SECRET_ACCESS_KEY }}/" functions/.env
|
|
||||||
sed -i "s/#{AWS_REGION}#/${{ secrets.AWS_REGION }}/" functions/.env
|
|
||||||
sed -i "s/#{PHONEPE_CLIENT_ID}#/${{ secrets.PHONEPE_CLIENT_ID }}/" functions/.env
|
|
||||||
sed -i "s/#{PHONEPE_CLIENT_SECRET}#/${{ secrets.PHONEPE_CLIENT_SECRET }}/" functions/.env
|
|
||||||
sed -i "s/#{PHONEPE_API_URL}#/${{ secrets.PHONEPE_API_URL }}/" functions/.env
|
|
||||||
sed -i "s/#{PHONEPE_WEBHOOK_USERNAME}#/${{ secrets.PHONEPE_WEBHOOK_USERNAME }}/" functions/.env
|
|
||||||
sed -i "s/#{PHONEPE_WEBHOOK_PASSWORD}#/${{ secrets.PHONEPE_WEBHOOK_PASSWORD }}/" functions/.env
|
|
||||||
|
|
||||||
cat functions/.env
|
|
||||||
- name: "Replace #{SERVICES_RGN}# in all .ts files"
|
|
||||||
run: |
|
|
||||||
find . -type f -name "*.ts" -exec sed -i "s/#{SERVICES_RGN}#/${{ vars.SERVICES_RGN }}/g" {} +
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
npm install -g typescript
|
|
||||||
cd functions
|
|
||||||
npm install
|
|
||||||
npx tsc
|
|
||||||
cd ..
|
|
||||||
ls -la
|
|
||||||
|
|
||||||
- name: Deploy
|
|
||||||
run: |
|
|
||||||
curl -sL firebase.tools | upgrade=true bash
|
|
||||||
firebase use --token ${{ secrets.FIREBASE_TOKEN }} release
|
|
||||||
firebase deploy --token "${{ secrets.FIREBASE_TOKEN }}" --force --non-interactive
|
|
||||||
@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"firestore": {
|
"firestore": {
|
||||||
"rules": "firestore.rules",
|
"rules": "firestore.rules",
|
||||||
"indexes": "firestore.indexes.json",
|
"indexes": "firestore.indexes.json"
|
||||||
"database": "(default)"
|
|
||||||
},
|
},
|
||||||
"functions": [
|
"functions": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -117,190 +117,22 @@ export const phonePeWebhook = onRequest({
|
|||||||
try {
|
try {
|
||||||
logger.info(`Starting payment update process for merchantOrderId: ${payload.merchantOrderId}`);
|
logger.info(`Starting payment update process for merchantOrderId: ${payload.merchantOrderId}`);
|
||||||
|
|
||||||
const orderData = orderDoc.data();
|
|
||||||
const membershipId = orderData.metaInfo?.membershipId;
|
|
||||||
const bookingId = orderData.metaInfo?.bookingId;
|
|
||||||
|
|
||||||
if (bookingId) {
|
|
||||||
await processDayPassBooking(payload, orderData, bookingId);
|
|
||||||
} else if (membershipId) {
|
|
||||||
const paymentUpdateSuccess = await updatePaymentDataAfterSuccess(
|
const paymentUpdateSuccess = await updatePaymentDataAfterSuccess(
|
||||||
payload.merchantOrderId,
|
payload.merchantOrderId,
|
||||||
payload.orderId,
|
payload.orderId,
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(`Payment update result for membershipId: ${membershipId}`, {
|
logger.info(`Payment update result for merchantOrderId: ${payload.merchantOrderId}`, {
|
||||||
success: paymentUpdateSuccess,
|
success: paymentUpdateSuccess,
|
||||||
orderId: payload.orderId
|
orderId: payload.orderId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (paymentUpdateSuccess) {
|
if (paymentUpdateSuccess) {
|
||||||
await processMembershipPayment(payload, orderData, membershipId);
|
const orderData = orderDoc.data();
|
||||||
}
|
const membershipId = orderData.metaInfo?.membershipId;
|
||||||
} else {
|
|
||||||
logger.error(`No membershipId or bookingId found in metaInfo for order: ${payload.merchantOrderId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Payment data updated for completed payment: ${payload.merchantOrderId}`);
|
logger.info(`Processing invoice for completed payment`, {
|
||||||
} catch (paymentUpdateError) {
|
|
||||||
logger.error('Error updating payment data:', paymentUpdateError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.status(200).json({ success: true });
|
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
logger.error('PhonePe webhook processing error:', error);
|
|
||||||
response.status(500).json({
|
|
||||||
success: false,
|
|
||||||
error: 'Failed to process webhook',
|
|
||||||
details: error.message
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function processDayPassBooking(payload: any, orderData: any, bookingId: string) {
|
|
||||||
try {
|
|
||||||
logger.info(`Processing day pass booking for bookingId: ${bookingId}`);
|
|
||||||
|
|
||||||
const bookingRef = admin.firestore().collection('day_pass_bookings').doc(bookingId);
|
|
||||||
const bookingDoc = await bookingRef.get();
|
|
||||||
|
|
||||||
if (!bookingDoc.exists) {
|
|
||||||
logger.error(`Day pass booking not found for bookingId: ${bookingId}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await bookingRef.update({
|
|
||||||
status: 'ACCEPTED',
|
|
||||||
paymentDetails: {
|
|
||||||
transactionId: payload.orderId,
|
|
||||||
merchantOrderId: payload.merchantOrderId,
|
|
||||||
amount: orderData.amount,
|
|
||||||
paymentDate: new Date(),
|
|
||||||
paymentMethod: 'PhonePe'
|
|
||||||
},
|
|
||||||
updatedAt: admin.firestore.FieldValue.serverTimestamp()
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(`Updated day pass booking status to 'Accepted' for bookingId: ${bookingId}`);
|
|
||||||
|
|
||||||
const bookingData = bookingDoc.data();
|
|
||||||
const gymId = orderData.metaInfo?.gymId || bookingData?.gymId;
|
|
||||||
|
|
||||||
if (gymId) {
|
|
||||||
try {
|
|
||||||
const gymDoc = await admin.firestore().collection('gyms').doc(gymId).get();
|
|
||||||
let gymName = 'Fitlien';
|
|
||||||
let gymAddress = '';
|
|
||||||
let gymOwnerEmail = '';
|
|
||||||
|
|
||||||
if (gymDoc.exists) {
|
|
||||||
const gymData = gymDoc.data();
|
|
||||||
gymName = gymData?.name || 'Fitlien';
|
|
||||||
gymAddress = gymData?.address || '';
|
|
||||||
|
|
||||||
if (gymData?.userId) {
|
|
||||||
const gymOwnerDoc = await admin.firestore()
|
|
||||||
.collection('users')
|
|
||||||
.doc(gymData.userId)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (gymOwnerDoc.exists) {
|
|
||||||
const gymOwnerData = gymOwnerDoc.data();
|
|
||||||
gymOwnerEmail = gymOwnerData?.email || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const invoiceNumber = `INV-${payload.merchantOrderId.substring(0, 8)}`;
|
|
||||||
|
|
||||||
logger.info(`Generated invoice number for day pass: ${invoiceNumber}`);
|
|
||||||
|
|
||||||
const invoiceData = {
|
|
||||||
invoiceNumber,
|
|
||||||
businessName: gymName,
|
|
||||||
address: gymAddress,
|
|
||||||
gstNumber: orderData.metaInfo?.gstNumber,
|
|
||||||
customerName: orderData.metaInfo?.customerName || bookingData?.customerName || '',
|
|
||||||
phoneNumber: orderData.metaInfo?.customerPhone || bookingData?.phoneNumber || '',
|
|
||||||
email: orderData.metaInfo?.customerEmail || bookingData?.email || '',
|
|
||||||
planName: 'Day Pass',
|
|
||||||
amount: orderData.amount,
|
|
||||||
transactionId: payload.orderId,
|
|
||||||
paymentDate: new Date(),
|
|
||||||
paymentMethod: 'Online'
|
|
||||||
};
|
|
||||||
|
|
||||||
const invoicePath = await invoiceService.generateInvoice(invoiceData);
|
|
||||||
logger.info(`Day pass invoice generated successfully at path: ${invoicePath}`);
|
|
||||||
|
|
||||||
await bookingRef.update({
|
|
||||||
invoicePath: invoicePath,
|
|
||||||
invoiceNumber: invoiceNumber
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(`Updated day pass booking with invoice path: ${invoicePath}`);
|
|
||||||
|
|
||||||
const downloadUrl = await invoiceService.getInvoiceDownloadUrl(invoicePath);
|
|
||||||
const formattedDate = format(new Date(), 'dd/MM/yyyy');
|
|
||||||
|
|
||||||
if (gymOwnerEmail) {
|
|
||||||
logger.info(`Preparing to send day pass invoice email to gym owner: ${gymOwnerEmail}`);
|
|
||||||
try {
|
|
||||||
const ownerEmailSubject = `New Day Pass Payment - ${gymName}`;
|
|
||||||
|
|
||||||
const gymOwnerEmailHtml = `
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h2>New Day Pass Payment Received</h2>
|
|
||||||
<p>Dear Gym Owner,</p>
|
|
||||||
<p>A new day pass payment has been received for your gym.</p>
|
|
||||||
<p>Customer Details:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Name: ${invoiceData.customerName}</li>
|
|
||||||
<li>Phone: ${invoiceData.phoneNumber}</li>
|
|
||||||
</ul>
|
|
||||||
<p>Day Pass Details:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Service: Day Pass</li>
|
|
||||||
<li>Amount: ₹${orderData.amount.toFixed(2)}</li>
|
|
||||||
<li>Transaction ID: ${payload.merchantOrderId}</li>
|
|
||||||
<li>Date: ${formattedDate}</li>
|
|
||||||
</ul>
|
|
||||||
<p>Please find the invoice attached.</p>
|
|
||||||
<p>Regards,<br>Fitlien Team</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
|
|
||||||
await sendEmailWithAttachmentUtil(
|
|
||||||
gymOwnerEmail,
|
|
||||||
ownerEmailSubject,
|
|
||||||
gymOwnerEmailHtml,
|
|
||||||
downloadUrl,
|
|
||||||
`Invoice_${path.basename(invoicePath)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info(`Day pass invoice email sent to gym owner (${gymOwnerEmail}) for payment: ${payload.merchantOrderId}`);
|
|
||||||
} catch (ownerEmailError) {
|
|
||||||
logger.error('Error sending gym owner day pass invoice email:', ownerEmailError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (invoiceError) {
|
|
||||||
logger.error('Error generating day pass invoice:', invoiceError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error processing day pass booking:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processMembershipPayment(payload: any, orderData: any, membershipId: string) {
|
|
||||||
logger.info(`Processing membership for completed payment`, {
|
|
||||||
merchantOrderId: payload.merchantOrderId,
|
merchantOrderId: payload.merchantOrderId,
|
||||||
orderId: payload.orderId,
|
orderId: payload.orderId,
|
||||||
membershipId: membershipId || 'not-provided'
|
membershipId: membershipId || 'not-provided'
|
||||||
@ -623,3 +455,21 @@ async function processMembershipPayment(payload: any, orderData: any, membership
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(`Payment data updated for completed payment: ${payload.merchantOrderId}`);
|
||||||
|
} catch (paymentUpdateError) {
|
||||||
|
logger.error('Error updating payment data:', paymentUpdateError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.status(200).json({ success: true });
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error('PhonePe webhook processing error:', error);
|
||||||
|
response.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Failed to process webhook',
|
||||||
|
details: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user