Skip to main content

Refund Processing

Complete guide to processing refunds and reversals.

Full Refund

Refund the entire amount of a transfer:

import { createClient } from "@easylabs/node";

const easy = await createClient({
apiKey: process.env.EASY_API_KEY!,
});

async function processFullRefund(transferId: string, reason: string) {
try {
// Get the original transfer
const transfer = await easy.getTransfer(transferId);

// Create a refund (negative amount)
const refund = await easy.createTransfer({
amount: -transfer.data.amount,
currency: transfer.data.currency,
source: transfer.data.source,
statement_descriptor: "Refund",
tags: {
type: "refund",
original_transfer_id: transferId,
reason: reason,
refunded_at: new Date().toISOString(),
},
});

// Update original transfer tags
await easy.updateTransfer(transferId, {
refund_status: "refunded",
refund_id: refund.data.id,
refunded_at: new Date().toISOString(),
});

return refund.data;
} catch (error) {
console.error("Refund failed:", error);
throw error;
}
}

Partial Refund

Refund part of the original amount:

async function processPartialRefund(
transferId: string,
refundAmount: number,
reason: string,
) {
const transfer = await easy.getTransfer(transferId);

// Validate refund amount
if (refundAmount > transfer.data.amount) {
throw new Error("Refund amount exceeds original transfer amount");
}

if (refundAmount <= 0) {
throw new Error("Refund amount must be positive");
}

// Create partial refund
const refund = await easy.createTransfer({
amount: -refundAmount,
currency: transfer.data.currency,
source: transfer.data.source,
statement_descriptor: "Partial Refund",
tags: {
type: "partial_refund",
original_transfer_id: transferId,
original_amount: transfer.data.amount.toString(),
refund_amount: refundAmount.toString(),
reason: reason,
},
});

// Update original transfer
await easy.updateTransfer(transferId, {
refund_status: "partially_refunded",
partial_refund_amount: refundAmount.toString(),
});

return refund.data;
}

Express.js Refund API

import express from "express";
import { createClient } from "@easylabs/node";

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

const easy = await createClient({
apiKey: process.env.EASY_API_KEY!,
});

// Full refund endpoint
app.post("/api/refunds/full", async (req, res) => {
try {
const { transferId, reason } = req.body;

const transfer = await easy.getTransfer(transferId);

const refund = await easy.createTransfer({
amount: -transfer.data.amount,
currency: transfer.data.currency,
source: transfer.data.source,
statement_descriptor: "Refund",
tags: {
type: "refund",
original_transfer_id: transferId,
reason,
},
});

await easy.updateTransfer(transferId, {
refund_status: "refunded",
refund_id: refund.data.id,
});

res.json({
success: true,
refund: refund.data,
original_amount: transfer.data.amount,
refunded_amount: Math.abs(refund.data.amount),
});
} catch (error: any) {
res.status(400).json({
success: false,
error: error.message,
});
}
});

// Partial refund endpoint
app.post("/api/refunds/partial", async (req, res) => {
try {
const { transferId, amount, reason } = req.body;

const transfer = await easy.getTransfer(transferId);

if (amount > transfer.data.amount) {
return res.status(400).json({
success: false,
error: "Refund amount exceeds original transfer amount",
});
}

const refund = await easy.createTransfer({
amount: -amount,
currency: transfer.data.currency,
source: transfer.data.source,
statement_descriptor: "Partial Refund",
tags: {
type: "partial_refund",
original_transfer_id: transferId,
reason,
},
});

await easy.updateTransfer(transferId, {
refund_status: "partially_refunded",
partial_refund_amount: amount.toString(),
});

res.json({
success: true,
refund: refund.data,
original_amount: transfer.data.amount,
refunded_amount: amount,
remaining_amount: transfer.data.amount - amount,
});
} catch (error: any) {
res.status(400).json({
success: false,
error: error.message,
});
}
});

// Get refund status
app.get("/api/transfers/:id/refunds", async (req, res) => {
try {
const transfer = await easy.getTransfer(req.params.id);

const refundStatus = {
transfer_id: transfer.data.id,
original_amount: transfer.data.amount,
refund_status: transfer.data.tags?.refund_status || "not_refunded",
refund_id: transfer.data.tags?.refund_id,
};

res.json(refundStatus);
} catch (error: any) {
res.status(404).json({ error: error.message });
}
});

app.listen(3000);

Refund with Order Update

async function refundOrder(
orderId: string,
transferId: string,
refundAmount: number,
reason: string,
) {
// Process refund
const transfer = await easy.getTransfer(transferId);

const refund = await easy.createTransfer({
amount: -refundAmount,
currency: transfer.data.currency,
source: transfer.data.source,
statement_descriptor: "Refund",
tags: {
type: "refund",
original_transfer_id: transferId,
order_id: orderId,
reason,
},
});

// Update order
await easy.updateOrderTags(orderId, {
refund_status:
refundAmount === transfer.data.amount ? "full_refund" : "partial_refund",
refunded_amount: refundAmount.toString(),
refund_id: refund.data.id,
refunded_at: new Date().toISOString(),
refund_reason: reason,
});

return {
refund: refund.data,
order_id: orderId,
};
}

Automated Refund Policies

// Auto-refund for certain conditions
async function autoRefundIfQualified(transferId: string) {
const transfer = await easy.getTransfer(transferId);

// Check if transfer qualifies for auto-refund
const orderAge = Date.now() - new Date(transfer.data.created_at).getTime();
const daysOld = orderAge / (1000 * 60 * 60 * 24);

// Auto-refund if within 30 days and meets criteria
if (daysOld <= 30 && transfer.data.amount < 10000) {
const refund = await easy.createTransfer({
amount: -transfer.data.amount,
currency: transfer.data.currency,
source: transfer.data.source,
statement_descriptor: "Auto Refund",
tags: {
type: "auto_refund",
original_transfer_id: transferId,
reason: "automated_policy",
},
});

return { success: true, refund: refund.data };
}

return { success: false, reason: "Does not qualify for auto-refund" };
}

Best Practices

1. Always Validate Before Refunding

async function validateRefund(transferId: string, amount: number) {
const transfer = await easy.getTransfer(transferId);

// Check transfer state
if (transfer.data.state !== "SUCCEEDED") {
throw new Error("Can only refund successful transfers");
}

// Check if already refunded
if (transfer.data.tags?.refund_status === "refunded") {
throw new Error("Transfer already refunded");
}

// Validate amount
if (amount > transfer.data.amount) {
throw new Error("Refund amount exceeds original amount");
}

return true;
}

2. Track Refund History

async function getRefundHistory(customerId: string) {
const transfers = await easy.getTransfers({ limit: 1000 });

const refunds = transfers.data.filter(
(t) => t.amount < 0 && t.tags?.type === "refund",
);

return refunds;
}

3. Send Notifications

async function refundWithNotification(
transferId: string,
customerEmail: string,
) {
const refund = await processFullRefund(transferId, "customer_request");

// Send email notification
// await sendEmail({
// to: customerEmail,
// subject: 'Refund Processed',
// body: `Your refund of $${Math.abs(refund.amount) / 100} has been processed.`,
// });

return refund;
}