
Mohamed JadlaPayment Gateways as Anti-Corruption Layers Most PHP applications integrate payments directly using...
Payment Gateways as Anti-Corruption Layers
Most PHP applications integrate payments directly using provider SDKs.
Example:\Stripe\Stripe::setApiKey($key);
\Stripe\PaymentIntent::create([...]);
This works.
Until it doesn’t.
The real architectural question isn’t how to charge a card.
It’s:
Should your domain know that Stripe exists?
The Hidden Coupling Problem
When business logic calls Stripe SDK directly, your domain layer now depends on:
Stripe’s request models
Stripe’s response objects
Stripe’s exception types
Stripe’s lifecycle semantics
This is infrastructure bleeding into your core domain.
And that’s where architecture begins to matter.
Hexagonal Architecture Perspective
In Hexagonal Architecture (Ports & Adapters):
The Domain sits at the center.
Infrastructure lives outside.
Communication happens through defined ports.
So the real question becomes:
Should Stripe be inside the hexagon?
The answer is no.
Stripe is infrastructure.
Introducing a Payment Port
Instead of calling Stripe directly, the domain defines a port:
interface PaymentPort {
public function charge(Money $amount): PaymentResult;
}
The application layer depends only on this interface.
No Stripe.
No PayPal.
No SDK types.
Only domain language.
Stripe as an Adapter Boundary
Stripe now becomes an Adapter:
Domain → PaymentPort → StripeAdapter → Stripe SDK
The adapter:
Translates domain objects into Stripe requests
Maps Stripe responses into domain DTOs
Converts SDK exceptions into domain exceptions
This is the Adapter Boundary.
Payment Layer as an Anti-Corruption Layer
In Domain-Driven Design, an Anti-Corruption Layer (ACL) protects your domain from external models.
Stripe has its own language:
PaymentIntent
SetupIntent
CheckoutSession
Your domain might speak:
Charge
Subscription
Invoice
Without an ACL, your domain starts speaking Stripe’s language.
That’s architectural erosion.
An abstraction layer acts as an Anti-Corruption Layer.
It prevents provider vocabulary from leaking inward.
Domain Isolation in Practice
With proper isolation:
✔ You can switch providers without touching business logic
✔ You can mock the PaymentPort in tests
✔ You can simulate failures cleanly
✔ You maintain language consistency in the domain
Your domain remains pure.
But Let’s Be Honest
This adds:
More files
More interfaces
More mapping logic
More maintenance cost
If you only ever use one provider,
this might be premature abstraction.
Architecture is about trade-offs, not dogma.
The Real Decision Framework
Ask yourself:
Is the payment provider core to the business?
Will we expand internationally?
Do we expect regulatory constraints?
Are we building a long-lived SaaS?
If yes → abstraction is insurance.
If not → direct SDK integration may be pragmatic.
A Subtle Insight
Payment gateways are not just APIs.
They are external bounded contexts.
Treating them as simple SDK calls ignores that reality.
The moment billing logic becomes business-critical,
you are no longer integrating an API —
you are integrating another domain.
That’s when Anti-Corruption Layers matter.
Final Thought
Architecture isn’t about adding layers.
It’s about controlling dependency direction.
The question isn’t:
“How do we integrate Stripe?”
The question is:
“How do we prevent Stripe from owning our domain?”
Curious how other senior engineers approach this.
Do you abstract payment providers from day one?
Or only after the second integration becomes real?
👀 githup https://github.com/jadlamed007007007/multi-payment
👀 packagist https://packagist.org/packages/mohamedjadla/multi-payment