# Order Refund Flow

A complete example demonstrating how to build an intelligent customer support agent that can handle order refunds with sophisticated routing and business logic.

## Overview

This example showcases:

* **Trigger handling** with qualification logic
* **Prompt-based routing** for intelligent conversation flow
* **Tool integration** for business logic execution
* **Memory management** for maintaining conversation state
* **Error handling** and edge cases

## Complete Implementation

### Project Structure

```
order-refund-agent/
├── minded.json          # Agent configuration
├── agentMemorySchema.ts           # Memory type definition
├── agent.ts            # Main agent implementation
├── flows/
│   └── refundFlow.yaml # Flow definition
└── tools/
    ├── lookupOrder.ts  # Order lookup tool
    ├── processRefund.ts # Refund processing tool
    └── index.ts        # Tool exports
```

### 1. Agent Configuration

Create your `minded.json` configuration file (see [Project Configuration](/getting-started/project-configuration.md) for detailed documentation):

**`minded.json`:**

```json
{
  "flows": ["./flows"],
  "agent": "./agent.ts",
  "llm": {
    "name": "ChatOpenAI",
    "properties": {
      "model": "gpt-4o",
      "temperature": 0.7
    }
  }
}
```

### 2. Memory Schema

**`agentMemorySchema.ts`:**

```ts
import { z } from 'zod';

const memorySchema = z.object({
  // Customer information
  customerName: z.string().optional(),
  customerEmail: z.string().optional(),
  customerId: z.string().optional(),

  // Order information
  orderId: z.string().optional(),
  orderInfo: z
    .object({
      id: z.string(),
      status: z.string(),
      total: z.number(),
      items: z.array(z.string()),
      purchaseDate: z.string(),
      canRefund: z.boolean(),
    })
    .optional(),

  // Conversation state
  conversationStage: z.enum(['greeting', 'gathering_info', 'processing_refund', 'complete']).optional(),

  // Refund information
  refundReason: z.string().optional(),
  refundAmount: z.number().optional(),
  refundProcessed: z.boolean().optional(),

  // Metadata
  timestamp: z.string().optional(),
  customerQuery: z.string().optional(),
});

export type Memory = z.infer<typeof memorySchema>;
export default memorySchema;
```

### 3. Flow Definition

**`flows/refundFlow.yaml`:**

```yaml
name: 'Advanced Order Refund Flow'
nodes:
  # Entry point
  - type: trigger
    triggerType: manual
    name: Customer Support Trigger

  # Initial customer interaction
  - type: promptNode
    name: Support Agent
    prompt: |
      You are a helpful customer support agent specializing in order issues and refunds.

      Current customer context:
      - Query: {{memory.customerQuery}}
      - Stage: {{memory.conversationStage}}

      If this is the first interaction:
      1. Greet the customer warmly
      2. Ask for their order ID if they mention order issues
      3. Ask for their name and email for verification

      If you have order information:
      - Customer: {{memory.customerName}}
      - Order ID: {{memory.orderId}}
      - Order Status: {{memory.orderInfo.status}}
      - Order Total: ${{memory.orderInfo.total}}

      Be empathetic, professional, and focused on resolving their issue.
    llmConfig:
      name: ChatOpenAI
      properties:
        model: gpt-4o
        temperature: 0

  # Order lookup functionality
  - type: tool
    name: Lookup Order
    toolName: lookupOrder

  # Junction for decision making
  - type: junction
    name: Refund Decision Point

  # Refund processing
  - type: tool
    name: Process Refund
    toolName: processRefund

  # Final confirmation
  - type: promptNode
    name: Refund Confirmation
    prompt: |
      Great news! Your refund has been processed successfully.

      Refund Details:
      - Order ID: {{memory.orderId}}
      - Refund Amount: ${{memory.refundAmount}}
      - Reason: {{memory.refundReason}}

      You can expect to see the refund in your original payment method within 3-5 business days.

      Is there anything else I can help you with today?

edges:
  # Initial flow
  - source: Customer Support Trigger
    target: Support Agent
    type: stepForward

  # Route to order lookup when customer provides order ID
  - source: Support Agent
    target: Lookup Order
    type: promptCondition
    prompt: 'Customer has provided an order ID and wants to look up order information'

  # After order lookup, go to decision point
  - source: Lookup Order
    target: Refund Decision Point
    type: stepForward

  # Decision point routing
  - source: Refund Decision Point
    target: Process Refund
    type: logicalCondition
    condition: "({ memory }) => memory.orderInfo?.canRefund && memory.customerQuery?.toLowerCase().includes('refund')"

  - source: Refund Decision Point
    target: Support Agent
    type: logicalCondition
    condition: '({ memory }) => !memory.orderInfo?.canRefund'

  # After refund processing
  - source: Process Refund
    target: Refund Confirmation
    type: stepForward

  # Allow continued conversation
  - source: Refund Confirmation
    target: Support Agent
    type: promptCondition
    prompt: 'Customer has additional questions or needs more help'
```

### 4. Tools Implementation

**`tools/lookupOrder.ts`:**

```ts
import { z } from 'zod';
import { Tool } from 'mindedjs/src/types/Tools.types';
import { Memory } from '../schema';

const schema = z.object({
  orderId: z.string(),
  customerEmail: z.string().optional(),
});

const lookupOrderTool: Tool<typeof schema, Memory> = {
  name: 'lookupOrder',
  description: 'Look up order details by order ID and optionally verify with customer email',
  input: schema,
  execute: async ({ input, memory }) => {
    try {
      // Simulate API call to order system
      const orderInfo = await fetchOrderFromAPI(input.orderId, input.customerEmail);

      if (!orderInfo) {
        throw new Error(`Order ${input.orderId} not found or email doesn't match`);
      }

      // Determine if refund is allowed based on business rules
      const canRefund = determineRefundEligibility(orderInfo);

      return {
        memory: {
          orderId: input.orderId,
          orderInfo: {
            ...orderInfo,
            canRefund,
          },
          conversationStage: 'gathering_info',
          customerId: orderInfo.customerId,
        },
      };
    } catch (err) {
      logger.error({ message: 'Order lookup failed:', err });
      return {
        memory: {
          orderId: input.orderId,
          conversationStage: 'gathering_info',
        },
      };
    }
  },
};

// Simulate order API
async function fetchOrderFromAPI(orderId: string, email?: string) {
  // Simulate database lookup
  const orders = {
    '12345': {
      id: '12345',
      customerId: 'cust_123',
      customerEmail: 'john@example.com',
      status: 'delivered',
      total: 89.99,
      items: ['Wireless Headphones', 'Phone Case'],
      purchaseDate: '2024-01-15T10:30:00Z',
    },
    '67890': {
      id: '67890',
      customerId: 'cust_456',
      customerEmail: 'jane@example.com',
      status: 'shipped',
      total: 199.99,
      items: ['Laptop Stand', 'Wireless Mouse'],
      purchaseDate: '2024-01-20T14:20:00Z',
    },
  };

  const order = orders[orderId];
  if (!order) return null;

  // Verify email if provided
  if (email && order.customerEmail !== email) {
    return null;
  }

  return order;
}

// Business rules for refund eligibility
function determineRefundEligibility(orderInfo: any): boolean {
  const purchaseDate = new Date(orderInfo.purchaseDate);
  const now = new Date();
  const daysSincePurchase = Math.floor((now.getTime() - purchaseDate.getTime()) / (1000 * 60 * 60 * 24));

  // Refund rules:
  // - Must be within 30 days
  // - Order must be delivered
  // - Order total must be less than $500 (higher amounts need manager approval)
  return daysSincePurchase <= 30 && orderInfo.status === 'delivered' && orderInfo.total < 500;
}

export default lookupOrderTool;
```

**`tools/processRefund.ts`:**

```ts
import { z } from 'zod';
import { Tool } from 'mindedjs/src/types/Tools.types';
import { Memory } from '../schema';

const schema = z.object({
  orderId: z.string(),
  customerName: z.string(),
  reason: z.string(),
  amount: z.number().optional(), // Optional partial refund amount
});

const processRefundTool: Tool<typeof schema, Memory> = {
  name: 'processRefund',
  description: 'Process a refund for the customer order',
  input: schema,
  execute: async ({ input, memory }) => {
    try {
      // Determine refund amount
      const refundAmount = input.amount || memory.orderInfo?.total || 0;

      // Validate refund amount
      if (refundAmount > (memory.orderInfo?.total || 0)) {
        throw new Error('Refund amount cannot exceed order total');
      }

      // Process the refund (simulate payment processor API)
      const refundResult = await processRefundWithPaymentProcessor({
        orderId: input.orderId,
        amount: refundAmount,
        reason: input.reason,
      });

      // Log the refund for auditing
      await logRefundTransaction({
        orderId: input.orderId,
        customerId: memory.customerId,
        amount: refundAmount,
        reason: input.reason,
        processedBy: 'ai-agent',
        timestamp: new Date().toISOString(),
      });

      console.log(`✅ Refund processed: Order ${input.orderId}, Amount: $${refundAmount}`);

      return {
        memory: {
          refundAmount,
          refundReason: input.reason,
          refundProcessed: true,
          conversationStage: 'complete',
        },
      };
    } catch (err) {
      logger.error({ message: 'Refund processing failed:', err });

      return {
        memory: {
          refundAmount: 0,
          refundReason: input.reason,
          refundProcessed: false,
          conversationStage: 'gathering_info', // Go back to gathering info
        },
      };
    }
  },
};

// Simulate payment processor API
async function processRefundWithPaymentProcessor(params: any) {
  // Simulate API call delay
  await new Promise((resolve) => setTimeout(resolve, 1000));

  // Simulate occasional failures for testing
  if (Math.random() < 0.1) {
    throw new Error('Payment processor temporarily unavailable');
  }

  return {
    refundId: `ref_${Date.now()}`,
    status: 'processed',
    amount: params.amount,
  };
}

// Simulate audit logging
async function logRefundTransaction(transaction: any) {
  console.log('📝 Logging refund transaction:', transaction);
  // In real implementation, this would write to a database or audit system
}

export default processRefundTool;
```

**`tools/index.ts`:**

```ts
import lookupOrderTool from './lookupOrder';
import processRefundTool from './processRefund';

export default [lookupOrderTool, processRefundTool];
```

### 5. Main Agent Implementation

**`agent.ts`:**

```ts
import { Agent } from 'mindedjs/src/agent';
import { HumanMessage } from '@langchain/core/messages';
import { events } from 'mindedjs/src/index';
import memorySchema, { Memory } from './schema';
import tools from './tools';
import config from './minded.json';

// Create the agent
const agent = new Agent<Memory>({
  memorySchema,
  config,
  tools,
});

// Handle AI messages (responses to user)
agent.on(events.AI_MESSAGE, async ({ message, memory }) => {
  console.log('🤖 Agent:', message);
  if (memory.conversationStage) {
    console.log(`   Stage: ${memory.conversationStage}`);
  }
});

// Handle trigger events with qualification logic
agent.on(events.TRIGGER_EVENT, async ({ triggerName, triggerBody }) => {
  if (triggerName === 'Customer Support Trigger') {
    // Qualify the trigger - only handle customer service related queries
    const lowerQuery = triggerBody.toLowerCase();
    const serviceKeywords = ['order', 'refund', 'help', 'support', 'issue', 'problem'];

    const isServiceQuery = serviceKeywords.some((keyword) => lowerQuery.includes(keyword));

    if (!isServiceQuery) {
      console.log('❌ Query not qualified for customer service');
      return { isQualified: false }; // Disqualify this trigger
    }

    console.log('✅ Customer service query qualified');

    // Transform the request and initialize memory
    return {
      isQualified: true,
      memory: {
        customerQuery: triggerBody,
        conversationStage: 'greeting',
        timestamp: new Date().toISOString(),
      },
      messages: [new HumanMessage(triggerBody)],
    };
  }

  return { isQualified: false }; // Unknown trigger
});

// Example usage function
async function runRefundExample() {
  console.log('🚀 Starting Order Refund Flow Example\n');

  const examples = [
    {
      description: 'Happy path - valid refund request',
      query: "Hi, I need to return my order #12345. The headphones don't work properly.",
    },
    {
      description: 'Order lookup required',
      query: 'I want to check the status of my recent order and possibly get a refund.',
    },
    {
      description: 'Non-service query (should be disqualified)',
      query: "What's the weather like today?",
    },
  ];

  for (const example of examples) {
    console.log(`\n📋 Example: ${example.description}`);
    console.log(`User: ${example.query}\n`);

    try {
      const result = await agent.invoke({
        triggerBody: example.query,
        triggerName: 'Customer Support Trigger',
        sessionId: `session_${Date.now()}`,
      });

      console.log('✅ Flow completed successfully');
      console.log('Final memory state:', JSON.stringify(result.memory, null, 2));
    } catch (err) {
      logger.error({ message: '❌ Flow execution failed:', err });
    }

    console.log('\n' + '─'.repeat(50));
  }
}

// Run the example if this file is executed directly
if (require.main === module) {
  runRefundExample().catch(console.error);
}

export { agent, runRefundExample };
```

## Running the Example

1. **Install dependencies:**

```bash
npm install @minded-ai/mindedjs langchain
```

2. **Set up environment:**

```bash
export OPENAI_API_KEY="your-openai-api-key"
```

3. **Run the agent:**

```bash
npx ts-node agent.ts
```

## Expected Output

```
🚀 Starting Order Refund Flow Example

📋 Example: Happy path - valid refund request
User: Hi, I need to return my order #12345. The headphones don't work properly.

✅ Customer service query qualified
🤖 Agent: Hello! I'm sorry to hear you're having trouble with your headphones. I'd be happy to help you with a return. I see you mentioned order #12345. Could you please provide your email address so I can look up your order details?

✅ Refund processed: Order 12345, Amount: $89.99
📝 Logging refund transaction: {...}
🤖 Agent: Great news! Your refund has been processed successfully...

✅ Flow completed successfully
```

## Key Features Demonstrated

1. **Intelligent Qualification**: The trigger only accepts customer service related queries
2. **Conversation State Management**: Memory tracks the conversation stage and relevant information
3. **Business Logic Integration**: Tools implement real business rules for refund eligibility
4. **Error Handling**: Graceful handling of failed API calls and invalid requests
5. **Audit Trail**: All refund transactions are logged for compliance
6. **Type Safety**: Full TypeScript type safety throughout the flow

This example provides a solid foundation for building production-ready customer service agents with MindedJS!


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.minded.com/examples/order-refund-flow.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
