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;
}