Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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({
Expand Down Expand Up @@ -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)
Expand Down