Skip to main content

Refunds

Handle refunds seamlessly with automated processing, partial refunds, and comprehensive audit trails. Process full and partial refunds with real-time status updates, automated notifications, and detailed reporting for complete transaction lifecycle management.

⚡ Instant Processing

Real-time refund processing and notifications

📊 Complete Tracking

Detailed refund history and analytics

How It Works

🔄 Refund Process Flow

1

Initiate Refund

Create refund request for original transaction
2

Validation

System validates refund eligibility and amount
3

Processing

Refund processed through original payment method
4

Notification

Customer automatically notified of refund status
5

Settlement

Funds returned to customer’s account

💡 Refund Types

💯 Full Refund

Complete transaction reversal

📊 Partial Refund

Refund specific amount or items

🔄 Chargeback Protection

Proactive dispute resolution

API Implementation

🚀 Create Refund

curl https://api.payvessel.com/api/v1/refunds \
  -H "api-key: PVKEY-3ZO1QOSQH83C5Q3PBCVUT1" \
  -H "api-secret: Bearer PVSECRET-OZJD0SZ2F2WOTXAF" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_id": "pmt_1234567890abcdef",
    "amount": 15000,
    "reason": "customer_request",
    "description": "Customer requested refund for defective product",
    "notify_customer": true,
    "refund_application_fee": false,
    "metadata": {
      "order_id": "ORD-2024-001",
      "return_tracking": "RT123456789",
      "customer_service_ticket": "CS-789"
    }
  }'

📝 Request Parameters

payment_id (string, required) - ID of the original payment amount (number, optional) - Refund amount in cents (defaults to full amount) reason (string, required) - Reason for refund description (string, optional) - Detailed description
customer_request - Customer requested refund fraudulent - Fraudulent transaction duplicate - Duplicate charge product_not_received - Product not delivered product_defective - Defective product cancelled_order - Order cancellation
notify_customer (boolean) - Send refund notification refund_application_fee (boolean) - Include processing fees instant_refund (boolean) - Process immediately if available metadata (object) - Custom tracking information

✅ Successful Response

{
  "id": "ref_1234567890abcdef",
  "object": "refund",
  "payment_id": "pmt_1234567890abcdef",
  "amount": 15000,
  "currency": "USD",
  "status": "succeeded",
  "reason": "customer_request",
  "description": "Customer requested refund for defective product",
  "created": 1634567890,
  "receipt_number": "RF-2024-001-123456",
  "refund_method": "card",
  "estimated_arrival": "2024-01-20T00:00:00Z",
  "fees_refunded": 0,
  "original_payment": {
    "id": "pmt_1234567890abcdef",
    "amount": 25000,
    "currency": "USD",
    "created": 1634467890
  },
  "metadata": {
    "order_id": "ORD-2024-001",
    "return_tracking": "RT123456789",
    "customer_service_ticket": "CS-789"
  }
}

Implementation Examples

🟨 Node.js Implementation

const payvessel = require('payvessel')('PVKEY-3ZO1QOSQH83C5Q3PBCVUT1');

async function processRefund(refundData) {
  try {
    const refund = await payvessel.refunds.create({
      payment_id: refundData.payment_id,
      amount: refundData.amount, // Optional for partial refund
      reason: refundData.reason,
      description: refundData.description,
      notify_customer: refundData.notify_customer !== false,
      refund_application_fee: refundData.refund_application_fee || false,
      metadata: refundData.metadata || {}
    });

    console.log('Refund processed:', refund.id);
    console.log('Status:', refund.status);
    console.log('Receipt number:', refund.receipt_number);

    // Log refund for internal tracking
    await logRefundActivity(refund);

    // Update order status if applicable
    if (refundData.metadata?.order_id) {
      await updateOrderStatus(refundData.metadata.order_id, 'refunded');
    }

    return refund;
  } catch (error) {
    console.error('Refund processing error:', error.message);
    
    // Handle specific error cases
    if (error.code === 'charge_already_refunded') {
      throw new Error('This payment has already been fully refunded');
    } else if (error.code === 'charge_expired_for_capture') {
      throw new Error('Payment is too old to refund');
    }
    
    throw error;
  }
}

async function getRefundStatus(refundId) {
  try {
    const refund = await payvessel.refunds.retrieve(refundId);
    return {
      id: refund.id,
      status: refund.status,
      amount: refund.amount,
      estimated_arrival: refund.estimated_arrival,
      receipt_number: refund.receipt_number
    };
  } catch (error) {
    console.error('Refund retrieval error:', error.message);
    throw error;
  }
}

async function listRefunds(filters = {}) {
  try {
    const refunds = await payvessel.refunds.list({
      limit: filters.limit || 10,
      created: filters.created,
      payment_id: filters.payment_id,
      status: filters.status
    });

    return refunds.data.map(refund => ({
      id: refund.id,
      payment_id: refund.payment_id,
      amount: refund.amount,
      status: refund.status,
      reason: refund.reason,
      created: refund.created
    }));
  } catch (error) {
    console.error('Refund listing error:', error.message);
    throw error;
  }
}

// Usage examples
const refund = await processRefund({
  payment_id: 'pmt_1234567890abcdef',
  amount: 15000, // $150.00 partial refund
  reason: 'customer_request',
  description: 'Customer not satisfied with product quality',
  metadata: {
    order_id: 'ORD-2024-001',
    customer_service_ticket: 'CS-789'
  }
});

🐍 Python Implementation

import payvessel
from datetime import datetime

payvessel.api_key = "PVKEY-3ZO1QOSQH83C5Q3PBCVUT1"

def process_refund(refund_data):
    try:
        refund = payvessel.Refund.create(
            payment_id=refund_data['payment_id'],
            amount=refund_data.get('amount'),  # None for full refund
            reason=refund_data['reason'],
            description=refund_data.get('description'),
            notify_customer=refund_data.get('notify_customer', True),
            refund_application_fee=refund_data.get('refund_application_fee', False),
            metadata=refund_data.get('metadata', {})
        )

        print(f'Refund processed: {refund.id}')
        print(f'Status: {refund.status}')
        print(f'Receipt number: {refund.receipt_number}')

        # Log refund activity
        log_refund_activity(refund)

        # Send confirmation email if needed
        if refund_data.get('send_confirmation'):
            send_refund_confirmation(refund)

        return refund
    
    except payvessel.error.InvalidRequestError as e:
        if 'already_refunded' in str(e):
            raise Exception('Payment has already been fully refunded')
        elif 'insufficient_funds' in str(e):
            raise Exception('Insufficient funds available for refund')
        else:
            raise Exception(f'Refund processing error: {e.user_message}')
    
    except payvessel.error.PayvesselError as e:
        print(f'Payvessel error: {e.user_message}')
        raise

def calculate_refund_eligibility(payment_id):
    try:
        payment = payvessel.Payment.retrieve(payment_id)
        refunds = payvessel.Refund.list(payment_id=payment_id)
        
        total_refunded = sum(refund.amount for refund in refunds.data)
        available_amount = payment.amount - total_refunded
        
        return {
            'payment_id': payment_id,
            'original_amount': payment.amount,
            'total_refunded': total_refunded,
            'available_for_refund': available_amount,
            'can_refund': available_amount > 0,
            'refund_deadline': calculate_refund_deadline(payment.created)
        }
    
    except payvessel.error.PayvesselError as e:
        print(f'Eligibility check error: {e.user_message}')
        raise

def bulk_refund_processing(refund_requests):
    results = []
    
    for request in refund_requests:
        try:
            refund = process_refund(request)
            results.append({
                'request_id': request.get('request_id'),
                'refund_id': refund.id,
                'status': 'success',
                'amount': refund.amount
            })
        except Exception as e:
            results.append({
                'request_id': request.get('request_id'),
                'status': 'failed',
                'error': str(e)
            })
    
    return results

# Usage example
refund = process_refund({
    'payment_id': 'pmt_1234567890abcdef',
    'amount': 15000,  # $150.00
    'reason': 'product_defective',
    'description': 'Product arrived damaged',
    'notify_customer': True,
    'metadata': {
        'order_id': 'ORD-2024-001',
        'return_reason': 'damaged_in_shipping'
    }
})

🐘 PHP Implementation

<?php
require_once('vendor/autoload.php');

\Payvessel\Payvessel::setApiKey("PVKEY-3ZO1QOSQH83C5Q3PBCVUT1");

function processRefund($refundData) {
    try {
        $refund = \Payvessel\Refund::create([
            'payment_id' => $refundData['payment_id'],
            'amount' => $refundData['amount'] ?? null, // null for full refund
            'reason' => $refundData['reason'],
            'description' => $refundData['description'] ?? null,
            'notify_customer' => $refundData['notify_customer'] ?? true,
            'refund_application_fee' => $refundData['refund_application_fee'] ?? false,
            'metadata' => $refundData['metadata'] ?? []
        ]);

        echo "Refund processed: " . $refund->id . "\n";
        echo "Status: " . $refund->status . "\n";
        echo "Receipt number: " . $refund->receipt_number . "\n";

        // Log refund for tracking
        logRefundActivity($refund);

        // Update internal systems
        if (isset($refundData['metadata']['order_id'])) {
            updateOrderStatus($refundData['metadata']['order_id'], 'refunded');
        }

        return $refund;
    } catch (\Payvessel\Exception\InvalidRequestException $e) {
        if (strpos($e->getMessage(), 'already_refunded') !== false) {
            throw new Exception('Payment has already been fully refunded');
        } elseif (strpos($e->getMessage(), 'charge_expired') !== false) {
            throw new Exception('Payment is too old to refund');
        } else {
            throw new Exception('Refund processing error: ' . $e->getMessage());
        }
    } catch (\Payvessel\Exception\ApiErrorException $e) {
        echo "Refund processing error: " . $e->getMessage() . "\n";
        throw $e;
    }
}

function getRefundAnalytics($dateRange = null) {
    try {
        $params = ['limit' => 100];
        if ($dateRange) {
            $params['created'] = $dateRange;
        }

        $refunds = \Payvessel\Refund::all($params);
        
        $analytics = [
            'total_refunds' => count($refunds->data),
            'total_amount' => 0,
            'by_reason' => [],
            'by_status' => []
        ];

        foreach ($refunds->data as $refund) {
            $analytics['total_amount'] += $refund->amount;
            
            // Group by reason
            $reason = $refund->reason;
            $analytics['by_reason'][$reason] = ($analytics['by_reason'][$reason] ?? 0) + 1;
            
            // Group by status
            $status = $refund->status;
            $analytics['by_status'][$status] = ($analytics['by_status'][$status] ?? 0) + 1;
        }

        return $analytics;
    } catch (\Payvessel\Exception\ApiErrorException $e) {
        echo "Analytics error: " . $e->getMessage() . "\n";
        throw $e;
    }
}

// Usage example
$refund = processRefund([
    'payment_id' => 'pmt_1234567890abcdef',
    'amount' => 15000, // $150.00
    'reason' => 'customer_request',
    'description' => 'Customer changed mind about purchase',
    'notify_customer' => true,
    'metadata' => [
        'order_id' => 'ORD-2024-001',
        'customer_service_rep' => 'agent_456'
    ]
]);
?>

Advanced Refund Features

🔄 Instant Refunds

Enable immediate refund processing for eligible transactions:
const instantRefund = await payvessel.refunds.create({
  payment_id: 'pmt_1234567890abcdef',
  amount: 10000,
  reason: 'customer_request',
  instant_refund: true, // Request immediate processing
  priority: 'high'
});

if (instantRefund.status === 'succeeded') {
  console.log('Instant refund completed');
} else {
  console.log('Refund queued for standard processing');
}

📊 Batch Refund Processing

Process multiple refunds efficiently:
const batchRefunds = await payvessel.refunds.createBatch([
  {
    payment_id: 'pmt_1111111111111111',
    reason: 'event_cancelled',
    description: 'Event cancelled due to weather'
  },
  {
    payment_id: 'pmt_2222222222222222',
    reason: 'event_cancelled',
    description: 'Event cancelled due to weather'
  }
]);

console.log('Batch refund status:', batchRefunds.status);
console.log('Successful refunds:', batchRefunds.successful_count);
console.log('Failed refunds:', batchRefunds.failed_count);

🎯 Conditional Refunds

Set up automatic refund conditions:
const conditionalRefund = await payvessel.refunds.create({
  payment_id: 'pmt_1234567890abcdef',
  amount: 20000,
  reason: 'product_not_received',
  conditions: {
    delay_days: 7, // Wait 7 days before processing
    require_confirmation: true, // Require customer confirmation
    auto_cancel_if: {
      product_delivered: true, // Cancel if product is delivered
      dispute_opened: true // Cancel if customer opens dispute
    }
  }
});

Refund Management

📋 List All Refunds

curl "https://api.payvessel.com/api/v1/refunds?limit=20&status=succeeded" \
  -H "api-key: PVKEY-3ZO1QOSQH83C5Q3PBCVUT1" \
  -H "api-secret: Bearer PVSECRET-OZJD0SZ2F2WOTXAF"

📊 Refund Analytics

{
  "object": "list",
  "data": [
    {
      "id": "ref_1234567890abcdef",
      "payment_id": "pmt_1234567890abcdef",
      "amount": 15000,
      "status": "succeeded",
      "reason": "customer_request",
      "created": 1634567890
    }
  ],
  "total_count": 45,
  "total_amount": 675000,
  "analytics": {
    "refund_rate": "3.2%",
    "average_refund_amount": 15000,
    "most_common_reason": "customer_request",
    "processing_time_avg": "2.3 hours"
  }
}

🔍 Refund Details

Get comprehensive refund information:
curl https://api.payvessel.com/api/v1/refunds/ref_1234567890abcdef \
  -H "api-key: PVKEY-3ZO1QOSQH83C5Q3PBCVUT1" \
  -H "api-secret: Bearer PVSECRET-OZJD0SZ2F2WOTXAF"

Industry Applications

🛍️ E-commerce Refunds

Return Processing:
  • Automatic refund on return receipt
  • Partial refunds for damaged items
  • Restocking fee deductions
  • Return shipping cost handling
Service Refunds:
  • Pro-rated refund calculations
  • Unused service period refunds
  • Downgrade refund processing
  • Cancellation refund automation
Event Cancellations:
  • Bulk refund processing
  • Tiered refund schedules
  • Transfer fee handling
  • Insurance claim integration

🏢 Service Industries

✈️ Travel & Hospitality

  • Booking cancellation refunds
  • Weather-related refunds
  • Schedule change compensation
  • Travel insurance integration

🎓 Education

  • Course cancellation refunds
  • Withdrawal refund schedules
  • Material fee refunds
  • Transfer credit processing

Dispute Prevention

🛡️ Proactive Refund Strategy

Reduce chargebacks with strategic refund management:
// Implement dispute prevention logic
async function preventDispute(paymentId, customerComplaint) {
  const payment = await payvessel.payments.retrieve(paymentId);
  
  // Check if proactive refund would be beneficial
  const disputeRisk = await assessDisputeRisk(payment, customerComplaint);
  
  if (disputeRisk.score > 0.7) {
    // Process proactive refund to prevent dispute
    const refund = await payvessel.refunds.create({
      payment_id: paymentId,
      reason: 'dispute_prevention',
      description: 'Proactive refund to resolve customer concern',
      notify_customer: true,
      metadata: {
        dispute_risk_score: disputeRisk.score,
        prevention_strategy: true
      }
    });
    
    console.log('Proactive refund processed to prevent dispute');
    return refund;
  }
}

📞 Customer Service Integration

// Integrate with customer service systems
function integrateWithCustomerService(refund) {
  // Create ticket in customer service system
  const ticket = customerService.createTicket({
    type: 'refund_processed',
    customer_email: refund.customer_email,
    refund_id: refund.id,
    amount: refund.amount,
    reason: refund.reason,
    priority: refund.amount > 50000 ? 'high' : 'normal'
  });
  
  // Send internal notification
  notificationService.send({
    to: '[email protected]',
    subject: `Refund Processed: ${refund.receipt_number}`,
    template: 'refund_notification',
    data: { refund, ticket }
  });
}

Refund Reporting

📊 Comprehensive Analytics

Track refund metrics and trends:
async function generateRefundReport(period) {
  const report = await payvessel.refunds.analytics({
    period: period,
    group_by: ['reason', 'status', 'payment_method'],
    include_trends: true
  });

  return {
    summary: {
      total_refunds: report.total_count,
      total_amount: report.total_amount,
      refund_rate: report.refund_rate,
      average_processing_time: report.avg_processing_time
    },
    breakdown: {
      by_reason: report.by_reason,
      by_payment_method: report.by_payment_method,
      by_status: report.by_status
    },
    trends: {
      volume_trend: report.volume_trend,
      amount_trend: report.amount_trend,
      reason_trends: report.reason_trends
    }
  };
}

Webhook Integration

🔔 Refund Events

Monitor refund status changes:
// Webhook handler for refund events
app.post('/webhook/refunds', (req, res) => {
  const event = req.body;
  
  switch (event.type) {
    case 'refund.created':
      handleRefundCreated(event.data);
      break;
    case 'refund.succeeded':
      handleRefundSucceeded(event.data);
      break;
    case 'refund.failed':
      handleRefundFailed(event.data);
      break;
    case 'refund.updated':
      handleRefundUpdated(event.data);
      break;
  }
  
  res.status(200).send('OK');
});

function handleRefundSucceeded(refundData) {
  // Update order status
  updateOrderStatus(refundData.metadata.order_id, 'refunded');
  
  // Send customer notification
  sendRefundConfirmation(refundData);
  
  // Update inventory if applicable
  if (refundData.metadata.return_to_inventory) {
    restockItems(refundData.metadata.items);
  }
  
  // Log for analytics
  logRefundCompletion(refundData);
}

Best Practices

✅ Refund Management Tips

⚡ Speed Optimization

  • Process refunds quickly
  • Use instant refunds when available
  • Automate common refund scenarios
  • Set clear processing timelines

🤝 Customer Experience

  • Provide clear refund policies
  • Send proactive status updates
  • Offer partial refund options
  • Maintain professional communication

🔍 Fraud Prevention

Warning Signs:
  • Frequent refund requests from same customer
  • Large refund amounts shortly after payment
  • Unusual refund patterns or timing
  • Requests to different payment methods
Prevention Measures:
  • Set refund amount limits
  • Implement approval workflows
  • Monitor refund velocity
  • Verify refund legitimacy

📈 Performance Optimization

  • Batch Processing - Handle multiple refunds efficiently
  • Conditional Logic - Automate refund decisions
  • Integration Points - Connect with existing systems
  • Reporting - Track performance metrics
Refund Limits: Most payment methods have time limits for refunds (typically 180 days). Check payment method specific limitations before processing refunds.