Stripe-Integration für Zahlungsabwicklung
Veröffentlicht am 22. Januar 2026
Stripe ist eine der beliebtesten Plattformen für die Zahlungsabwicklung in Webanwendungen. In diesem Tutorial zeige ich dir, wie du Stripe in dein Nuxt-Projekt integrierst und sichere Zahlungen implementierst.
1. Warum Stripe?
Stripe bietet zahlreiche Vorteile für Entwickler:
- Einfache Integration mit gut dokumentierter API
- Unterstützung für viele Zahlungsmethoden (Kreditkarten, SEPA, etc.)
- PCI-Compliance out of the box
- Keine monatlichen Fixkosten, nur Transaktionsgebühren
- Hervorragende Entwickler-Tools und Test-Modus
- Subscriptions und wiederkehrende Zahlungen
2. Stripe-Account erstellen
Besuche stripe.com und erstelle einen Account. Nach der Registrierung erhältst du:
- Publishable Key - Für Frontend (öffentlich)
- Secret Key - Für Backend (geheim!)
- Test-Keys für Entwicklung
3. Stripe in Nuxt installieren
npm install stripe @stripe/stripe-jsKonfiguriere die Environment-Variablen in .env:
# Test-Keys (später durch Production-Keys ersetzen)
STRIPE_SECRET_KEY=sk_test_...
NUXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...4. Stripe-Client initialisieren
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
public: {
stripePublishableKey: process.env.NUXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
}
}
})5. Payment Intent erstellen (Backend)
Erstelle eine Server-API-Route für das Payment Intent:
// server/api/create-payment-intent.post.ts
import Stripe from 'stripe'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const stripe = new Stripe(config.stripeSecretKey, {
apiVersion: '2023-10-16'
})
const body = await readBody(event)
const { amount, currency = 'eur' } = body
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: amount * 100, // Stripe verwendet Cents
currency,
automatic_payment_methods: {
enabled: true,
},
})
return {
clientSecret: paymentIntent.client_secret
}
} catch (error) {
throw createError({
statusCode: 500,
message: error.message
})
}
})6. Payment-Formular im Frontend
// pages/checkout.vue
<template>
<div class='max-w-md mx-auto p-6'>
<h1 class='text-2xl font-bold mb-6'>Zahlung abschließen</h1>
<div v-if='!clientSecret' class='text-center'>
<button
@click='initializePayment'
class='bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600'
>
Zahlung starten ({{ amount }}€)
</button>
</div>
<div v-else>
<div id='payment-element'></div>
<button
@click='handleSubmit'
:disabled='loading'
class='w-full mt-6 bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 disabled:opacity-50'
>
{{ loading ? 'Verarbeite...' : 'Jetzt bezahlen' }}
</button>
<div v-if='errorMessage' class='mt-4 text-red-500'>
{{ errorMessage }}
</div>
</div>
</div>
</template>
<script setup lang='ts'>
import { loadStripe } from '@stripe/stripe-js'
const config = useRuntimeConfig()
const amount = ref(49.99)
const clientSecret = ref('')
const errorMessage = ref('')
const loading = ref(false)
let stripe = null
let elements = null
const initializePayment = async () => {
try {
// Payment Intent erstellen
const { data } = await useFetch('/api/create-payment-intent', {
method: 'POST',
body: { amount: amount.value }
})
clientSecret.value = data.value.clientSecret
// Stripe laden
stripe = await loadStripe(config.public.stripePublishableKey)
// Elements erstellen
elements = stripe.elements({ clientSecret: clientSecret.value })
// Payment Element mounten
const paymentElement = elements.create('payment')
paymentElement.mount('#payment-element')
} catch (error) {
errorMessage.value = 'Fehler beim Initialisieren der Zahlung'
console.error(error)
}
}
const handleSubmit = async () => {
if (!stripe || !elements) return
loading.value = true
errorMessage.value = ''
try {
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/payment-success`,
},
})
if (error) {
errorMessage.value = error.message
}
} catch (error) {
errorMessage.value = 'Ein unerwarteter Fehler ist aufgetreten'
} finally {
loading.value = false
}
}
</script>7. Erfolgsseite erstellen
// pages/payment-success.vue
<template>
<div class='max-w-md mx-auto p-6 text-center'>
<div v-if='loading'>
Überprüfe Zahlung...
</div>
<div v-else-if='success' class='bg-green-50 p-6 rounded-lg'>
<div class='text-green-500 text-5xl mb-4'>✓</div>
<h1 class='text-2xl font-bold text-green-900 mb-2'>
Zahlung erfolgreich!
</h1>
<p class='text-green-700'>
Vielen Dank für deine Zahlung. Du erhältst eine Bestätigung per E-Mail.
</p>
</div>
<div v-else class='bg-red-50 p-6 rounded-lg'>
<h1 class='text-2xl font-bold text-red-900 mb-2'>
Zahlung fehlgeschlagen
</h1>
<p class='text-red-700'>{{ errorMessage }}</p>
</div>
</div>
</template>
<script setup lang='ts'>
import { loadStripe } from '@stripe/stripe-js'
const config = useRuntimeConfig()
const route = useRoute()
const loading = ref(true)
const success = ref(false)
const errorMessage = ref('')
onMounted(async () => {
const stripe = await loadStripe(config.public.stripePublishableKey)
const clientSecret = route.query.payment_intent_client_secret
if (!clientSecret) {
loading.value = false
errorMessage.value = 'Keine Zahlungsinformationen gefunden'
return
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret)
loading.value = false
if (paymentIntent.status === 'succeeded') {
success.value = true
} else {
errorMessage.value = `Zahlung fehlgeschlagen: ${paymentIntent.status}`
}
})
</script>8. Webhooks für Backend-Verarbeitung
Für zuverlässige Zahlungsverarbeitung solltest du Webhooks nutzen:
// server/api/webhook.post.ts
import Stripe from 'stripe'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const stripe = new Stripe(config.stripeSecretKey, {
apiVersion: '2023-10-16'
})
const body = await readRawBody(event)
const signature = getHeader(event, 'stripe-signature')
let stripeEvent
try {
stripeEvent = stripe.webhooks.constructEvent(
body,
signature,
config.stripeWebhookSecret
)
} catch (error) {
throw createError({
statusCode: 400,
message: `Webhook Error: ${error.message}`
})
}
// Event verarbeiten
switch (stripeEvent.type) {
case 'payment_intent.succeeded':
const paymentIntent = stripeEvent.data.object
// Bestellung als bezahlt markieren
console.log('PaymentIntent succeeded:', paymentIntent.id)
break
case 'payment_intent.payment_failed':
const failedPayment = stripeEvent.data.object
// Fehlgeschlagene Zahlung behandeln
console.log('PaymentIntent failed:', failedPayment.id)
break
default:
console.log(`Unhandled event type ${stripeEvent.type}`)
}
return { received: true }
})9. Subscriptions implementieren
Für wiederkehrende Zahlungen kannst du Stripe Subscriptions nutzen:
// server/api/create-subscription.post.ts
import Stripe from 'stripe'
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
const stripe = new Stripe(config.stripeSecretKey, {
apiVersion: '2023-10-16'
})
const body = await readBody(event)
const { customerId, priceId } = body
try {
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
payment_behavior: 'default_incomplete',
expand: ['latest_invoice.payment_intent'],
})
return {
subscriptionId: subscription.id,
clientSecret: subscription.latest_invoice.payment_intent.client_secret
}
} catch (error) {
throw createError({
statusCode: 500,
message: error.message
})
}
})Sicherheits-Best-Practices
Secret Key schützen
Der Secret Key darf NIEMALS im Frontend-Code landen. Nutze ihn nur in Server-API-Routes.
Beträge serverseitig validieren
Vertraue nie Frontend-Daten für Preise. Speichere Preise in der Datenbank und validiere sie auf dem Server.
Webhooks verifizieren
Nutze Stripe's Webhook-Signatur-Verifizierung, um sicherzustellen, dass Webhooks wirklich von Stripe kommen.
Test-Modus nutzen
Teste ausgiebig im Test-Modus mit Test-Karten, bevor du live gehst. Test-Karte: 4242 4242 4242 4242
10. Testen der Integration
Stripe bietet Test-Karten für verschiedene Szenarien:
// Erfolgreiche Zahlung
Kartennummer: 4242 4242 4242 4242
Ablaufdatum: Beliebiges Datum in der Zukunft
CVC: Beliebige 3 Ziffern
// Zahlung mit 3D Secure
Kartennummer: 4000 0027 6000 3184
// Abgelehnte Zahlung
Kartennummer: 4000 0000 0000 0002
// Unzureichende Deckung
Kartennummer: 4000 0000 0000 9995Fazit
Stripe macht es einfach, sichere Zahlungen in deine Anwendung zu integrieren. Mit der richtigen Implementierung und unter Beachtung der Sicherheits-Best-Practices kannst du eine robuste Zahlungslösung aufbauen. Denke daran, immer im Test-Modus zu entwickeln und erst nach ausgiebigen Tests in den Production-Modus zu wechseln.