Stripe is a financial infrastructure company that helps businesses accept and manage online payments. It provides developer-friendly APIs for things like credit card payments, subscriptions, invoices, fraud detection, and marketplace payouts.
In simple terms, Stripe lets companies add payment features without building the entire banking and payment system themselves. It is widely used by startups, SaaS companies, e-commerce stores, and marketplaces to handle payments securely and at scale.
Product Design Requirements
Functional Requirements
- Merchants can create payment requests for specific amounts.
- Customers can pay using credit or debit cards.
- Merchants can track payment status, such as pending, successful, or failed
Non-Functional Requirements
- Security: protect sensitive payment and card data.
- Durability and auditability: never lose transaction data and keep a reliable audit trail.
- Transaction integrity: ensure financial correctness despite asynchronous payment flows.
- Scalability: support 10,000+ TPS and bursty traffic during peak periods.
Design Setup
Data Model
To support the core payment flows, we need three main entities:
-
Merchant: The Merchant entity represents a business using our payment platform. It stores merchant identity information, bank account details, and API credentials used to create and manage payments.
-
PaymentIntent: The PaymentIntent represents a merchant’s intent to collect a specific amount from a customer. It tracks the overall payment lifecycle, such as created, authorized, captured, canceled, or refunded. From the merchant’s perspective, this is the primary object they interact with. The merchant creates a PaymentIntent, and the system manages the underlying payment attempts internally.
-
Transaction: The Transaction entity represents an actual money movement or payment attempt associated with a PaymentIntent. A PaymentIntent can have multiple Transactions.
API Design
Firstly, we need a POST endpoint to create a PaymentIntent POST /payment-intents, when a customer reaches checkout, the merchant creates a PaymentIntent to represent the payment request.
Request { "amountInCents": 2499, "currency": "usd", "description": "Order #1234" } Response: { "paymentIntentId": "pi_123" }
Then, after the PaymentIntent is created, the customer can submit a credit or debit card payment, we need another POST endpoint POST /payment-intents/{paymentIntentId}/transactions to support this.
Request: { "type": "charge", "card": { "number": "4242424242424242", "exp_month": 12, "exp_year": 2025, "cvc": "123" } }
In a real system, we should not send raw card details directly to our backend. Instead, we would use tokenization so sensitive card data is handled securely.
Next, merchants should be able to query the current status of a PaymentIntent, thus we need a GET endpoint for this: GET /payment-intents/{paymentIntentId}
Response: { "paymentIntentId": "pi_123", "status": "succeeded" | "pending" | "failed" "amountInCents": 2499, "currency": "usd" }
Lastly, for real-time updates, the system should notify merchants through webhooks when a payment status changes.
POST {merchant_webhook_url}
Payload:
{ "type": "payment.succeeded", "data": { "paymentIntentId": "pi_123", "amountInCents": 2499, "currency": "usd", "status": "succeeded" } }
This allows merchants to receive asynchronous payment updates without repeatedly polling the payment status endpoint.