Fundamentals 13 min read

How the Adapter Pattern Seamlessly Integrates PayPal and Stripe Payments

This article explains the Adapter design pattern, demonstrates step‑by‑step how to wrap existing PayPal payment code with a Stripe adapter in TypeScript, and discusses best practices, common pitfalls, and extensions such as factories and nested adapters for flexible payment integration.

Code Mala Tang
Code Mala Tang
Code Mala Tang
How the Adapter Pattern Seamlessly Integrates PayPal and Stripe Payments

Background

The Adapter Pattern is a structural design pattern that provides a wrapper or "adapter" around existing classes so that incompatible interfaces can work together. In real‑world payment gateways or third‑party services, each often defines its own unique interface, making direct substitution impossible.

Step 1: Define the Target Interface

We create a PaymentProcessor interface that represents the service offered to the rest of the application.

<code>interface PaymentProcessor {
    pay(amount: number): void;
}</code>

Step 2: Existing PayPal Payment Processor

The current system uses PayPalPaymentProcessor , which implements PaymentProcessor and provides the pay() method.

<code>class PayPalPaymentProcessor implements PaymentProcessor {
    public pay(amount: number): void {
        console.log(`Paid $${amount} using PayPal.`);
    }
}</code>

Step 3: New Stripe Payment Processor

Stripe offers a different interface with a makePayment() method.

<code>class StripePaymentProcessor {
    public makePayment(amountInCents: number): void {
        console.log(`Paid $${amountInCents / 100} using Stripe.`);
    }
}</code>

Step 4: Create the Adapter

The adapter implements the target PaymentProcessor interface and internally delegates to StripePaymentProcessor , converting dollars to cents.

<code>class StripePaymentAdapter implements PaymentProcessor {
    private stripePaymentProcessor: StripePaymentProcessor;

    constructor(stripePaymentProcessor: StripePaymentProcessor) {
        this.stripePaymentProcessor = stripePaymentProcessor;
    }

    public pay(amount: number): void {
        // Convert dollars to cents and call Stripe's method
        this.stripePaymentProcessor.makePayment(amount * 100);
    }
}</code>

Step 5: Use the Adapter in the Application

The application can now work with either PayPal or Stripe without changing existing code.

<code>// Application code
const amountToPay = 100; // $100

// Existing PayPal processor
const paypalProcessor: PaymentProcessor = new PayPalPaymentProcessor();
paypalProcessor.pay(amountToPay);

// New Stripe processor via adapter
const stripeProcessor: PaymentProcessor = new StripePaymentAdapter(new StripePaymentProcessor());
stripeProcessor.pay(amountToPay);
</code>

Output:

<code>Paid $100 using PayPal.
Paid $100 using Stripe.
</code>

Key Points

PaymentProcessor is the target interface shared by both processors.

PayPalPaymentProcessor already implements this interface.

StripePaymentProcessor has a different interface, so we create StripePaymentAdapter to adapt it to PaymentProcessor .

Richer Adapters

Adapters can also add validation, logging, error handling, or integrate configuration files. Examples include supporting multiple payment gateways, logging systems, storage services, or email providers.

Factory for Multiple Adapters

<code>interface PaymentProcessor {
    pay(amount: number): void;
}

class PaymentAdapterFactory {
    static getPaymentProcessor(type: string, version?: string): PaymentProcessor {
        if (type === 'alipay') {
            if (version === 'v1') {
                return new AlipayV1Adapter(new AlipayV1Processor());
            } else if (version === 'v2') {
                return new AlipayV2Adapter(new AlipayV2Processor());
            }
        } else if (type === 'stripe') {
            return new StripeAdapter(new StripeProcessor());
        }
        throw new Error(`Unsupported payment type: ${type}`);
    }
}

const alipayProcessorV1 = PaymentAdapterFactory.getPaymentProcessor('alipay', 'v1');
alipayProcessorV1.pay(100);

const alipayProcessorV2 = PaymentAdapterFactory.getPaymentProcessor('alipay', 'v2');
alipayProcessorV2.pay(200);
</code>

Nested Adapters

Adapters can be composed to form chains, each handling a specific transformation.

<code>class StripeAdapter implements PaymentProcessor {
    private stripeProcessor: StripePaymentProcessor;
    constructor(stripeProcessor: StripePaymentProcessor) {
        this.stripeProcessor = stripeProcessor;
    }
    public pay(amount: number): void {
        this.stripeProcessor.makePayment(amount * 100);
    }
}

class CurrencyFormatterAdapter implements PaymentProcessor {
    private paymentProcessor: PaymentProcessor;
    constructor(paymentProcessor: PaymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }
    public pay(amount: number): void {
        const formattedAmount = parseFloat(amount.toFixed(2));
        console.log(`Formatted amount: $${formattedAmount}`);
        this.paymentProcessor.pay(formattedAmount);
    }
}

const stripeProcessor = new StripeAdapter(new StripePaymentProcessor());
const formattedStripeProcessor = new CurrencyFormatterAdapter(stripeProcessor);

formattedStripeProcessor.pay(123.456);
// Output:
// Formatted amount: $123.46
// Paid $123.46 using Stripe.
</code>

Common Misconceptions

Adapter pattern is not a universal solution; overusing it adds complexity.

It does more than simple encapsulation—it translates interfaces.

It cannot solve deep business‑logic incompatibilities.

Adapters require maintenance as third‑party APIs evolve.

Performance overhead is usually minimal but should be considered in high‑throughput scenarios.

Proper naming and documentation keep adapters readable.

Use adapters as a remediation strategy, not as a first‑choice design.

Conclusion

The essence of the Adapter Pattern is to resolve interface incompatibility, improving flexibility and reusability. Apply it when integrating legacy code, third‑party libraries, or multiple implementations without altering existing client code.

Adapter illustration
Adapter illustration
Power adapter analogy
Power adapter analogy
design patternssoftware architecturetypescriptAdapter PatternObject-Oriented DesignPayment Integration
Code Mala Tang
Written by

Code Mala Tang

Read source code together, write articles together, and enjoy spicy hot pot together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.