fix: guard against undefined order in blueprint checkout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,11 @@
|
|||||||
|
import { eq } from "drizzle-orm";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
import Stripe from "stripe";
|
import Stripe from "stripe";
|
||||||
|
|
||||||
|
import { db } from "@turbostarter/db/server";
|
||||||
|
import { blueprintOrder } from "@turbostarter/db/schema";
|
||||||
|
|
||||||
const BLUEPRINT_PRICE_CENTS = 4700;
|
const BLUEPRINT_PRICE_CENTS = 4700;
|
||||||
const CURRENCY = "eur";
|
const CURRENCY = "eur";
|
||||||
|
|
||||||
@@ -16,10 +20,11 @@ function getStripe(): Stripe {
|
|||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { email, businessName, placeId, locale } = body as {
|
const { email, businessName, placeId, mapsUrl, locale } = body as {
|
||||||
email: string;
|
email: string;
|
||||||
businessName: string;
|
businessName: string;
|
||||||
placeId: string;
|
placeId: string;
|
||||||
|
mapsUrl?: string;
|
||||||
locale?: string;
|
locale?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -30,10 +35,87 @@ export async function POST(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stripe = getStripe();
|
const origin =
|
||||||
const origin = request.headers.get("origin") || process.env.NEXT_PUBLIC_URL || "http://localhost:3000";
|
request.headers.get("origin") ||
|
||||||
|
process.env.NEXT_PUBLIC_URL ||
|
||||||
|
"http://localhost:3000";
|
||||||
const lang = locale || "en";
|
const lang = locale || "en";
|
||||||
|
|
||||||
|
// ── Bypass Stripe: create order + trigger engine directly ──
|
||||||
|
if (process.env.BYPASS_STRIPE === "true") {
|
||||||
|
const [order] = await db
|
||||||
|
.insert(blueprintOrder)
|
||||||
|
.values({
|
||||||
|
email,
|
||||||
|
businessName,
|
||||||
|
placeId: placeId || null,
|
||||||
|
mapsUrl: mapsUrl || null,
|
||||||
|
language: locale || "en",
|
||||||
|
stripeSessionId: `test_${Date.now()}`,
|
||||||
|
status: "pending",
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (!order) {
|
||||||
|
return NextResponse.json({ error: "Failed to create order" }, { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const engineUrl = process.env.ENGINE_API_URL;
|
||||||
|
const callbackSecret = process.env.ENGINE_CALLBACK_SECRET;
|
||||||
|
|
||||||
|
if (!engineUrl || !callbackSecret) {
|
||||||
|
await db
|
||||||
|
.update(blueprintOrder)
|
||||||
|
.set({ status: "failed", errorMessage: "ENGINE_API_URL or ENGINE_CALLBACK_SECRET not set" })
|
||||||
|
.where(eq(blueprintOrder.id, order.id));
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Engine not configured" },
|
||||||
|
{ status: 503 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicUrl = process.env.NEXT_PUBLIC_URL || origin;
|
||||||
|
const fulfillUrl =
|
||||||
|
mapsUrl ||
|
||||||
|
`https://www.google.com/maps/place/?q=place_id:${placeId}`;
|
||||||
|
|
||||||
|
const fulfillRes = await fetch(`${engineUrl}/api/blueprint/fulfill`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: fulfillUrl,
|
||||||
|
business_name: businessName,
|
||||||
|
place_id: placeId,
|
||||||
|
email,
|
||||||
|
language: locale || "en",
|
||||||
|
callback_url: `${publicUrl}/api/webhooks/engine`,
|
||||||
|
callback_secret: callbackSecret,
|
||||||
|
order_id: order.id,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fulfillRes.ok) {
|
||||||
|
const data = (await fulfillRes.json()) as { job_id: string };
|
||||||
|
await db
|
||||||
|
.update(blueprintOrder)
|
||||||
|
.set({ status: "processing", engineJobId: data.job_id })
|
||||||
|
.where(eq(blueprintOrder.id, order.id));
|
||||||
|
} else {
|
||||||
|
const errText = await fulfillRes.text();
|
||||||
|
await db
|
||||||
|
.update(blueprintOrder)
|
||||||
|
.set({ status: "failed", errorMessage: errText })
|
||||||
|
.where(eq(blueprintOrder.id, order.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to success page with the order ID as session_id
|
||||||
|
const successUrl = `${origin}/${lang}/get-started?step=success&session_id=${order.id}`;
|
||||||
|
return NextResponse.json({ url: successUrl });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Normal Stripe checkout ──
|
||||||
|
const stripe = getStripe();
|
||||||
|
|
||||||
const session = await stripe.checkout.sessions.create({
|
const session = await stripe.checkout.sessions.create({
|
||||||
mode: "payment",
|
mode: "payment",
|
||||||
payment_method_types: ["card"],
|
payment_method_types: ["card"],
|
||||||
@@ -56,6 +138,8 @@ export async function POST(request: NextRequest) {
|
|||||||
business_name: businessName,
|
business_name: businessName,
|
||||||
place_id: placeId || "",
|
place_id: placeId || "",
|
||||||
email,
|
email,
|
||||||
|
maps_url: mapsUrl || "",
|
||||||
|
language: locale || "en",
|
||||||
},
|
},
|
||||||
success_url: `${origin}/${lang}/get-started?step=success&session_id={CHECKOUT_SESSION_ID}`,
|
success_url: `${origin}/${lang}/get-started?step=success&session_id={CHECKOUT_SESSION_ID}`,
|
||||||
cancel_url: `${origin}/${lang}/get-started?step=payment`,
|
cancel_url: `${origin}/${lang}/get-started?step=payment`,
|
||||||
|
|||||||
Reference in New Issue
Block a user