REDROOM
PHP 8.2.30
Path:
Logout
Edit File
Size: 8.51 KB
Close
//home/byroehnu/easepay.easetack.com/controllers/webhookController.js
Text
Base64
// controllers/webhookController.js - Flutterwave Webhook Handler const crypto = require('crypto'); const { prisma } = require('../config/prisma'); const { updateUserLimits } = require('./subscriptionController'); /** * Flutterwave Webhook Handler * Processes subscription events from Flutterwave */ const handleFlutterwaveWebhook = async (req, res) => { try { // 1. Verify the signature (Security First!) const secretHash = process.env.FLW_WEBHOOK_HASH; const signature = req.headers['verif-hash']; if (!signature || signature !== secretHash) { console.error('❌ Invalid webhook signature'); return res.status(401).json({ success: false, message: "Invalid webhook signature" }); } const payload = req.body; console.log('📥 Webhook received:', JSON.stringify(payload, null, 2)); const { status, customer, event, amount, plan, tx_ref } = payload.data || payload; // 2. Process successful payments if (status === "successful") { await handleSuccessfulPayment({ status, customer, event, amount, plan, tx_ref }); } // 3. Handle failed payments if (status === "failed") { await handleFailedPayment({ customer, event, amount, plan, tx_ref }); } // 4. Handle subscription events if (event === "subscription.activated" || event === "subscription.cancelled") { await handleSubscriptionEvent({ event, customer, plan, status }); } // Always return a 200 to acknowledge receipt to Flutterwave res.status(200).json({ success: true, message: "Webhook processed successfully" }); } catch (error) { console.error('❌ Webhook processing error:', error); res.status(500).json({ success: false, message: "Webhook processing failed", error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }; /** * Handle successful payment */ const handleSuccessfulPayment = async (paymentData) => { try { const { customer, amount, plan, tx_ref } = paymentData; console.log(`💰 Processing successful payment for ${customer.email}`); // Find user by email const user = await prisma.user.findUnique({ where: { email: customer.email }, include: { subscription: true } }); if (!user) { console.error(`❌ User not found: ${customer.email}`); return; } // Extract plan name from tx_ref or plan data let planName = 'Basic'; // Default fallback if (tx_ref && tx_ref.includes('_')) { // tx_ref format: easepay_sub_userId_timestamp // We need to determine plan from the amount or other data if (amount >= 10000) planName = 'Enterprise'; else if (amount >= 5000) planName = 'Pro'; else if (amount >= 2000) planName = 'Basic'; } // Calculate new billing date (Add 30 days for monthly, 365 for yearly) const newDate = new Date(); const isYearly = amount >= 20000; // Assuming yearly plans are higher amounts if (isYearly) { newDate.setFullYear(newDate.getFullYear() + 1); } else { newDate.setMonth(newDate.getMonth() + 1); } // Update user subscription await prisma.userSubscription.upsert({ where: { userId: user.id }, update: { status: 'ACTIVE', nextBillingDate: newDate, planName, amount: parseFloat(amount), flwSubscriptionId: plan?.id || null, gracePeriodEnd: null // Clear grace period }, create: { userId: user.id, status: 'ACTIVE', nextBillingDate: newDate, planName, amount: parseFloat(amount), currency: 'NGN', interval: isYearly ? 'yearly' : 'monthly', flwSubscriptionId: plan?.id || null } }); // Reset monthly usage counters for new billing cycle await prisma.userUsage.upsert({ where: { userId: user.id }, update: { invoiceCount: 0, reportExports: 0, lastResetDate: new Date() // Don't reset teamMemberCount as it's cumulative }, create: { userId: user.id, invoiceCount: 0, teamMemberCount: 1, reportExports: 0, lastResetDate: new Date() } }); // Update user plan limits await updateUserLimits(user.id, planName); console.log(`✅ Successfully renewed subscription for ${customer.email} - Plan: ${planName}`); } catch (error) { console.error('❌ Handle successful payment error:', error); } }; /** * Handle failed payment */ const handleFailedPayment = async (paymentData) => { try { const { customer } = paymentData; console.log(`💸 Processing failed payment for ${customer.email}`); // Find user by email const user = await prisma.user.findUnique({ where: { email: customer.email } }); if (!user) { console.error(`❌ User not found: ${customer.email}`); return; } // Set grace period (7 days from now) const graceDate = new Date(); graceDate.setDate(graceDate.getDate() + 7); // Update subscription status to PAST_DUE await prisma.userSubscription.upsert({ where: { userId: user.id }, update: { status: 'PAST_DUE', gracePeriodEnd: graceDate }, create: { userId: user.id, status: 'PAST_DUE', planName: 'Free', gracePeriodEnd: graceDate } }); console.log(`⚠️ Set grace period for ${customer.email} until ${graceDate.toDateString()}`); // TODO: Send payment failed notification email } catch (error) { console.error('❌ Handle failed payment error:', error); } }; /** * Handle subscription lifecycle events */ const handleSubscriptionEvent = async (eventData) => { try { const { event, customer, plan, status } = eventData; console.log(`📅 Processing subscription event: ${event} for ${customer.email}`); // Find user by email const user = await prisma.user.findUnique({ where: { email: customer.email } }); if (!user) { console.error(`❌ User not found: ${customer.email}`); return; } switch (event) { case 'subscription.activated': await prisma.userSubscription.upsert({ where: { userId: user.id }, update: { status: 'ACTIVE', flwSubscriptionId: plan?.id }, create: { userId: user.id, status: 'ACTIVE', planName: 'Basic', flwSubscriptionId: plan?.id } }); console.log(`✅ Subscription activated for ${customer.email}`); break; case 'subscription.cancelled': await prisma.userSubscription.update({ where: { userId: user.id }, data: { status: 'CANCELLED' } }); console.log(`❌ Subscription cancelled for ${customer.email}`); break; default: console.log(`ℹ️ Unhandled subscription event: ${event}`); } } catch (error) { console.error('❌ Handle subscription event error:', error); } }; /** * Test webhook endpoint (for development) */ const testWebhook = async (req, res) => { try { // Simulate successful payment webhook const testPayload = { event: "charge.completed", data: { status: "successful", amount: 5000, currency: "NGN", customer: { email: req.user.email, name: `${req.user.firstName} ${req.user.lastName}` }, tx_ref: `easepay_sub_${req.user.id}_${Date.now()}`, plan: { id: "test_plan_id", name: "Pro Plan" } } }; // Process the test webhook await handleSuccessfulPayment(testPayload.data); res.json({ success: true, message: 'Test webhook processed successfully', data: testPayload }); } catch (error) { console.error('❌ Test webhook error:', error); res.status(500).json({ success: false, message: 'Test webhook failed', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }; module.exports = { handleFlutterwaveWebhook, testWebhook };
Save
Close
Exit & Reset
Text mode: syntax highlighting auto-detects file type.
Directory Contents
Dirs: 1 × Files: 17
Delete Selected
Select All
Select None
Sort:
Name
Size
Modified
Enable drag-to-move
Name
Size
Perms
Modified
Actions
controllers
DIR
-
drwxr-xr-x
2026-03-20 23:25:48
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
.htaccess
127 B
lr--r--r--
2026-03-14 01:49:22
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
advancedAuthController.js
15.34 KB
lrw-r--r--
2026-02-19 22:06:47
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
authController.js
30.77 KB
lrw-r--r--
2026-03-04 06:33:18
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
authController_copy.js
62.98 KB
lrw-r--r--
2026-03-01 23:46:52
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
businessController.js
10.79 KB
lrw-r--r--
2026-02-07 16:28:16
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
businessProfileController.js
14.17 KB
lrw-r--r--
2026-02-27 04:02:38
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
creditScoreController.js
6.32 KB
lrw-r--r--
2026-02-06 17:10:19
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
dashboardController.js
15.13 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
expenseController.js
8.50 KB
lrw-r--r--
2026-03-01 05:05:52
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
inventoryController.js
12.15 KB
lrw-r--r--
2026-02-27 04:02:38
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
invoiceController.js
25.39 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
reportController.js
17.61 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
saleController.js
6.45 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
settingsController.js
2.33 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
subscriptionController.js
12.78 KB
lrw-r--r--
2026-02-21 08:13:30
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
taxController.js
9.96 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
webhookController.js
8.51 KB
lrw-r--r--
2026-02-20 19:31:28
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
Zip Selected
If ZipArchive is unavailable, a
.tar
will be created (no compression).