/stripe-guides

How to refund a payment in Stripe?

Learn how to refund a payment in Stripe using the Dashboard or API (Node.js, PHP, Python, cURL), monitor refund status, handle errors, and follow best practices.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to refund a payment in Stripe?

How to Refund a Payment in Stripe

 

Stripe provides a straightforward way to process refunds for payments. This comprehensive tutorial will walk you through different methods of refunding payments in Stripe, covering both the Dashboard interface and API implementations.

 

Step 1: Understanding Stripe Refunds

 

Before diving into the refund process, it's important to understand how refunds work in Stripe:

  • Refunds can be full or partial
  • Refunds are tied to the original charge
  • Processing time varies by payment method and bank (typically 5-10 business days)
  • Stripe refund fees policy varies by country and payment type

 

Step 2: Refunding a Payment Using the Stripe Dashboard

 

The simplest way to process a refund is through the Stripe Dashboard:

  • Log in to your Stripe Dashboard at https://dashboard.stripe.com
  • Navigate to the "Payments" section from the left sidebar
  • Find the payment you want to refund and click on it
  • Click the "Refund payment" button in the top-right corner
  • Choose between a full refund or a partial refund
  • For partial refunds, specify the amount to be refunded
  • Optionally add a reason for the refund
  • Click "Submit" to process the refund

 

Step 3: Refunding a Payment Using the Stripe API with Node.js

 

For programmatic refunds, you can use Stripe's API. Here's how to implement it with Node.js:

const stripe = require('stripe')('sk_test_your_secret_key');

async function refundPayment(chargeId, amount = null, reason = null) {
  try {
    const refundParams = {
      charge: chargeId,
      // Optional parameters
      ...(amount && { amount: amount }), // For partial refunds (in cents)
      ...(reason && { reason: reason }), // Possible values: 'duplicate', 'fraudulent', 'requested_by_customer'
    };

    const refund = await stripe.refunds.create(refundParams);
    console.log('Refund processed successfully:', refund);
    return refund;
  } catch (error) {
    console.error('Error processing refund:', error);
    throw error;
  }
}

// Example usage:
// Full refund
refundPayment('ch\_1234567890');

// Partial refund ($10.00)
refundPayment('ch\_1234567890', 1000);

// Refund with reason
refundPayment('ch_1234567890', null, 'requested_by\_customer');

 

Step 4: Refunding a Payment Using the Stripe API with PHP

 

If you're using PHP, here's how to implement refunds:

// Include Stripe PHP SDK
require\_once 'vendor/autoload.php';

// Set your secret key
\Stripe\Stripe::setApiKey('sk_test_your_secret_key');

function refundPayment($chargeId, $amount = null, $reason = null) {
  try {
    $refundParams = [
      'charge' => $chargeId,
    ];
    
    // Add optional parameters if provided
    if ($amount !== null) {
      $refundParams['amount'] = $amount; // For partial refunds (in cents)
    }
    
    if ($reason !== null) {
      $refundParams['reason'] = $reason; // 'duplicate', 'fraudulent', 'requested_by_customer'
    }
    
    $refund = \Stripe\Refund::create($refundParams);
    echo "Refund processed successfully: " . $refund->id;
    return $refund;
  } catch (\Stripe\Exception\ApiErrorException $e) {
    echo "Error processing refund: " . $e->getMessage();
    throw $e;
  }
}

// Example usage:
// Full refund
refundPayment('ch\_1234567890');

// Partial refund ($15.50)
refundPayment('ch\_1234567890', 1550);

 

Step 5: Refunding a Payment Using the Stripe API with Python

 

For Python developers, here's how to implement refunds:

import stripe

# Set your secret key
stripe.api_key = 'sk_test_your_secret\_key'

def refund_payment(charge_id, amount=None, reason=None):
    try:
        refund\_params = {
            'charge': charge\_id,
        }
        
        # Add optional parameters if provided
        if amount is not None:
            refund\_params['amount'] = amount  # For partial refunds (in cents)
        
        if reason is not None:
            refund_params['reason'] = reason  # 'duplicate', 'fraudulent', 'requested_by\_customer'
        
        refund = stripe.Refund.create(\*\*refund\_params)
        print(f"Refund processed successfully: {refund.id}")
        return refund
    except stripe.error.StripeError as e:
        print(f"Error processing refund: {e}")
        raise e

# Example usage:
# Full refund
refund_payment('ch_1234567890')

# Partial refund ($25.00)
refund_payment('ch_1234567890', 2500)

# Refund with reason
refund_payment('ch_1234567890', reason='requested_by_customer')

 

Step 6: Refunding a Payment Using cURL

 

If you prefer using cURL for API requests:

curl https://api.stripe.com/v1/refunds \\
  -u sk_test_your_secret_key: \\
  -d charge=ch\_1234567890 \\
  -d amount=1000 \\
  -d reason=requested_by_customer

 

Step 7: Handling Refund Webhooks

 

To keep your system in sync with refund statuses, set up webhooks to listen for refund events:

// Node.js example with Express
const express = require('express');
const stripe = require('stripe')('sk_test_your_secret_key');
const app = express();

// Use JSON parser for webhook requests
app.use('/webhook', express.raw({type: 'application/json'}));

app.post('/webhook', async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      'whsec_your_webhook_signing_secret'
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'charge.refunded':
      const chargeRefunded = event.data.object;
      console.log('Charge refunded:', chargeRefunded.id);
      // Update your database or notify your users
      break;
    case 'refund.created':
      const refundCreated = event.data.object;
      console.log('Refund created:', refundCreated.id);
      break;
    case 'refund.updated':
      const refundUpdated = event.data.object;
      console.log('Refund updated:', refundUpdated.id, 'Status:', refundUpdated.status);
      break;
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  // Return a 200 response to acknowledge receipt of the event
  res.send();
});

app.listen(3000, () => {
  console.log('Webhook listener running on port 3000');
});

 

Step 8: Monitoring Refund Status

 

Refunds can have various statuses that you should monitor:

  • pending: The refund has been submitted but not yet processed
  • succeeded: The refund has been successfully processed
  • failed: The refund failed to process
  • canceled: The refund was canceled

You can check a refund's status using the API:

// Node.js example
async function checkRefundStatus(refundId) {
  try {
    const refund = await stripe.refunds.retrieve(refundId);
    console.log(`Refund ${refundId} status: ${refund.status}`);
    return refund;
  } catch (error) {
    console.error('Error checking refund status:', error);
    throw error;
  }
}

// Example usage
checkRefundStatus('re\_1234567890');

 

Step 9: Handling Failed Refunds

 

Sometimes refunds can fail. Here's how to handle failed refunds:

// Node.js example
async function handleFailedRefund(refundId) {
  try {
    const refund = await stripe.refunds.retrieve(refundId);
    
    if (refund.status === 'failed') {
      console.log(`Refund ${refundId} failed. Reason: ${refund.failure_reason}`);
      
      // Common failure reasons:
      // - expired_or_canceled\_card
      // - lost_or_stolen\_card
      // - unknown
      
      // You might want to:
      // 1. Notify the customer
      // 2. Try an alternative refund method
      // 3. Provide manual support
      
      return {
        success: false,
        reason: refund.failure\_reason,
        refund: refund
      };
    }
    
    return { success: true, refund: refund };
  } catch (error) {
    console.error('Error handling failed refund:', error);
    throw error;
  }
}

 

Step 10: Best Practices for Refunds

 

Follow these best practices when implementing refunds:

  • Always verify the original payment exists before attempting a refund
  • Store refund IDs in your database for reference
  • Implement error handling for all refund operations
  • Set up webhook handlers to automatically update refund statuses
  • Communicate refund status and expected timing to customers
  • Consider implementing refund policies in your terms of service
  • Keep detailed logs of all refund operations for accounting purposes

 

Conclusion

 

Refunding payments in Stripe can be done through the Dashboard for manual processing or via the API for automated solutions. Whether you're using Node.js, PHP, Python, or direct API calls, Stripe provides flexible options to handle refunds according to your business needs. Remember to properly monitor refund statuses and implement appropriate error handling to ensure a smooth refund experience for your customers.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022