Chapter 137 min read

Chapter 13: Payment Gateway Integration

Why This Exists

Handling money on the internet is incredibly dangerous. If a company stores raw credit card numbers on their servers and gets hacked, they face catastrophic fines from credit card networks (Visa, Mastercard), massive lawsuits, and immediate bankruptcy. Payment gateways exist to abstract the risk, compliance, and complexity of moving money between consumer banks and merchant bank accounts, allowing developers to process payments securely without ever seeing a credit card number.

Real World Problem

In the early 2000s, developers would build checkout forms that sent credit card numbers to their backend SQL databases. Hackers would use SQL injection to steal millions of card numbers at once. The real-world problem is that building software is hard, and most companies cannot afford the millions of dollars required to secure their infrastructure to the standards required by the Payment Card Industry Data Security Standard (PCI-DSS).

Everyday Analogy

Think of a payment gateway like an armored cash transport truck. If you own a jewelry store, you don't load a million dollars in cash into the back of your personal Honda Civic and drive it to the bank. You hire an armored truck company. They handle the security, the guards, and the secure route. You just hand them the money, and they guarantee it gets to the bank safely. Payment gateways (like Stripe or PayPal) are the armored trucks of the internet.

Beginner Explanation

A Payment Gateway is a service (like Stripe, PayPal, or Adyen) that acts as a middleman. When you buy something online, the website doesn't take your card. The website loads a secure little box (an iframe) provided by the gateway. You type your card into that box. The gateway talks to the bank, makes sure you have money, and then sends the website a message saying, "Payment Successful!" The website never touches the card.

Intermediate Explanation

Payment integration relies on two critical concepts: Tokenization and Auth/Capture.

Tokenization: The frontend sends the credit card data directly to the gateway (e.g., Stripe). Stripe securely stores the card and returns a Token (e.g., tok_123xyz). The frontend sends this Token to your backend. Your backend uses this token to charge the card. If your backend is hacked, the hackers only get useless tokens, not credit card numbers.

Auth and Capture:

  • Authorization: Checking if the card is valid and placing a "hold" on the funds. The customer's bank statement shows a "Pending" charge.
  • Capture: Actually moving the money from the customer's bank to the merchant's bank. By law, in many jurisdictions, you cannot Capture funds until the item physically ships. Therefore, e-commerce systems Auth at checkout, and Capture via an API call from the OMS when the warehouse ships the box.

Advanced Explanation

Modern payment gateways require compliance with SCA (Strong Customer Authentication) and 3D Secure (e.g., Verified by Visa).

Payments are no longer synchronous. When your backend attempts to charge a card, the bank might say, "We need a text message OTP (One Time Password) from the user." The backend must return a status of Requires_Action to the frontend. The frontend then pops up a secure bank modal. The user enters the OTP. The bank approves the charge, and sends an asynchronous Webhook to your backend confirming the payment. Architectures must be built to handle these asynchronous, multi-step "Payment Intents" rather than expecting an instant success/fail response.

Real World Example

Uber's Auth/Capture Flow: When you request an Uber, the app does an Authorization for the estimated fare (e.g., $20). This proves your card is valid and holds the money. During the ride, there is traffic, and the final cost is $22. When you step out of the car, Uber's backend issues a Capture request for the exact final amount of $22. If you canceled the ride within 2 minutes, they simply Void the authorization, and the pending charge disappears from your bank without any money actually moving.

Architecture Design

Here is the modern Tokenized Payment flow using Payment Intents:

sequenceDiagram
    participant Browser
    participant Gateway(Stripe)
    participant Backend
    
    Browser->>Backend: 1. Init Checkout ($100)
    Backend->>Gateway(Stripe): 2. Create PaymentIntent ($100)
    Gateway(Stripe)-->>Backend: 3. Return Client Secret
    Backend-->>Browser: 4. Pass Client Secret to Frontend
    
    Note over Browser,Gateway(Stripe): User types CC into Stripe iframe
    
    Browser->>Gateway(Stripe): 5. Confirm Payment with CC + Secret
    
    alt Needs 3D Secure (OTP)
        Gateway(Stripe)-->>Browser: 6a. Show Bank OTP Modal
        Browser->>Gateway(Stripe): 7a. User enters OTP
    end
    
    Gateway(Stripe)-->>Browser: 8. Payment Success
    
    Note over Gateway(Stripe),Backend: Async Webhook confirms to Backend
    Gateway(Stripe)->>Backend: 9. Webhook: payment_intent.succeeded
    Backend->>Backend: 10. Update Order to PAID

Database Design

Store transaction logs, but NEVER raw PCI data.

CREATE TABLE payments (
    id UUID PRIMARY KEY,
    order_id UUID,
    gateway_name VARCHAR(50), -- 'Stripe', 'PayPal'
    gateway_transaction_id VARCHAR(100), -- e.g., 'pi_12345'
    amount DECIMAL(10,2),
    currency VARCHAR(3),
    status VARCHAR(50), -- 'AUTHORIZED', 'CAPTURED', 'FAILED', 'REFUNDED'
    last_4_digits VARCHAR(4), -- Safe to store
    card_brand VARCHAR(20) -- 'Visa', 'Mastercard'
);

API Design

Create Intent (Checkout Start): POST /api/payments/intent Response: { "client_secret": "pi_123_secret_456" }

Capture Payment (Warehouse Shipped): POST /api/payments/{transaction_id}/capture Payload: { "amount": 100.00 } (Allows partial captures if an item was out of stock)

Production Considerations

  • Idempotency: Network drops happen. If your backend tells Stripe to charge $100, and the connection drops before Stripe replies, your backend might retry. If you don't use an Idempotency Key (a unique string like the order_id), Stripe will charge the customer twice.
  • Webhook Reliability: Your frontend might tell the user "Success!", but if the asynchronous webhook from Stripe fails to reach your backend, your database will still say PENDING_PAYMENT. The backend must be the ultimate source of truth.

Security Considerations

  • PCI Scope Reduction: If you use a hosted checkout page or iframes (Stripe Elements), your PCI compliance questionnaire (SAQ A) is a few pages long. If your backend API accepts raw card numbers, the audit (SAQ D) requires hundreds of strict network and physical security controls. Avoid touching PANs (Primary Account Numbers) at all costs.

Common Mistakes

  • Capturing too early: Capturing funds before having physical stock. If the item is out of stock, you have to issue a Refund. Refunds cost money (processing fees are rarely returned) and take 5-10 days to reach the customer's bank, causing support tickets.
  • Trusting the Frontend: If the frontend API call says, "Payment Success for Order 123," the backend must never trust it. The backend must query the Gateway API directly or rely on securely signed Webhooks to confirm the payment.

Tradeoffs and Alternatives

  • Aggregators vs. Direct Merchant Accounts:
    • Aggregators (Stripe, PayPal, Square): Instant setup, great developer APIs, but charge high flat fees (e.g., 2.9% + $0.30).
    • Merchant Accounts/Payment Processors (Authorize.net, Adyen): Harder to integrate, requires underwriting, but offers significantly lower rates for high-volume enterprise businesses.

Interview Questions

  1. Explain the difference between Authorizing a payment and Capturing a payment. When should you do each?
  2. What is Tokenization, and why is it critical for PCI compliance?
  3. How do you handle a scenario where a user's bank requires a 3D Secure SMS code to approve a transaction?

Hands-On Exercise

  1. Go to the Stripe API Documentation (or Braintree).
  2. Look up the API reference for PaymentIntents.
  3. Read the lifecycle of a PaymentIntent. Note the differences between the requires_payment_method, requires_action, and succeeded states.

Key Takeaways

  • Payment Gateways abstract the massive security and legal risks of handling credit cards.
  • Architectures must use Tokenization to keep their servers entirely out of PCI scope.
  • Modern payments are asynchronous; they require orchestrating intents, frontend redirects for bank approvals (SCA), and backend webhooks.
  • Always separate Authorization (at checkout) from Capture (at fulfillment).

Further Reading

  • Stripe Documentation: The PaymentIntents API
  • PCI Security Standards Council (Understand SAQ A vs SAQ D)
    Chapter 13: Payment Gateway Integration — Architecting Modern E-Commerce Systems: From First Principles to AI-Powered Marketplaces | Krishna Tiwari