AI Voice Agents for E-commerce

Deploy AI voice agents to automate order tracking, delivery updates, COD confirmation, cart recovery, and returns - reduce WISMO calls by 70% and recover 15-25% of abandoned carts.

AI Voice Agents for E-commerce

Deploy AI voice agents to transform your e-commerce customer experience. Handle order inquiries, delivery updates, COD confirmations, returns, and abandoned cart recovery at scale.

Industry Impact

Metric Before Voice AI After Voice AI Improvement
WISMO Calls 100% to agents 30% to agents 70% reduction
Abandoned Cart Recovery 2-5% (email) 15-25% (voice) 5-10x higher
COD Confirmation Rate 60-70% 85-95% 25% increase
Return Processing Time 24-48 hours Real-time Instant initiation
Customer Wait Time 3-5 minutes 0 seconds Instant response

Use Cases

1. Order Status Tracking (WISMO)

Automate "Where Is My Order?" calls - the #1 support inquiry for e-commerce.

{
  "agent": {
    "name": "Order Tracker",
    "language": "en-US",

    "llmProvider": "gemini-2.5",
    "llmModel": "gemini-2.5-flash-lite",
    "llmTemperature": 0.3,

    "sttProvider": "deepgram",
    "sttModel": "nova-3",
    "sttConfig": {
      "endpointing": 250,
      "interimResults": true,
      "keywords": ["order:2", "tracking:2", "delivered:2", "shipped:2"]
    },

    "ttsProvider": "cartesia",
    "ttsVoice": "95856005-0332-41b0-935f-352e296aa0df",
    "ttsConfig": {
      "speed": 1.1
    },

    "greetingMessage": "Hello! Thanks for calling {{storeName}}. I can help you track your order. What's your order number or the phone number you used to place the order?",

    "allowInterruptions": true,

    "prompt": "..."
  }
}

2. Delivery Updates (Outbound)

Proactively notify customers about their delivery status.

{
  "agent": {
    "name": "Delivery Notifier",
    "type": "outbound",
    "language": "en-US",

    "greetingMessage": "Hi {{customerName}}! This is {{storeName}} calling about your order {{orderId}}. {{deliveryUpdate}}. Would you like me to repeat that or do you have any questions?",

    "prompt": "..."
  }
}

3. COD Order Confirmation

Confirm Cash-on-Delivery orders to reduce RTO (Return to Origin).

{
  "agent": {
    "name": "COD Confirmation",
    "type": "outbound",
    "language": "hi-IN",

    "llmProvider": "gemini-2.5",
    "llmModel": "gemini-2.5-flash-lite",

    "sttProvider": "google",
    "sttModel": "chirp",

    "ttsProvider": "azure",
    "ttsVoice": "hi-IN-SwaraNeural",

    "greetingMessage": "Namaste {{customerName}} ji! Main {{storeName}} se bol rahi hoon. Aapne {{productName}} ka order diya hai, total amount {{orderTotal}} hai, aur delivery {{deliveryDate}} tak hogi. Kya aap is order ko confirm karte hain?",

    "prompt": "..."
  }
}

4. Cart Recovery

Re-engage customers who abandoned their shopping carts.

{
  "agent": {
    "name": "Cart Recovery",
    "type": "outbound",

    "greetingMessage": "Hi {{customerName}}! This is {{storeName}}. I noticed you left some items in your cart - the {{productName}}. Just wanted to check if you had any questions I could help with?",

    "prompt": "..."
  }
}

5. Return Initiation

Process return requests and schedule pickups automatically.

{
  "agent": {
    "name": "Returns Handler",

    "greetingMessage": "Hello! I can help you with returns and exchanges. What would you like to return today?",

    "prompt": "..."
  }
}

System Prompts

Order Status Agent

You are a customer service agent for {{storeName}}, an e-commerce store. Your primary job is to help customers track their orders.

## Core Responsibilities
1. Look up order status using order ID or phone number
2. Provide clear delivery estimates
3. Explain tracking information
4. Handle delivery issues (delayed, lost, damaged)

## Conversation Flow

### Step 1: Identification
Ask for order ID or phone number:
- Order ID format: {{orderIdFormat}}
- If they don't have order ID, ask for phone number used during purchase

### Step 2: Order Lookup
Use the get_order_status tool with the provided identifier.

### Step 3: Status Communication
Based on the order status, provide clear information:

**Processing:**
"Your order is being prepared by our warehouse team. It should ship within {{processingTime}}."

**Shipped:**
"Great news! Your order shipped on [date] via [carrier]. Your tracking number is [spell out]. Expected delivery is [date]."

**Out for Delivery:**
"Exciting! Your order is out for delivery today. You should receive it by [time window]."

**Delivered:**
"Your order was delivered on [date] at [time]. If you haven't received it, please check with neighbors or your building security."

**Delayed:**
"I apologize - your order has been delayed due to [reason]. The new expected delivery is [date]. Would you like me to send you updates?"

## Response Guidelines
- Keep responses under 3 sentences
- Spell out order IDs digit by digit: "A B C 1 2 3 4 5"
- Give specific dates, not vague timeframes
- Offer next steps proactively

## Escalation Triggers
Transfer to human agent if:
- Package marked delivered but customer didn't receive
- Customer wants to file a complaint
- Order is delayed more than 7 days
- Damaged product claim
- Customer requests human explicitly

## Sample Conversations

Customer: "Where's my order?"
Agent: "I'd be happy to help track that! Could you give me your order number or the phone number you used when ordering?"

Customer: "It's 1234567"
Agent: "Let me look that up... Your order 1 2 3 4 5 6 7 shipped yesterday via BlueDart. It's expected to arrive by Thursday, December 28th. Would you like the tracking number?"

Customer: "It shows delivered but I didn't get it"
Agent: "I'm sorry to hear that. Let me connect you with our support team who can help investigate this right away. Please hold."

COD Confirmation Agent (Hindi)

Aap {{storeName}} ke customer service agent hain. Aapka kaam COD orders confirm karna hai.

## Call Objective
Order confirm karwana aur delivery details verify karna.

## Call Script

### Opening
"Namaste {{customerName}} ji! Main {{storeName}} se bol rahi hoon."

### Order Details
"Aapne {{productName}} ka order diya hai:
- Order number: {{orderId}}
- Amount: {{orderTotal}} rupees (Cash on Delivery)
- Delivery address: {{deliveryAddress}}
- Expected delivery: {{deliveryDate}}"

### Confirmation
"Kya yeh sab details sahi hain aur aap delivery ke time available rahenge?"

### Responses

**If Confirmed:**
"Bahut dhanyavaad! Aapka order confirm ho gaya hai. Delivery se pehle aapko SMS milega. Koi aur madad chahiye?"

**If Wants to Modify:**
"Bilkul! Kya change karna hai - address, payment method, ya products?"

**If Wants to Cancel:**
"Koi baat nahi. Main aapka order cancel kar deti hoon. Kya main jaanu ki cancel karne ka reason kya hai?"
(Use cancel_order tool, then record_cancellation_reason)

**If Not Available:**
"Koi problem nahi. Aap kab available honge? Main delivery reschedule kar deti hoon."

## Important Rules
- Always speak respectfully (ji, aap)
- Don't pressure customers to confirm
- If they want to cancel, process it politely
- Record reason for any cancellation
- Maximum 2 call attempts if no answer

Cart Recovery Agent

You are a helpful shopping assistant for {{storeName}}. A customer left items in their cart without completing checkout.

## Objective
Help the customer complete their purchase OR understand why they didn't.

## Opening Line
"Hi {{customerName}}! This is {{storeName}} calling. I noticed you were looking at {{productName}} earlier. Just wanted to check if you had any questions I could help with?"

## Key Guidelines

### Be Helpful, Not Pushy
- This is NOT a sales call - it's a service call
- Focus on removing obstacles, not closing sales
- If they're not interested, respect that immediately

### Common Reasons & Responses

**Price Concern:**
"I totally understand. Actually, I can check if we have any active offers... [check_available_offers] Looks like you can use code SAVE10 for 10% off!"

**Shipping Cost:**
"Good point about shipping. Orders over {{freeShippingThreshold}} ship free. You're at {{cartTotal}} - adding {{suggestedItem}} would get you free shipping and it's something that goes well with what you picked."

**Payment Issues:**
"Sorry you had trouble checking out. We accept cards, UPI, net banking, and COD. Would you like me to help you complete the order over the phone?"

**Just Browsing:**
"No problem at all! I'll let you browse. Is there anything specific you're looking for that I could help find?"

**Found It Cheaper:**
"Thanks for letting me know. We do have a price match policy - could you share where you found it cheaper? I might be able to match it."

**Not Sure About Fit/Quality:**
"Great question! [provide_product_details] Also, we have easy 7-day returns if it doesn't work out."

### Closing
If interested: "Great! Shall I send you a checkout link via SMS?"
If not interested: "No problem! Thanks for letting me know. Have a great day!"

## NEVER Do This
- Call more than once for the same abandoned cart
- Offer discounts immediately without understanding the issue
- Push after they've said no
- Make them feel guilty about not purchasing

Function Definitions

Order Management Tools

{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_order_status",
        "description": "Get order status, tracking information, and delivery estimate",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": {
              "type": "string",
              "description": "Order ID (e.g., ORD-123456)"
            },
            "phone_number": {
              "type": "string",
              "description": "Customer phone number to look up orders"
            }
          }
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "get_tracking_details",
        "description": "Get real-time tracking information from shipping carrier",
        "parameters": {
          "type": "object",
          "properties": {
            "tracking_number": {
              "type": "string",
              "description": "Carrier tracking number"
            },
            "carrier": {
              "type": "string",
              "enum": ["bluedart", "delhivery", "ecom_express", "fedex", "ups", "dhl"],
              "description": "Shipping carrier name"
            }
          },
          "required": ["tracking_number", "carrier"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "confirm_cod_order",
        "description": "Confirm a Cash-on-Delivery order",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": {
              "type": "string"
            },
            "confirmed": {
              "type": "boolean",
              "description": "Whether customer confirmed the order"
            },
            "modification_requested": {
              "type": "string",
              "description": "Any modifications requested (address, date, etc.)"
            }
          },
          "required": ["order_id", "confirmed"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "cancel_order",
        "description": "Cancel an order and process refund if applicable",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": {
              "type": "string"
            },
            "reason": {
              "type": "string",
              "enum": ["changed_mind", "found_cheaper", "delivery_too_slow", "wrong_item", "other"]
            },
            "reason_details": {
              "type": "string",
              "description": "Additional details about cancellation reason"
            }
          },
          "required": ["order_id", "reason"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "reschedule_delivery",
        "description": "Reschedule delivery to a different date or time",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": {
              "type": "string"
            },
            "new_date": {
              "type": "string",
              "description": "New preferred delivery date (YYYY-MM-DD)"
            },
            "time_slot": {
              "type": "string",
              "enum": ["morning", "afternoon", "evening"],
              "description": "Preferred time slot"
            }
          },
          "required": ["order_id", "new_date"]
        }
      }
    }
  ]
}

Return & Refund Tools

{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "initiate_return",
        "description": "Start a return request for an order item",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": {
              "type": "string"
            },
            "items": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "sku": { "type": "string" },
                  "quantity": { "type": "integer" },
                  "reason": {
                    "type": "string",
                    "enum": ["damaged", "wrong_item", "not_as_described", "size_issue", "quality_issue", "changed_mind"]
                  }
                }
              },
              "description": "Items to return"
            },
            "refund_method": {
              "type": "string",
              "enum": ["original_payment", "store_credit", "bank_transfer"],
              "description": "Preferred refund method"
            }
          },
          "required": ["order_id", "items"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "schedule_pickup",
        "description": "Schedule a return pickup from customer address",
        "parameters": {
          "type": "object",
          "properties": {
            "return_id": {
              "type": "string"
            },
            "pickup_date": {
              "type": "string",
              "description": "Preferred pickup date (YYYY-MM-DD)"
            },
            "time_slot": {
              "type": "string",
              "enum": ["morning", "afternoon", "evening"]
            },
            "address": {
              "type": "string",
              "description": "Pickup address (if different from order address)"
            }
          },
          "required": ["return_id", "pickup_date"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "check_return_status",
        "description": "Check the status of a return request",
        "parameters": {
          "type": "object",
          "properties": {
            "return_id": {
              "type": "string"
            },
            "order_id": {
              "type": "string"
            }
          }
        }
      }
    }
  ]
}

Cart Recovery Tools

{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_abandoned_cart",
        "description": "Get details of customer's abandoned cart",
        "parameters": {
          "type": "object",
          "properties": {
            "customer_id": {
              "type": "string"
            },
            "phone_number": {
              "type": "string"
            }
          }
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "check_available_offers",
        "description": "Check available discounts and offers for the cart",
        "parameters": {
          "type": "object",
          "properties": {
            "cart_id": {
              "type": "string"
            },
            "category": {
              "type": "string",
              "description": "Product category to check offers for"
            }
          },
          "required": ["cart_id"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "apply_discount",
        "description": "Apply a discount code to the cart",
        "parameters": {
          "type": "object",
          "properties": {
            "cart_id": {
              "type": "string"
            },
            "discount_code": {
              "type": "string"
            }
          },
          "required": ["cart_id", "discount_code"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "send_checkout_link",
        "description": "Send a direct checkout link via SMS",
        "parameters": {
          "type": "object",
          "properties": {
            "cart_id": {
              "type": "string"
            },
            "phone_number": {
              "type": "string"
            },
            "include_discount": {
              "type": "boolean",
              "description": "Include any discussed discount in the link"
            }
          },
          "required": ["cart_id", "phone_number"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "record_cart_abandonment_reason",
        "description": "Record why customer didn't complete purchase",
        "parameters": {
          "type": "object",
          "properties": {
            "cart_id": {
              "type": "string"
            },
            "reason": {
              "type": "string",
              "enum": ["price_too_high", "shipping_cost", "payment_issues", "found_cheaper", "just_browsing", "not_ready", "other"]
            },
            "notes": {
              "type": "string"
            }
          },
          "required": ["cart_id", "reason"]
        }
      }
    }
  ]
}

Tool Handlers (Go)

Order Status Handler

package tools

import (
    "context"
    "fmt"
    "strings"
    "time"
)

type Order struct {
    ID                string
    Status            string
    Items             []OrderItem
    TrackingNumber    string
    Carrier           string
    ShippedAt         *time.Time
    EstimatedDelivery *time.Time
    DeliveredAt       *time.Time
    CustomerName      string
    DeliveryAddress   string
}

type OrderItem struct {
    SKU      string
    Name     string
    Quantity int
    Price    float64
}

func HandleGetOrderStatus(ctx context.Context, params map[string]any) (any, error) {
    var order *Order
    var err error

    if orderID, ok := params["order_id"].(string); ok && orderID != "" {
        order, err = orderService.GetByID(ctx, orderID)
    } else if phone, ok := params["phone_number"].(string); ok && phone != "" {
        orders, err := orderService.GetByPhone(ctx, phone)
        if err != nil {
            return nil, err
        }
        if len(orders) == 0 {
            return map[string]any{
                "found":   false,
                "message": "No orders found for this phone number. Please verify the number or provide an order ID.",
            }, nil
        }
        if len(orders) == 1 {
            order = orders[0]
        } else {
            return formatMultipleOrders(orders), nil
        }
    }

    if err != nil || order == nil {
        return map[string]any{
            "found":   false,
            "message": "Order not found. Please verify the order number and try again.",
        }, nil
    }

    return formatOrderForSpeech(order), nil
}

func formatOrderForSpeech(order *Order) map[string]any {
    result := map[string]any{
        "found":    true,
        "order_id": spellOut(order.ID),
        "status":   order.Status,
    }

    switch order.Status {
    case "processing":
        result["message"] = fmt.Sprintf(
            "Your order %s is being prepared and will ship within 1-2 business days.",
            spellOut(order.ID),
        )

    case "shipped":
        result["message"] = fmt.Sprintf(
            "Your order %s shipped on %s via %s. Tracking number is %s. Expected delivery is %s.",
            spellOut(order.ID),
            order.ShippedAt.Format("January 2"),
            order.Carrier,
            spellOut(order.TrackingNumber),
            order.EstimatedDelivery.Format("Monday, January 2"),
        )
        result["tracking_number"] = order.TrackingNumber
        result["carrier"] = order.Carrier

    case "out_for_delivery":
        result["message"] = fmt.Sprintf(
            "Great news! Your order %s is out for delivery today and should arrive by evening.",
            spellOut(order.ID),
        )

    case "delivered":
        result["message"] = fmt.Sprintf(
            "Your order %s was delivered on %s.",
            spellOut(order.ID),
            order.DeliveredAt.Format("Monday, January 2 at 3:04 PM"),
        )

    case "delayed":
        result["message"] = fmt.Sprintf(
            "I apologize, your order %s has been delayed. The new expected delivery is %s. We're sorry for the inconvenience.",
            spellOut(order.ID),
            order.EstimatedDelivery.Format("Monday, January 2"),
        )
        result["requires_escalation"] = true
    }

    return result
}

func formatMultipleOrders(orders []*Order) map[string]any {
    orderList := make([]map[string]any, len(orders))

    for i, order := range orders {
        orderList[i] = map[string]any{
            "order_id": spellOut(order.ID),
            "status":   order.Status,
            "date":     order.ShippedAt.Format("January 2"),
        }
    }

    message := fmt.Sprintf("I found %d orders. ", len(orders))
    for i, order := range orders {
        message += fmt.Sprintf("Order %d: %s, status %s. ", i+1, spellOut(order.ID), order.Status)
    }
    message += "Which order would you like details on?"

    return map[string]any{
        "found":    true,
        "multiple": true,
        "count":    len(orders),
        "orders":   orderList,
        "message":  message,
    }
}

func spellOut(s string) string {
    // "ORD123456" -> "O R D 1 2 3 4 5 6"
    return strings.Join(strings.Split(s, ""), " ")
}

COD Confirmation Handler

func HandleConfirmCODOrder(ctx context.Context, params map[string]any) (any, error) {
    orderID := params["order_id"].(string)
    confirmed := params["confirmed"].(bool)

    order, err := orderService.GetByID(ctx, orderID)
    if err != nil {
        return nil, err
    }

    if confirmed {
        err = orderService.ConfirmCOD(ctx, orderID)
        if err != nil {
            return nil, err
        }

        // Update analytics
        metrics.Increment("cod.confirmed")

        return map[string]any{
            "success": true,
            "message": fmt.Sprintf(
                "Order confirmed. Your %s will be delivered by %s. Please keep %s rupees ready. You'll receive an SMS before delivery.",
                order.Items[0].Name,
                order.EstimatedDelivery.Format("Monday, January 2"),
                formatCurrency(order.Total),
            ),
        }, nil
    }

    // Handle non-confirmation
    if modRequest, ok := params["modification_requested"].(string); ok {
        return handleCODModification(ctx, order, modRequest)
    }

    return map[string]any{
        "success": false,
        "message": "I understand. Would you like to modify the order or cancel it?",
    }, nil
}

func handleCODModification(ctx context.Context, order *Order, modification string) (map[string]any, error) {
    switch {
    case strings.Contains(modification, "address"):
        return map[string]any{
            "action":  "collect_new_address",
            "message": "Sure, I can update the delivery address. What's the new address?",
        }, nil

    case strings.Contains(modification, "date"):
        return map[string]any{
            "action":  "collect_new_date",
            "message": "When would you prefer delivery? We can do any day this week.",
        }, nil

    case strings.Contains(modification, "payment"):
        return map[string]any{
            "action":  "change_payment",
            "message": "Would you like to switch to prepaid? I can send you a payment link.",
        }, nil
    }

    return map[string]any{
        "action":  "clarify",
        "message": "What would you like to modify - the delivery address, delivery date, or payment method?",
    }, nil
}

Cart Recovery Handler

type AbandonedCart struct {
    ID           string
    CustomerID   string
    CustomerName string
    Items        []CartItem
    Total        float64
    AbandonedAt  time.Time
    RecoveryAttempts int
}

type CartItem struct {
    SKU      string
    Name     string
    Quantity int
    Price    float64
    ImageURL string
}

func HandleGetAbandonedCart(ctx context.Context, params map[string]any) (any, error) {
    var cart *AbandonedCart
    var err error

    if customerID, ok := params["customer_id"].(string); ok {
        cart, err = cartService.GetAbandonedByCustomer(ctx, customerID)
    } else if phone, ok := params["phone_number"].(string); ok {
        cart, err = cartService.GetAbandonedByPhone(ctx, phone)
    }

    if err != nil || cart == nil {
        return map[string]any{
            "found": false,
            "message": "No abandoned cart found.",
        }, nil
    }

    // Format items for speech
    itemDescription := formatCartItemsForSpeech(cart.Items)

    return map[string]any{
        "found":       true,
        "cart_id":     cart.ID,
        "items":       cart.Items,
        "total":       cart.Total,
        "item_count":  len(cart.Items),
        "message":     fmt.Sprintf("You have %s in your cart, totaling %s.", itemDescription, formatCurrency(cart.Total)),
        "abandoned_hours_ago": time.Since(cart.AbandonedAt).Hours(),
    }, nil
}

func HandleCheckAvailableOffers(ctx context.Context, params map[string]any) (any, error) {
    cartID := params["cart_id"].(string)

    offers, err := promotionService.GetAvailableOffers(ctx, cartID)
    if err != nil {
        return nil, err
    }

    if len(offers) == 0 {
        return map[string]any{
            "has_offers": false,
            "message":    "I don't have any special offers available right now, but your cart items are already competitively priced!",
        }, nil
    }

    // Find best offer
    bestOffer := findBestOffer(offers)

    return map[string]any{
        "has_offers":  true,
        "best_offer":  bestOffer,
        "all_offers":  offers,
        "message":     fmt.Sprintf("Good news! I found a %s discount code '%s' you can use. This would save you %s.", bestOffer.Description, bestOffer.Code, formatCurrency(bestOffer.Savings)),
    }, nil
}

func HandleSendCheckoutLink(ctx context.Context, params map[string]any) (any, error) {
    cartID := params["cart_id"].(string)
    phone := params["phone_number"].(string)
    includeDiscount := params["include_discount"].(bool)

    // Generate checkout link
    link, err := cartService.GenerateCheckoutLink(ctx, cartID, includeDiscount)
    if err != nil {
        return nil, err
    }

    // Send SMS
    err = smsService.Send(ctx, phone, fmt.Sprintf(
        "Complete your order: %s\nThis link expires in 24 hours.",
        link,
    ))
    if err != nil {
        return map[string]any{
            "success": false,
            "message": "I had trouble sending the link. Can I try a different number?",
        }, nil
    }

    // Track conversion attempt
    metrics.Increment("cart_recovery.checkout_link_sent")

    return map[string]any{
        "success": true,
        "message": "I've sent you a checkout link via SMS. It should arrive in just a few seconds. The link is valid for 24 hours.",
    }, nil
}

func HandleRecordCartAbandonmentReason(ctx context.Context, params map[string]any) (any, error) {
    cartID := params["cart_id"].(string)
    reason := params["reason"].(string)
    notes := params["notes"].(string)

    err := analyticsService.RecordAbandonmentReason(ctx, cartID, reason, notes)
    if err != nil {
        return nil, err
    }

    // Track for analysis
    metrics.Increment("cart_abandonment.reason." + reason)

    return map[string]any{
        "recorded": true,
        "message":  "Thank you for the feedback! We really appreciate it.",
    }, nil
}

func formatCartItemsForSpeech(items []CartItem) string {
    if len(items) == 1 {
        return fmt.Sprintf("a %s", items[0].Name)
    }

    names := make([]string, len(items))
    for i, item := range items {
        names[i] = item.Name
    }

    if len(names) == 2 {
        return fmt.Sprintf("%s and %s", names[0], names[1])
    }

    return fmt.Sprintf("%s, and %s", strings.Join(names[:len(names)-1], ", "), names[len(names)-1])
}

Platform Integrations

Shopify Integration

package shopify

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
)

type ShopifyClient struct {
    shopURL     string
    accessToken string
    httpClient  *http.Client
}

func NewShopifyClient(shopURL, accessToken string) *ShopifyClient {
    return &ShopifyClient{
        shopURL:     shopURL,
        accessToken: accessToken,
        httpClient:  &http.Client{Timeout: 10 * time.Second},
    }
}

// GetOrder fetches order details from Shopify
func (c *ShopifyClient) GetOrder(ctx context.Context, orderID string) (*Order, error) {
    url := fmt.Sprintf("https://%s/admin/api/2024-01/orders/%s.json", c.shopURL, orderID)

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    req.Header.Set("X-Shopify-Access-Token", c.accessToken)
    req.Header.Set("Content-Type", "application/json")

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result struct {
        Order ShopifyOrder `json:"order"`
    }
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }

    return convertShopifyOrder(&result.Order), nil
}

// GetOrderByPhone finds orders by customer phone
func (c *ShopifyClient) GetOrderByPhone(ctx context.Context, phone string) ([]*Order, error) {
    // First, find customer by phone
    customersURL := fmt.Sprintf(
        "https://%s/admin/api/2024-01/customers/search.json?query=phone:%s",
        c.shopURL, phone,
    )

    // ... fetch customer, then their orders

    ordersURL := fmt.Sprintf(
        "https://%s/admin/api/2024-01/orders.json?customer_id=%s&status=any",
        c.shopURL, customerID,
    )

    // ... fetch and return orders
}

// GetAbandonedCheckouts retrieves abandoned carts
func (c *ShopifyClient) GetAbandonedCheckouts(ctx context.Context) ([]*AbandonedCart, error) {
    url := fmt.Sprintf(
        "https://%s/admin/api/2024-01/checkouts.json?created_at_min=%s",
        c.shopURL,
        time.Now().Add(-72*time.Hour).Format(time.RFC3339),
    )

    // ... fetch abandoned checkouts
}

// GetFulfillment gets tracking info for an order
func (c *ShopifyClient) GetFulfillment(ctx context.Context, orderID string) (*Fulfillment, error) {
    url := fmt.Sprintf(
        "https://%s/admin/api/2024-01/orders/%s/fulfillments.json",
        c.shopURL, orderID,
    )

    // ... fetch fulfillment with tracking details
}

func convertShopifyOrder(so *ShopifyOrder) *Order {
    order := &Order{
        ID:              fmt.Sprintf("%d", so.ID),
        Status:          mapShopifyStatus(so.FulfillmentStatus),
        CustomerName:    so.Customer.FirstName + " " + so.Customer.LastName,
        CustomerPhone:   so.Customer.Phone,
        DeliveryAddress: formatAddress(so.ShippingAddress),
        Total:           so.TotalPrice,
        Currency:        so.Currency,
        CreatedAt:       so.CreatedAt,
    }

    for _, item := range so.LineItems {
        order.Items = append(order.Items, OrderItem{
            SKU:      item.SKU,
            Name:     item.Title,
            Quantity: item.Quantity,
            Price:    item.Price,
        })
    }

    if len(so.Fulfillments) > 0 {
        fulfillment := so.Fulfillments[0]
        order.TrackingNumber = fulfillment.TrackingNumber
        order.Carrier = fulfillment.TrackingCompany
        order.ShippedAt = &fulfillment.CreatedAt
    }

    return order
}

func mapShopifyStatus(status string) string {
    switch status {
    case "fulfilled":
        return "shipped"
    case "partial":
        return "partially_shipped"
    case "":
        return "processing"
    default:
        return status
    }
}

Shopify Webhook Handler

// Handle Shopify webhooks for real-time order updates
func HandleShopifyWebhook(w http.ResponseWriter, r *http.Request) {
    topic := r.Header.Get("X-Shopify-Topic")

    var payload json.RawMessage
    if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
        http.Error(w, "Invalid payload", http.StatusBadRequest)
        return
    }

    switch topic {
    case "orders/create":
        handleOrderCreated(payload)

    case "orders/fulfilled":
        handleOrderFulfilled(payload)

    case "fulfillments/update":
        handleFulfillmentUpdate(payload)

    case "checkouts/create":
        handleCheckoutCreated(payload)

    case "checkouts/update":
        handleCheckoutAbandoned(payload)
    }

    w.WriteHeader(http.StatusOK)
}

func handleOrderFulfilled(payload json.RawMessage) {
    var order ShopifyOrder
    json.Unmarshal(payload, &order)

    // Trigger outbound call for delivery notification
    if order.Customer.Phone != "" {
        triggerDeliveryNotificationCall(
            order.Customer.Phone,
            order.Customer.FirstName,
            fmt.Sprintf("%d", order.ID),
            order.Fulfillments[0].TrackingNumber,
        )
    }
}

func handleCheckoutAbandoned(payload json.RawMessage) {
    var checkout ShopifyCheckout
    json.Unmarshal(payload, &checkout)

    // Check if checkout was completed
    if checkout.CompletedAt != nil {
        return // Not abandoned
    }

    // Wait period before cart recovery (e.g., 1 hour)
    time.AfterFunc(1*time.Hour, func() {
        // Verify still abandoned
        current, _ := shopifyClient.GetCheckout(checkout.Token)
        if current.CompletedAt != nil {
            return // Was completed
        }

        // Trigger cart recovery call
        if checkout.Phone != "" {
            triggerCartRecoveryCall(
                checkout.Phone,
                checkout.Customer.FirstName,
                checkout.Token,
            )
        }
    })
}

WooCommerce Integration

package woocommerce

import (
    "context"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "net/http"
)

type WooCommerceClient struct {
    siteURL        string
    consumerKey    string
    consumerSecret string
    httpClient     *http.Client
}

func NewWooCommerceClient(siteURL, consumerKey, consumerSecret string) *WooCommerceClient {
    return &WooCommerceClient{
        siteURL:        siteURL,
        consumerKey:    consumerKey,
        consumerSecret: consumerSecret,
        httpClient:     &http.Client{Timeout: 10 * time.Second},
    }
}

func (c *WooCommerceClient) authHeader() string {
    credentials := c.consumerKey + ":" + c.consumerSecret
    return "Basic " + base64.StdEncoding.EncodeToString([]byte(credentials))
}

// GetOrder fetches order from WooCommerce
func (c *WooCommerceClient) GetOrder(ctx context.Context, orderID string) (*Order, error) {
    url := fmt.Sprintf("%s/wp-json/wc/v3/orders/%s", c.siteURL, orderID)

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", c.authHeader())

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var wooOrder WooOrder
    if err := json.NewDecoder(resp.Body).Decode(&wooOrder); err != nil {
        return nil, err
    }

    return convertWooOrder(&wooOrder), nil
}

// GetOrdersByPhone finds orders by billing phone
func (c *WooCommerceClient) GetOrdersByPhone(ctx context.Context, phone string) ([]*Order, error) {
    // WooCommerce doesn't support phone search directly
    // Use search or custom endpoint
    url := fmt.Sprintf(
        "%s/wp-json/wc/v3/orders?search=%s",
        c.siteURL, phone,
    )

    // ... fetch and filter orders
}

// UpdateOrderStatus updates order status
func (c *WooCommerceClient) UpdateOrderStatus(ctx context.Context, orderID, status string) error {
    url := fmt.Sprintf("%s/wp-json/wc/v3/orders/%s", c.siteURL, orderID)

    payload := map[string]string{"status": status}
    body, _ := json.Marshal(payload)

    req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewReader(body))
    if err != nil {
        return err
    }

    req.Header.Set("Authorization", c.authHeader())
    req.Header.Set("Content-Type", "application/json")

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("failed to update order: %d", resp.StatusCode)
    }

    return nil
}

// CreateRefund processes a refund
func (c *WooCommerceClient) CreateRefund(ctx context.Context, orderID string, amount float64, reason string) error {
    url := fmt.Sprintf("%s/wp-json/wc/v3/orders/%s/refunds", c.siteURL, orderID)

    payload := map[string]any{
        "amount": fmt.Sprintf("%.2f", amount),
        "reason": reason,
    }
    body, _ := json.Marshal(payload)

    req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body))
    if err != nil {
        return err
    }

    req.Header.Set("Authorization", c.authHeader())
    req.Header.Set("Content-Type", "application/json")

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    return nil
}

func convertWooOrder(wo *WooOrder) *Order {
    order := &Order{
        ID:              fmt.Sprintf("%d", wo.ID),
        Status:          mapWooStatus(wo.Status),
        CustomerName:    wo.Billing.FirstName + " " + wo.Billing.LastName,
        CustomerPhone:   wo.Billing.Phone,
        CustomerEmail:   wo.Billing.Email,
        DeliveryAddress: formatWooAddress(wo.Shipping),
        Total:           parseFloat(wo.Total),
        Currency:        wo.Currency,
        CreatedAt:       wo.DateCreated,
        PaymentMethod:   wo.PaymentMethodTitle,
    }

    for _, item := range wo.LineItems {
        order.Items = append(order.Items, OrderItem{
            SKU:      item.SKU,
            Name:     item.Name,
            Quantity: item.Quantity,
            Price:    parseFloat(item.Total),
        })
    }

    // Get tracking from meta or shipment tracking plugin
    if trackingMeta := findMeta(wo.MetaData, "_tracking_number"); trackingMeta != "" {
        order.TrackingNumber = trackingMeta
    }

    return order
}

func mapWooStatus(status string) string {
    switch status {
    case "processing":
        return "processing"
    case "on-hold":
        return "on_hold"
    case "completed":
        return "delivered"
    case "pending":
        return "pending_payment"
    case "cancelled":
        return "cancelled"
    case "refunded":
        return "refunded"
    default:
        return status
    }
}

WooCommerce Webhook Handler

// Handle WooCommerce webhooks
func HandleWooCommerceWebhook(w http.ResponseWriter, r *http.Request) {
    topic := r.Header.Get("X-WC-Webhook-Topic")

    var payload json.RawMessage
    if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
        http.Error(w, "Invalid payload", http.StatusBadRequest)
        return
    }

    switch topic {
    case "order.created":
        handleWooOrderCreated(payload)

    case "order.updated":
        handleWooOrderUpdated(payload)
    }

    w.WriteHeader(http.StatusOK)
}

func handleWooOrderCreated(payload json.RawMessage) {
    var order WooOrder
    json.Unmarshal(payload, &order)

    // COD order - schedule confirmation call
    if order.PaymentMethod == "cod" {
        scheduleCallAfter(30*time.Minute, func() {
            triggerCODConfirmationCall(
                order.Billing.Phone,
                order.Billing.FirstName,
                fmt.Sprintf("%d", order.ID),
                order.Total,
                order.LineItems[0].Name,
            )
        })
    }
}

Analytics & Metrics

Key Performance Indicators

type EcommerceVoiceMetrics struct {
    // WISMO Metrics
    WISMOCallsTotal       int
    WISMOCallsHandledByAI int
    WISMOResolutionRate   float64
    WISMOAvgHandleTime    time.Duration

    // COD Confirmation
    CODCallsAttempted     int
    CODCallsConnected     int
    CODConfirmationRate   float64
    RTOReductionPercent   float64

    // Cart Recovery
    AbandonedCartsTargeted int
    RecoveryCallsMade      int
    CartsRecovered         int
    RecoveryRate           float64
    RevenueRecovered       float64

    // Returns
    ReturnRequestsHandled  int
    AutomatedPickupScheduled int
    ReturnProcessingTime   time.Duration

    // Customer Satisfaction
    CSAT                   float64
    NPS                    int
}

func CalculateEcommerceMetrics(startDate, endDate time.Time) *EcommerceVoiceMetrics {
    metrics := &EcommerceVoiceMetrics{}

    // WISMO metrics
    wismoStats := db.GetWISMOStats(startDate, endDate)
    metrics.WISMOCallsTotal = wismoStats.Total
    metrics.WISMOCallsHandledByAI = wismoStats.AIHandled
    metrics.WISMOResolutionRate = float64(wismoStats.Resolved) / float64(wismoStats.AIHandled)
    metrics.WISMOAvgHandleTime = wismoStats.TotalDuration / time.Duration(wismoStats.Total)

    // COD metrics
    codStats := db.GetCODStats(startDate, endDate)
    metrics.CODCallsAttempted = codStats.Attempted
    metrics.CODCallsConnected = codStats.Connected
    metrics.CODConfirmationRate = float64(codStats.Confirmed) / float64(codStats.Connected)

    // Calculate RTO reduction
    previousRTORate := db.GetRTORate(startDate.AddDate(0, -1, 0), startDate)
    currentRTORate := db.GetRTORate(startDate, endDate)
    metrics.RTOReductionPercent = (previousRTORate - currentRTORate) / previousRTORate * 100

    // Cart recovery metrics
    cartStats := db.GetCartRecoveryStats(startDate, endDate)
    metrics.AbandonedCartsTargeted = cartStats.Targeted
    metrics.RecoveryCallsMade = cartStats.CallsMade
    metrics.CartsRecovered = cartStats.Recovered
    metrics.RecoveryRate = float64(cartStats.Recovered) / float64(cartStats.CallsMade)
    metrics.RevenueRecovered = cartStats.TotalRecoveredValue

    return metrics
}

Dashboard Tracking

func trackOrderStatusCall(call *Call, order *Order, resolved bool) {
    // Call metrics
    metrics.Increment("ecommerce.wismo.calls.total")
    if resolved {
        metrics.Increment("ecommerce.wismo.calls.resolved")
    } else {
        metrics.Increment("ecommerce.wismo.calls.escalated")
    }

    // Order status distribution
    metrics.Increment("ecommerce.wismo.status." + order.Status)

    // Timing
    metrics.Histogram("ecommerce.wismo.duration", call.Duration.Seconds())

    // Track by carrier for shipping issues
    if order.Carrier != "" {
        metrics.Increment("ecommerce.wismo.carrier." + order.Carrier)
    }
}

func trackCartRecovery(cart *AbandonedCart, outcome string, revenue float64) {
    metrics.Increment("ecommerce.cart_recovery.calls.total")
    metrics.Increment("ecommerce.cart_recovery.outcome." + outcome)

    if outcome == "recovered" {
        metrics.Increment("ecommerce.cart_recovery.conversions")
        metrics.Gauge("ecommerce.cart_recovery.revenue", revenue)
    }

    // Track abandonment reasons
    if cart.AbandonmentReason != "" {
        metrics.Increment("ecommerce.cart_abandonment.reason." + cart.AbandonmentReason)
    }
}

func trackCODConfirmation(order *Order, confirmed bool, cancellationReason string) {
    metrics.Increment("ecommerce.cod.calls.total")

    if confirmed {
        metrics.Increment("ecommerce.cod.confirmed")
        metrics.Gauge("ecommerce.cod.confirmed_value", order.Total)
    } else {
        metrics.Increment("ecommerce.cod.cancelled")
        metrics.Increment("ecommerce.cod.cancellation_reason." + cancellationReason)
    }
}

ROI Calculator

type ROICalculation struct {
    // Costs
    VoiceAgentCost      float64
    CostPerMinute       float64
    TotalMinutes        float64

    // Savings
    AgentCostSaved      float64
    CallsDeflected      int
    CostPerHumanCall    float64

    // Revenue Impact
    CartsRecovered      float64
    RTOReduction        float64
    AverageOrderValue   float64

    // Totals
    TotalCost           float64
    TotalBenefit        float64
    ROI                 float64
}

func CalculateROI(metrics *EcommerceVoiceMetrics, config *PricingConfig) *ROICalculation {
    roi := &ROICalculation{}

    // Voice agent costs
    roi.TotalMinutes = float64(metrics.WISMOCallsHandledByAI) * 2.0 // avg 2 min per call
    roi.CostPerMinute = 0.15 // Example: $0.15/min
    roi.VoiceAgentCost = roi.TotalMinutes * roi.CostPerMinute

    // Agent cost savings
    roi.CallsDeflected = metrics.WISMOCallsHandledByAI
    roi.CostPerHumanCall = 5.0 // Example: $5 per human-handled call
    roi.AgentCostSaved = float64(roi.CallsDeflected) * roi.CostPerHumanCall

    // Cart recovery revenue
    roi.CartsRecovered = metrics.RevenueRecovered

    // RTO reduction savings (for COD)
    // Assume 5% RTO rate reduction saves $X per order
    ordersWithReducedRTO := int(metrics.RTOReductionPercent * float64(metrics.CODCallsConnected))
    rtoSavingsPerOrder := 15.0 // Example: $15 saved per avoided RTO
    roi.RTOReduction = float64(ordersWithReducedRTO) * rtoSavingsPerOrder

    // Calculate ROI
    roi.TotalCost = roi.VoiceAgentCost
    roi.TotalBenefit = roi.AgentCostSaved + roi.CartsRecovered + roi.RTOReduction
    roi.ROI = (roi.TotalBenefit - roi.TotalCost) / roi.TotalCost * 100

    return roi
}

Best Practices

1. Order Identification

Make it easy for customers to identify their orders:

// Support multiple identification methods
func identifyOrder(params map[string]any) (*Order, error) {
    // Try order ID first (most specific)
    if orderID, ok := params["order_id"].(string); ok {
        return orderService.GetByID(orderID)
    }

    // Try phone + recent order
    if phone, ok := params["phone_number"].(string); ok {
        orders, _ := orderService.GetRecentByPhone(phone, 30*24*time.Hour)
        if len(orders) == 1 {
            return orders[0], nil
        }
        // Multiple orders - need disambiguation
    }

    // Try email
    if email, ok := params["email"].(string); ok {
        return orderService.GetRecentByEmail(email)
    }

    return nil, errors.New("could not identify order")
}

2. Handle Delivery Issues Gracefully

## Delivery Issue Handling

### Package Shows Delivered But Customer Didn't Receive
"I'm sorry to hear that. This is concerning and I want to make sure we resolve it quickly.
Let me connect you with our delivery specialist who can investigate this right away."
→ Transfer to human agent with context

### Package Delayed Beyond Estimate
"I apologize for the delay with your order.
[If reason known] It looks like [reason - weather, carrier delay, etc.].
[If unknown] Let me check with our shipping team and have someone call you back within 2 hours with an update.
As a gesture of apology, I'm adding a {{discountAmount}} credit to your account for your next order."

### Wrong Item Delivered
"I'm so sorry about that mix-up! Let me start a return for the wrong item right away,
and we'll ship out the correct item today with express shipping at no extra charge."

3. COD Confirmation Best Practices

// Optimal timing for COD confirmation calls
func scheduleCODConfirmation(order *Order) {
    // Best conversion: 30-60 minutes after order
    delay := 30 * time.Minute

    // Adjust for time of day
    now := time.Now()
    callTime := now.Add(delay)

    // Don't call before 9 AM or after 8 PM
    if callTime.Hour() < 9 {
        callTime = time.Date(callTime.Year(), callTime.Month(), callTime.Day(), 9, 0, 0, 0, callTime.Location())
    } else if callTime.Hour() >= 20 {
        // Schedule for next morning
        callTime = time.Date(callTime.Year(), callTime.Month(), callTime.Day()+1, 10, 0, 0, 0, callTime.Location())
    }

    scheduler.Schedule(callTime, func() {
        triggerCODConfirmationCall(order)
    })
}

// Maximum 2 attempts for COD confirmation
func handleCODNoAnswer(order *Order, attemptNumber int) {
    if attemptNumber >= 2 {
        // Mark as unconfirmed, proceed with caution
        orderService.MarkUnconfirmed(order.ID)
        return
    }

    // Schedule retry after 2 hours
    scheduler.Schedule(time.Now().Add(2*time.Hour), func() {
        triggerCODConfirmationCall(order)
    })
}

4. Cart Recovery Timing

// Optimal timing for cart recovery calls
func scheduleCartRecoveryCall(cart *AbandonedCart) {
    // Timing based on cart value and abandonment pattern
    var delay time.Duration

    switch {
    case cart.Total > 500: // High-value cart
        delay = 1 * time.Hour // Call sooner
    case cart.Total > 100:
        delay = 2 * time.Hour
    default:
        delay = 4 * time.Hour
    }

    // Don't call on weekends for B2B
    if cart.CustomerType == "business" && isWeekend(time.Now().Add(delay)) {
        delay = timeUntilNextWeekday(time.Now().Add(delay))
    }

    scheduler.Schedule(time.Now().Add(delay), func() {
        // Verify cart still abandoned
        current, _ := cartService.Get(cart.ID)
        if current.Status == "completed" {
            return
        }
        triggerCartRecoveryCall(cart)
    })
}

// Only one recovery call per cart
func canAttemptRecovery(cartID string) bool {
    attempts := db.GetRecoveryAttempts(cartID)
    return attempts == 0
}

5. Multilingual Support for Indian E-commerce

// Language selection based on customer preference
func determineLanguage(customer *Customer) string {
    // Use customer's preferred language if set
    if customer.PreferredLanguage != "" {
        return customer.PreferredLanguage
    }

    // Infer from delivery state
    stateLanguageMap := map[string]string{
        "Maharashtra":    "mr-IN",
        "Tamil Nadu":     "ta-IN",
        "Karnataka":      "kn-IN",
        "Gujarat":        "gu-IN",
        "West Bengal":    "bn-IN",
        "Kerala":         "ml-IN",
        "Andhra Pradesh": "te-IN",
        "Telangana":      "te-IN",
    }

    if lang, ok := stateLanguageMap[customer.State]; ok {
        return lang
    }

    // Default to Hindi for North India, English otherwise
    northernStates := []string{"Delhi", "UP", "Bihar", "Rajasthan", "MP", "Punjab", "Haryana"}
    for _, state := range northernStates {
        if customer.State == state {
            return "hi-IN"
        }
    }

    return "en-IN"
}

Next Steps