Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Next.js App Router webhook example that leverages Route Handlers #2259

Merged
merged 1 commit into from
Feb 7, 2025
Merged
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
69 changes: 69 additions & 0 deletions examples/webhook-signing/nextjs/app/api/webhooks/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {Stripe} from 'stripe';
import {NextResponse} from 'next/server';
import {headers} from 'next/headers';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string);

export async function POST(req: Request) {
let event: Stripe.Event;

try {
const stripeSignature = (await headers()).get('stripe-signature');

event = stripe.webhooks.constructEvent(
await req.text(),
stripeSignature as string,
process.env.STRIPE_WEBHOOK_SECRET as string
);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
// On error, log and return the error message.
if (err! instanceof Error) console.log(err);
console.log(`❌ Error message: ${errorMessage}`);
return NextResponse.json(
{message: `Webhook Error: ${errorMessage}`},
{status: 400}
);
}

// Successfully constructed event.
console.log('✅ Success:', event.id);

const permittedEvents: string[] = [
'checkout.session.completed',
'payment_intent.succeeded',
'payment_intent.payment_failed',
];

if (permittedEvents.includes(event.type)) {
let data;

try {
switch (event.type) {
case 'checkout.session.completed':
data = event.data.object as Stripe.Checkout.Session;
console.log(`💰 CheckoutSession status: ${data.payment_status}`);
break;
case 'payment_intent.payment_failed':
data = event.data.object as Stripe.PaymentIntent;
console.log(`❌ Payment failed: ${data.last_payment_error?.message}`);
break;
case 'payment_intent.succeeded':
data = event.data.object as Stripe.PaymentIntent;
console.log(`💰 PaymentIntent status: ${data.status}`);
break;
default:
throw new Error(`Unhandled event: ${event.type}`);
}
} catch (error) {
console.log(error);
return NextResponse.json(
{message: 'Webhook handler failed'},
{status: 500}
);
}
}

// Return a response to acknowledge receipt of the event.
return NextResponse.json({message: 'Received'}, {status: 200});
}

Unchanged files with check annotations Beta

this._maxBufferedRequestMetric = maxBufferedRequestMetric;
}
_addHeadersDirectlyToObject(obj: any, headers: RequestHeaders): void {

Check warning on line 53 in src/RequestSender.ts

GitHub Actions / Static Checks

Argument 'obj' should be typed with a non-any type
// For convenience, make some headers easily accessible on
// lastResponse.
sleepSeconds = Math.max(initialNetworkRetryDelay, sleepSeconds);
// And never sleep less than the time the API asks us to wait, assuming it's a reasonable ask.
if (Number.isInteger(retryAfter) && retryAfter! <= MAX_RETRY_AFTER_WAIT) {

Check warning on line 301 in src/RequestSender.ts

GitHub Actions / Static Checks

Forbidden non-null assertion
sleepSeconds = Math.max(sleepSeconds, retryAfter!);

Check warning on line 302 in src/RequestSender.ts

GitHub Actions / Static Checks

Forbidden non-null assertion
}
return sleepSeconds * 1000;
removeListener(eventName: string, listener: Listener): void {
const listenerWrapper = this.listenerMapping.get(listener);
this.listenerMapping.delete(listener);
return this.eventTarget.removeEventListener(eventName, listenerWrapper!);

Check warning on line 40 in src/StripeEmitter.ts

GitHub Actions / Static Checks

Forbidden non-null assertion
}
once(eventName: string, listener: Listener): void {
resourcePath: '',
// Methods that don't use the API's default '/v1' path can override it with this setting.
basePath: null!,

Check warning on line 63 in src/StripeResource.ts

GitHub Actions / Static Checks

Forbidden non-null assertion
initialize(): void {},
const isUsingFullPath = !!spec.fullPath;
const commandPath: UrlInterpolator = makeURLInterpolator(
isUsingFullPath ? spec.fullPath! : spec.path || ''

Check warning on line 132 in src/StripeResource.ts

GitHub Actions / Static Checks

Forbidden non-null assertion
);
// When using fullPath, we ignore the resource path as it should already be
// fully qualified.
if (!webhooksCryptoProviderInstance) {
webhooksCryptoProviderInstance = platformFunctions.createDefaultCryptoProvider();
}
return webhooksCryptoProviderInstance!;

Check warning on line 445 in src/Webhooks.ts

GitHub Actions / Static Checks

Forbidden non-null assertion
}
function prepareOptions(
if (err) {
return resolve(null);
}
resolve(uname!);

Check warning on line 57 in src/platform/NodePlatformFunctions.ts

GitHub Actions / Static Checks

Forbidden non-null assertion
});
} catch (e) {
resolve(null);
Stripe.prototype = {
// Properties are set in the constructor above
_appInfo: undefined!,

Check warning on line 206 in src/stripe.core.ts

GitHub Actions / Static Checks

Forbidden non-null assertion
on: null!,

Check warning on line 207 in src/stripe.core.ts

GitHub Actions / Static Checks

Forbidden non-null assertion
off: null!,
once: null!,
VERSION: null!,