@aruns @anshumanb
Here’s the code for both /api/webhooks/stripe
and stripe.ts
, It worked fine on vercel.app domain (which I unfortunately deleted after connecting custom domain) but doesn’t work on my custom domain.
The error I keep getting is 405 error “Cannot connect to remote host” when viewing events in Stripe dashboard…
src/lib/stripe.ts
import Stripe from "stripe"
import { env } from "@/env.mjs"
import { prisma } from "@/lib/db"
export const stripe = new Stripe(env.STRIPE_SECRET_KEY, {
apiVersion: "2024-04-10",
typescript: true,
})
// commonly used Stripe related functions
export async function createCheckoutSession(
amount: number,
quantity: number,
description: string,
userId: string,
emailAddress: string,
metadata?: Record<string, string>
) {
// Verify user exists first
const user = await prisma.user.findUnique({
where: { id: userId },
});
if (!user) {
throw new Error("User not found");
}
const isTeamPurchase = metadata?.type === "TEAM";
const successUrl = isTeamPurchase
? `${env.NEXT_PUBLIC_APP_URL}/dashboard/create/team/verify?session_id={CHECKOUT_SESSION_ID}`
: `${env.NEXT_PUBLIC_APP_URL}/dashboard/create/individual/customize?session_id={CHECKOUT_SESSION_ID}`;
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: {
name: `${quantity} Credits - fhotosonic.com`,
description: description,
},
unit_amount: Math.round(amount * 100), // ensure amount is an integer
},
quantity: 1,
},
],
mode: 'payment',
success_url: successUrl,
cancel_url: `${env.NEXT_PUBLIC_APP_URL}/pricing`,
metadata: {
userId,
credits: quantity.toString(),
...metadata,
},
customer_email: emailAddress,
allow_promotion_codes: true,
});
// Create a StripeTransaction record
await prisma.stripeTransaction.create({
data: {
userId,
stripeSessionId: session.id,
amount: amount * 100, // Store amount in cents
status: "pending",
},
});
return session;
}
export async function handleStripeWebhook(/* parameters */) {
// implement the logic to handle Stripe webhooks
}
export async function handleSuccessfulPayment(sessionId: string) {
try {
const transaction = await prisma.stripeTransaction.findUnique({
where: { stripeSessionId: sessionId },
});
if (transaction && transaction.status === 'completed') {
return transaction; // Payment already processed
}
const session = await stripe.checkout.sessions.retrieve(sessionId);
if (session.payment_status !== 'paid') {
return null; // Payment not successful
}
const userId = session.metadata?.userId;
const credits = parseInt(session.metadata?.credits || "0", 10);
if (!userId || !credits) {
throw new Error("Invalid metadata in session");
}
// Update transaction status
const updatedTransaction = await prisma.stripeTransaction.update({
where: { stripeSessionId: sessionId },
data: {
status: "completed",
stripePaymentIntentId: session.payment_intent as string || null
},
});
// Update user credits
await prisma.user.update({
where: { id: userId },
data: {
credits: { increment: credits },
},
});
// Record credit transaction
await prisma.creditTransaction.create({
data: {
userId,
amount: credits,
type: "PURCHASE",
},
});
return updatedTransaction;
} catch (error) {
console.error("Error handling successful payment:", error);
throw error;
}
}
src/app/api/webhooks/stripe/route.ts
import Stripe from 'stripe';
import { headers } from "next/headers";
import { stripe, handleSuccessfulPayment } from "@/lib/stripe";
import { NextResponse } from "next/server";
import { env } from "@/env.mjs";
import { prisma } from "@/lib/db";
export async function POST(req: Request) {
const body = await req.text();
const signature = headers().get("Stripe-Signature") as string;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature, env.STRIPE_WEBHOOK_SECRET);
} catch (err: any) {
return NextResponse.json({ error: `Webhook Error: ${err.message}` }, { status: 400 });
}
if (event.type === "checkout.session.completed") {
const session = event.data.object as Stripe.Checkout.Session;
try {
// Verify the transaction exists
const transaction = await prisma.stripeTransaction.findUnique({
where: { stripeSessionId: session.id },
});
if (!transaction) {
console.error("Transaction not found for session:", session.id);
return NextResponse.json({ error: "Transaction not found" }, { status: 404 });
}
await handleSuccessfulPayment(session.id);
} catch (error) {
console.error("Error processing successful payment:", error);
// Don't expose internal error details to the client
return NextResponse.json({ error: "Error processing payment" }, { status: 500 });
}
}
return NextResponse.json({ received: true });
}