Stripe-Zahlungsintegration in Next.js-Anwendungen
Stripe-Zahlungsintegration in Next.js-Anwendungen
poradnikiStripe-Zahlungsintegration in Next.js
Die Abwicklung von Online-Zahlungen ist ein Schlüsselelement jedes Online-Shops und jeder SaaS-Anwendung. Stripe ist seit Jahren einer der beliebtesten Zahlungsabwickler weltweit und bietet eine umfangreiche API, hervorragende Dokumentation und breite Unterstützung für moderne Frameworks. In diesem Leitfaden zeigen wir Ihnen Schritt für Schritt, wie Sie Stripe in eine Next.js-Anwendung integrieren, unter Verwendung von TypeScript, API Routes, Webhooks, Abonnements und vielen weiteren Funktionen.
Was ist Stripe?#
Stripe ist eine Plattform zur Abwicklung von Online-Zahlungen, die sowohl von Startups als auch von globalen Konzernen genutzt wird (u.a. Shopify, Amazon, Google). Stripe bietet:
- Zahlungsabwicklung mit Kredit- und Debitkarten in über 135 Währungen
- Stripe Elements — fertige, konfigurierbare UI-Komponenten zur sicheren Erfassung von Kartendaten
- Checkout Sessions — gehostete Zahlungsseiten mit vollständiger UI
- Payment Intents API — eine flexible API zum Aufbau eigener Zahlungsabläufe
- Billing — Abonnement- und Rechnungsverwaltung
- Stripe Dashboard — ein Administrationspanel zur Überwachung von Transaktionen, Rückerstattungen und Analysen
- Stripe CLI — ein Werkzeug zum lokalen Testen von Webhooks
Stripe ist PCI DSS Level 1 konform, was bedeutet, dass Kartendaten niemals über Ihren Server laufen, wenn Sie Elements oder Checkout verwenden.
Projektkonfiguration#
Beginnen wir mit der Installation der erforderlichen Pakete in einem Next.js-Projekt:
// Install dependencies
// npm install stripe @stripe/stripe-js @stripe/react-stripe-js
// Environment variables (.env.local)
// NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
// STRIPE_SECRET_KEY=sk_test_...
// STRIPE_WEBHOOK_SECRET=whsec_...
Anschließend konfigurieren wir die Stripe-Instanz auf der Server- und Client-Seite:
// lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
typescript: true,
});
// lib/stripe-client.ts
import { loadStripe, Stripe } from '@stripe/stripe-js';
let stripePromise: Promise<Stripe | null>;
export const getStripe = () => {
if (!stripePromise) {
stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);
}
return stripePromise;
};
Stripe Checkout Sessions#
Der einfachste Weg, Zahlungen zu akzeptieren, ist Stripe Checkout — eine gehostete Zahlungsseite, die vollständig von Stripe verwaltet wird. Sie müssen kein eigenes Formular erstellen und sich keine Sorgen um PCI-Compliance machen.
API Route zur Erstellung von Sessions#
// app/api/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
interface CheckoutRequestBody {
priceId: string;
quantity?: number;
mode?: 'payment' | 'subscription';
}
export async function POST(request: NextRequest) {
try {
const body: CheckoutRequestBody = await request.json();
const { priceId, quantity = 1, mode = 'payment' } = body;
if (!priceId) {
return NextResponse.json(
{ error: 'Missing required parameter priceId' },
{ status: 400 }
);
}
const session = await stripe.checkout.sessions.create({
mode,
payment_method_types: ['card'],
line_items: [
{
price: priceId,
quantity,
},
],
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/checkout/cancel`,
metadata: {
source: 'next-app',
},
});
return NextResponse.json({ sessionId: session.id, url: session.url });
} catch (error) {
console.error('Error creating Checkout session:', error);
return NextResponse.json(
{ error: 'Failed to create payment session' },
{ status: 500 }
);
}
}
Checkout-Weiterleitungskomponente#
// components/CheckoutButton.tsx
'use client';
import { useState } from 'react';
import { getStripe } from '@/lib/stripe-client';
interface CheckoutButtonProps {
priceId: string;
mode?: 'payment' | 'subscription';
label?: string;
}
export function CheckoutButton({
priceId,
mode = 'payment',
label = 'Buy Now',
}: CheckoutButtonProps) {
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
setLoading(true);
try {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId, mode }),
});
const { sessionId } = await response.json();
const stripe = await getStripe();
if (stripe) {
const { error } = await stripe.redirectToCheckout({ sessionId });
if (error) {
console.error('Redirect error:', error.message);
}
}
} catch (error) {
console.error('Checkout error:', error);
} finally {
setLoading(false);
}
};
return (
<button
onClick={handleCheckout}
disabled={loading}
className="bg-indigo-600 text-white px-6 py-3 rounded-lg
hover:bg-indigo-700 disabled:opacity-50 transition-colors"
>
{loading ? 'Processing...' : label}
</button>
);
}
Payment Intents — Erweiterte Abläufe#
Wenn Sie die volle Kontrolle über das Erscheinungsbild des Zahlungsformulars benötigen, verwenden Sie die Payment Intents API in Kombination mit Stripe Elements. So erstellen Sie Ihre eigene UI, während Stripe sich um die sichere Verarbeitung der Kartendaten kümmert.
Erstellen eines Payment Intent auf der Serverseite#
// app/api/payment-intent/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
interface PaymentIntentRequest {
amount: number;
currency?: string;
description?: string;
customerEmail?: string;
}
export async function POST(request: NextRequest) {
try {
const body: PaymentIntentRequest = await request.json();
const { amount, currency = 'usd', description, customerEmail } = body;
// Server-side validation
if (!amount || amount < 50) {
return NextResponse.json(
{ error: 'Minimum amount is $0.50' },
{ status: 400 }
);
}
if (amount > 99999999) {
return NextResponse.json(
{ error: 'Amount exceeds the allowed limit' },
{ status: 400 }
);
}
// Optionally: find or create a Stripe customer
let customerId: string | undefined;
if (customerEmail) {
const customers = await stripe.customers.list({
email: customerEmail,
limit: 1,
});
if (customers.data.length > 0) {
customerId = customers.data[0].id;
} else {
const customer = await stripe.customers.create({
email: customerEmail,
});
customerId = customer.id;
}
}
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
description,
customer: customerId,
automatic_payment_methods: {
enabled: true,
},
metadata: {
source: 'custom-form',
},
});
return NextResponse.json({
clientSecret: paymentIntent.client_secret,
paymentIntentId: paymentIntent.id,
});
} catch (error) {
console.error('Error creating PaymentIntent:', error);
if (error instanceof Stripe.errors.StripeError) {
return NextResponse.json(
{ error: error.message },
{ status: error.statusCode || 500 }
);
}
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
Stripe Elements — Sicheres Zahlungsformular#
Stripe Elements ist ein Satz vorgefertigter UI-Komponenten, die Zahlungskartendaten sicher erfassen. Kartendaten gelangen niemals auf Ihren Server — sie werden direkt an Stripe übermittelt.
// components/PaymentForm.tsx
'use client';
import { useState, FormEvent } from 'react';
import {
Elements,
PaymentElement,
useStripe,
useElements,
} from '@stripe/react-stripe-js';
import { getStripe } from '@/lib/stripe-client';
import type { StripeElementsOptions } from '@stripe/stripe-js';
function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [error, setError] = useState<string | null>(null);
const [processing, setProcessing] = useState(false);
const [succeeded, setSucceeded] = useState(false);
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
if (!stripe || !elements) return;
setProcessing(true);
setError(null);
const { error: submitError } = await elements.submit();
if (submitError) {
setError(submitError.message || 'An error occurred');
setProcessing(false);
return;
}
const { error: confirmError } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/checkout/success`,
},
});
if (confirmError) {
setError(confirmError.message || 'Payment failed');
setProcessing(false);
} else {
setSucceeded(true);
}
};
if (succeeded) {
return (
<div className="text-center p-8">
<h2 className="text-2xl font-bold text-green-600">
Payment successful!
</h2>
</div>
);
}
return (
<form onSubmit={handleSubmit} className="max-w-md mx-auto space-y-6">
<PaymentElement />
{error && (
<div className="text-red-500 text-sm bg-red-50 p-3 rounded">
{error}
</div>
)}
<button
type="submit"
disabled={!stripe || processing}
className="w-full bg-indigo-600 text-white py-3 px-4 rounded-lg
hover:bg-indigo-700 disabled:opacity-50 transition-colors"
>
{processing ? 'Processing...' : 'Pay Now'}
</button>
</form>
);
}
interface PaymentFormProps {
clientSecret: string;
}
export function PaymentForm({ clientSecret }: PaymentFormProps) {
const options: StripeElementsOptions = {
clientSecret,
appearance: {
theme: 'stripe',
variables: {
colorPrimary: '#4f46e5',
borderRadius: '8px',
},
},
};
return (
<Elements stripe={getStripe()} options={options}>
<CheckoutForm />
</Elements>
);
}
Stripe Webhooks#
Webhooks sind ein Mechanismus, über den Stripe Ihre Anwendung über Ereignisse informiert (z.B. erfolgreiche Zahlung, fehlgeschlagenes Abonnement, Rückerstattung). Dies ist der einzige zuverlässige Weg, eine Zahlung zu bestätigen — verlassen Sie sich niemals ausschließlich auf die Weiterleitung des Kunden zur Erfolgsseite.
// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
import Stripe from 'stripe';
export const runtime = 'nodejs';
async function handleCheckoutComplete(
session: Stripe.Checkout.Session
) {
console.log('Checkout completed:', session.id);
// Update the order in your database
// await db.order.update({
// where: { stripeSessionId: session.id },
// data: { status: 'paid', paidAt: new Date() },
// });
// Send a confirmation email
// await sendOrderConfirmation(session.customer_email);
}
async function handlePaymentFailed(
paymentIntent: Stripe.PaymentIntent
) {
console.error('Payment failed:', paymentIntent.id);
// Notify the user about the failed payment
}
async function handleSubscriptionUpdated(
subscription: Stripe.Subscription
) {
console.log('Subscription updated:', subscription.id);
// Update subscription status in your database
}
async function handleInvoicePaid(invoice: Stripe.Invoice) {
console.log('Invoice paid:', invoice.id);
// Record the paid invoice
}
export async function POST(request: NextRequest) {
const body = await request.text();
const signature = request.headers.get('stripe-signature');
if (!signature) {
return NextResponse.json(
{ error: 'Missing stripe-signature header' },
{ status: 400 }
);
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error';
console.error('Webhook verification failed:', message);
return NextResponse.json(
{ error: `Webhook Error: ${message}` },
{ status: 400 }
);
}
try {
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutComplete(
event.data.object as Stripe.Checkout.Session
);
break;
case 'payment_intent.payment_failed':
await handlePaymentFailed(
event.data.object as Stripe.PaymentIntent
);
break;
case 'customer.subscription.updated':
await handleSubscriptionUpdated(
event.data.object as Stripe.Subscription
);
break;
case 'invoice.paid':
await handleInvoicePaid(event.data.object as Stripe.Invoice);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
return NextResponse.json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
return NextResponse.json(
{ error: 'Webhook processing failed' },
{ status: 500 }
);
}
}
Abonnements und wiederkehrende Abrechnungen#
Stripe Billing ermöglicht die Verwaltung von Abonnements. Sie können Preispläne (Products und Prices) im Stripe Dashboard erstellen und diese dann Ihren Kunden anbieten.
// app/api/subscriptions/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
interface SubscriptionRequest {
email: string;
priceId: string;
paymentMethodId: string;
}
export async function POST(request: NextRequest) {
try {
const { email, priceId, paymentMethodId }: SubscriptionRequest =
await request.json();
// Find or create customer
const customers = await stripe.customers.list({
email,
limit: 1,
});
let customer = customers.data[0];
if (!customer) {
customer = await stripe.customers.create({
email,
payment_method: paymentMethodId,
invoice_settings: {
default_payment_method: paymentMethodId,
},
});
} else {
await stripe.paymentMethods.attach(paymentMethodId, {
customer: customer.id,
});
await stripe.customers.update(customer.id, {
invoice_settings: {
default_payment_method: paymentMethodId,
},
});
}
// Create subscription
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: priceId }],
payment_settings: {
payment_method_types: ['card'],
save_default_payment_method: 'on_subscription',
},
expand: ['latest_invoice.payment_intent'],
});
const invoice = subscription.latest_invoice as Stripe.Invoice;
const paymentIntent =
invoice.payment_intent as Stripe.PaymentIntent;
return NextResponse.json({
subscriptionId: subscription.id,
clientSecret: paymentIntent.client_secret,
status: subscription.status,
});
} catch (error) {
console.error('Error creating subscription:', error);
return NextResponse.json(
{ error: 'Failed to create subscription' },
{ status: 500 }
);
}
}
// Subscription management — cancellation
export async function DELETE(request: NextRequest) {
try {
const { subscriptionId } = await request.json();
const subscription = await stripe.subscriptions.update(
subscriptionId,
{
cancel_at_period_end: true,
}
);
return NextResponse.json({
status: subscription.status,
cancelAt: subscription.cancel_at,
});
} catch (error) {
console.error('Error canceling subscription:', error);
return NextResponse.json(
{ error: 'Failed to cancel subscription' },
{ status: 500 }
);
}
}
SCA und 3D Secure#
Strong Customer Authentication (SCA) ist eine regulatorische Anforderung in Europa (PSD2), die eine zusätzliche Zahlungsauthentifizierung vorschreibt. Stripe behandelt 3D Secure automatisch, wenn es erforderlich ist. Verwenden Sie einfach die Payment Intents API mit der Option automatic_payment_methods:
// Stripe automatically triggers 3D Secure when the bank requires it.
// On the client side, you just need to handle the requires_action state:
const { error, paymentIntent } = await stripe.confirmCardPayment(
clientSecret,
{
payment_method: {
card: elements.getElement('card')!,
billing_details: {
name: 'John Doe',
email: 'john@example.com',
},
},
}
);
if (error) {
// User did not pass 3D Secure verification
console.error('3DS error:', error.message);
} else if (paymentIntent?.status === 'succeeded') {
// Payment completed successfully
console.log('Payment confirmed');
}
Unterstützung mehrerer Währungen#
Stripe unterstützt über 135 Währungen. Sie können die Währung dynamisch basierend auf dem Standort des Benutzers festlegen:
// lib/currency.ts
interface CurrencyConfig {
currency: string;
locale: string;
symbol: string;
minAmount: number;
}
const CURRENCY_MAP: Record<string, CurrencyConfig> = {
PL: { currency: 'pln', locale: 'pl-PL', symbol: 'zl', minAmount: 200 },
DE: { currency: 'eur', locale: 'de-DE', symbol: 'E', minAmount: 50 },
US: { currency: 'usd', locale: 'en-US', symbol: '$', minAmount: 50 },
GB: { currency: 'gbp', locale: 'en-GB', symbol: 'P', minAmount: 30 },
};
export function getCurrencyConfig(
countryCode: string
): CurrencyConfig {
return CURRENCY_MAP[countryCode] || CURRENCY_MAP['US'];
}
export function formatAmount(
amount: number,
currency: string,
locale: string
): string {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency.toUpperCase(),
}).format(amount / 100);
}
Testen mit Stripe CLI#
Mit der Stripe CLI können Sie Webhook-Ereignisse lokal simulieren, ohne Ihre Anwendung bereitstellen zu müssen:
// 1. Install Stripe CLI
// brew install stripe/stripe-cli/stripe (macOS)
// scoop install stripe (Windows)
// 2. Log in
// stripe login
// 3. Listen to webhooks locally
// stripe listen --forward-to localhost:3000/api/webhooks/stripe
// 4. Send a test event
// stripe trigger payment_intent.succeeded
// 5. Test card numbers:
// 4242 4242 4242 4242 — successful payment
// 4000 0025 0000 3155 — requires 3D Secure
// 4000 0000 0000 9995 — declined payment
Die Stripe CLI zeigt ein webhook signing secret an (beginnend mit whsec_), das Sie in der Variable STRIPE_WEBHOOK_SECRET festlegen müssen.
PCI-Compliance#
Die Einhaltung von PCI DSS ist für jeden obligatorisch, der Zahlungskartendaten verarbeitet. Durch die Verwendung von Stripe Elements oder Checkout erreichen Sie die höchste Vereinfachungsstufe:
- SAQ A — Checkout-Formular (von Stripe gehostet) — wenigste Anforderungen
- SAQ A-EP — Stripe Elements (in Ihre Seite eingebettet) — moderate Anforderungen
- Kartendaten berühren niemals Ihren Server — sie gehen direkt an Stripe
- Stripe ist als PCI DSS Level 1 zertifiziert — die höchste Sicherheitsstufe
Stellen Sie sicher, dass Sie HTTPS in Ihrer Produktionsumgebung verwenden und speichern Sie niemals rohe Kartendaten.
Stripe Dashboard#
Das Stripe Dashboard ist das Verwaltungszentrum für Ihre Zahlungen. Es bietet:
- Transaktionsübersicht in Echtzeit mit Filtern und Suche
- Kundenverwaltung — Zahlungshistorie, Abonnements, Zahlungsmethoden
- Rückerstattungen — vollständig und teilweise, direkt aus dem Panel
- Berichte und Analysen — Einnahmen, Konversionsraten, MRR (Monthly Recurring Revenue)
- Testumgebung — separate API-Schlüssel und Daten, ohne Risiko
- API-Logs — Ansicht jeder Anfrage mit vollständigem Payload
- Radar — Betrugserkennungssystem mit ML-basierten Regeln
Vergleich mit anderen Zahlungsabwicklern#
| Merkmal | Stripe | PayPal | Square | Adyen | |---|---|---|---|---| | Entwickler-API | Ausgezeichnet | Gut | Gut | Ausgezeichnet | | Dokumentation | Erstklassig | Gut | Gut | Gut | | TypeScript-Unterstützung | Vollständig | Teilweise | Teilweise | Teilweise | | Abonnements | Integriert | Integriert | Integriert | Integriert | | 3D Secure | Automatisch | Automatisch | Manuell | Automatisch | | Gebühren (USA) | 2,9% + $0,30 | 2,9% + $0,30 | 2,6% + $0,10 | Individuell | | Webhooks | Erweitert | Erweitert | Grundlegend | Erweitert | | No-Code-Tools | Begrenzt | Umfangreich | Umfangreich | Begrenzt | | Globale Reichweite | 46+ Länder | 200+ Länder | 8 Länder | 30+ Länder |
Stripe zeichnet sich durch hervorragende DX (Developer Experience), ein umfassendes TypeScript-SDK und das reichhaltigste Integrations-Ökosystem aus.
Fehlerbehandlung — Best Practices#
Eine robuste Fehlerbehandlung ist in Zahlungssystemen entscheidend. Hier ist ein Muster, das die häufigsten Szenarien abdeckt:
// lib/stripe-errors.ts
import Stripe from 'stripe';
interface StripeErrorResponse {
message: string;
code: string;
statusCode: number;
}
export function handleStripeError(
error: unknown
): StripeErrorResponse {
if (error instanceof Stripe.errors.StripeCardError) {
return {
message: getCardErrorMessage(error.code),
code: error.code || 'card_error',
statusCode: 402,
};
}
if (error instanceof Stripe.errors.StripeRateLimitError) {
return {
message: 'Too many requests. Please try again shortly.',
code: 'rate_limit',
statusCode: 429,
};
}
if (error instanceof Stripe.errors.StripeInvalidRequestError) {
return {
message: 'Invalid payment request.',
code: 'invalid_request',
statusCode: 400,
};
}
if (error instanceof Stripe.errors.StripeAuthenticationError) {
return {
message: 'Payment configuration error.',
code: 'auth_error',
statusCode: 500,
};
}
return {
message: 'An unexpected error occurred. Please try again.',
code: 'unknown',
statusCode: 500,
};
}
function getCardErrorMessage(code: string | undefined): string {
const messages: Record<string, string> = {
card_declined: 'Your card was declined.',
insufficient_funds: 'Insufficient funds on the card.',
expired_card: 'Your card has expired.',
incorrect_cvc: 'Incorrect CVC code.',
processing_error: 'Processing error. Please try again.',
incorrect_number: 'Incorrect card number.',
};
return messages[code || ''] || 'Payment failed.';
}
Serverseitige Validierung#
Vertrauen Sie niemals Daten, die vom Client kommen. Jede Zahlungsanfrage sollte eine serverseitige Validierung durchlaufen:
// lib/validation.ts
import { z } from 'zod';
export const paymentSchema = z.object({
amount: z
.number()
.int('Amount must be an integer')
.min(50, 'Minimum amount is $0.50')
.max(99999999, 'Amount exceeds limit'),
currency: z.enum(['pln', 'eur', 'usd', 'gbp']),
description: z
.string()
.max(500, 'Description cannot exceed 500 characters')
.optional(),
customerEmail: z.string().email('Invalid email address').optional(),
metadata: z.record(z.string()).optional(),
});
export type PaymentInput = z.infer<typeof paymentSchema>;
// Usage in API Route:
// const result = paymentSchema.safeParse(body);
// if (!result.success) {
// return NextResponse.json(
// { error: result.error.flatten() },
// { status: 400 }
// );
// }
Zusammenfassung#
Die Integration von Stripe mit Next.js bietet enorme Möglichkeiten — von einfachen Einmalzahlungen bis hin zu fortgeschrittenen Abonnementsystemen. Die Schlüsselelemente einer erfolgreichen Integration sind:
- Stripe Elements oder Checkout zur sicheren Erfassung von Kartendaten
- Payment Intents API für flexible Zahlungsabläufe
- Webhooks als einzige Quelle der Wahrheit für den Transaktionsstatus
- Serverseitige Validierung jeder Anfrage
- Fehlerbehandlung mit verständlichen Meldungen für Benutzer
- Stripe CLI zum lokalen Testen von Webhooks
- SCA/3D Secure wird automatisch durch Payment Intents behandelt
Stripe in Kombination mit Next.js und TypeScript schafft eine robuste, typsichere und sichere Zahlungslösung, die bereit zur Skalierung ist.
Benötigen Sie eine professionelle Stripe-Zahlungsintegration für Ihre Next.js-Anwendung? Das Team von MDS Software Solutions Group ist spezialisiert auf den Aufbau sicherer E-Commerce- und SaaS-Systeme mit vollständiger Online-Zahlungsunterstützung. Kontaktieren Sie uns, um Ihr Projekt zu besprechen und ein kostenloses Angebot zu erhalten.
Team von Programmierexperten, die sich auf moderne Webtechnologien spezialisiert haben.