Loading...
Loading...
Automate your return and refund process with configurable return windows, restocking fees, and rule-based approval logic for each product type
npx skill4agent add finsilabs/awesome-ecommerce-skills returns-refund-policy| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Loop Returns or AfterShip Returns | Loop is the most feature-complete: supports per-product-type policies, restocking fees, final-sale blocking, and loyalty tier overrides |
| WooCommerce | ReturnGo or WooCommerce Returns and Warranty Requests | ReturnGo supports custom policy rules per product category and automated approval logic |
| BigCommerce | AfterShip Returns Center or Loop Returns | Both support per-category policy rules and restocking fees |
| Custom / Headless | Build a policy evaluation engine + Shippo for return labels | Store policies in a database; evaluate them programmatically when return requests come in |
| Product Category | Return Window | Restocking Fee | Auto-Approve | Notes |
|---|---|---|---|---|
| Default (apparel, accessories) | 30 days from delivery | 0% | Yes | Most items |
| Electronics | 15 days from delivery | 15% | No — manual review | Opened electronics |
| Final Sale | 0 days | — | No | No returns |
| VIP / Gold members | 60 days from delivery | 0% | Yes | Override for loyalty tier |
| Defective / Wrong item | 90 days from delivery | 0% | Yes | Customer not at fault |
/returns/return-policy// Return policy rules stored in database and evaluated programmatically
interface ReturnPolicy {
id: string;
name: string;
priority: number; // higher = evaluated first
conditions: {
productTags?: string[]; // match any of these tags
customerTags?: string[]; // match any of these customer tags
orderTags?: string[]; // e.g., ['final-sale']
};
windowDays: number; // 0 = no returns allowed
restockingFeePct: number; // 0–100
autoApprove: boolean;
}
async function evaluateReturnEligibility(params: {
orderId: string;
productId: string;
customerId: string;
returnReason: string;
deliveredAt: Date;
}): Promise<{
eligible: boolean;
policy: ReturnPolicy | null;
restockingFeeCents: number;
requiresManualReview: boolean;
daysRemaining: number;
reason?: string;
}> {
const order = await db.orders.findById(params.orderId);
const product = await db.products.findById(params.productId, { include: ['tags'] });
const customer = await db.customers.findById(params.customerId, { include: ['tags'] });
// Find highest-priority matching policy
const policies = await db.returnPolicies.findAll({ is_active: true }, { orderBy: ['priority', 'desc'] });
const policy = policies.find(p => {
const productMatch = !p.conditions.productTags?.length ||
p.conditions.productTags.some(tag => product.tags.includes(tag));
const customerMatch = !p.conditions.customerTags?.length ||
p.conditions.customerTags.some(tag => customer.tags.includes(tag));
const orderMatch = !p.conditions.orderTags?.length ||
p.conditions.orderTags.some(tag => order.tags?.includes(tag));
return productMatch && customerMatch && orderMatch;
}) ?? null;
if (!policy || policy.windowDays === 0) {
return { eligible: false, policy, restockingFeeCents: 0, requiresManualReview: false, daysRemaining: 0, reason: 'FINAL_SALE_OR_NO_POLICY' };
}
// Calculate days since delivery
const daysSinceDelivery = Math.floor((Date.now() - params.deliveredAt.getTime()) / 86400000);
const daysRemaining = policy.windowDays - daysSinceDelivery;
if (daysRemaining < 0) {
return { eligible: false, policy, restockingFeeCents: 0, requiresManualReview: false, daysRemaining: 0, reason: 'WINDOW_EXPIRED' };
}
// Calculate restocking fee on the item's original price
const orderLine = await db.orderLines.findOne({ order_id: params.orderId, product_id: params.productId });
const itemValueCents = orderLine.unit_price_cents * orderLine.quantity;
const restockingFeeCents = Math.round(itemValueCents * (policy.restockingFeePct / 100));
return {
eligible: true,
policy,
restockingFeeCents,
requiresManualReview: !policy.autoApprove,
daysRemaining,
};
}| Problem | Solution |
|---|---|
| Return window calculated from order date instead of delivery date | Check your returns app settings explicitly for "return window starts from" — default in some tools is order date; change to delivery date |
| Multiple policies match and the wrong one applies | Sort by |
| Customer disputes restocking fee | Show the fee amount and the policy name ("Electronics Policy — 15% restocking fee") in the return confirmation email so customers have documentation |
| Final sale tag not applied consistently | Create a process: every product added to a sale must have the "final-sale" tag applied; audit monthly using a product tag report |