Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to refund a payment in Stripe

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.

What you'll learn

  • How to refund a payment via the Stripe Dashboard
  • How to issue full and partial refunds via the API
  • How refund timelines and fee handling work
  • How to track refund status and handle failures
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner5 min read10 minutesStripe API v2024-12+, Node.js 18+, Stripe DashboardMarch 2026RapidDev Engineering Team
TL;DR

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

1

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.

2

Issue a full refund via the API

Call stripe.refunds.create() with the payment_intent ID. Omitting the amount field creates a full refund.

typescript
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
2
3const refund = await stripe.refunds.create({
4 payment_intent: 'pi_ABC123',
5 reason: 'requested_by_customer', // or 'duplicate', 'fraudulent'
6});
7
8console.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.

3

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.

typescript
1const partialRefund = await stripe.refunds.create({
2 payment_intent: 'pi_ABC123',
3 amount: 1500, // Refund $15.00 of a larger charge
4});
5
6console.log('Partial refund:', partialRefund.amount / 100);

Expected result: A $15.00 partial refund is created. The payment shows as 'Partially refunded' in the Dashboard.

4

Check refund status

Retrieve a refund to check its current status. Possible values are succeeded, pending, failed, and canceled.

typescript
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

refund-payment.js
1const express = require('express');
2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
3
4const app = express();
5app.use(express.json());
6
7// Issue a refund
8app.post('/api/refunds', async (req, res) => {
9 try {
10 const { paymentIntentId, amount, reason } = req.body;
11
12 if (!paymentIntentId) {
13 return res.status(400).json({ error: 'paymentIntentId is required' });
14 }
15
16 const refundParams = {
17 payment_intent: paymentIntentId,
18 reason: reason || 'requested_by_customer',
19 };
20
21 // If amount is provided, do a partial refund
22 if (amount) {
23 refundParams.amount = amount; // in cents
24 }
25
26 const refund = await stripe.refunds.create(refundParams);
27
28 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);
37
38 if (err.code === 'charge_already_refunded') {
39 return res.status(400).json({ error: 'This payment has already been fully refunded.' });
40 }
41
42 res.status(500).json({ error: err.message });
43 }
44});
45
46// Get refund status
47app.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});
60
61const 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.

ChatGPT Prompt

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.

Stripe Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.