Refund a Stripe payment through the Dashboard (Payments → select payment → Refund) or via the API with stripe.refunds.create({ payment_intent }). You can issue full or partial refunds. Refunds take 5-10 business days to appear on the customer's statement. Stripe does not return processing fees on refunds.
Issuing Refunds in Stripe
Refunds are a normal part of running a business — whether for returns, cancellations, or customer service goodwill. Stripe lets you refund any successful payment, either fully or partially, from the Dashboard or the API. A refund reverses the charge and credits the customer's original payment method. Stripe processes the refund immediately on its end, but banks typically take 5-10 business days to post the credit to the customer's account.
Prerequisites
- A Stripe account with at least one successful payment
- Node.js 18 or newer installed (for API method)
- Your Stripe secret key (sk_test_...) from Dashboard → Developers → API keys
Step-by-step guide
Refund via the Stripe Dashboard
Refund via the Stripe Dashboard
Go to Payments in the left sidebar, find and click the payment you want to refund, then click the 'Refund' button in the top right. Choose full or partial refund, enter the amount if partial, add an optional reason, and confirm.
Expected result: The payment shows a 'Refunded' or 'Partially refunded' badge. The customer receives a credit to their original payment method.
Issue a full refund via the API
Issue a full refund via the API
Call stripe.refunds.create() with the payment_intent ID. Omitting the amount field creates a full refund.
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);23const refund = await stripe.refunds.create({4 payment_intent: 'pi_ABC123',5 reason: 'requested_by_customer', // or 'duplicate', 'fraudulent'6});78console.log('Refund ID:', refund.id);9console.log('Amount refunded:', refund.amount / 100);Expected result: A full refund is created. The refund object shows the amount, status, and reason.
Issue a partial refund
Issue a partial refund
Pass an amount (in cents) to refund only part of the original charge. You can issue multiple partial refunds until the total equals the original charge amount.
1const partialRefund = await stripe.refunds.create({2 payment_intent: 'pi_ABC123',3 amount: 1500, // Refund $15.00 of a larger charge4});56console.log('Partial refund:', partialRefund.amount / 100);Expected result: A $15.00 partial refund is created. The payment shows as 'Partially refunded' in the Dashboard.
Check refund status
Check refund status
Retrieve a refund to check its current status. Possible values are succeeded, pending, failed, and canceled.
1const refund = await stripe.refunds.retrieve('re_XYZ789');2console.log('Refund status:', refund.status);Expected result: The refund status is returned (typically 'succeeded' for card refunds).
Complete working example
1const express = require('express');2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);34const app = express();5app.use(express.json());67// Issue a refund8app.post('/api/refunds', async (req, res) => {9 try {10 const { paymentIntentId, amount, reason } = req.body;1112 if (!paymentIntentId) {13 return res.status(400).json({ error: 'paymentIntentId is required' });14 }1516 const refundParams = {17 payment_intent: paymentIntentId,18 reason: reason || 'requested_by_customer',19 };2021 // If amount is provided, do a partial refund22 if (amount) {23 refundParams.amount = amount; // in cents24 }2526 const refund = await stripe.refunds.create(refundParams);2728 res.json({29 id: refund.id,30 amount: refund.amount / 100,31 currency: refund.currency,32 status: refund.status,33 reason: refund.reason,34 });35 } catch (err) {36 console.error('Refund error:', err.message);3738 if (err.code === 'charge_already_refunded') {39 return res.status(400).json({ error: 'This payment has already been fully refunded.' });40 }4142 res.status(500).json({ error: err.message });43 }44});4546// Get refund status47app.get('/api/refunds/:id', async (req, res) => {48 try {49 const refund = await stripe.refunds.retrieve(req.params.id);50 res.json({51 id: refund.id,52 amount: refund.amount / 100,53 status: refund.status,54 created: new Date(refund.created * 1000).toISOString(),55 });56 } catch (err) {57 res.status(500).json({ error: err.message });58 }59});6061const PORT = process.env.PORT || 3000;62app.listen(PORT, () => console.log(`Server on port ${PORT}`));Common mistakes when refunding a payment in Stripe
Why it's a problem: Trying to refund more than the original charge amount
How to avoid: Stripe rejects refunds that exceed the original amount. Check the charge's amount_captured before issuing a refund.
Why it's a problem: Expecting Stripe to return processing fees on refunds
How to avoid: Stripe does not refund the original processing fee (2.9% + $0.30). This means a full refund still costs you the Stripe fee.
Why it's a problem: Telling customers the refund is instant
How to avoid: Stripe processes refunds immediately, but banks take 5-10 business days to post the credit. Set customer expectations accordingly.
Why it's a problem: Not checking the refund status for non-card payments
How to avoid: Bank transfer refunds can fail. Check refund.status and set up a webhook for refund.failed events.
Best practices
- Always include a reason (requested_by_customer, duplicate, or fraudulent) for tracking and reporting
- Use partial refunds when appropriate — they are less costly than full refunds since you keep revenue on the non-refunded portion
- Set up webhook listeners for charge.refunded and refund.failed events
- Keep a record of refund IDs and amounts in your database for reconciliation
- Communicate refund timelines clearly to customers (5-10 business days for card refunds)
- Test refunds in test mode with card 4242424242424242 to verify your flow before going live
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Write a Node.js Express endpoint that issues Stripe refunds. Accept a payment intent ID and an optional amount for partial refunds. Include a reason parameter. Handle the charge_already_refunded error. Use the stripe npm package.
Add a refund system to my app. Create an endpoint that accepts a payment ID and refund amount, issues the refund through the Stripe API, and handles errors like already-refunded charges. Support both full and partial refunds.
Frequently asked questions
How long does a Stripe refund take?
Stripe processes the refund within seconds, but it takes 5-10 business days for the credit to appear on the customer's bank statement. Some banks may take longer.
Does Stripe return the processing fee when I refund?
No. Stripe keeps the original processing fee. A full refund on a $100 charge still costs you approximately $3.20 (2.9% + $0.30).
Can I refund a payment older than 90 days?
Card refunds can be issued up to 90-180 days after the original charge depending on the card network. After that window, you may need to issue a credit or manual bank transfer.
Can I cancel a refund after issuing it?
You can cancel a refund only if its status is still pending. Once it reaches succeeded, it cannot be reversed. Use stripe.refunds.cancel(refundId) to cancel a pending refund.
What is the difference between a refund and a dispute?
A refund is initiated by you (the merchant). A dispute (chargeback) is initiated by the customer through their bank. Disputes carry a $15 fee and require evidence submission.
What if I need help building an automated refund workflow?
For businesses that need automated refund policies, approval workflows, and integration with inventory systems, the RapidDev team can build a custom refund management system on top of the Stripe API.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation