Skip to main content

Overview

The Mis Compras platform implements shopping cart functionality primarily on the client-side using browser storage (localStorage or sessionStorage). The cart data is then submitted to the checkout endpoints when the user completes their purchase.
Unlike users, products, and orders, the cart does not have dedicated server-side CRUD endpoints. Cart management is handled client-side, and the cart contents are sent to the server only during checkout.

Cart Data Structure

The cart should be maintained client-side with the following structure:
Cart Structure
{
  "items": [
    {
      "id": 1,
      "nombre": "iPhone 15 Pro",
      "precio": 999.99,
      "cantidad": 2,
      "subtotal": 1999.98,
      "imagen": "1704123456_abc123def456.jpg"
    },
    {
      "id": 3,
      "nombre": "AirPods Pro",
      "precio": 249.99,
      "cantidad": 1,
      "subtotal": 249.99,
      "imagen": "1704123789_airpods.jpg"
    }
  ],
  "total": 2249.97,
  "item_count": 3
}

Cart Item Properties

id
integer
required
Product ID from the products table
nombre
string
required
Product name for display purposes
precio
decimal
required
Unit price of the product
cantidad
integer
required
Quantity of this product in the cart
subtotal
decimal
required
Calculated as precio × cantidad
imagen
string
Product image filename for display

Client-Side Cart Operations

Add to Cart

Add Product to Cart
function addToCart(product) {
  let cart = JSON.parse(localStorage.getItem('cart')) || { items: [], total: 0 };
  
  // Check if product already exists in cart
  const existingItem = cart.items.find(item => item.id === product.id);
  
  if (existingItem) {
    // Increment quantity
    existingItem.cantidad++;
    existingItem.subtotal = existingItem.precio * existingItem.cantidad;
  } else {
    // Add new item
    cart.items.push({
      id: product.id_producto,
      nombre: product.nombre,
      precio: product.precio,
      cantidad: 1,
      subtotal: product.precio,
      imagen: product.imagen
    });
  }
  
  // Recalculate total
  cart.total = cart.items.reduce((sum, item) => sum + item.subtotal, 0);
  cart.item_count = cart.items.reduce((sum, item) => sum + item.cantidad, 0);
  
  localStorage.setItem('cart', JSON.stringify(cart));
  return cart;
}

Update Quantity

Update Item Quantity
function updateCartQuantity(productId, newQuantity) {
  let cart = JSON.parse(localStorage.getItem('cart'));
  const item = cart.items.find(item => item.id === productId);
  
  if (item) {
    if (newQuantity <= 0) {
      // Remove item if quantity is 0 or negative
      cart.items = cart.items.filter(item => item.id !== productId);
    } else {
      item.cantidad = newQuantity;
      item.subtotal = item.precio * item.cantidad;
    }
    
    // Recalculate total
    cart.total = cart.items.reduce((sum, item) => sum + item.subtotal, 0);
    cart.item_count = cart.items.reduce((sum, item) => sum + item.cantidad, 0);
    
    localStorage.setItem('cart', JSON.stringify(cart));
  }
  
  return cart;
}

Remove from Cart

Remove Item from Cart
function removeFromCart(productId) {
  let cart = JSON.parse(localStorage.getItem('cart'));
  cart.items = cart.items.filter(item => item.id !== productId);
  
  // Recalculate total
  cart.total = cart.items.reduce((sum, item) => sum + item.subtotal, 0);
  cart.item_count = cart.items.reduce((sum, item) => sum + item.cantidad, 0);
  
  localStorage.setItem('cart', JSON.stringify(cart));
  return cart;
}

Clear Cart

Clear Entire Cart
function clearCart() {
  localStorage.removeItem('cart');
  return { items: [], total: 0, item_count: 0 };
}

Get Cart

Retrieve Current Cart
function getCart() {
  return JSON.parse(localStorage.getItem('cart')) || { items: [], total: 0, item_count: 0 };
}

Checkout Integration

When the user is ready to complete their purchase, send the cart data to one of the checkout endpoints:

Using Node.js Endpoint

Checkout with Node.js API
async function checkout(userId, shippingAddress) {
  const cart = getCart();
  
  if (cart.items.length === 0) {
    alert('Your cart is empty!');
    return;
  }
  
  const checkoutData = {
    usuario_id: userId,
    items: cart.items,
    total: cart.total,
    direccion: shippingAddress
  };
  
  try {
    const response = await fetch('/api/pedidos/checkout', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(checkoutData)
    });
    
    const result = await response.json();
    
    if (result.success) {
      // Clear cart after successful checkout
      clearCart();
      window.location.href = '/order-confirmation?id=' + result.pedido_id;
    } else {
      alert('Checkout failed: ' + result.message);
    }
  } catch (error) {
    console.error('Checkout error:', error);
    alert('An error occurred during checkout');
  }
}

Using PHP Endpoint

Checkout with PHP API
async function checkoutPHP(userId) {
  const cart = getCart();
  
  if (cart.items.length === 0) {
    alert('Your cart is empty!');
    return;
  }
  
  const checkoutData = {
    usuario_id: userId,
    items: cart.items,
    total: cart.total
  };
  
  try {
    const response = await fetch('/php/checkout.php', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(checkoutData)
    });
    
    const result = await response.json();
    
    if (result.success) {
      clearCart();
      window.location.href = result.redirect || 'gracias.html';
    } else {
      alert('Checkout failed: ' + result.message);
    }
  } catch (error) {
    console.error('Checkout error:', error);
    alert('An error occurred during checkout');
  }
}

Stock Validation

Before submitting to checkout, it’s recommended to validate product availability:
Validate Stock Before Checkout
async function validateCartStock() {
  const cart = getCart();
  const validationErrors = [];
  
  for (const item of cart.items) {
    try {
      const response = await fetch(`/php/obtener_producto.php?id=${item.id}`);
      const result = await response.json();
      
      if (!result.exito) {
        validationErrors.push(`Product "${item.nombre}" is no longer available`);
        continue;
      }
      
      const product = result.producto;
      
      // Check if stock is sufficient
      if (product.stock < item.cantidad) {
        validationErrors.push(
          `Only ${product.stock} units of "${item.nombre}" available (you have ${item.cantidad} in cart)`
        );
      }
      
      // Check if price has changed
      if (Math.abs(product.precio - item.precio) > 0.01) {
        validationErrors.push(
          `Price of "${item.nombre}" has changed from $${item.precio} to $${product.precio}`
        );
      }
    } catch (error) {
      validationErrors.push(`Error validating "${item.nombre}"`);
    }
  }
  
  return validationErrors;
}

// Use before checkout
async function safeCheckout(userId, shippingAddress) {
  const errors = await validateCartStock();
  
  if (errors.length > 0) {
    alert('Cart validation failed:\n' + errors.join('\n'));
    return;
  }
  
  await checkout(userId, shippingAddress);
}
Always validate stock availability before checkout. The checkout endpoint will reduce stock levels, which could lead to negative stock if not validated beforehand.

Cart Display Example

Display Cart in UI
function renderCart() {
  const cart = getCart();
  const cartContainer = document.getElementById('cart-items');
  const totalElement = document.getElementById('cart-total');
  
  if (cart.items.length === 0) {
    cartContainer.innerHTML = '<p>Your cart is empty</p>';
    totalElement.textContent = '$0.00';
    return;
  }
  
  cartContainer.innerHTML = cart.items.map(item => `
    <div class="cart-item">
      <img src="/imagenes/${item.imagen}" alt="${item.nombre}" width="80">
      <div class="item-details">
        <h3>${item.nombre}</h3>
        <p>Price: $${item.precio.toFixed(2)}</p>
        <div class="quantity-controls">
          <button onclick="updateCartQuantity(${item.id}, ${item.cantidad - 1})">-</button>
          <span>${item.cantidad}</span>
          <button onclick="updateCartQuantity(${item.id}, ${item.cantidad + 1})">+</button>
        </div>
        <p>Subtotal: $${item.subtotal.toFixed(2)}</p>
        <button onclick="removeFromCart(${item.id})">Remove</button>
      </div>
    </div>
  `).join('');
  
  totalElement.textContent = `$${cart.total.toFixed(2)}`;
}

// Update display when cart changes
window.addEventListener('storage', renderCart);

Best Practices

Cart Persistence: Use localStorage for persistent carts that survive browser sessions, or sessionStorage for carts that clear when the browser closes.
Price Integrity: Always fetch current prices from the server during checkout to prevent price manipulation.
Stock Validation: Validate stock availability before allowing checkout to prevent overselling.
Security: Never trust cart totals calculated client-side. The server should recalculate totals based on current prices during checkout.
User Experience: Update cart counts in the UI navbar/header when cart contents change using event listeners or custom events.

Build docs developers (and LLMs) love