BeanQuick implements a complete order management system with distinct workflows for customers and businesses. Orders progress through multiple states from creation to delivery, with integrated payment validation.
Dual Status System: BeanQuick separates logistical status (estado) from payment status (estado_pago) to handle cases where payment may be pending or fail while the order exists.
$productosTienda = $carrito->productos->filter(function ($producto) use ($request) { return (int)$producto->empresa_id === (int)$request->empresa_id;});if ($productosTienda->isEmpty()) { return response()->json([ 'message' => 'No hay productos de esta empresa en tu carrito.' ], 422);}
3
Validate Stock (Without Deducting)
Checks availability but doesn’t reserve stock yet:
Stock Reservation: Stock is NOT deducted when the order is created. It’s only deducted when payment is confirmed via the Mercado Pago webhook. See Payments for details.
Complete Controller Logic:
public function store(Request $request): JsonResponse{ $user = Auth::user(); // Prevent duplicate pending orders $pedidoExistente = Pedido::where('user_id', $user->id) ->where('estado', 'Pendiente') ->where('estado_pago', 'pendiente') ->first(); if ($pedidoExistente) { return response()->json([ 'message' => 'Ya tienes un pedido pendiente de pago.', 'pedido' => $pedidoExistente->load('productos') ], 200); } $request->validate([ 'empresa_id' => 'required|exists:empresas,id', 'hora_recogida' => 'required|date_format:H:i' ]); return \DB::transaction(function () use ($user, $request) { $carrito = Carrito::where('user_id', $user->id) ->with('productos') ->first(); if (!$carrito || $carrito->productos->isEmpty()) { return response()->json(['message' => 'Tu carrito está vacío.'], 400); } $productosTienda = $carrito->productos->filter(function ($producto) use ($request) { return (int)$producto->empresa_id === (int)$request->empresa_id; }); if ($productosTienda->isEmpty()) { return response()->json([ 'message' => 'No hay productos de esta empresa en tu carrito.' ], 422); } // Validate stock (but don't deduct) foreach ($productosTienda as $producto) { $cantidadPedida = $producto->pivot->cantidad ?? 1; if ($producto->stock < $cantidadPedida) { return response()->json([ 'message' => "Stock insuficiente para: {$producto->nombre}" ], 422); } } // Calculate total $total = 0; foreach ($productosTienda as $producto) { $cantidad = $producto->pivot->cantidad ?? 1; $precio = $producto->precio ?? 0; $total += $precio * $cantidad; } // Create order $pedido = Pedido::create([ 'empresa_id' => $request->empresa_id, 'user_id' => $user->id, 'estado' => 'Pendiente', 'hora_recogida' => $request->hora_recogida, 'total' => $total, 'estado_pago' => 'pendiente' ]); // Record products foreach ($productosTienda as $producto) { PedidoProducto::create([ 'pedido_id' => $pedido->id, 'producto_id' => $producto->id, 'cantidad' => $producto->pivot->cantidad ?? 1, 'precio_unitario' => $producto->precio ?? 0, ]); } return response()->json([ 'message' => 'Pedido generado pendiente de pago.', 'pedido' => $pedido->load('productos') ], 201); });}
Endpoint:POST /api/pedido/{id}/cancelarAuthentication: Required (cliente role)Authorization: Order must belong to authenticated customerBusiness Rules:
Can only cancel orders in 'Pendiente' estado
If order was already paid (estado_pago = 'aprobado'), stock is restored
If order was unpaid, stock was never deducted
Success Response:
{ "message": "Pedido cancelado y stock devuelto"}
Error Responses:
// Order not in Pendiente state{ "message": "No puedes cancelar un pedido Preparando"}// Order not found{ "message": "Pedido no encontrado"}
Controller Logic:
public function cancelar($id): JsonResponse{ try { $user = Auth::user(); $pedido = Pedido::where('id', $id) ->where('user_id', $user->id) ->with('productos') ->first(); if (!$pedido) { return response()->json(['message' => 'Pedido no encontrado'], 404); } if (strtolower($pedido->estado) !== 'pendiente') { return response()->json([ 'message' => 'No puedes cancelar un pedido ' . $pedido->estado ], 400); } \DB::transaction(function () use ($pedido) { // Only restore stock if already paid if ($pedido->estado_pago === 'aprobado') { foreach ($pedido->productos as $producto) { $producto->increment('stock', $producto->pivot->cantidad); } } $pedido->update([ 'estado' => 'Cancelado', 'estado_pago' => 'rechazado' ]); }); return response()->json(['message' => 'Pedido cancelado y stock devuelto']); } catch (\Exception $e) { return response()->json([ 'message' => 'Error', 'error' => $e->getMessage() ], 500); }}
Endpoint:PUT /api/empresa/pedido/{id}/estadoAuthentication: Required (empresa role)Authorization: Order must belong to authenticated businessRequest Body:
// Order not paid yet{ "message": "No puedes modificar un pedido que no ha sido pagado."}
Controller Logic:
public function actualizarEstado(Request $request, $id): JsonResponse{ $request->validate([ 'estado' => 'required|in:Preparando,Listo,Entregado,Cancelado' ]); try { $pedido = Pedido::findOrFail($id); // Block if not paid if ($pedido->estado_pago !== 'aprobado') { return response()->json([ 'message' => 'No puedes modificar un pedido que no ha sido pagado.' ], 400); } $nuevoEstado = ucfirst(strtolower($request->estado)); $pedido->update([ 'estado' => $nuevoEstado ]); return response()->json([ 'message' => 'Estado actualizado con éxito', 'pedido' => $pedido ]); } catch (\Exception $e) { return response()->json([ 'message' => 'Error en el servidor', 'error' => $e->getMessage() ], 500); }}
Payment Required: Businesses can only update the status of orders that have been paid (estado_pago = 'aprobado'). This prevents managing orders that may never be completed.