> ## Documentation Index
> Fetch the complete documentation index at: https://twigz.arqqin.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Cart to Order

> Convert shopping carts to orders with payment processing and validation

## Overview

The Cart to Order API handles the critical conversion from shopping cart to confirmed orders. This system manages payment processing, inventory validation, cart cleanup, and order creation with comprehensive error handling and real-time updates.

**Location:** `convex/customers/cartToOrder.ts`

## Checkout Process

<Steps>
  <Step title="Validate Cart">
    Check item availability, stock levels, and price changes
  </Step>

  <Step title="Process Payment">
    Handle payment via Stripe, cash, or digital wallet
  </Step>

  <Step title="Create Order">
    Convert cart items to confirmed order with delivery details
  </Step>

  <Step title="Clear Cart">
    Automatically remove items from cart after successful order
  </Step>

  <Step title="Update Inventory">
    Reduce product stock levels and update analytics
  </Step>
</Steps>

## Create Order from Cart

Creates an order from the items in a customer's cart for a specific store.

<ParamField path="customerId" type="Id<'customers'>" required>
  Customer ID placing the order
</ParamField>

<ParamField path="storeId" type="Id<'stores'>" required>
  Store ID receiving the order
</ParamField>

<ParamField path="deliveryAddress" type="DeliveryAddress">
  Delivery address information (required for delivery orders, optional for pickup)
</ParamField>

<ParamField path="deliveryType" type="'delivery' | 'pickup'" required>
  Delivery method selection
</ParamField>

<ParamField path="deliveryFee" type="number" required>
  Delivery fee amount (0 for pickup)
</ParamField>

<ParamField path="paymentMethod" type="'cash' | 'card' | 'wallet' | 'google_pay' | 'apple_pay'" required>
  Payment method used
</ParamField>

<ParamField path="paymentIntentId" type="string">
  Stripe PaymentIntent ID (required for card payments)
</ParamField>

<ParamField path="paymentStatus" type="'pending' | 'paid' | 'failed'">
  Payment status (default: "pending")
</ParamField>

<ParamField path="notes" type="string">
  Optional order notes or special instructions
</ParamField>

<ParamField path="estimatedDeliveryTime" type="number">
  Estimated delivery time in minutes
</ParamField>

<CodeGroup>
  ```typescript Cash Payment theme={null}
  // Basic order creation with cash payment
  const order = await convex.mutation(api.customers.cartToOrder.createOrderFromCart, {
    customerId: "c123456789",
    storeId: "j123456789",
    deliveryAddress: {
      fullAddress: "123 Main St, Apt 4B",
      city: "ct123456789",
      area: "ar123456789",
      phoneNumber: "+971501234567",
      flatVilaNumber: "4B",
      buildingNameNumber: "123",
      landmark: "Near Central Park"
    },
    deliveryType: "delivery",
    deliveryFee: 5.00,
    paymentMethod: "cash",
    notes: "Please ring the doorbell twice"
  });
  ```

  ```typescript Stripe Payment theme={null}
  // Order creation with Stripe payment integration
  const orderWithStripe = await convex.mutation(api.customers.cartToOrder.createOrderFromCart, {
    customerId: "c123456789",
    storeId: "j123456789",
    deliveryAddress: {
      fullAddress: "123 Main St, Apt 4B",
      city: "ct123456789",
      area: "ar123456789",
      phoneNumber: "+971501234567"
    },
    deliveryType: "delivery",
    deliveryFee: 5.00,
    paymentMethod: "card",
    paymentIntentId: "pi_123456789", // From Stripe PaymentIntent
    paymentStatus: "paid", // Set to "paid" for successful Stripe payments
    notes: "Paid via Stripe"
  });
  ```

  ```typescript Pickup Order theme={null}
  // Pickup order (no delivery fee or address)
  const pickupOrder = await convex.mutation(api.customers.cartToOrder.createOrderFromCart, {
    customerId: "c123456789",
    storeId: "j123456789",
    deliveryAddress: {
      fullAddress: "Pickup from store",
      city: "ct123456789", // Store's city
      area: "ar123456789", // Store's area
      phoneNumber: "+971501234567"
    },
    deliveryType: "pickup",
    deliveryFee: 0.00,
    paymentMethod: "card",
    paymentIntentId: "pi_987654321",
    paymentStatus: "paid",
    notes: "Customer pickup - call when ready"
  });
  ```

  ```javascript JavaScript theme={null}
  const order = await convex.mutation("customers.cartToOrder.createOrderFromCart", {
    customerId: "c123456789",
    storeId: "j123456789",
    deliveryAddress: {
      fullAddress: "123 Main St, Apt 4B",
      city: "ct123456789",
      area: "ar123456789",
      phoneNumber: "+971501234567"
    },
    deliveryType: "delivery",
    deliveryFee: 5.00,
    paymentMethod: "cash"
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  {
    "orderId": "o123456789",
    "orderSummary": {
      "subtotal": 45.97,
      "deliveryFee": 5.00,
      "platformCommission": 3.20,
      "storeEarnings": 47.77,
      "totalAmount": 50.97,
      "itemsCount": 3
    }
  }
  ```
</ResponseExample>

## Validate Cart for Checkout

Validates cart items before checkout, checking availability, stock, and price changes.

<ParamField path="customerId" type="Id<'customers'>" required>
  Customer ID
</ParamField>

<ParamField path="storeId" type="Id<'stores'>" required>
  Store ID
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const validation = await convex.mutation(api.customers.cartToOrder.validateCartForCheckout, {
    customerId: "c123456789",
    storeId: "j123456789"
  });

  if (!validation.isValid) {
    console.log("Validation issues:", validation.issues);
    // Handle issues before proceeding to checkout
  }
  ```

  ```javascript JavaScript theme={null}
  const validation = await convex.mutation("customers.cartToOrder.validateCartForCheckout", {
    customerId: "c123456789",
    storeId: "j123456789"
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Valid Cart Response theme={null}
  {
    "isValid": true,
    "issues": [],
    "updatedSubtotal": 47.50
  }
  ```

  ```json Invalid Cart Response theme={null}
  {
    "isValid": false,
    "issues": [
      {
        "type": "insufficient_stock",
        "productId": "p123456789",
        "productName": "Pizza Margherita",
        "requestedQuantity": 5,
        "availableQuantity": 3,
        "message": "Only 3 items available"
      },
      {
        "type": "price_changed",
        "productId": "p987654321",
        "productName": "Garlic Bread",
        "oldPrice": 6.99,
        "newPrice": 7.99,
        "message": "Price has increased by $1.00"
      },
      {
        "type": "product_unavailable",
        "productId": "p555666777",
        "productName": "Caesar Salad",
        "message": "Product is no longer available"
      }
    ],
    "updatedSubtotal": 42.50
  }
  ```
</ResponseExample>

## Complete Checkout Flow

Here's a comprehensive checkout implementation:

<CodeGroup>
  ```typescript React Checkout Component theme={null}
  import { useState, useEffect } from 'react';
  import { useQuery, useMutation, useAction } from 'convex/react';
  import { api } from './convex/_generated/api';
  import { loadStripe } from '@stripe/stripe-js';

  const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

  interface CheckoutFlowProps {
    customerId: string;
    storeId: string;
    onSuccess: (orderId: string) => void;
    onError: (error: string) => void;
  }

  function CheckoutFlow({ customerId, storeId, onSuccess, onError }: CheckoutFlowProps) {
    const [step, setStep] = useState<'cart' | 'address' | 'payment' | 'processing'>('cart');
    const [deliveryType, setDeliveryType] = useState<'delivery' | 'pickup'>('delivery');
    const [deliveryAddress, setDeliveryAddress] = useState<any>(null);
    const [paymentMethod, setPaymentMethod] = useState<'cash' | 'card'>('card');
    const [validationIssues, setValidationIssues] = useState<any[]>([]);

    // API functions
    const cart = useQuery(api.customers.cart.getCart, { customerId, storeId });
    const validateCart = useMutation(api.customers.cartToOrder.validateCartForCheckout);
    const createPaymentIntent = useAction(api.customers.payments.createPaymentIntent);
    const createOrder = useMutation(api.customers.cartToOrder.createOrderFromCart);
    const calculateCommission = useQuery(api.customers.checkoutHelpers.calculatePlatformCommission, 
      cart ? { 
        subtotal: cart.subtotal, 
        storeId,
        categoryIds: cart.items.map(item => item.categoryId).filter(Boolean)
      } : "skip"
    );

    // Step 1: Validate cart
    const handleCartValidation = async () => {
      try {
        const validation = await validateCart({ customerId, storeId });
        
        if (!validation.isValid) {
          setValidationIssues(validation.issues);
          return false;
        }
        
        setValidationIssues([]);
        return true;
      } catch (error) {
        onError('Cart validation failed');
        return false;
      }
    };

    // Step 2: Process checkout
    const handleCheckout = async () => {
      if (!cart || !calculateCommission) return;

      setStep('processing');

      try {
        const deliveryFee = deliveryType === 'delivery' ? 5.00 : 0;
        const platformFee = calculateCommission.platformCommission;
        const totalAmount = cart.subtotal + deliveryFee;

        if (paymentMethod === 'card') {
          // Stripe payment flow
          const paymentSetup = await createPaymentIntent({
            customerId,
            storeId,
            subtotal: cart.subtotal,
            deliveryFee,
            platformFee,
            totalAmount,
            cartItems: cart.items.map(item => ({
              productId: item.productId,
              productName: item.productName,
              quantity: item.quantity,
              price: item.productPrice
            })),
            deliveryType,
            deliveryAddress
          });

          // Process payment with Stripe
          const stripe = await stripePromise;
          if (!stripe) throw new Error('Stripe not loaded');

          const { error, paymentIntent } = await stripe.confirmCardPayment(
            paymentSetup.clientSecret,
            {
              payment_method: {
                card: {
                  // Card element would be here
                }
              }
            }
          );

          if (error) {
            throw new Error(error.message);
          }

          // Create order with successful payment
          const order = await createOrder({
            customerId,
            storeId,
            deliveryAddress,
            deliveryType,
            deliveryFee,
            paymentMethod: 'card',
            paymentIntentId: paymentIntent.id,
            paymentStatus: 'paid'
          });

          onSuccess(order.orderId);

        } else {
          // Cash payment flow
          const order = await createOrder({
            customerId,
            storeId,
            deliveryAddress,
            deliveryType,
            deliveryFee,
            paymentMethod: 'cash',
            paymentStatus: 'pending'
          });

          onSuccess(order.orderId);
        }

      } catch (error) {
        console.error('Checkout failed:', error);
        onError(error.message || 'Checkout failed');
        setStep('payment');
      }
    };

    // Render current step
    const renderStep = () => {
      switch (step) {
        case 'cart':
          return (
            <CartReview 
              cart={cart}
              validationIssues={validationIssues}
              onValidate={handleCartValidation}
              onNext={() => setStep('address')}
            />
          );
        
        case 'address':
          return (
            <AddressForm
              deliveryType={deliveryType}
              onDeliveryTypeChange={setDeliveryType}
              onAddressSubmit={(address) => {
                setDeliveryAddress(address);
                setStep('payment');
              }}
            />
          );
        
        case 'payment':
          return (
            <PaymentMethodSelection
              paymentMethod={paymentMethod}
              onPaymentMethodChange={setPaymentMethod}
              totalAmount={cart ? cart.subtotal + (deliveryType === 'delivery' ? 5 : 0) : 0}
              onConfirmOrder={handleCheckout}
            />
          );
        
        case 'processing':
          return (
            <div className="text-center p-8">
              <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
              <p className="text-lg font-medium">Processing your order...</p>
              <p className="text-gray-600">Please don't close this page</p>
            </div>
          );
        
        default:
          return null;
      }
    };

    return (
      <div className="max-w-2xl mx-auto">
        {/* Progress Indicator */}
        <div className="mb-8">
          <div className="flex items-center">
            {['cart', 'address', 'payment'].map((stepName, index) => (
              <React.Fragment key={stepName}>
                <div className={`flex items-center justify-center w-8 h-8 rounded-full text-sm font-medium ${
                  step === stepName ? 'bg-blue-600 text-white' :
                  ['cart', 'address', 'payment'].indexOf(step) > index ? 'bg-green-600 text-white' :
                  'bg-gray-300 text-gray-600'
                }`}>
                  {index + 1}
                </div>
                {index < 2 && (
                  <div className={`flex-1 h-1 mx-2 ${
                    ['cart', 'address', 'payment'].indexOf(step) > index ? 'bg-green-600' : 'bg-gray-300'
                  }`}></div>
                )}
              </React.Fragment>
            ))}
          </div>
          <div className="flex justify-between mt-2 text-sm text-gray-600">
            <span>Review Cart</span>
            <span>Delivery</span>
            <span>Payment</span>
          </div>
        </div>

        {/* Current Step Content */}
        {renderStep()}
      </div>
    );
  }

  export default CheckoutFlow;
  ```

  ```typescript Cart Validation Helper theme={null}
  import { useMutation } from 'convex/react';
  import { api } from './convex/_generated/api';

  interface ValidationIssue {
    type: 'insufficient_stock' | 'price_changed' | 'product_unavailable' | 'store_inactive';
    productId?: string;
    productName?: string;
    message: string;
    [key: string]: any;
  }

  function useCartValidation() {
    const validateCart = useMutation(api.customers.cartToOrder.validateCartForCheckout);

    const handleValidation = async (customerId: string, storeId: string): Promise<{
      isValid: boolean;
      issues: ValidationIssue[];
      updatedSubtotal?: number;
    }> => {
      try {
        const result = await validateCart({ customerId, storeId });
        return result;
      } catch (error) {
        console.error('Validation failed:', error);
        return {
          isValid: false,
          issues: [{
            type: 'store_inactive',
            message: 'Unable to validate cart. Please try again.'
          }]
        };
      }
    };

    const resolveValidationIssues = async (
      issues: ValidationIssue[],
      customerId: string,
      storeId: string
    ) => {
      const updateCartItem = useMutation(api.customers.cart.updateCartItemQuantity);
      const removeFromCart = useMutation(api.customers.cart.removeFromCart);

      for (const issue of issues) {
        switch (issue.type) {
          case 'insufficient_stock':
            if (issue.productId && issue.availableQuantity !== undefined) {
              // Update to available quantity
              await updateCartItem({
                customerId,
                storeId,
                productId: issue.productId,
                quantity: issue.availableQuantity
              });
            }
            break;

          case 'product_unavailable':
            if (issue.productId) {
              // Remove unavailable product
              await removeFromCart({
                customerId,
                storeId,
                productId: issue.productId
              });
            }
            break;

          case 'price_changed':
            // Price changes are automatically handled by the system
            // Just notify the user about the change
            console.log(`Price updated for ${issue.productName}: ${issue.oldPrice} → ${issue.newPrice}`);
            break;
        }
      }
    };

    return {
      validateCart: handleValidation,
      resolveIssues: resolveValidationIssues
    };
  }

  export { useCartValidation };
  ```
</CodeGroup>

## Delivery Address Structure

Complete delivery address format:

```typescript theme={null}
interface DeliveryAddress {
  fullAddress: string;           // Complete address string
  city: Id<"cities">;           // City ID from locations API
  area: Id<"areas">;            // Area ID from locations API
  phoneNumber: string;          // Contact phone number
  flatVilaNumber?: string;      // Apartment/villa number
  buildingNameNumber?: string;  // Building name or number
  landmark?: string;            // Nearby landmark
  latitude?: string;            // GPS coordinates
  longitude?: string;           // GPS coordinates
}
```

## Payment Integration Notes

<CardGroup cols={3}>
  <Card title="Stripe Payments" icon="credit-card">
    **Method**: `"card"`
    **Required**: `paymentIntentId`
    **Status**: `"paid"` for successful payments
  </Card>

  <Card title="Cash Payments" icon="banknotes">
    **Method**: `"cash"`
    **Required**: None
    **Status**: `"pending"` until delivery
  </Card>

  <Card title="Wallet Payments" icon="wallet">
    **Method**: `"wallet"`
    **Required**: Sufficient wallet balance
    **Status**: `"paid"` immediately
  </Card>

  <Card title="Google Pay" icon="google">
    **Method**: `"google_pay"`
    **Required**: `paymentIntentId`
    **Status**: `"paid"` for successful payments
  </Card>

  <Card title="Apple Pay" icon="apple">
    **Method**: `"apple_pay"`
    **Required**: `paymentIntentId`
    **Status**: `"paid"` for successful payments
  </Card>
</CardGroup>

## Automatic Processing

When an order is created, the system automatically:

<AccordionGroup>
  <Accordion title="Cart Cleanup">
    **Automatic**: Cart is cleared after successful order creation

    ```typescript theme={null}
    // After order creation:
    // 1. All cart items are removed
    // 2. Cart subtotal reset to 0
    // 3. Cart timestamp updated
    ```
  </Accordion>

  <Accordion title="Stock Updates">
    **Automatic**: Product inventory is updated

    ```typescript theme={null}
    // For each cart item:
    // 1. Product stock reduced by quantity ordered
    // 2. Stock alerts triggered if below threshold
    // 3. Product availability updated if out of stock
    ```
  </Accordion>

  <Accordion title="Wallet Processing">
    **Automatic**: Store wallet is credited with earnings

    ```typescript theme={null}
    // Financial processing:
    // 1. Platform commission deducted
    // 2. Store earnings added to wallet
    // 3. Transaction history recorded
    // 4. Payout eligibility updated
    ```
  </Accordion>

  <Accordion title="Notifications">
    **Automatic**: Relevant parties are notified

    ```typescript theme={null}
    // Notifications sent to:
    // 1. Customer: Order confirmation
    // 2. Store: New order notification
    // 3. Admin: High-value order alerts (if applicable)
    ```
  </Accordion>
</AccordionGroup>

## Error Handling

<AccordionGroup>
  <Accordion title="Empty cart">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "Cannot create order from empty cart",
      "cartItemCount": 0
    }
    ```
  </Accordion>

  <Accordion title="Store inactive">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "Store is not accepting orders",
      "storeStatus": "inactive",
      "storeId": "j123456789"
    }
    ```
  </Accordion>

  <Accordion title="Insufficient stock">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "Insufficient stock for one or more items",
      "details": [
        {
          "productId": "p123456789",
          "requested": 5,
          "available": 3
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Payment processing failed">
    **Status Code**: `402`

    ```json theme={null}
    {
      "error": "Payment processing failed",
      "paymentIntentId": "pi_123456789",
      "paymentError": "Your card was declined"
    }
    ```
  </Accordion>

  <Accordion title="Invalid delivery address">
    **Status Code**: `400`

    ```json theme={null}
    {
      "error": "Invalid delivery address",
      "details": {
        "city": "City ID is required",
        "area": "Area ID is required",
        "phoneNumber": "Valid phone number is required"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Best Practices

<CardGroup cols={2}>
  <Card title="Always Validate" icon="shield-check">
    Always validate the cart before creating an order to handle stock changes and price updates
  </Card>

  <Card title="Handle Failures" icon="exclamation-triangle">
    Implement comprehensive error handling for payment failures and validation issues
  </Card>

  <Card title="User Feedback" icon="comment">
    Provide clear feedback during each step of the checkout process
  </Card>

  <Card title="Security" icon="lock">
    Never store sensitive payment information - let Stripe handle all card data
  </Card>
</CardGroup>

## Order Creation Flow

```mermaid theme={null}
graph TD
    A[Cart Items] --> B[Validate Cart]
    B --> C{Valid?}
    C -->|No| D[Show Issues]
    C -->|Yes| E[Create Payment Intent]
    E --> F[Process Payment]
    F --> G{Payment Success?}
    G -->|No| H[Show Error]
    G -->|Yes| I[Create Order]
    I --> J[Clear Cart]
    J --> K[Update Stock]
    K --> L[Credit Store Wallet]
    L --> M[Send Notifications]
    M --> N[Order Complete]
```

<Note>
  The cart-to-order conversion is atomic - either the entire process succeeds or fails completely, ensuring data consistency.
</Note>

<Tip>
  Use the validation function before showing the payment screen to catch issues early and provide a better user experience.
</Tip>
