Abonnement-Billing ist technisch komplexer als es zunächst erscheint. Einfache wiederkehrende Zahlungen sind schnell implementiert — aber ein produktionsreifes Billing-System mit Trials, Upgrades, Downgrades, fehlgeschlagenen Zahlungen und Steuer-Compliance erfordert Wochen sorgfältiger Entwicklung.
Dieser Leitfaden zeigt die wichtigsten Konzepte und Implementierungsdetails am Beispiel von Stripe Billing — dem De-facto-Standard für SaaS-Abonnements.
Kernkonzepte in Stripe Billing
Customers, Products, Prices und Subscriptions
// Produkt erstellen (einmalig)
const product = await stripe.products.create({
name: 'Pro Plan',
description: 'Unbegrenzte Projekte und Priority Support',
});
// Preis erstellen (wiederkehrend)
const price = await stripe.prices.create({
product: product.id,
unit_amount: 4900, // 49,00 EUR in Cent
currency: 'eur',
recurring: {
interval: 'month',
interval_count: 1,
},
nickname: 'Pro Monthly',
});
// Abonnement erstellen
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: price.id }],
trial_period_days: 14, // 14-tägige Testphase
payment_behavior: 'default_incomplete', // Warten auf Zahlungsbestätigung
payment_settings: {
save_default_payment_method: 'on_subscription',
},
expand: ['latest_invoice.payment_intent'],
});
Trial-Perioden
// Mit Trial starten (kein Zahlungsmittel erforderlich)
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
trial_end: Math.floor(Date.now() / 1000) + (14 * 24 * 60 * 60), // 14 Tage
trial_settings: {
end_behavior: { missing_payment_method: 'pause' }, // Nicht kündigen, sondern pausieren
},
});
// Trial ohne Kreditkarte ermöglichen (Free Trial)
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
trial_period_days: 14,
payment_behavior: 'allow_incomplete',
// Zahlungsmethode erst bei Trial-Ende anfordern
});
Webhooks: Das Herzstück des Billing-Systems
Webhooks sind der wichtigste Teil Ihrer Stripe-Integration. Alle wichtigen Billing-Events werden über Webhooks kommuniziert.
// Webhook-Handler (Express.js)
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case 'customer.subscription.created':
await handleSubscriptionCreated(event.data.object);
break;
case 'customer.subscription.updated':
await handleSubscriptionUpdated(event.data.object);
break;
case 'customer.subscription.deleted':
await handleSubscriptionCanceled(event.data.object);
break;
case 'invoice.payment_succeeded':
await handlePaymentSucceeded(event.data.object);
break;
case 'invoice.payment_failed':
await handlePaymentFailed(event.data.object);
break;
case 'customer.subscription.trial_will_end':
await sendTrialEndingEmail(event.data.object);
break;
}
res.json({ received: true });
});
async function handlePaymentFailed(invoice) {
const subscription = await stripe.subscriptions.retrieve(invoice.subscription);
const customer = await stripe.customers.retrieve(invoice.customer);
// Benutzer in Datenbank auf 'past_due' setzen
await db.users.update({
where: { stripe_customer_id: customer.id },
data: {
subscription_status: 'past_due',
access_level: 'restricted',
}
});
// E-Mail mit Update-Payment-Link senden
const portalSession = await stripe.billingPortal.sessions.create({
customer: customer.id,
return_url: `${BASE_URL}/dashboard`,
});
await sendEmail({
to: customer.email,
template: 'payment_failed',
data: { portal_url: portalSession.url, amount: invoice.amount_due }
});
}
Dunning: Fehlgeschlagene Zahlungen automatisch wiederholen
Dunning ist der automatische Prozess, fehlgeschlagene Zahlungen erneut zu versuchen. Stripe Smart Retries analysiert die Ausfallgründe und wählt den optimalen Wiederholungszeitpunkt.
// Stripe Billing-Einstellungen konfigurieren
// (im Stripe Dashboard unter Billing > Einstellungen)
// Empfohlene Dunning-Strategie:
// Tag 1: Erster fehlgeschlagener Versuch
// Tag 3: Erneuter Versuch
// Tag 7: Erneuter Versuch + E-Mail-Erinnerung
// Tag 14: Letzter Versuch + dringliche E-Mail
// Tag 21: Subscription kündigen, Zugang sperren
// Kunden-Portal für Self-Service-Zahlungsupdate
const portalSession = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${BASE_URL}/dashboard`,
// Kunden können im Portal: Zahlungsmethode updaten, Abo kündigen,
// Rechnungen ansehen, Plan wechseln
});
Plan-Wechsel und Proration
// Upgrade von Basic zu Pro mitten im Monat
async function upgradeSubscription(subscriptionId, newPriceId) {
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
items: [{
id: subscription.items.data[0].id,
price: newPriceId,
}],
proration_behavior: 'create_prorations', // Anteilige Berechnung
// Stripe berechnet automatisch:
// - Gutschrift für nicht genutzte Zeit des alten Plans
// - Rechnung für neue Zeit des neuen Plans
});
return updatedSubscription;
}
// Preview vor dem Upgrade anzeigen
async function previewUpgrade(subscriptionId, newPriceId) {
const proration_date = Math.floor(Date.now() / 1000);
const invoice = await stripe.invoices.retrieveUpcoming({
customer: customerId,
subscription: subscriptionId,
subscription_items: [{
id: currentItemId,
price: newPriceId,
}],
subscription_proration_date: proration_date,
});
return {
amount_due: invoice.amount_due,
next_charge: invoice.next_payment_attempt,
};
}
Steuer-Compliance mit Stripe Tax
// Automatische Steuerberechnung aktivieren
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
automatic_tax: { enabled: true },
// Stripe berechnet automatisch:
// - MwSt. für EU-Kunden (inkl. OSS-Regelung)
// - Sales Tax für US-Kunden
// - GST für australische Kunden
// Voraussetzung: Kundenadressen müssen vollständig sein
});
// Produkt als steuerpflichtig konfigurieren
await stripe.products.update(productId, {
tax_code: 'txcd_10103001', // SaaS-Produkte in der EU
});
Customer Portal: Self-Service für Ihre Kunden
Das Stripe Customer Portal ermöglicht Ihren Kunden, ihr Abonnement selbst zu verwalten — ohne dass Sie diese Features selbst entwickeln müssen.
// Portal-Session erstellen
app.post('/api/billing/portal', requireAuth, async (req, res) => {
const user = req.user;
const session = await stripe.billingPortal.sessions.create({
customer: user.stripe_customer_id,
return_url: `${BASE_URL}/dashboard/billing`,
// Konfigurierbar im Stripe Dashboard:
// - Welche Plan-Wechsel erlaubt sind
// - Ob Kündigung erlaubt ist (und wenn ja: mit welcher Verzögerung)
// - Rechnungshistorie
});
res.json({ url: session.url });
});
Metered Billing (nutzungsbasierte Abrechnung)
Für APIs, KI-Services oder andere nutzungsbasierte Produkte:
// Nutzung melden
async function reportUsage(subscriptionItemId, quantity) {
await stripe.subscriptionItems.createUsageRecord(subscriptionItemId, {
quantity: quantity,
timestamp: Math.floor(Date.now() / 1000),
action: 'increment', // oder 'set' für absoluten Wert
});
}
// Beispiel: Nach jedem API-Call
app.use('/api/', async (req, res, next) => {
next();
res.on('finish', async () => {
if (req.user?.subscription_item_id) {
await reportUsage(req.user.subscription_item_id, 1);
}
});
});
Abonnement-Billing korrekt zu implementieren erfordert Erfahrung mit den Edge Cases: Was passiert bei gleichzeitigem Upgrade und fehlgeschlagener Zahlung? Wie gehen Sie mit Refunds bei Stornierung mid-cycle um? Wenn Sie ein SaaS aufbauen und Billing richtig machen wollen, sprechen Sie mit unserem Team.