# How to refund a payment in Stripe

- Tool: Stripe
- Difficulty: Beginner
- Time required: 10 minutes
- Compatibility: Stripe API v2024-12+, Node.js 18+, Stripe Dashboard
- Last updated: March 2026

## 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.

## Before you start

- 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.

```
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const refund = await stripe.refunds.create({
  payment_intent: 'pi_ABC123',
  reason: 'requested_by_customer', // or 'duplicate', 'fraudulent'
});

console.log('Refund ID:', refund.id);
console.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.

```
const partialRefund = await stripe.refunds.create({
  payment_intent: 'pi_ABC123',
  amount: 1500, // Refund $15.00 of a larger charge
});

console.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.

```
const refund = await stripe.refunds.retrieve('re_XYZ789');
console.log('Refund status:', refund.status);
```

**Expected result:** The refund status is returned (typically 'succeeded' for card refunds).

## Complete code example

File: `refund-payment.js`

```javascript
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const app = express();
app.use(express.json());

// Issue a refund
app.post('/api/refunds', async (req, res) => {
  try {
    const { paymentIntentId, amount, reason } = req.body;

    if (!paymentIntentId) {
      return res.status(400).json({ error: 'paymentIntentId is required' });
    }

    const refundParams = {
      payment_intent: paymentIntentId,
      reason: reason || 'requested_by_customer',
    };

    // If amount is provided, do a partial refund
    if (amount) {
      refundParams.amount = amount; // in cents
    }

    const refund = await stripe.refunds.create(refundParams);

    res.json({
      id: refund.id,
      amount: refund.amount / 100,
      currency: refund.currency,
      status: refund.status,
      reason: refund.reason,
    });
  } catch (err) {
    console.error('Refund error:', err.message);

    if (err.code === 'charge_already_refunded') {
      return res.status(400).json({ error: 'This payment has already been fully refunded.' });
    }

    res.status(500).json({ error: err.message });
  }
});

// Get refund status
app.get('/api/refunds/:id', async (req, res) => {
  try {
    const refund = await stripe.refunds.retrieve(req.params.id);
    res.json({
      id: refund.id,
      amount: refund.amount / 100,
      status: refund.status,
      created: new Date(refund.created * 1000).toISOString(),
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server on port ${PORT}`));
```

## Common mistakes

- **Trying to refund more than the original charge amount** — undefined Fix: Stripe rejects refunds that exceed the original amount. Check the charge's amount_captured before issuing a refund.
- **Expecting Stripe to return processing fees on refunds** — undefined Fix: Stripe does not refund the original processing fee (2.9% + $0.30). This means a full refund still costs you the Stripe fee.
- **Telling customers the refund is instant** — undefined Fix: Stripe processes refunds immediately, but banks take 5-10 business days to post the credit. Set customer expectations accordingly.
- **Not checking the refund status for non-card payments** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-refund-a-payment-in-stripe
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-refund-a-payment-in-stripe
