From ac6af7f17ab6566f9db0d5a08964071182e53ab9 Mon Sep 17 00:00:00 2001 From: rishab11250 Date: Wed, 27 May 2026 11:08:25 +0530 Subject: [PATCH] fix: add payment backend routes for Razorpay order creation and verification --- server.js | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/server.js b/server.js index 6323ec8..b5430c2 100644 --- a/server.js +++ b/server.js @@ -10,6 +10,7 @@ const crypto = require('crypto'); const supabase = require('./db'); const { OAuth2Client } = require('google-auth-library'); const rateLimit = require('express-rate-limit'); +const Razorpay = require('razorpay'); const app = express(); const PORT = process.env.PORT || 3000; if (!process.env.JWT_SECRET) { @@ -21,7 +22,19 @@ if (!process.env.JWT_SECRET) { process.exit(1); } const JWT_SECRET = process.env.JWT_SECRET; +const RAZORPAY_KEY_ID = process.env.RAZORPAY_KEY_ID; +const RAZORPAY_KEY_SECRET = process.env.RAZORPAY_KEY_SECRET; +let razorpay; +if (RAZORPAY_KEY_ID && RAZORPAY_KEY_SECRET) { + razorpay = new Razorpay({ key_id: RAZORPAY_KEY_ID, key_secret: RAZORPAY_KEY_SECRET }); +} + +// Pricing (amounts in rupees, converted to paise * 100 in create-order) +const PLANS = { + plus: { monthly: 50, yearly: 500 }, + professional: { monthly: 399, yearly: 4000 } +}; //LOGIN limiter const loginLimiter = rateLimit({ @@ -1201,6 +1214,109 @@ app.post('/api/contact', async (req, res) => { res.status(201).json({ success: true, message: 'Message sent successfully!' }); }); +// ─── Payment Routes ─── + +// POST /api/payment/create-order — Create Razorpay order +app.post('/api/payment/create-order', requireAuth, async (req, res) => { + try { + const { planId, billing } = req.body; + + if (!planId || !billing) { + return res.status(400).json({ error: 'planId and billing are required.' }); + } + + if (!PLANS[planId]) { + return res.status(400).json({ error: 'Invalid plan. Choose plus or professional.' }); + } + + if (!['monthly', 'yearly'].includes(billing)) { + return res.status(400).json({ error: 'Invalid billing cycle. Choose monthly or yearly.' }); + } + + if (!razorpay) { + return res.status(500).json({ error: 'Payment not configured. RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET must be set.' }); + } + + const amountInRupees = PLANS[planId][billing]; + const amountInPaise = amountInRupees * 100; + + const order = await razorpay.orders.create({ + amount: amountInPaise, + currency: 'INR', + receipt: `conn_${req.auth.userId}_${Date.now()}`, + notes: { planId, billing, userId: req.auth.userId } + }); + + res.json({ + orderId: order.id, + amount: order.amount, + currency: order.currency, + key: RAZORPAY_KEY_ID + }); + } catch (err) { + console.error('Create order error:', err); + res.status(500).json({ error: 'Failed to create payment order.' }); + } +}); + +// POST /api/payment/verify — Verify Razorpay payment signature +app.post('/api/payment/verify', requireAuth, async (req, res) => { + try { + const { razorpay_order_id, razorpay_payment_id, razorpay_signature } = req.body; + + if (!razorpay_order_id || !razorpay_payment_id || !razorpay_signature) { + return res.status(400).json({ error: 'Missing payment verification fields.' }); + } + + if (!razorpay) { + return res.status(500).json({ error: 'Payment not configured.' }); + } + + const isValid = Razorpay.validatePaymentVerification( + { order_id: razorpay_order_id, payment_id: razorpay_payment_id }, + razorpay_signature, + RAZORPAY_KEY_SECRET + ); + + if (!isValid) { + return res.status(400).json({ success: false, error: 'Invalid payment signature.' }); + } + + // Fetch the order to get plan details from notes + let planId = 'plus'; + let billing = 'monthly'; + try { + const order = await razorpay.orders.fetch(razorpay_order_id); + if (order.notes) { + planId = order.notes.planId || 'plus'; + billing = order.notes.billing || 'monthly'; + } + } catch { + // fall back to defaults if we can't fetch the order + } + + // Update user's subscription in the database + const { error: updateError } = await supabase + .from('users') + .update({ + subscription_plan: planId, + subscription_billing: billing, + subscribed_at: new Date().toISOString() + }) + .eq('id', req.auth.userId); + + if (updateError) { + console.error('Subscription update error:', updateError); + return res.status(500).json({ success: false, error: 'Failed to update subscription.' }); + } + + res.json({ success: true }); + } catch (err) { + console.error('Payment verification error:', err); + res.status(500).json({ success: false, error: 'Payment verification failed.' }); + } +}); + // ──────────────────── START SERVER ──────────────────── // Only start listener when not running on Vercel (Vercel uses serverless, not a listener)