save
This commit is contained in:
parent
2e714534b9
commit
11ab825cf6
2 changed files with 235 additions and 189 deletions
|
|
@ -21,37 +21,55 @@ import {
|
|||
const stripe = getStripe();
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
let eventId: string | undefined;
|
||||
|
||||
try {
|
||||
console.log("🔔 Stripe webhook received");
|
||||
|
||||
// --------------------------------------------------
|
||||
// 0️⃣ Verify Stripe signature
|
||||
// --------------------------------------------------
|
||||
const sig = req.headers.get("stripe-signature");
|
||||
if (!sig) {
|
||||
return NextResponse.json({ error: "Missing Stripe signature" }, { status: 400 });
|
||||
console.error("❌ Missing stripe-signature header");
|
||||
return NextResponse.json(
|
||||
{ error: "Missing Stripe signature" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const body = await req.text();
|
||||
let event: Stripe.Event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(
|
||||
const event = stripe.webhooks.constructEvent(
|
||||
body,
|
||||
sig,
|
||||
process.env.STRIPE_WEBHOOK_SECRET!
|
||||
);
|
||||
} catch (err: any) {
|
||||
console.error("❌ Invalid Stripe signature", err.message);
|
||||
return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
|
||||
}
|
||||
|
||||
eventId = event.id;
|
||||
|
||||
console.log("✅ Event verified", {
|
||||
id: event.id,
|
||||
type: event.type,
|
||||
});
|
||||
|
||||
// --------------------------------------------------
|
||||
// 🔕 Only handle checkout.session.completed
|
||||
// --------------------------------------------------
|
||||
if (event.type !== "checkout.session.completed") {
|
||||
console.log("⏭️ Ignored event type:", event.type);
|
||||
return NextResponse.json({ ignored: true });
|
||||
}
|
||||
|
||||
const session = event.data.object as Stripe.Checkout.Session;
|
||||
|
||||
console.log("🧾 Checkout session", {
|
||||
id: session.id,
|
||||
amount_total: session.amount_total,
|
||||
currency: session.currency,
|
||||
customer_details: session.customer_details,
|
||||
});
|
||||
|
||||
// --------------------------------------------------
|
||||
// 1️⃣ Stripe account context
|
||||
// --------------------------------------------------
|
||||
|
|
@ -61,12 +79,10 @@ export async function POST(req: NextRequest) {
|
|||
? "acct_1Sds1LB99GOwj1Ea" // DEV ONLY
|
||||
: null);
|
||||
|
||||
console.log("🔑 Stripe account context", { stripeAccountId });
|
||||
|
||||
if (!stripeAccountId) {
|
||||
console.error("❌ Missing stripe-account header in production");
|
||||
return NextResponse.json(
|
||||
{ error: "Missing Stripe account context" },
|
||||
{ status: 400 }
|
||||
);
|
||||
throw new Error("Missing stripe-account header in production");
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
|
|
@ -78,8 +94,12 @@ export async function POST(req: NextRequest) {
|
|||
.where(eq(processedStripeEvents.stripeEventId, event.id))
|
||||
.limit(1);
|
||||
|
||||
console.log("🧠 Idempotency check", {
|
||||
eventId: event.id,
|
||||
alreadyProcessed: existing.length > 0,
|
||||
});
|
||||
|
||||
if (existing.length > 0) {
|
||||
console.log("⏭️ Event already processed:", event.id);
|
||||
return NextResponse.json({ received: true });
|
||||
}
|
||||
|
||||
|
|
@ -92,11 +112,10 @@ export async function POST(req: NextRequest) {
|
|||
.where(eq(stripeAccounts.stripeAccountId, stripeAccountId))
|
||||
.limit(1);
|
||||
|
||||
console.log("👤 Stripe account lookup", { stripeAccount });
|
||||
|
||||
if (!stripeAccount) {
|
||||
return NextResponse.json(
|
||||
{ error: "Stripe account not registered" },
|
||||
{ status: 500 }
|
||||
);
|
||||
throw new Error(`Stripe account not registered: ${stripeAccountId}`);
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
|
|
@ -108,11 +127,13 @@ export async function POST(req: NextRequest) {
|
|||
.where(eq(xeroConnections.userId, stripeAccount.userId))
|
||||
.limit(1);
|
||||
|
||||
console.log("📘 Xero connection", {
|
||||
tenantId: xeroConn?.tenantId,
|
||||
salesAccountCode: xeroConn?.salesAccountCode,
|
||||
});
|
||||
|
||||
if (!xeroConn) {
|
||||
return NextResponse.json(
|
||||
{ error: "User has no Xero connection" },
|
||||
{ status: 500 }
|
||||
);
|
||||
throw new Error("User has no Xero connection");
|
||||
}
|
||||
|
||||
if (!xeroConn.salesAccountCode) {
|
||||
|
|
@ -130,10 +151,7 @@ export async function POST(req: NextRequest) {
|
|||
// --------------------------------------------------
|
||||
const email = session.customer_details?.email;
|
||||
if (!email) {
|
||||
return NextResponse.json(
|
||||
{ error: "Missing customer email" },
|
||||
{ status: 400 }
|
||||
);
|
||||
throw new Error("Missing customer email");
|
||||
}
|
||||
|
||||
const name =
|
||||
|
|
@ -141,6 +159,8 @@ export async function POST(req: NextRequest) {
|
|||
session.customer_details?.name ??
|
||||
email;
|
||||
|
||||
console.log("📨 Resolving Xero contact", { email });
|
||||
|
||||
const contactsResponse = await xero.accountingApi.getContacts(
|
||||
xeroConn.tenantId,
|
||||
undefined,
|
||||
|
|
@ -169,12 +189,19 @@ export async function POST(req: NextRequest) {
|
|||
}
|
||||
|
||||
const amount = session.amount_total / 100;
|
||||
const currencyKey = session.currency.toUpperCase() as keyof typeof CurrencyCode;
|
||||
const currencyKey =
|
||||
session.currency.toUpperCase() as keyof typeof CurrencyCode;
|
||||
|
||||
if (!(currencyKey in CurrencyCode)) {
|
||||
throw new Error(`Unsupported currency: ${session.currency}`);
|
||||
}
|
||||
|
||||
console.log("🧮 Creating invoice", {
|
||||
amount,
|
||||
currency: currencyKey,
|
||||
accountCode: xeroConn.salesAccountCode,
|
||||
});
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
|
||||
const invoiceResponse = await xero.accountingApi.createInvoices(
|
||||
|
|
@ -222,4 +249,20 @@ export async function POST(req: NextRequest) {
|
|||
});
|
||||
|
||||
return NextResponse.json({ received: true });
|
||||
} catch (err: any) {
|
||||
console.error("🔥 WEBHOOK 500", {
|
||||
eventId,
|
||||
message: err?.message,
|
||||
stack: err?.stack,
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Webhook processing failed",
|
||||
message: err?.message,
|
||||
eventId,
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
stripe_to_invoice/stripe_webhook.sh
Normal file
3
stripe_to_invoice/stripe_webhook.sh
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
echo "note you need to do 'stripe login' to make the below command work"
|
||||
stripe listen --forward-to http://localhost:3000/api/stripe/webhook
|
||||
|
||||
Loading…
Add table
Reference in a new issue