2 min read
SaaSPayment Integration (Multiple Providers)
Payment Provider Integration. Stripe, PayPal, Klarna und lokale Zahlungsmethoden für globale E-Commerce Anwendungen.
Payment IntegrationStripePayPalKlarnaPayment GatewayCheckout

Payment Integration (Multiple Providers)
Meta-Description: Payment Provider Integration. Stripe, PayPal, Klarna und lokale Zahlungsmethoden für globale E-Commerce Anwendungen.
Keywords: Payment Integration, Stripe, PayPal, Klarna, Payment Gateway, Checkout, E-Commerce, Payment Methods
Einführung
Globale E-Commerce erfordert multiple Payment Provider. Kunden erwarten ihre bevorzugte Zahlungsmethode – von Kreditkarte über PayPal bis Klarna. Dieser Guide zeigt die Integration verschiedener Provider in einer einheitlichen Architektur.
Payment Architecture
┌─────────────────────────────────────────────────────────────┐
│ PAYMENT PROVIDER ABSTRACTION │
├─────────────────────────────────────────────────────────────┤
│ │
│ Unified Interface: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PaymentService │ │
│ │ ├── createPayment(order, method) │ │
│ │ ├── capturePayment(paymentId) │ │
│ │ ├── refundPayment(paymentId, amount) │ │
│ │ └── getPaymentStatus(paymentId) │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ Provider Adapters: │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Stripe │ │ PayPal │ │ Klarna │ │ Local │ │
│ │ Adapter │ │ Adapter │ │ Adapter │ │ Adapter │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ ↓ ↓ ↓ ↓ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Stripe │ │ PayPal │ │ Klarna │ │ SEPA/ │ │
│ │ API │ │ API │ │ API │ │ iDEAL/etc│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ Payment Methods by Region: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Germany: Klarna, SEPA, Giropay, Credit Card │ │
│ │ Netherlands: iDEAL, Credit Card, PayPal │ │
│ │ USA: Credit Card, PayPal, Apple Pay, Google Pay │ │
│ │ UK: Credit Card, PayPal, Klarna │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘Payment Service Interface
// lib/payments/types.ts
export interface PaymentMethod {
id: string;
type: PaymentMethodType;
name: string;
icon: string;
countries: string[];
currencies: string[];
minAmount?: number;
maxAmount?: number;
}
export type PaymentMethodType =
| 'card'
| 'paypal'
| 'klarna'
| 'sepa_debit'
| 'ideal'
| 'giropay'
| 'sofort'
| 'bancontact'
| 'eps'
| 'apple_pay'
| 'google_pay';
export interface CreatePaymentInput {
orderId: string;
amount: number;
currency: string;
method: PaymentMethodType;
customer: {
email: string;
name?: string;
phone?: string;
};
billing?: Address;
shipping?: Address;
metadata?: Record<string, string>;
returnUrl: string;
cancelUrl?: string;
}
export interface PaymentResult {
id: string;
provider: string;
status: PaymentStatus;
redirectUrl?: string;
clientSecret?: string;
error?: string;
}
export type PaymentStatus =
| 'pending'
| 'processing'
| 'requires_action'
| 'succeeded'
| 'failed'
| 'canceled'
| 'refunded';
export interface PaymentProvider {
name: string;
supportedMethods: PaymentMethodType[];
createPayment(input: CreatePaymentInput): Promise<PaymentResult>;
capturePayment(paymentId: string): Promise<PaymentResult>;
refundPayment(paymentId: string, amount?: number): Promise<PaymentResult>;
getPaymentStatus(paymentId: string): Promise<PaymentStatus>;
handleWebhook(payload: unknown, signature: string): Promise<void>;
}Stripe Adapter
// lib/payments/providers/stripe.ts
import Stripe from 'stripe';
import { PaymentProvider, CreatePaymentInput, PaymentResult, PaymentStatus } from '../types';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-06-20'
});
export class StripeProvider implements PaymentProvider {
name = 'stripe';
supportedMethods: PaymentMethodType[] = [
'card', 'sepa_debit', 'ideal', 'giropay',
'sofort', 'bancontact', 'eps', 'klarna'
];
async createPayment(input: CreatePaymentInput): Promise<PaymentResult> {
try {
// Map method to Stripe payment method type
const paymentMethodTypes = this.mapPaymentMethod(input.method);
const session = await stripe.checkout.sessions.create({
mode: 'payment',
payment_method_types: paymentMethodTypes,
customer_email: input.customer.email,
line_items: [
{
price_data: {
currency: input.currency.toLowerCase(),
unit_amount: input.amount,
product_data: {
name: `Order ${input.orderId}`
}
},
quantity: 1
}
],
success_url: input.returnUrl,
cancel_url: input.cancelUrl || input.returnUrl,
metadata: {
orderId: input.orderId,
...input.metadata
}
});
return {
id: session.payment_intent as string,
provider: 'stripe',
status: 'pending',
redirectUrl: session.url!
};
} catch (error) {
return {
id: '',
provider: 'stripe',
status: 'failed',
error: (error as Error).message
};
}
}
async capturePayment(paymentId: string): Promise<PaymentResult> {
const paymentIntent = await stripe.paymentIntents.capture(paymentId);
return {
id: paymentIntent.id,
provider: 'stripe',
status: this.mapStatus(paymentIntent.status)
};
}
async refundPayment(paymentId: string, amount?: number): Promise<PaymentResult> {
const refund = await stripe.refunds.create({
payment_intent: paymentId,
amount
});
return {
id: refund.id,
provider: 'stripe',
status: refund.status === 'succeeded' ? 'refunded' : 'pending'
};
}
async getPaymentStatus(paymentId: string): Promise<PaymentStatus> {
const paymentIntent = await stripe.paymentIntents.retrieve(paymentId);
return this.mapStatus(paymentIntent.status);
}
async handleWebhook(payload: string, signature: string): Promise<void> {
const event = stripe.webhooks.constructEvent(
payload,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
switch (event.type) {
case 'payment_intent.succeeded':
await this.handlePaymentSucceeded(event.data.object as Stripe.PaymentIntent);
break;
case 'payment_intent.payment_failed':
await this.handlePaymentFailed(event.data.object as Stripe.PaymentIntent);
break;
}
}
private mapPaymentMethod(method: PaymentMethodType): Stripe.Checkout.SessionCreateParams.PaymentMethodType[] {
const mapping: Record<PaymentMethodType, Stripe.Checkout.SessionCreateParams.PaymentMethodType[]> = {
card: ['card'],
sepa_debit: ['sepa_debit'],
ideal: ['ideal'],
giropay: ['giropay'],
sofort: ['sofort'],
bancontact: ['bancontact'],
eps: ['eps'],
klarna: ['klarna'],
paypal: ['paypal'],
apple_pay: ['card'],
google_pay: ['card']
};
return mapping[method] || ['card'];
}
private mapStatus(status: Stripe.PaymentIntent.Status): PaymentStatus {
const mapping: Record<string, PaymentStatus> = {
requires_payment_method: 'pending',
requires_confirmation: 'pending',
requires_action: 'requires_action',
processing: 'processing',
requires_capture: 'processing',
canceled: 'canceled',
succeeded: 'succeeded'
};
return mapping[status] || 'pending';
}
}PayPal Adapter
// lib/payments/providers/paypal.ts
import { PaymentProvider, CreatePaymentInput, PaymentResult } from '../types';
const PAYPAL_API = process.env.PAYPAL_MODE === 'live'
? 'https://api-m.paypal.com'
: 'https://api-m.sandbox.paypal.com';
export class PayPalProvider implements PaymentProvider {
name = 'paypal';
supportedMethods: PaymentMethodType[] = ['paypal'];
private async getAccessToken(): Promise<string> {
const auth = Buffer.from(
`${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_SECRET}`
).toString('base64');
const response = await fetch(`${PAYPAL_API}/v1/oauth2/token`, {
method: 'POST',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'grant_type=client_credentials'
});
const data = await response.json();
return data.access_token;
}
async createPayment(input: CreatePaymentInput): Promise<PaymentResult> {
const accessToken = await this.getAccessToken();
const response = await fetch(`${PAYPAL_API}/v2/checkout/orders`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
intent: 'CAPTURE',
purchase_units: [
{
reference_id: input.orderId,
amount: {
currency_code: input.currency.toUpperCase(),
value: (input.amount / 100).toFixed(2)
}
}
],
application_context: {
return_url: input.returnUrl,
cancel_url: input.cancelUrl || input.returnUrl,
brand_name: 'Your Store',
user_action: 'PAY_NOW'
}
})
});
const order = await response.json();
if (order.error) {
return {
id: '',
provider: 'paypal',
status: 'failed',
error: order.error.message
};
}
const approveLink = order.links.find((l: any) => l.rel === 'approve');
return {
id: order.id,
provider: 'paypal',
status: 'pending',
redirectUrl: approveLink?.href
};
}
async capturePayment(paymentId: string): Promise<PaymentResult> {
const accessToken = await this.getAccessToken();
const response = await fetch(
`${PAYPAL_API}/v2/checkout/orders/${paymentId}/capture`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
const result = await response.json();
return {
id: result.id,
provider: 'paypal',
status: result.status === 'COMPLETED' ? 'succeeded' : 'failed'
};
}
async refundPayment(paymentId: string, amount?: number): Promise<PaymentResult> {
const accessToken = await this.getAccessToken();
// Get capture ID from order
const orderResponse = await fetch(
`${PAYPAL_API}/v2/checkout/orders/${paymentId}`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const order = await orderResponse.json();
const captureId = order.purchase_units[0]?.payments?.captures?.[0]?.id;
if (!captureId) {
return {
id: '',
provider: 'paypal',
status: 'failed',
error: 'No capture found'
};
}
const refundResponse = await fetch(
`${PAYPAL_API}/v2/payments/captures/${captureId}/refund`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: amount ? JSON.stringify({
amount: {
value: (amount / 100).toFixed(2),
currency_code: order.purchase_units[0].amount.currency_code
}
}) : undefined
}
);
const refund = await refundResponse.json();
return {
id: refund.id,
provider: 'paypal',
status: refund.status === 'COMPLETED' ? 'refunded' : 'pending'
};
}
async getPaymentStatus(paymentId: string): Promise<PaymentStatus> {
const accessToken = await this.getAccessToken();
const response = await fetch(
`${PAYPAL_API}/v2/checkout/orders/${paymentId}`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const order = await response.json();
const statusMap: Record<string, PaymentStatus> = {
CREATED: 'pending',
SAVED: 'pending',
APPROVED: 'requires_action',
COMPLETED: 'succeeded',
VOIDED: 'canceled'
};
return statusMap[order.status] || 'pending';
}
async handleWebhook(payload: unknown, signature: string): Promise<void> {
// Verify webhook signature
// Process webhook events
}
}Payment Service
// lib/payments/service.ts
import { StripeProvider } from './providers/stripe';
import { PayPalProvider } from './providers/paypal';
import { PaymentProvider, CreatePaymentInput, PaymentResult, PaymentMethodType } from './types';
import { db } from '@/lib/db';
const providers: Record<string, PaymentProvider> = {
stripe: new StripeProvider(),
paypal: new PayPalProvider()
};
function getProviderForMethod(method: PaymentMethodType): PaymentProvider {
for (const provider of Object.values(providers)) {
if (provider.supportedMethods.includes(method)) {
return provider;
}
}
throw new Error(`No provider supports payment method: ${method}`);
}
export async function createPayment(input: CreatePaymentInput): Promise<PaymentResult> {
const provider = getProviderForMethod(input.method);
const result = await provider.createPayment(input);
// Store payment record
await db.payment.create({
data: {
id: result.id,
orderId: input.orderId,
provider: provider.name,
method: input.method,
amount: input.amount,
currency: input.currency,
status: result.status,
metadata: input.metadata
}
});
return result;
}
export async function capturePayment(paymentId: string): Promise<PaymentResult> {
const payment = await db.payment.findUnique({ where: { id: paymentId } });
if (!payment) throw new Error('Payment not found');
const provider = providers[payment.provider];
const result = await provider.capturePayment(paymentId);
await db.payment.update({
where: { id: paymentId },
data: { status: result.status }
});
return result;
}
export async function refundPayment(
paymentId: string,
amount?: number
): Promise<PaymentResult> {
const payment = await db.payment.findUnique({ where: { id: paymentId } });
if (!payment) throw new Error('Payment not found');
const provider = providers[payment.provider];
const result = await provider.refundPayment(paymentId, amount);
await db.payment.update({
where: { id: paymentId },
data: {
status: result.status,
refundedAmount: amount || payment.amount
}
});
return result;
}
export function getAvailablePaymentMethods(
country: string,
currency: string,
amount: number
): PaymentMethod[] {
const methods: PaymentMethod[] = [
{
id: 'card',
type: 'card',
name: 'Credit/Debit Card',
icon: '/icons/card.svg',
countries: ['*'],
currencies: ['*']
},
{
id: 'paypal',
type: 'paypal',
name: 'PayPal',
icon: '/icons/paypal.svg',
countries: ['*'],
currencies: ['EUR', 'USD', 'GBP']
},
{
id: 'klarna',
type: 'klarna',
name: 'Klarna',
icon: '/icons/klarna.svg',
countries: ['DE', 'AT', 'NL', 'SE', 'FI', 'NO', 'DK', 'GB', 'US'],
currencies: ['EUR', 'USD', 'GBP', 'SEK', 'NOK', 'DKK'],
minAmount: 100,
maxAmount: 500000
},
{
id: 'ideal',
type: 'ideal',
name: 'iDEAL',
icon: '/icons/ideal.svg',
countries: ['NL'],
currencies: ['EUR']
},
{
id: 'giropay',
type: 'giropay',
name: 'Giropay',
icon: '/icons/giropay.svg',
countries: ['DE'],
currencies: ['EUR']
},
{
id: 'sepa_debit',
type: 'sepa_debit',
name: 'SEPA Direct Debit',
icon: '/icons/sepa.svg',
countries: ['DE', 'AT', 'NL', 'BE', 'FR', 'ES', 'IT'],
currencies: ['EUR']
}
];
return methods.filter(method => {
const countryMatch = method.countries.includes('*') || method.countries.includes(country);
const currencyMatch = method.currencies.includes('*') || method.currencies.includes(currency);
const amountMatch = (!method.minAmount || amount >= method.minAmount) &&
(!method.maxAmount || amount <= method.maxAmount);
return countryMatch && currencyMatch && amountMatch;
});
}Checkout UI
// components/checkout/PaymentMethodSelector.tsx
'use client';
import { useState } from 'react';
import Image from 'next/image';
import { PaymentMethod } from '@/lib/payments/types';
interface Props {
methods: PaymentMethod[];
selected: string | null;
onSelect: (methodId: string) => void;
}
export function PaymentMethodSelector({ methods, selected, onSelect }: Props) {
return (
<div className="space-y-3">
<h3 className="font-semibold">Payment Method</h3>
<div className="grid gap-2">
{methods.map((method) => (
<button
key={method.id}
onClick={() => onSelect(method.id)}
className={`flex items-center gap-3 p-4 border rounded-lg transition ${
selected === method.id
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-gray-300'
}`}
>
<Image
src={method.icon}
alt={method.name}
width={40}
height={24}
className="object-contain"
/>
<span className="font-medium">{method.name}</span>
{selected === method.id && (
<span className="ml-auto text-blue-500">✓</span>
)}
</button>
))}
</div>
</div>
);
}Provider Comparison
| Provider | Card | PayPal | Klarna | Local Methods | Fees |
|---|---|---|---|---|---|
| **Stripe** | ✓ | ✓ | ✓ | iDEAL, SEPA, etc. | 1.4%+ |
| **PayPal** | ✓ | ✓ | - | - | 2.9%+ |
| **Adyen** | ✓ | ✓ | ✓ | 250+ methods | Custom |
| **Mollie** | ✓ | ✓ | ✓ | iDEAL, Bancontact | 1.8%+ |
Fazit
Payment Integration erfordert:
- Abstraction Layer: Einheitliches Interface
- Multiple Providers: Regionale Methoden
- Error Handling: Graceful Degradation
- Webhooks: Asynchrone Statusupdates
Die richtige Payment-Strategie erhöht Conversion.
Bildprompts
- "Payment method selection UI, card icons and options"
- "Checkout flow diagram, multiple payment providers"
- "Payment dashboard, transaction history and analytics"