Menu
Nazad na Blog
2 min read
SaaS

Payment 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)

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

ProviderCardPayPalKlarnaLocal MethodsFees
**Stripe**iDEAL, SEPA, etc.1.4%+
**PayPal**--2.9%+
**Adyen**250+ methodsCustom
**Mollie**iDEAL, Bancontact1.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