Integrating Multiple Payment Gateways
A unified payment abstraction for M-Pesa, Stripe, and PayPal
Problem
The platform needed to accept payments through M-Pesa (local), Stripe (international cards), and PayPal (global). Each gateway had different APIs, error handling, and webhook patterns. Hardcoding each integration created maintenance burden and made adding new gateways difficult.
Requirements
- ✓Unified API for all payment gateways
- ✓Adding a new gateway should be a configuration change, not a code change
- ✓Idempotent payment processing
- ✓Webhook handling for all gateways
- ✓Automatic retry for transient failures
- ✓Dead-letter queue for permanent failures
Architecture
Trade-offs
The adapter pattern adds a layer of abstraction, which means some gateway-specific features are not directly accessible. But the benefit of a unified interface far outweighs the cost of occasionally needing to bypass the adapter for a specific gateway feature.
Implementation
Built a payment service with the adapter pattern. Each gateway has an adapter that implements a common interface: initiatePayment, checkStatus, handleWebhook, refund. A factory selects the adapter based on configuration. Payment processing is async via RabbitMQ, with idempotency keys to prevent duplicate charges. Failed payments go to a dead-letter queue for manual review.
Scaling Considerations
Payment workers scale independently on RabbitMQ consumer count. The adapter pattern means new gateways require no changes to the core payment flow. PostgreSQL handles transaction records with appropriate indexing for status queries.
Testing Strategy
Unit tests for each adapter with mocked gateway responses. Integration tests with gateway sandbox environments. Idempotency tests to verify no duplicate charges. Webhook signature verification tests. Chaos tests for gateway timeout scenarios.
Performance Optimization
Async processing via RabbitMQ reduced payment API latency by 60%. Idempotency keys prevent duplicate processing. Redis caching for gateway configuration reduces database lookups. Connection pooling for gateway HTTP clients prevents connection exhaustion.
Lessons Learned
The adapter pattern is the right choice for multiple integrations with similar interfaces. Idempotency is not optional for payment systems. Dead-letter queues are essential for handling permanent failures without losing data. Always test with gateway sandboxes before production.