Process Returns
Why return processing matters
Section titled “Why return processing matters”Returns are inevitable in commerce. A smooth return process builds customer trust and encourages repeat purchases. A clunky process creates support tickets and negative reviews. Commerce provides the building blocks for return merchandise authorization (RMA), refund processing, and inventory restocking so you can handle returns consistently.
This guide covers creating RMA requests, authorizing returns, processing refunds through your payment gateway, and restocking returned items.
What you will do
Section titled “What you will do”- Create an RMA request against a purchase order
- Authorize the return and validate line items
- Process a refund through the payment gateway
- Restock returned items in inventory
Create an RMA request
Section titled “Create an RMA request”An RMA (Return Merchandise Authorization) tracks which items from an order a customer wants to return, the reason, and the requested resolution (refund, exchange, store credit).
using EPiServer.Commerce.Order;
public class ReturnService
{
private readonly IOrderRepository _orderRepo;
private readonly IReturnLineItemCalculator
_returnCalc;
public ReturnService(
IOrderRepository orderRepo,
IReturnLineItemCalculator returnCalc)
{
_orderRepo = orderRepo;
_returnCalc = returnCalc;
}
public IReturnOrderForm CreateReturn(
IPurchaseOrder order,
IEnumerable<ReturnRequest> items)
{
var returnForm = order.ReturnForms
.FirstOrDefault()
?? order.CreateReturnOrderForm();
foreach (var item in items)
{
var shipment = returnForm.Shipments
.FirstOrDefault()
?? returnForm.CreateShipment();
var returnItem = shipment.CreateLineItem();
returnItem.Code = item.VariantCode;
returnItem.Quantity = item.Quantity;
returnItem.ReturnReason = item.Reason;
shipment.LineItems.Add(returnItem);
}
returnForm.ReturnType =
ReturnFormType.Refund.ToString();
returnForm.Status =
ReturnFormStatus.AwaitingApproval
.ToString();
_orderRepo.Save(order);
return returnForm;
}
}
public class ReturnRequest
{
public string VariantCode { get; set; }
public decimal Quantity { get; set; }
public string Reason { get; set; }
} Authorize the return
Section titled “Authorize the return”Review the RMA and approve or reject it based on your return policy.
public class ReturnAuthorizationService
{
private readonly IOrderRepository _orderRepo;
public ReturnAuthorizationService(
IOrderRepository orderRepo)
{
_orderRepo = orderRepo;
}
public void Authorize(IPurchaseOrder order,
IReturnOrderForm returnForm)
{
returnForm.Status =
ReturnFormStatus.AwaitingCompletion
.ToString();
order.Properties["ReturnAuthorizedDate"]
= DateTime.UtcNow;
_orderRepo.Save(order);
}
public void Reject(IPurchaseOrder order,
IReturnOrderForm returnForm, string reason)
{
returnForm.Status =
ReturnFormStatus.Canceled.ToString();
returnForm.Properties["RejectionReason"]
= reason;
_orderRepo.Save(order);
}
} Process the refund
Section titled “Process the refund”After receiving the returned items, issue a refund through your payment gateway. Commerce creates a credit payment against the original transaction.
public class RefundService
{
private readonly IOrderRepository _orderRepo;
private readonly IPaymentProcessor _paymentProc;
public RefundService(
IOrderRepository orderRepo,
IPaymentProcessor paymentProc)
{
_orderRepo = orderRepo;
_paymentProc = paymentProc;
}
public bool ProcessRefund(IPurchaseOrder order,
IReturnOrderForm returnForm)
{
var refundAmount = returnForm.Shipments
.SelectMany(s => s.LineItems)
.Sum(li => li.PlacedPrice * li.Quantity);
var originalPayment = order.Forms
.SelectMany(f => f.Payments)
.First(p => p.Status ==
PaymentStatus.Processed.ToString());
var refundPayment = order.CreatePayment();
refundPayment.Amount = refundAmount;
refundPayment.TransactionType =
TransactionType.Credit.ToString();
refundPayment.TransactionID =
originalPayment.TransactionID;
refundPayment.PaymentMethodId =
originalPayment.PaymentMethodId;
var message = string.Empty;
var result = _paymentProc
.ProcessPayment(order, refundPayment,
ref message);
if (result)
{
returnForm.Status =
ReturnFormStatus.Complete.ToString();
}
_orderRepo.Save(order);
return result;
}
} Restock returned items
Section titled “Restock returned items”After inspecting returned items, add them back to inventory if they are in sellable condition.
using Mediachase.Commerce.InventoryService;
public void RestockItems(
IInventoryService inventoryService,
IReturnOrderForm returnForm,
string warehouseCode)
{
foreach (var shipment in returnForm.Shipments)
{
foreach (var item in shipment.LineItems)
{
var existing = inventoryService
.QueryByEntry(new[] { item.Code })
.FirstOrDefault(r =>
r.WarehouseCode == warehouseCode);
if (existing != null)
{
var updated = new InventoryRecord
{
CatalogEntryCode = item.Code,
WarehouseCode = warehouseCode,
PurchaseAvailableQuantity =
existing.PurchaseAvailableQuantity
+ item.Quantity,
PurchaseAvailableUtc = DateTime.UtcNow,
IsTracked = existing.IsTracked
};
inventoryService.Save(
new[] { updated });
}
}
}
} Common issues
Section titled “Common issues”| Issue | Cause | Fix |
|---|---|---|
| Refund fails with “transaction not found” | Original payment transaction ID missing or expired | Verify the original transaction ID exists in the payment provider |
| Return quantity exceeds ordered quantity | Validation not checking against original order | Compare return quantities against the original order line items |
| Inventory not updated after restock | Restock step skipped or warehouse code incorrect | Verify the warehouse code matches and the restock method runs after inspection |
| Customer sees “return not available” | Return window has expired based on your policy | Check the return eligibility window in your return policy configuration |