Order Refund Flow
Last updated
Last updated
A complete example demonstrating how to build an intelligent customer support agent that can handle order refunds with sophisticated routing and business logic.
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
order-refund-agent/
āāā minded.json # Agent configuration
āāā schema.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
Create your minded.json
configuration file (see Project Configuration for detailed documentation):
minded.json
:
{
"flows": ["./flows"],
"agent": "./agent.ts",
"llm": {
"name": "ChatOpenAI",
"properties": {
"model": "gpt-4o",
"temperature": 0.7
}
}
}
schema.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;
flows/refundFlow.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.7
# 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'
tools/lookupOrder.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: '[email protected]',
status: 'delivered',
total: 89.99,
items: ['Wireless Headphones', 'Phone Case'],
purchaseDate: '2024-01-15T10:30:00Z',
},
'67890': {
id: '67890',
customerId: 'cust_456',
customerEmail: '[email protected]',
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
:
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
:
import lookupOrderTool from './lookupOrder';
import processRefundTool from './processRefund';
export default [lookupOrderTool, processRefundTool];
agent.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 };
Install dependencies:
npm install @minded-ai/mindedjs langchain
Set up environment:
export OPENAI_API_KEY="your-openai-api-key"
Run the agent:
npx ts-node agent.ts
š 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
Intelligent Qualification: The trigger only accepts customer service related queries
Conversation State Management: Memory tracks the conversation stage and relevant information
Business Logic Integration: Tools implement real business rules for refund eligibility
Error Handling: Graceful handling of failed API calls and invalid requests
Audit Trail: All refund transactions are logged for compliance
Type Safety: Full TypeScript type safety throughout the flow
This example provides a solid foundation for building production-ready customer service agents with MindedJS!