Initial commit: Portfolio Website
Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,703 @@
|
||||
# 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/)
|
||||
Reference in New Issue
Block a user