Refund Flow Design
Designing the end-to-end process for returning funds to customers, including full and partial refunds, internal state management, and integration with Stripe's Refund API.
Description
Refund flow design covers the complete process of returning funds to a customer, from the business logic that determines refund eligibility through the Stripe API call to the post-refund state updates in your system. Stripe's Refund API (stripe.refunds.create()) issues refunds against a specific Charge or PaymentIntent, returning funds to the customer's original payment method. Refunds typically take 5-10 business days to appear on the customer's statement, though the refund object is created immediately in Stripe.
A well-designed refund flow includes eligibility checks (refund window, maximum refund amount, business rules), authorization controls (who can initiate refunds and up to what amount), an internal refund record that tracks the reason, amount, initiator, and approval chain, and post-refund side effects like revoking access, adjusting usage quotas, updating analytics, and sending confirmation emails. The refund reason should be captured and categorized (requested_by_customer, duplicate, fraudulent, product_not_delivered) both for Stripe's records and your own analytics.
Edge cases to handle include: refunding a payment that has already been partially refunded (the maximum additional refund is the original amount minus existing refunds), refunding a payment that is still processing (wait for the charge to succeed first), refunding after a dispute has been filed (not possible, the funds are already with the bank), and refunding a subscription payment (which doesn't automatically cancel the subscription). Your refund model should maintain its own state machine (pending, processing, succeeded, failed) independent of Stripe's refund status to handle these transitions cleanly.
Prompt Snippet
Create refunds via stripe.refunds.create({ payment_intent: pi_id, amount: amountInCents, reason: 'requested_by_customer', metadata: { internal_refund_id, initiated_by } }) and track them in a local refunds table with columns (id, payment_intent_id, amount, reason, status, initiated_by, approved_by, created_at). Implement refund eligibility checks as a policy function: verify the charge is not disputed, the total refunded amount plus this request does not exceed the original charge, and the refund is within your configurable refund window. Listen for charge.refunded and charge.refund.updated webhook events to sync the refund status, handling the edge case where Stripe refund fails after initial acceptance (status transitions from pending to failed). Enforce role-based refund limits: support agents can refund up to $50, managers up to $500, and anything above requires async approval workflow.Tags
Related Terms
Partial Refunds
Issuing a refund for less than the full payment amount, typically for individual line items, prorated usage, or goodwill credits, while tracking the remaining refundable balance.
Dispute/Chargeback Handling
Managing the process when a customer contests a charge with their bank, including automated evidence collection, response submission, and prevention strategies to minimize dispute rates.
Ledger / Transaction Log Design
Designing an append-only transaction log that records every financial event with double-entry bookkeeping principles, providing a complete audit trail and enabling balance reconciliation.
Financial Data Audit Trail
Maintaining a complete, immutable record of all financial actions and state changes for compliance, debugging, dispute resolution, and regulatory requirements.