Invoice Refunds

Process refunds for paid invoices using the refund endpoint. This section covers refund conditions, processing, and error handling for different payment types.

Overview

When an invoice has been paid, you can trigger a refund event to return money to the customer. Refunds are only available for specific payment types and have certain conditions that must be met.

Refund Conditions

Prerequisites

  • The merchant must have a Card- or MobilePay Recurring merchant agreement
  • The invoice must be paid with a refundable payment option
  • The refundable amount must be equal to or less than the original invoice amount
  • The payment must comply with general refundable payment terms

Refundable Payment Types

  • Cards: Dankort, Visa, MasterCard
  • MobilePay: MobilePay Recurring, MobilePay E-Payment

Non-Refundable Payment Types

  • FI: Payment slip (manual payment)
  • LS: LeverandørService (direct debit for businesses)
  • BS: BetalingsService (direct debit for private customers)

API Endpoint

Refund Invoice

POST https://api.farpay.io/v2/invoices/{id}/refund

Parameters:

  • id (path) - The invoice ID to refund

Request Body:

{
  "Amount": 250.50
}

Headers:

X-API-KEY: your-api-key
Content-Type: application/json
Accept: application/json

Refund Processing

Amount Specification

  • Partial refund: Specify the amount to refund
  • Full refund: Omit the Amount field or set to original amount
  • Multiple refunds: You can refund multiple times until the original amount is exhausted

Refund Example

Original Invoice: 500.00 DKK

Partial Refund: 250.50 DKK

{
  "Amount": 250.50
}

Full Refund: 500.00 DKK

{
  "Amount": 500.00
}

Full Refund (no amount specified):

{}

Response Handling

Success Response (HTTP 200)

Returns a text description confirming the refund amount (partial or full).

Error Response (HTTP 400)

Returns specific error codes with descriptions:

Error CodeDescription
20050:100Den oprindelige faktura findes ikke. (Original invoice not found)
20050:200Beløbet er større end det oprindelige betalte beløb. (Amount is greater than original paid amount)
20050:300Fakturaen har ingen angivelse af den oprindelige betalingstype (kort, eller MobilePay), der kan refunderes. (Invoice has no indication of original payment type that can be refunded)
20050:400Den oprindelige betaling blev ikke fundet. (Original payment was not found)
20050:450Beløbet er større end det oprindelige betalte beløb. (Amount is greater than original paid amount)
20050:460Ønskede beløb er for højt. Det kan højest være: {beløb} {valuta} (Requested amount is too high. It can be at most: {amount} {currency})

Implementation Examples

JavaScript Example

async function refundInvoice(invoiceId, amount) {
  try {
    const response = await fetch(`https://api.farpay.io/v2/invoices/${invoiceId}/refund`, {
      method: 'POST',
      headers: {
        'X-API-KEY': 'your-api-key',
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify({ Amount: amount })
    });

    if (response.ok) {
      const result = await response.text();
      console.log('Refund successful:', result);
      return { success: true, message: result };
    } else {
      const error = await response.json();
      console.error('Refund failed:', error);
      return { success: false, error: error };
    }
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}

// Usage examples
// Partial refund
refundInvoice(12345, 250.50)
  .then(result => {
    if (result.success) {
      console.log('Partial refund processed');
    } else {
      console.error('Refund failed:', result.error);
    }
  });

// Full refund
refundInvoice(12345, 500.00)
  .then(result => {
    if (result.success) {
      console.log('Full refund processed');
    }
  });

Python Example

import requests
import json

def refund_invoice(invoice_id, amount, api_key):
    url = f'https://api.farpay.io/v2/invoices/{invoice_id}/refund'
    
    headers = {
        'X-API-KEY': api_key,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }
    
    payload = {
        'Amount': amount
    }
    
    try:
        response = requests.post(url, headers=headers, json=payload)
        
        if response.status_code == 200:
            result = response.text
            print('Refund successful:', result)
            return {'success': True, 'message': result}
        else:
            error = response.json()
            print('Refund failed:', error)
            return {'success': False, 'error': error}
            
    except requests.exceptions.RequestException as e:
        print('Request failed:', e)
        raise e

# Usage
api_key = 'your-api-key'

# Partial refund
result = refund_invoice(12345, 250.50, api_key)
if result['success']:
    print('Partial refund processed')
else:
    print('Refund failed:', result['error'])

# Full refund
result = refund_invoice(12345, 500.00, api_key)
if result['success']:
    print('Full refund processed')

cURL Example

# Partial refund
curl -X POST \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --header "X-API-KEY: mySecretKey" \
  -d '{ "Amount": 250.50 }' \
  "https://api.farpay.io/v2/invoices/12345/refund"

# Full refund
curl -X POST \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --header "X-API-KEY: mySecretKey" \
  -d '{ "Amount": 500.00 }' \
  "https://api.farpay.io/v2/invoices/12345/refund"

Error Handling

Common Error Scenarios

async function handleRefundError(error) {
  const errorCode = error.errorCode || error;
  
  switch (errorCode) {
    case '20050:100':
      console.error('Original invoice not found');
      break;
    case '20050:200':
    case '20050:450':
      console.error('Amount exceeds original payment');
      break;
    case '20050:300':
      console.error('Payment type not refundable');
      break;
    case '20050:400':
      console.error('Original payment not found');
      break;
    case '20050:460':
      console.error('Amount too high - check maximum refundable amount');
      break;
    default:
      console.error('Unknown refund error:', error);
  }
}

Validation Before Refund

async function validateRefundEligibility(invoiceId) {
  try {
    // Get invoice details
    const response = await fetch(`https://api.farpay.io/v2/invoices/${invoiceId}`, {
      headers: {
        'X-API-KEY': 'your-api-key',
        'Accept': 'application/json'
      }
    });
    
    const invoice = await response.json();
    
    // Check if invoice is paid
    if (invoice.PaymentStatus !== 'Paid') {
      return { eligible: false, reason: 'Invoice not paid' };
    }
    
    // Check payment type
    const refundableTypes = ['Visa', 'MasterCard', 'Dankort', 'MobilePayRecurring', 'MobilePayEPayment'];
    if (!refundableTypes.includes(invoice.PaymentType)) {
      return { eligible: false, reason: 'Payment type not refundable' };
    }
    
    // Check if already refunded
    if (invoice.RefundedAmount > 0) {
      const remainingAmount = invoice.PaidAmount - invoice.RefundedAmount;
      return { 
        eligible: true, 
        reason: 'Partial refund possible', 
        maxRefundAmount: remainingAmount 
      };
    }
    
    return { 
      eligible: true, 
      reason: 'Full refund possible', 
      maxRefundAmount: invoice.PaidAmount 
    };
    
  } catch (error) {
    console.error('Error validating refund eligibility:', error);
    return { eligible: false, reason: 'Validation error' };
  }
}

Refund Tracking

Check Refund Status

async function checkRefundStatus(invoiceId) {
  const response = await fetch(`https://api.farpay.io/v2/invoices/${invoiceId}`, {
    headers: {
      'X-API-KEY': 'your-api-key',
      'Accept': 'application/json'
    }
  });
  
  const invoice = await response.json();
  
  return {
    originalAmount: invoice.PaidAmount,
    refundedAmount: invoice.RefundedAmount || 0,
    remainingAmount: invoice.PaidAmount - (invoice.RefundedAmount || 0),
    refundStatus: invoice.RefundStatus
  };
}

Best Practices

Before Processing Refunds

  1. Validate eligibility - Check if invoice and payment type are refundable
  2. Verify amounts - Ensure refund amount doesn't exceed original payment
  3. Check payment status - Confirm invoice is actually paid
  4. Review customer request - Ensure refund is legitimate

During Processing

  1. Handle errors gracefully - Implement proper error handling
  2. Log all refunds - Keep detailed records of refund transactions
  3. Notify customers - Inform customers when refunds are processed
  4. Monitor refunds - Track refund patterns for fraud detection

After Processing

  1. Update records - Update your internal records
  2. Reconcile accounts - Ensure accounting records are accurate
  3. Follow up - Confirm refund appears on customer's statement
  4. Document reasons - Keep records of why refunds were issued

Use Cases

Customer Service Refund

async function processCustomerServiceRefund(invoiceId, amount, reason) {
  // Validate refund eligibility
  const eligibility = await validateRefundEligibility(invoiceId);
  
  if (!eligibility.eligible) {
    throw new Error(`Refund not eligible: ${eligibility.reason}`);
  }
  
  if (amount > eligibility.maxRefundAmount) {
    throw new Error(`Amount exceeds maximum refundable amount: ${eligibility.maxRefundAmount}`);
  }
  
  // Process refund
  const result = await refundInvoice(invoiceId, amount);
  
  if (result.success) {
    // Log refund for customer service
    await logRefund(invoiceId, amount, reason, 'customer-service');
    
    // Notify customer
    await notifyCustomerOfRefund(invoiceId, amount, reason);
    
    return result;
  } else {
    throw new Error(`Refund failed: ${result.error}`);
  }
}

Partial Refund for Defective Product

async function refundDefectiveProduct(invoiceId, defectiveItems) {
  // Calculate refund amount based on defective items
  const refundAmount = calculateRefundAmount(defectiveItems);
  
  // Process partial refund
  const result = await refundInvoice(invoiceId, refundAmount);
  
  if (result.success) {
    // Update inventory
    await updateInventory(defectiveItems);
    
    // Schedule replacement shipment
    await scheduleReplacement(defectiveItems);
    
    return result;
  }
}

Related Endpoints