Returns and Refund AI Agent: Automating Complex Support Workflows
Build an AI agent that handles returns and refunds by enforcing company policies, managing approval workflows, processing partial refunds, and handling exceptions with proper escalation for edge cases.
Why Returns Are Harder Than They Look
Returns and refunds seem straightforward until you account for the real-world complexity: time-window policies, restocking fees, partial refunds for bundles, different rules for digital versus physical products, and exceptions for VIP customers or defective items. A returns agent must encode all of this business logic while remaining conversational and helpful.
The key insight is separating policy evaluation from the conversation. The agent talks to the customer; a policy engine determines what is allowed.
Policy Engine
The policy engine evaluates return eligibility based on structured rules. It takes the order details and return reason and produces a clear allow/deny decision with explanations.
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
class ReturnReason(Enum):
DEFECTIVE = "defective"
WRONG_ITEM = "wrong_item"
NOT_AS_DESCRIBED = "not_as_described"
CHANGED_MIND = "changed_mind"
DUPLICATE_ORDER = "duplicate_order"
class RefundType(Enum):
FULL = "full"
PARTIAL = "partial"
STORE_CREDIT = "store_credit"
DENIED = "denied"
@dataclass
class ReturnPolicy:
return_window_days: int = 30
restocking_fee_pct: float = 0.15
free_return_reasons: list = None
digital_returnable: bool = False
def __post_init__(self):
if self.free_return_reasons is None:
self.free_return_reasons = [
ReturnReason.DEFECTIVE,
ReturnReason.WRONG_ITEM,
]
@dataclass
class ReturnEvaluation:
eligible: bool
refund_type: RefundType
refund_amount: float
restocking_fee: float
reason: str
requires_approval: bool
class PolicyEngine:
def __init__(self, policy: ReturnPolicy):
self.policy = policy
def evaluate(
self,
order_date: datetime,
order_total: float,
return_reason: ReturnReason,
is_digital: bool = False,
is_vip: bool = False,
) -> ReturnEvaluation:
days_since_order = (datetime.utcnow() - order_date).days
# Digital products
if is_digital and not self.policy.digital_returnable:
if return_reason not in (
ReturnReason.DEFECTIVE,
ReturnReason.WRONG_ITEM,
):
return ReturnEvaluation(
eligible=False,
refund_type=RefundType.DENIED,
refund_amount=0,
restocking_fee=0,
reason="Digital products are non-refundable",
requires_approval=False,
)
# Return window check
window = self.policy.return_window_days
if is_vip:
window = int(window * 1.5)
if days_since_order > window:
if return_reason == ReturnReason.DEFECTIVE:
return ReturnEvaluation(
eligible=True,
refund_type=RefundType.FULL,
refund_amount=order_total,
restocking_fee=0,
reason="Defective items eligible outside return window",
requires_approval=True,
)
return ReturnEvaluation(
eligible=False,
refund_type=RefundType.DENIED,
refund_amount=0,
restocking_fee=0,
reason=f"Return window of {window} days has passed",
requires_approval=False,
)
# Calculate refund
if return_reason in self.policy.free_return_reasons:
return ReturnEvaluation(
eligible=True,
refund_type=RefundType.FULL,
refund_amount=order_total,
restocking_fee=0,
reason="Full refund for defective/wrong item",
requires_approval=False,
)
# Changed mind — restocking fee applies
fee = order_total * self.policy.restocking_fee_pct
return ReturnEvaluation(
eligible=True,
refund_type=RefundType.PARTIAL,
refund_amount=order_total - fee,
restocking_fee=fee,
reason=f"Refund minus {self.policy.restocking_fee_pct*100:.0f}% restocking fee",
requires_approval=False,
)
Approval Workflow
Some returns require manager approval — high-value orders, out-of-window exceptions, or repeated returns from the same customer. The approval workflow queues these cases and tracks their resolution.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
from enum import Enum as PyEnum
import uuid
class ApprovalStatus(PyEnum):
PENDING = "pending"
APPROVED = "approved"
DENIED = "denied"
@dataclass
class ApprovalRequest:
id: str
order_id: str
evaluation: ReturnEvaluation
customer_id: str
status: ApprovalStatus = ApprovalStatus.PENDING
reviewer: Optional[str] = None
notes: str = ""
class ApprovalWorkflow:
def __init__(self):
self.pending: dict[str, ApprovalRequest] = {}
def submit(
self, order_id: str, customer_id: str, evaluation: ReturnEvaluation
) -> ApprovalRequest:
request = ApprovalRequest(
id=str(uuid.uuid4()),
order_id=order_id,
evaluation=evaluation,
customer_id=customer_id,
)
self.pending[request.id] = request
return request
def approve(self, request_id: str, reviewer: str, notes: str = ""):
req = self.pending.get(request_id)
if req:
req.status = ApprovalStatus.APPROVED
req.reviewer = reviewer
req.notes = notes
def deny(self, request_id: str, reviewer: str, notes: str = ""):
req = self.pending.get(request_id)
if req:
req.status = ApprovalStatus.DENIED
req.reviewer = reviewer
req.notes = notes
The Returns Agent
The conversational agent collects information from the customer, evaluates the policy, and either processes the return immediately or submits it for approval.
from agents import Agent, function_tool
policy_engine = PolicyEngine(ReturnPolicy())
approval_workflow = ApprovalWorkflow()
@function_tool
def evaluate_return(
order_id: str,
order_date: str,
order_total: float,
return_reason: str,
is_digital: bool,
is_vip: bool,
) -> str:
"""Evaluate whether a return is eligible per company policy."""
reason_enum = ReturnReason(return_reason)
dt = datetime.fromisoformat(order_date)
result = policy_engine.evaluate(
order_date=dt,
order_total=order_total,
return_reason=reason_enum,
is_digital=is_digital,
is_vip=is_vip,
)
if result.requires_approval:
req = approval_workflow.submit(order_id, "customer", result)
return (
f"Return requires approval. Request ID: {req.id}. "
f"Reason: {result.reason}. "
f"Estimated refund: ${result.refund_amount:.2f}"
)
if result.eligible:
return (
f"Return approved. Refund: ${result.refund_amount:.2f}. "
f"Restocking fee: ${result.restocking_fee:.2f}. "
f"Reason: {result.reason}"
)
return f"Return denied. Reason: {result.reason}"
returns_agent = Agent(
name="Returns Agent",
instructions="""You handle return and refund requests.
Collect these details before evaluating:
1. Order ID
2. Reason for return (defective, wrong_item, not_as_described,
changed_mind, duplicate_order)
3. Confirm the item and order details
Use evaluate_return to check eligibility. Explain the result
clearly to the customer. If a restocking fee applies, explain
why. If approval is needed, tell the customer their request
has been submitted and they will hear back within 24 hours.
Never override policy decisions. Never promise outcomes you
cannot guarantee.""",
tools=[evaluate_return],
)
FAQ
How do I handle partial refunds for bundled products?
Break the bundle into individual item prices and apply the return policy to each item separately. If the customer returns only one item from a three-item bundle, refund that item's prorated price minus any bundle discount they received. Store individual item prices at order time to make this calculation possible.
Should the AI agent have the authority to issue refunds directly?
For standard, policy-compliant returns below a dollar threshold (e.g., under $100), yes — the agent should process automatically. For high-value orders, out-of-window exceptions, or repeated returns, require human approval. This balances speed with financial risk management.
How do I prevent return fraud?
Track return frequency per customer and flag accounts that exceed a threshold (e.g., more than three returns in 90 days). The policy engine should consider return history as an input. For flagged accounts, route all returns to human review regardless of the policy evaluation.
#Returns #RefundAutomation #WorkflowAutomation #PolicyEnforcement #AIAgents #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.