Skip to main content
POST
/
refund-transaction
/
:id
curl -X POST https://api.blnkfinance.com/refund-transaction/txn_original_xyz789 \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "transaction_id": "txn_refund_abc123",
  "parent_transaction": "txn_original_xyz789",
  "source": "bln_merchant456",
  "destination": "bln_customer123",
  "reference": "refund-txn_original_xyz789",
  "amount": 100.50,
  "precise_amount": "10050",
  "precision": 100,
  "currency": "USD",
  "description": "Refund for original transaction",
  "status": "APPLIED",
  "hash": "refund_hash_123",
  "created_at": "2024-01-15T15:30:00Z",
  "meta_data": {
    "refund_type": "full",
    "original_transaction_id": "txn_original_xyz789"
  }
}
Create a refund transaction that reverses a previously applied transaction. The refund creates a new transaction with reversed source and destination.

Path Parameters

id
string
required
The transaction ID of the original transaction to refund.

Response

Returns the newly created refund transaction.
transaction_id
string
Unique identifier for the refund transaction.
parent_transaction
string
ID of the original transaction being refunded.
source
string
Source balance (reversed from original destination).
destination
string
Destination balance (reversed from original source).
amount
number
Refund amount (same as original transaction).
precise_amount
string
Refund amount in minor units.
reference
string
Generated reference for the refund transaction.
status
string
Refund transaction status (typically APPLIED or QUEUED).
See Get Transaction for complete field documentation.
{
  "transaction_id": "txn_refund_abc123",
  "parent_transaction": "txn_original_xyz789",
  "source": "bln_merchant456",
  "destination": "bln_customer123",
  "reference": "refund-txn_original_xyz789",
  "amount": 100.50,
  "precise_amount": "10050",
  "precision": 100,
  "currency": "USD",
  "description": "Refund for original transaction",
  "status": "APPLIED",
  "hash": "refund_hash_123",
  "created_at": "2024-01-15T15:30:00Z",
  "meta_data": {
    "refund_type": "full",
    "original_transaction_id": "txn_original_xyz789"
  }
}
curl -X POST https://api.blnkfinance.com/refund-transaction/txn_original_xyz789 \
  -H "Authorization: Bearer YOUR_API_KEY"

How Refunds Work

Original Transaction

{
  "transaction_id": "txn_original_xyz789",
  "source": "bln_customer123",
  "destination": "bln_merchant456",
  "amount": 100.50,
  "reference": "order-12345"
}
Balance Changes:
  • Customer (source): -$100.50
  • Merchant (destination): +$100.50

Refund Transaction

{
  "transaction_id": "txn_refund_abc123",
  "parent_transaction": "txn_original_xyz789",
  "source": "bln_merchant456",      // Reversed
  "destination": "bln_customer123",  // Reversed
  "amount": 100.50,
  "reference": "refund-txn_original_xyz789"
}
Balance Changes:
  • Merchant (source): -$100.50
  • Customer (destination): +$100.50
Net Result: Both balances return to original state.

Refund Behavior

Full Refunds

The refund endpoint creates a full refund for the entire transaction amount:
// Original: $100 from customer to merchant
const original = await createTransaction({
  amount: 100.00,
  source: 'customer',
  destination: 'merchant',
  reference: 'order-123'
});

// Refund: $100 from merchant back to customer
const refund = await refundTransaction(original.transaction_id);
// refund.amount === 100.00
// refund.source === 'merchant'
// refund.destination === 'customer'

Partial Refunds

For partial refunds, create a new transaction manually:
// Get original transaction
const original = await getTransaction('txn_original_xyz789');

// Create partial refund (e.g., 50% refund)
const partialRefund = await createTransaction({
  amount: original.amount * 0.5,
  source: original.destination,      // Reverse direction
  destination: original.source,      // Reverse direction
  currency: original.currency,
  reference: `partial-refund-${original.transaction_id}`,
  description: `Partial refund for ${original.reference}`,
  meta_data: {
    original_transaction_id: original.transaction_id,
    refund_type: 'partial',
    refund_percentage: 50
  }
});

Multiple Partial Refunds

async function createPartialRefund(originalTxnId, refundAmount, reason) {
  const original = await getTransaction(originalTxnId);
  
  // Track total refunded so far
  const existingRefunds = await fetch(
    `/transactions?reference_contains=refund-${originalTxnId}`
  ).then(r => r.json());
  
  const totalRefunded = existingRefunds.reduce(
    (sum, txn) => sum + txn.amount, 
    0
  );
  
  // Validate refund amount
  if (totalRefunded + refundAmount > original.amount) {
    throw new Error(
      `Refund amount $${refundAmount} exceeds remaining ` +
      `refundable amount $${original.amount - totalRefunded}`
    );
  }
  
  // Create partial refund
  return await createTransaction({
    amount: refundAmount,
    source: original.destination,
    destination: original.source,
    currency: original.currency,
    reference: `refund-${originalTxnId}-${Date.now()}`,
    description: `Partial refund: ${reason}`,
    meta_data: {
      original_transaction_id: originalTxnId,
      refund_type: 'partial',
      refund_reason: reason,
      refund_number: existingRefunds.length + 1
    }
  });
}

// Usage
await createPartialRefund('txn_123', 25.00, 'damaged_item');
await createPartialRefund('txn_123', 15.00, 'shipping_delay');
await createPartialRefund('txn_123', 60.50, 'cancel_remaining');

Error Responses

error
string
Error message describing what went wrong.

Common Errors

400 Bad Request - Missing ID
{
  "error": "id is required. pass id in the route /:id"
}
400 Bad Request - No Transaction to Refund
{
  "error": "no transaction to refund"
}
This can occur when:
  • Transaction doesn’t exist
  • Transaction is not in a refundable state
  • Transaction has already been refunded
400 Bad Request - Transaction Not Found
{
  "error": "transaction not found: txn_invalid_123"
}

Use Cases

E-commerce Order Refund

async function refundOrder(orderId) {
  // Get original payment transaction
  const payment = await getTransactionByReference(`order-${orderId}`);
  
  if (payment.status !== 'APPLIED') {
    throw new Error('Can only refund applied transactions');
  }
  
  // Create full refund
  const refund = await refundTransaction(payment.transaction_id);
  
  // Update order status
  await updateOrder(orderId, {
    status: 'refunded',
    refund_transaction_id: refund.transaction_id,
    refunded_at: new Date()
  });
  
  // Send customer notification
  await sendEmail({
    to: customer.email,
    subject: 'Refund Processed',
    body: `Your refund of $${refund.amount} has been processed.`
  });
  
  return refund;
}

Subscription Cancellation Refund

def refund_subscription(subscription_id, refund_type='full'):
    # Get subscription payment
    payment = get_transaction_by_reference(f'sub-{subscription_id}')
    
    if refund_type == 'full':
        # Full refund
        refund = refund_transaction(payment['transaction_id'])
    else:
        # Pro-rated refund based on unused days
        days_used = calculate_days_used(subscription_id)
        days_total = 30
        unused_percentage = (days_total - days_used) / days_total
        
        refund_amount = payment['amount'] * unused_percentage
        
        refund = create_transaction({
            'amount': refund_amount,
            'source': payment['destination'],
            'destination': payment['source'],
            'currency': payment['currency'],
            'reference': f'refund-sub-{subscription_id}',
            'description': f'Pro-rated refund ({days_total - days_used} unused days)',
            'meta_data': {
                'original_transaction_id': payment['transaction_id'],
                'refund_type': 'prorated',
                'days_used': days_used,
                'days_total': days_total
            }
        })
    
    return refund

Failed Service Refund

async function handleFailedService(serviceId) {
  const service = await getService(serviceId);
  const payment = await getTransactionByReference(
    `service-${serviceId}`
  );
  
  // Automatic refund for failed services
  const refund = await refundTransaction(payment.transaction_id);
  
  // Log incident
  await logIncident({
    type: 'service_failure',
    service_id: serviceId,
    original_transaction: payment.transaction_id,
    refund_transaction: refund.transaction_id,
    customer_notified: true
  });
  
  // Send apology with refund confirmation
  await sendApology(service.customer_id, {
    service_id: serviceId,
    refund_amount: refund.amount,
    refund_reference: refund.reference
  });
}

Dispute Resolution Refund

def process_dispute_refund(dispute_id):
    dispute = get_dispute(dispute_id)
    original_txn = get_transaction(dispute['transaction_id'])
    
    if dispute['resolution'] == 'full_refund':
        refund = refund_transaction(original_txn['transaction_id'])
    elif dispute['resolution'] == 'partial_refund':
        refund = create_transaction({
            'amount': dispute['refund_amount'],
            'source': original_txn['destination'],
            'destination': original_txn['source'],
            'currency': original_txn['currency'],
            'reference': f'dispute-{dispute_id}-refund',
            'description': f'Dispute resolution refund',
            'meta_data': {
                'dispute_id': dispute_id,
                'original_transaction_id': original_txn['transaction_id'],
                'resolution_type': dispute['resolution'],
                'dispute_reason': dispute['reason']
            }
        })
    
    # Update dispute status
    update_dispute(dispute_id, {
        'status': 'resolved',
        'refund_transaction_id': refund['transaction_id'],
        'resolved_at': datetime.now()
    })
    
    return refund

Refund Tracking

// Store original transaction ID in refund metadata
async function createTrackedRefund(originalTxnId) {
  const refund = await refundTransaction(originalTxnId);
  
  // Refund automatically includes parent_transaction
  console.log('Original:', refund.parent_transaction);
  console.log('Refund:', refund.transaction_id);
  
  return refund;
}

// Query all refunds for a transaction
async function getRefunds(originalTxnId) {
  // Method 1: Using parent_transaction field
  const refunds = await fetch(
    `/transactions?parent_transaction_eq=${originalTxnId}`
  ).then(r => r.json());
  
  return refunds;
}

Calculate Total Refunded

def calculate_refunded_amount(original_transaction_id):
    # Get all refunds for this transaction
    refunds = get_transactions(
        parent_transaction_eq=original_transaction_id,
        status_eq='APPLIED'
    )
    
    total_refunded = sum(
        Decimal(r['precise_amount']) 
        for r in refunds
    )
    
    return total_refunded

def get_refundable_amount(original_transaction_id):
    original = get_transaction(original_transaction_id)
    total_refunded = calculate_refunded_amount(original_transaction_id)
    
    original_amount = Decimal(original['precise_amount'])
    remaining = original_amount - total_refunded
    
    return {
        'original_amount': original_amount,
        'total_refunded': total_refunded,
        'remaining_refundable': remaining
    }

Best Practices

  1. Verify transaction status - Only refund APPLIED transactions
  2. Check for existing refunds - Prevent duplicate refunds for the same transaction
  3. Use metadata - Store refund reasons and tracking information
  4. Notify customers - Send confirmation emails/notifications for refunds
  5. Handle partial refunds - Use custom transactions for partial refunds with proper tracking
  6. Audit trail - Maintain links between original and refund transactions
  7. Async refunds - For large refund batches, use async processing
  8. Validate amounts - Ensure partial refunds don’t exceed original amount

Refund vs Void

FeatureRefundVoid
When to useAfter transaction is appliedBefore inflight transaction is committed
Creates new transactionYesYes
Reverses fundsYesYes (releases reserved funds)
Original transaction statusStays APPLIEDStays INFLIGHT
Use caseReturn processed paymentsCancel pending authorizations
EndpointPOST /refund-transaction/:idPUT /transactions/inflight/:id with status: "void"

Build docs developers (and LLMs) love