# 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](https://docs.minded.com/getting-started/project-configuration) 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!
