- Add 100 blog posts covering AI, development, and tech topics - Add .env.example for environment configuration - Add accessibility and lighthouse audit scripts - Remove obsolete SEO reports and temporary files - Remove dev-dist build artifacts and backup files - Remove unused portrait images (moved/consolidated elsewhere) - Update contact form and component improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
704 lines
21 KiB
Markdown
704 lines
21 KiB
Markdown
# 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
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```typescript
|
||
// 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
|
||
|
||
```tsx
|
||
// 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:
|
||
|
||
1. **Abstraction Layer**: Einheitliches Interface
|
||
2. **Multiple Providers**: Regionale Methoden
|
||
3. **Error Handling**: Graceful Degradation
|
||
4. **Webhooks**: Asynchrone Statusupdates
|
||
|
||
Die richtige Payment-Strategie erhöht Conversion.
|
||
|
||
---
|
||
|
||
## Bildprompts
|
||
|
||
1. "Payment method selection UI, card icons and options"
|
||
2. "Checkout flow diagram, multiple payment providers"
|
||
3. "Payment dashboard, transaction history and analytics"
|
||
|
||
---
|
||
|
||
## Quellen
|
||
|
||
- [Stripe Payment Methods](https://stripe.com/docs/payments/payment-methods)
|
||
- [PayPal Checkout](https://developer.paypal.com/docs/checkout/)
|
||
- [Klarna Integration](https://docs.klarna.com/)
|
||
- [Payment Provider Comparison](https://www.merchantmaverick.com/)
|