Payment Integrations

Stripe Payment Integration: Production Patterns for React and Node.js

Production Stripe integration with Payment Intents, webhooks, and 3D Secure. Covers subscription billing, error handling, and PCI compliance patterns.

Khalid Aboubakr
28 min read
StripePayment GatewayReactNodejsPci ComplianceWebhooksSca3D SecurePayment Intents

Table of Contents

  1. Why This Guide Exists
  2. Architecture Overview
  3. Setting Up Stripe Correctly
  4. Implementing Payment Intents
  5. Building the React Payment Form
  6. Webhook Implementation
  7. Handling 3D Secure and SCA
  8. Subscription Billing
  9. Error Handling That Actually Works
  10. Testing in Production-Like Environments
  11. Common Mistakes I've Seen in Production

Why This Guide Exists

I've integrated Stripe into over a dozen production systems—e-commerce platforms processing thousands of daily transactions, SaaS applications with complex subscription tiers, and marketplace systems with split payments. The official Stripe documentation is good, but it doesn't tell you what happens when things go wrong at 2 AM on a Friday.

This guide covers what I wish I knew before my first Stripe integration, and what I've learned from debugging payment failures in production.

Architecture Overview

Before writing code, understand the payment flow architecture:

┌─────────────────────────────────────────────────────────────────────┐
│                        Your Application                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌──────────────┐     ┌──────────────┐     ┌──────────────┐        │
│  │   React UI   │────▶│  Your API    │────▶│   Database   │        │
│  │  (Stripe.js) │     │  (Node.js)   │     │              │        │
│  └──────┬───────┘     └──────┬───────┘     └──────────────┘        │
│         │                    │                                       │
│         │ Card tokenized     │ PaymentIntent                        │
│         │ client-side        │ created server-side                  │
│         │                    │                                       │
└─────────┼────────────────────┼───────────────────────────────────────┘
          │                    │
          ▼                    ▼
┌─────────────────────────────────────────────────────────────────────┐
│                         Stripe API                                   │
│                                                                      │
│  • Never sees raw card numbers from your server                     │
│  • Handles 3D Secure challenges                                      │
│  • Sends webhooks for async events                                   │
│                                                                      │
└──────────────────────────────┬──────────────────────────────────────┘
                               │
                               │ Webhooks (async)
                               ▼
                    ┌──────────────────────┐
                    │  Your Webhook Handler │
                    │  (Idempotent!)        │
                    └──────────────────────┘

Critical principle: Your server never handles raw card data. Stripe.js tokenizes card details client-side. This keeps you out of PCI DSS scope for most requirements.

Setting Up Stripe Correctly

Environment Configuration

// config/stripe.ts import Stripe from 'stripe'; if (!process.env.STRIPE_SECRET_KEY) { throw new Error('STRIPE_SECRET_KEY is required'); } if (!process.env.STRIPE_WEBHOOK_SECRET) { throw new Error('STRIPE_WEBHOOK_SECRET is required'); } export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2023-10-16', // Always pin the API version typescript: true, maxNetworkRetries: 2, timeout: 30000, }); export const STRIPE_CONFIG = { publishableKey: process.env.STRIPE_PUBLISHABLE_KEY!, webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!, // Currency should be configurable per merchant/region defaultCurrency: 'usd', // Statement descriptor appears on customer's bank statement statementDescriptor: 'YOURCOMPANY', // Maximum 22 characters, alphanumeric statementDescriptorSuffix: '', // Dynamic per order };

Why pin the API version? Stripe makes breaking changes. I've seen production systems break because they auto-upgraded to a new API version. Pin it, test upgrades in staging, then update deliberately.

Project Structure

src/
├── config/
│   └── stripe.ts
├── services/
│   ├── payment/
│   │   ├── PaymentService.ts      # Core payment logic
│   │   ├── WebhookService.ts      # Webhook handling
│   │   ├── SubscriptionService.ts # Subscription management
│   │   └── RefundService.ts       # Refund processing
│   └── index.ts
├── controllers/
│   ├── PaymentController.ts
│   └── WebhookController.ts
├── models/
│   ├── Payment.ts
│   ├── Subscription.ts
│   └── WebhookEvent.ts
└── middleware/
    └── stripeWebhook.ts

Implementing Payment Intents

Payment Intents is the modern Stripe API. Never use the legacy Charges API for new integrations.

Server-Side: Creating Payment Intents

// services/payment/PaymentService.ts import Stripe from 'stripe'; import { stripe, STRIPE_CONFIG } from '@/config/stripe'; import { PaymentRepository } from '@/repositories/PaymentRepository'; import { OrderRepository } from '@/repositories/OrderRepository'; interface CreatePaymentParams { orderId: string; customerId?: string; amount: number; // In cents currency: string; metadata?: Record<string, string>; savePaymentMethod?: boolean; } export class PaymentService { constructor( private paymentRepo: PaymentRepository, private orderRepo: OrderRepository ) {} async createPaymentIntent(params: CreatePaymentParams): Promise<{ clientSecret: string; paymentIntentId: string; }> { const { orderId, customerId, amount, currency, metadata, savePaymentMethod } = params; // Validate order exists and is payable const order = await this.orderRepo.findById(orderId); if (!order) { throw new PaymentError('ORDER_NOT_FOUND', 'Order does not exist'); } if (order.status !== 'pending_payment') { throw new PaymentError('INVALID_ORDER_STATUS', 'Order is not awaiting payment'); } // Validate amount matches order total (prevent price manipulation) if (amount !== order.totalAmountCents) { throw new PaymentError('AMOUNT_MISMATCH', 'Payment amount does not match order'); } // Check for existing PaymentIntent (idempotency) const existingPayment = await this.paymentRepo.findByOrderId(orderId); if (existingPayment?.stripePaymentIntentId) { // Retrieve and return existing intent if still valid const existingIntent = await stripe.paymentIntents.retrieve( existingPayment.stripePaymentIntentId ); if (['requires_payment_method', 'requires_confirmation'].includes(existingIntent.status)) { return { clientSecret: existingIntent.client_secret!, paymentIntentId: existingIntent.id, }; } } // Create PaymentIntent const paymentIntentParams: Stripe.PaymentIntentCreateParams = { amount, currency, metadata: { orderId, ...metadata, }, statement_descriptor_suffix: order.orderNumber.slice(0, 22), // Automatic payment methods lets Stripe show relevant options automatic_payment_methods: { enabled: true, }, }; // Attach to customer for saved cards if (customerId) { paymentIntentParams.customer = customerId; if (savePaymentMethod) { paymentIntentParams.setup_future_usage = 'off_session'; } } const paymentIntent = await stripe.paymentIntents.create(paymentIntentParams); // Record in database await this.paymentRepo.create({ orderId, stripePaymentIntentId: paymentIntent.id, amount, currency, status: 'pending', createdAt: new Date(), }); return { clientSecret: paymentIntent.client_secret!, paymentIntentId: paymentIntent.id, }; } async confirmPaymentIntent(paymentIntentId: string): Promise<Stripe.PaymentIntent> { // This is called after client-side confirmation for 3D Secure flows return stripe.paymentIntents.retrieve(paymentIntentId); } } // Custom error class for payment-specific errors export class PaymentError extends Error { constructor( public code: string, message: string, public isRetryable: boolean = false ) { super(message); this.name = 'PaymentError'; } }

API Endpoint

// controllers/PaymentController.ts import { Router, Request, Response } from 'express'; import { z } from 'zod'; import { PaymentService, PaymentError } from '@/services/payment/PaymentService'; import { authenticate } from '@/middleware/auth'; import { rateLimit } from '@/middleware/rateLimit'; const router = Router(); const createPaymentSchema = z.object({ orderId: z.string().uuid(), savePaymentMethod: z.boolean().optional().default(false), }); router.post( '/create-payment-intent', authenticate, rateLimit({ windowMs: 60000, max: 10 }), // 10 requests per minute async (req: Request, res: Response) => { try { const { orderId, savePaymentMethod } = createPaymentSchema.parse(req.body); const order = await orderService.getOrderForUser(orderId, req.user.id); const result = await paymentService.createPaymentIntent({ orderId, customerId: req.user.stripeCustomerId, amount: order.totalAmountCents, currency: order.currency, savePaymentMethod, metadata: { userId: req.user.id, userEmail: req.user.email, }, }); res.json({ clientSecret: result.clientSecret, }); } catch (error) { if (error instanceof PaymentError) { return res.status(400).json({ error: error.code, message: error.message, retryable: error.isRetryable, }); } if (error instanceof z.ZodError) { return res.status(400).json({ error: 'VALIDATION_ERROR', details: error.errors, }); } console.error('Payment intent creation failed:', error); res.status(500).json({ error: 'PAYMENT_FAILED', message: 'Unable to process payment. Please try again.', }); } } ); export default router;

Building the React Payment Form

Setting Up Stripe Elements

// components/payment/StripeProvider.tsx import { Elements } from '@stripe/react-stripe-js'; import { loadStripe, StripeElementsOptions } from '@stripe/stripe-js'; // Load Stripe outside component to avoid recreating on re-render const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!); interface StripeProviderProps { clientSecret: string; children: React.ReactNode; } export function StripeProvider({ clientSecret, children }: StripeProviderProps) { const options: StripeElementsOptions = { clientSecret, appearance: { theme: 'stripe', variables: { colorPrimary: '#0070f3', colorBackground: '#ffffff', colorText: '#1a1a1a', colorDanger: '#df1b41', fontFamily: 'system-ui, -apple-system, sans-serif', borderRadius: '8px', }, rules: { '.Input': { border: '1px solid #e5e7eb', boxShadow: '0 1px 2px 0 rgb(0 0 0 / 0.05)', }, '.Input:focus': { border: '1px solid #0070f3', boxShadow: '0 0 0 3px rgba(0, 112, 243, 0.1)', }, '.Label': { fontWeight: '500', marginBottom: '8px', }, }, }, loader: 'auto', }; return ( <Elements stripe={stripePromise} options={options}> {children} </Elements> ); }

The Payment Form Component

// components/payment/PaymentForm.tsx import { useState, FormEvent } from 'react'; import { PaymentElement, useStripe, useElements, } from '@stripe/react-stripe-js'; import { StripePaymentElementChangeEvent } from '@stripe/stripe-js'; interface PaymentFormProps { orderId: string; amount: number; currency: string; onSuccess: (paymentIntentId: string) => void; onError: (error: string) => void; } export function PaymentForm({ orderId, amount, currency, onSuccess, onError, }: PaymentFormProps) { const stripe = useStripe(); const elements = useElements(); const [isProcessing, setIsProcessing] = useState(false); const [isComplete, setIsComplete] = useState(false); const [errorMessage, setErrorMessage] = useState<string | null>(null); const handleElementChange = (event: StripePaymentElementChangeEvent) => { setIsComplete(event.complete); setErrorMessage(event.error?.message ?? null); }; const handleSubmit = async (event: FormEvent) => { event.preventDefault(); if (!stripe || !elements) { // Stripe.js hasn't loaded yet return; } if (isProcessing) { return; } setIsProcessing(true); setErrorMessage(null); try { const { error, paymentIntent } = await stripe.confirmPayment({ elements, confirmParams: { return_url: `${window.location.origin}/order/${orderId}/confirmation`, receipt_email: undefined, // Let Stripe handle based on PaymentIntent }, redirect: 'if_required', // Only redirect for 3D Secure }); if (error) { // Show error to customer if (error.type === 'card_error' || error.type === 'validation_error') { setErrorMessage(error.message ?? 'Payment failed'); } else { setErrorMessage('An unexpected error occurred. Please try again.'); console.error('Payment error:', error); } onError(error.message ?? 'Payment failed'); } else if (paymentIntent) { // Payment succeeded without redirect if (paymentIntent.status === 'succeeded') { onSuccess(paymentIntent.id); } else if (paymentIntent.status === 'processing') { // Bank transfers, etc. - show pending state onSuccess(paymentIntent.id); } } } catch (err) { setErrorMessage('An unexpected error occurred. Please try again.'); console.error('Payment submission error:', err); } finally { setIsProcessing(false); } }; const formatAmount = (cents: number, curr: string) => { return new Intl.NumberFormat('en-US', { style: 'currency', currency: curr, }).format(cents / 100); }; return ( <form onSubmit={handleSubmit} className="space-y-6"> <PaymentElement onChange={handleElementChange} options={{ layout: 'tabs', business: { name: 'Your Company' }, }} /> {errorMessage && ( <div role="alert" className="rounded-lg bg-red-50 p-4 text-sm text-red-700" > {errorMessage} </div> )} <button type="submit" disabled={!stripe || !isComplete || isProcessing} className="w-full rounded-lg bg-blue-600 px-6 py-3 font-semibold text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-gray-400" > {isProcessing ? ( <span className="flex items-center justify-center gap-2"> <svg className="h-5 w-5 animate-spin" viewBox="0 0 24 24"> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" /> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" /> </svg> Processing... </span> ) : ( `Pay ${formatAmount(amount, currency)}` )} </button> <p className="text-center text-xs text-gray-500"> Your payment is secured by Stripe. We never see your card details. </p> </form> ); }

Webhook Implementation

This is where most Stripe integrations fail. Webhooks are how Stripe tells you about async events. If you don't handle them correctly, you'll have customers who paid but never got their order.

Webhook Security

// middleware/stripeWebhook.ts import { Request, Response, NextFunction } from 'express'; import { stripe, STRIPE_CONFIG } from '@/config/stripe'; // CRITICAL: Use raw body for signature verification export function stripeWebhookMiddleware( req: Request, res: Response, next: NextFunction ) { const signature = req.headers['stripe-signature']; if (!signature) { return res.status(400).json({ error: 'Missing stripe-signature header' }); } try { const event = stripe.webhooks.constructEvent( req.body, // Must be raw buffer, not parsed JSON signature, STRIPE_CONFIG.webhookSecret ); req.stripeEvent = event; next(); } catch (err) { console.error('Webhook signature verification failed:', err); return res.status(400).json({ error: 'Invalid signature' }); } } // Express config - raw body only for webhook endpoint app.use('/api/webhooks/stripe', express.raw({ type: 'application/json' })); app.use('/api', express.json()); // JSON parsing for other routes

Idempotent Webhook Handler

// services/payment/WebhookService.ts import Stripe from 'stripe'; import { WebhookEventRepository } from '@/repositories/WebhookEventRepository'; import { PaymentRepository } from '@/repositories/PaymentRepository'; import { OrderService } from '@/services/OrderService'; import { NotificationService } from '@/services/NotificationService'; export class WebhookService { constructor( private webhookRepo: WebhookEventRepository, private paymentRepo: PaymentRepository, private orderService: OrderService, private notificationService: NotificationService ) {} async handleEvent(event: Stripe.Event): Promise<void> { // Idempotency check - have we processed this event? const existingEvent = await this.webhookRepo.findByEventId(event.id); if (existingEvent) { console.log(`Webhook event ${event.id} already processed, skipping`); return; } // Record event before processing (to prevent concurrent processing) await this.webhookRepo.create({ eventId: event.id, eventType: event.type, status: 'processing', receivedAt: new Date(), data: event.data.object, }); try { await this.processEvent(event); await this.webhookRepo.markCompleted(event.id); } catch (error) { await this.webhookRepo.markFailed(event.id, error); throw error; // Re-throw to trigger Stripe retry } } private async processEvent(event: Stripe.Event): Promise<void> { switch (event.type) { case 'payment_intent.succeeded': await this.handlePaymentSucceeded(event.data.object as Stripe.PaymentIntent); break; case 'payment_intent.payment_failed': await this.handlePaymentFailed(event.data.object as Stripe.PaymentIntent); break; case 'payment_intent.canceled': await this.handlePaymentCanceled(event.data.object as Stripe.PaymentIntent); break; case 'charge.refunded': await this.handleRefund(event.data.object as Stripe.Charge); break; case 'charge.dispute.created': await this.handleDispute(event.data.object as Stripe.Dispute); break; // Subscription events case 'customer.subscription.created': case 'customer.subscription.updated': case 'customer.subscription.deleted': await this.handleSubscriptionChange(event); break; case 'invoice.paid': await this.handleInvoicePaid(event.data.object as Stripe.Invoice); break; case 'invoice.payment_failed': await this.handleInvoicePaymentFailed(event.data.object as Stripe.Invoice); break; default: console.log(`Unhandled webhook event type: ${event.type}`); } } private async handlePaymentSucceeded(paymentIntent: Stripe.PaymentIntent): Promise<void> { const orderId = paymentIntent.metadata.orderId; if (!orderId) { console.error('PaymentIntent missing orderId in metadata:', paymentIntent.id); return; } // Update payment record await this.paymentRepo.updateByPaymentIntentId(paymentIntent.id, { status: 'succeeded', paidAt: new Date(), chargeId: typeof paymentIntent.latest_charge === 'string' ? paymentIntent.latest_charge : paymentIntent.latest_charge?.id, }); // Fulfill the order await this.orderService.fulfillOrder(orderId); // Send confirmation await this.notificationService.sendOrderConfirmation(orderId); } private async handlePaymentFailed(paymentIntent: Stripe.PaymentIntent): Promise<void> { const orderId = paymentIntent.metadata.orderId; if (!orderId) return; const failureMessage = paymentIntent.last_payment_error?.message ?? 'Payment failed'; await this.paymentRepo.updateByPaymentIntentId(paymentIntent.id, { status: 'failed', failureReason: failureMessage, }); // Notify customer await this.notificationService.sendPaymentFailedEmail(orderId, failureMessage); } private async handleDispute(dispute: Stripe.Dispute): Promise<void> { // Disputes are serious - immediate notification required const chargeId = typeof dispute.charge === 'string' ? dispute.charge : dispute.charge.id; const payment = await this.paymentRepo.findByChargeId(chargeId); if (payment) { await this.paymentRepo.update(payment.id, { status: 'disputed', disputeId: dispute.id, disputeReason: dispute.reason, }); // Alert the team immediately await this.notificationService.alertTeam({ type: 'DISPUTE_CREATED', severity: 'high', orderId: payment.orderId, amount: dispute.amount, reason: dispute.reason, }); } } }

Webhook Endpoint

// controllers/WebhookController.ts import { Router, Request, Response } from 'express'; import { stripeWebhookMiddleware } from '@/middleware/stripeWebhook'; import { webhookService } from '@/services'; const router = Router(); router.post( '/stripe', stripeWebhookMiddleware, async (req: Request, res: Response) => { try { await webhookService.handleEvent(req.stripeEvent!); res.json({ received: true }); } catch (error) { console.error('Webhook processing error:', error); // Return 500 to trigger Stripe retry res.status(500).json({ error: 'Webhook processing failed' }); } } ); export default router;

Handling 3D Secure and SCA

Strong Customer Authentication (SCA) is required in Europe and increasingly elsewhere. Your integration must handle it.

// The PaymentElement handles most SCA automatically // But you need to handle the redirect flow // pages/order/[orderId]/confirmation.tsx import { useEffect, useState } from 'react'; import { useRouter } from 'next/router'; import { useStripe } from '@stripe/react-stripe-js'; export default function PaymentConfirmation() { const router = useRouter(); const stripe = useStripe(); const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading'); const [message, setMessage] = useState(''); useEffect(() => { if (!stripe || !router.isReady) return; const clientSecret = new URLSearchParams(window.location.search).get( 'payment_intent_client_secret' ); if (!clientSecret) { setStatus('error'); setMessage('Missing payment information'); return; } stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => { if (!paymentIntent) { setStatus('error'); setMessage('Could not retrieve payment status'); return; } switch (paymentIntent.status) { case 'succeeded': setStatus('success'); setMessage('Payment successful! Your order is being processed.'); break; case 'processing': setStatus('success'); setMessage('Payment is processing. We\'ll update you when it completes.'); break; case 'requires_payment_method': setStatus('error'); setMessage('Payment failed. Please try again with a different payment method.'); break; default: setStatus('error'); setMessage('Something went wrong. Please contact support.'); } }); }, [stripe, router.isReady]); // ... render status }

Error Handling That Actually Works

// utils/stripeErrors.ts import Stripe from 'stripe'; export interface PaymentErrorResponse { code: string; message: string; userMessage: string; isRetryable: boolean; requiresAction?: 'update_card' | 'contact_bank' | 'try_again'; } export function mapStripeError(error: Stripe.StripeError): PaymentErrorResponse { // Card errors - show to user if (error.type === 'card_error') { switch (error.code) { case 'card_declined': return { code: 'card_declined', message: error.message || 'Card declined', userMessage: 'Your card was declined. Please try a different card or contact your bank.', isRetryable: true, requiresAction: 'contact_bank', }; case 'insufficient_funds': return { code: 'insufficient_funds', message: 'Insufficient funds', userMessage: 'Your card has insufficient funds. Please try a different card.', isRetryable: true, requiresAction: 'update_card', }; case 'expired_card': return { code: 'expired_card', message: 'Card expired', userMessage: 'Your card has expired. Please use a different card.', isRetryable: true, requiresAction: 'update_card', }; case 'incorrect_cvc': return { code: 'incorrect_cvc', message: 'Incorrect CVC', userMessage: 'The security code (CVC) is incorrect. Please check and try again.', isRetryable: true, requiresAction: 'try_again', }; default: return { code: error.code || 'card_error', message: error.message || 'Card error', userMessage: 'There was a problem with your card. Please try again or use a different card.', isRetryable: true, }; } } // Rate limit errors if (error.type === 'rate_limit_error') { return { code: 'rate_limit', message: 'Rate limited by Stripe', userMessage: 'We\'re experiencing high traffic. Please wait a moment and try again.', isRetryable: true, }; } // API errors - internal, don't expose details return { code: 'payment_error', message: error.message || 'Payment processing error', userMessage: 'We couldn\'t process your payment. Please try again or contact support.', isRetryable: false, }; }

Common Mistakes I've Seen in Production

1. Not Using Idempotency Keys

// ❌ BAD: Creates duplicate charges on retry const paymentIntent = await stripe.paymentIntents.create({ amount: 1000, currency: 'usd', }); // ✅ GOOD: Safe to retry const paymentIntent = await stripe.paymentIntents.create( { amount: 1000, currency: 'usd', }, { idempotencyKey: `order_${orderId}_payment`, } );

2. Trusting Client-Side Amount

// ❌ BAD: Amount comes from client app.post('/create-payment', (req, res) => { const { amount } = req.body; // NEVER trust this stripe.paymentIntents.create({ amount }); }); // ✅ GOOD: Amount from server-side order app.post('/create-payment', (req, res) => { const order = await Order.findById(req.body.orderId); stripe.paymentIntents.create({ amount: order.totalCents }); });

3. Not Handling Webhook Failures

// ❌ BAD: Single point of failure router.post('/webhook', (req, res) => { const event = stripe.webhooks.constructEvent(...); await processEvent(event); // If this fails, payment is lost res.json({ received: true }); }); // ✅ GOOD: Persistent queue for reliability router.post('/webhook', (req, res) => { const event = stripe.webhooks.constructEvent(...); await webhookQueue.add('process-stripe-event', event); res.json({ received: true }); // Ack immediately });

4. Not Testing with Stripe's Test Cards

Always test these scenarios:

  • 4242424242424242 - Successful payment
  • 4000000000000002 - Card declined
  • 4000002500003155 - Requires 3D Secure
  • 4000000000009995 - Insufficient funds

Conclusion

A production Stripe integration requires:

  1. Never handle raw card data - Use Stripe.js and Payment Elements
  2. Idempotent webhooks - Your primary source of truth for payment status
  3. Proper error handling - Map Stripe errors to user-friendly messages
  4. SCA/3D Secure support - Required in many regions
  5. Idempotency keys - Prevent duplicate charges
  6. Server-side amount validation - Never trust the client

The official Stripe docs get you started. This guide helps you stay running in production.

Related Articles

Security Engineering18 min read

API Security Hardening: A Practitioner's Guide

Secure your APIs with rate limiting, input validation, and CORS configuration. Production-tested checklist covering authentication, encryption, and error handling.

Backend Design17 min read

Queue-Based Architecture for Reliable Processing

Build reliable message queue systems with Redis, RabbitMQ, and AWS SQS. Covers dead letter queues, idempotency, and real-world processing patterns.