133 lines
3.7 KiB
JavaScript
133 lines
3.7 KiB
JavaScript
// controllers/payment.controller.js
|
|
import Stripe from "stripe";
|
|
import { Payment } from "../models/payment.model.js";
|
|
|
|
// ✅ Load Stripe Secret Key from .env
|
|
const stripe = new Stripe("sk_test_51SB8SnIFk8fh986GkYaNPVSfZzh6gcuXhq3tOa5hyE4U4vYIqrHwyGRu2OE1N5TNW39tJmfFOyYfsh4HcZOjlsj100xIeM46zU", {
|
|
apiVersion: "2022-11-15",
|
|
});
|
|
|
|
/**
|
|
* 🔹 Option 1: PaymentIntent API (client uses clientSecret)
|
|
*/
|
|
export async function createPaymentIntent(req, res) {
|
|
try {
|
|
const { amount } = req.body;
|
|
if (!amount) return res.status(400).json({ error: "amount is required" });
|
|
|
|
const paymentIntent = await stripe.paymentIntents.create({
|
|
amount: Math.round(amount * 100), // dollars → cents
|
|
currency: "usd",
|
|
automatic_payment_methods: { enabled: true },
|
|
});
|
|
|
|
await Payment.create({
|
|
amount: Math.round(amount * 100),
|
|
stripePaymentIntentId: paymentIntent.id,
|
|
status: "pending",
|
|
});
|
|
|
|
res.json({ clientSecret: paymentIntent.client_secret });
|
|
} catch (err) {
|
|
console.error("❌ Error creating PaymentIntent:", err);
|
|
res.status(500).json({ error: "Internal Server Error" });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 🔹 Option 2: Stripe Checkout Session (redirect flow)
|
|
*/
|
|
export async function createCheckoutSession(req, res) {
|
|
try {
|
|
const { email, amount, planId } = req.body;
|
|
if (!email || !amount) {
|
|
return res.status(400).json({ error: "email and amount are required" });
|
|
}
|
|
|
|
const session = await stripe.checkout.sessions.create({
|
|
payment_method_types: ["card"],
|
|
mode: "payment",
|
|
customer_email: email,
|
|
line_items: [
|
|
{
|
|
price_data: {
|
|
currency: "usd",
|
|
product_data: { name: planId || "SEO Plan" },
|
|
unit_amount: Math.round(amount * 100),
|
|
},
|
|
quantity: 1,
|
|
},
|
|
],
|
|
success_url: "https://app.crawlerx.co/success",
|
|
cancel_url: "https://app.crawlerx.co/cancel",
|
|
});
|
|
|
|
// Save to DB using stripeSessionId instead of stripePaymentIntentId
|
|
await Payment.create({
|
|
email,
|
|
amount: Math.round(amount * 100),
|
|
stripeSessionId: session.id, // ✅ use session id
|
|
status: "pending",
|
|
});
|
|
|
|
res.json({ sessionId: session.id });
|
|
} catch (err) {
|
|
console.error("❌ Error creating checkout session:", err);
|
|
res.status(500).json({ error: "Internal Server Error" });
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 🔹 Stripe Webhook
|
|
* Stripe requires `express.raw({ type: "application/json" })` in route
|
|
*/
|
|
export async function handleWebhook(req, res) {
|
|
const sig = req.headers["stripe-signature"];
|
|
let event;
|
|
|
|
try {
|
|
event = stripe.webhooks.constructEvent(
|
|
req.rawBody, // Must be raw body
|
|
sig,
|
|
process.env.STRIPE_WEBHOOK_SECRET
|
|
);
|
|
} catch (err) {
|
|
console.error("❌ Webhook signature verification failed:", err.message);
|
|
return res.status(400).send(`Webhook Error: ${err.message}`);
|
|
}
|
|
|
|
switch (event.type) {
|
|
case "payment_intent.succeeded": {
|
|
const paymentIntent = event.data.object;
|
|
console.log("✅ PaymentIntent succeeded:", paymentIntent.id);
|
|
|
|
await Payment.findOneAndUpdate(
|
|
{ stripePaymentIntentId: paymentIntent.id },
|
|
{ status: "succeeded" }
|
|
);
|
|
break;
|
|
}
|
|
|
|
case "checkout.session.completed": {
|
|
const session = event.data.object;
|
|
console.log("✅ Checkout session completed:", session.id);
|
|
|
|
// Update DB record created earlier
|
|
await Payment.findOneAndUpdate(
|
|
{ email: session.customer_email, status: "pending" },
|
|
{
|
|
stripePaymentIntentId: session.payment_intent,
|
|
status: "succeeded",
|
|
}
|
|
);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
console.log(`Unhandled event type ${event.type}`);
|
|
}
|
|
|
|
res.json({ received: true });
|
|
}
|