Skip to content

Payment Processing System Design (HLD & LLD)

Design a payment processing system similar to Stripe, Razorpay, or PayPal that:

  • Accepts payments from users
  • Supports multiple payment methods
  • Communicates with external payment gateways
  • Ensures idempotency
  • Handles failures and retries
  • Maintains transaction history
  • Supports refunds
  • Provides high reliability and consistency

  1. User initiates a payment.
  2. Support multiple payment methods:
    • Credit Card
    • Debit Card
    • UPI
    • Wallet
    • Net Banking
  3. Process payments through external payment gateways.
  4. Persist transaction details.
  5. Handle payment success, failure, and pending states.
  6. Support refunds.
  7. Provide APIs for transaction status.

  • High availability
  • Strong consistency for financial transactions
  • Idempotent APIs
  • PCI-compliant security
  • Horizontal scalability
  • Low latency

flowchart TD
    C[Client]
    A[API Gateway]
    P[Payment Service]
    PR[Payment Processor]
    F[Fraud Detection]
    G[Gateway Adapter]
    X[External Payment Gateway]
    B[Bank / Card Network]

    C --> A --> P
    P --> PR
    P --> F
    PR --> G --> X --> B

Additional supporting components:

  • Transaction Database
  • Redis Cache
  • Kafka / Message Queue
  • Notification Service
  • Ledger Service

sequenceDiagram
    participant User
    participant API as API Gateway
    participant PS as Payment Service
    participant PP as Payment Processor
    participant PG as Payment Gateway
    participant Bank

    User->>API: Create Payment
    API->>PS: Validate & Create Order
    PS->>PP: Process Payment
    PP->>PG: Gateway Request
    PG->>Bank: Authorize
    Bank-->>PG: Result
    PG-->>PS: Callback/Webhook
    PS-->>User: Payment Status

Responsibilities:

  • Create payment
  • Validate request
  • Query payment status

APIs:

POST /payments
GET /payments/{id}
POST /payments/{id}/refund

Responsible for:

  • Selecting payment gateway
  • Retrying failed requests
  • Idempotency handling

Implements the Strategy Pattern.

Examples:

  • StripeAdapter
  • RazorpayAdapter
  • PaypalAdapter

Maintains double-entry bookkeeping.

Account Entry
User Debit
Merchant Credit

Sends:

  • Payment success
  • Payment failure
  • Refund confirmation

Channels:

  • Email
  • SMS
  • Push Notification

payments
---------
id (UUID)
user_id
amount
currency
status
payment_method
gateway
created_at
updated_at
transactions
-------------
id
payment_id
gateway_transaction_id
status
response_code
created_at
refunds
--------
id
payment_id
amount
status
created_at

To prevent duplicate payments when users retry requests:

Use an Idempotency-Key header.

Idempotency-Key: UUID

Store:

idempotency_key
request_hash
response

Duplicate requests return the previously stored response.


Possible failures:

  • Gateway timeout
  • Network issues
  • Bank decline
  • Internal service failure

Solutions:

  1. Retry with exponential backoff.
  2. Use Kafka/RabbitMQ for asynchronous processing.
  3. Apply Circuit Breaker (e.g., Resilience4j).

stateDiagram-v2
    [*] --> CREATED
    CREATED --> PROCESSING
    PROCESSING --> SUCCESS
    PROCESSING --> FAILED
    SUCCESS --> REFUNDED

States:

  • CREATED
  • PROCESSING
  • SUCCESS
  • FAILED
  • PENDING
  • REFUNDED

  • PCI-DSS compliance
  • Tokenization of card data
  • Encryption
  • HTTPS
  • HMAC signature verification for webhooks

Webhook verification flow:

Gateway -> Webhook -> Verify Signature

Payment Service -> Multiple Instances

Behind a load balancer.

  • Read replicas
  • Sharding by merchant_id

Events:

PAYMENT_CREATED
PAYMENT_COMPLETED
PAYMENT_FAILED
REFUND_INITIATED

class Payment {
private String id;
private String userId;
private double amount;
private PaymentStatus status;
private PaymentMethod method;
private String gateway;
}

public enum PaymentStatus {
CREATED,
PROCESSING,
SUCCESS,
FAILED,
REFUNDED
}

public enum PaymentMethod {
CARD,
UPI,
NETBANKING,
WALLET
}

public interface PaymentGateway {
PaymentResponse processPayment(PaymentRequest request);
}

public class StripeGateway implements PaymentGateway {
public PaymentResponse processPayment(PaymentRequest request) {
// call stripe API
return response;
}
}

public class RazorpayGateway implements PaymentGateway {
public PaymentResponse processPayment(PaymentRequest request) {
// call razorpay API
return response;
}
}

public class PaymentGatewayFactory {
public static PaymentGateway getGateway(String gateway) {
switch(gateway) {
case "STRIPE":
return new StripeGateway();
case "RAZORPAY":
return new RazorpayGateway();
default:
throw new IllegalArgumentException();
}
}
}

@Service
public class PaymentService {
public PaymentResponse processPayment(PaymentRequest request) {
PaymentGateway gateway =
PaymentGatewayFactory.getGateway(request.getGateway());
return gateway.processPayment(request);
}
}

Pattern Usage
Strategy Gateway adapters
Factory Gateway selection
State Payment lifecycle
Observer Notifications
Circuit Breaker Gateway resilience

  • Distributed locking (Redis) to prevent duplicate processing.
  • Outbox Pattern for reliable DB + event publishing.
  • Saga Pattern for refund orchestration.
  • Rate limiting for fraud prevention.

  • How do you ensure exactly-once payment processing?
  • How do you design a financial ledger?
  • How do you reconcile payment mismatches?
  • How do you recover from partial failures?

  • Separate orchestration, gateway integration, ledger, and notification concerns.
  • Idempotency is critical to avoid duplicate charges.
  • Event-driven architecture improves scalability and resilience.
  • Strategy and Factory patterns simplify multi-gateway support.
  • Strong consistency, security, and auditability are essential for payment systems.