Back to all terms
Payment
Paymentsintermediate

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.

Also known as: payment refunds, refund processing, refund API, money-back flow

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

refundspaymentsstripecustomer-servicemoney-flow