Skip to content
Learn Agentic AI11 min read0 views

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

Share this article
C

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.