File manager - Edit - /home/webapp69.cm.in.th/u69319090039/Shop39/app/Controllers/CheckoutController.php
Back
<?php class CheckoutController extends Controller { public function index(): void { $cart = Session::get('cart', []); if (empty($cart)) { Session::setFlash('warning', 'ตะกร้าสินค้าว่างเปล่า กรุณาเลือกสินค้าก่อนทำการสั่งซื้อ'); $this->redirect('products'); } $user = Auth::user(); $productModel = new Product(); $db = Database::getInstance(); $items = []; $total = 0; // ตรวจสอบคอลัมน์ของ product_variants บนฐานข้อมูล $colsStmt = $db->query("SHOW COLUMNS FROM product_variants"); $variantCols = $colsStmt ? $colsStmt->fetchAll(PDO::FETCH_COLUMN) : []; $hasVarPrice = in_array('price', $variantCols); $hasVarSalePrice = in_array('sale_price', $variantCols); foreach ($cart as $key => $cartItem) { $product = $productModel->findById($cartItem['product_id']); if ($product) { // Determine item price (variant price if available or product sale_price/price) $price = (float)($product['sale_price'] ?? $product['price']); $variantId = (int)($cartItem['variant_id'] ?? 0); if ($variantId > 0 && ($hasVarPrice || $hasVarSalePrice)) { $selectFields = []; if ($hasVarPrice) $selectFields[] = 'price'; if ($hasVarSalePrice) $selectFields[] = 'sale_price'; $vStmt = $db->prepare("SELECT " . implode(', ', $selectFields) . " FROM product_variants WHERE id = :vid AND product_id = :pid"); $vStmt->execute(['vid' => $variantId, 'pid' => $product['id']]); $vRow = $vStmt->fetch(); if ($vRow) { $vPrice = isset($vRow['sale_price']) && $vRow['sale_price'] !== null && $vRow['sale_price'] > 0 ? (float)$vRow['sale_price'] : (isset($vRow['price']) && $vRow['price'] !== null ? (float)$vRow['price'] : null); if ($vPrice !== null) { $price = $vPrice; } } } $subtotal = $price * $cartItem['quantity']; $total += $subtotal; // Load product primary image or variant image $variantImage = ''; if ($variantId > 0 && in_array('image', $variantCols)) { $viStmt = $db->prepare("SELECT image FROM product_variants WHERE id = :vid AND product_id = :pid"); $viStmt->execute(['vid' => $variantId, 'pid' => $product['id']]); $variantImage = $viStmt->fetchColumn() ?: ''; } if (!empty($variantImage)) { $product['primary_image'] = $variantImage; } else { $imgStmt = $db->prepare("SELECT image_path FROM product_images WHERE product_id = :pid ORDER BY is_primary DESC, id ASC LIMIT 1"); $imgStmt->execute(['pid' => $product['id']]); $product['primary_image'] = $imgStmt->fetchColumn() ?: ''; } $items[] = [ 'product' => $product, 'quantity' => $cartItem['quantity'], 'price' => $price, 'subtotal' => $subtotal, 'variant_id' => $variantId, 'variant_info' => $cartItem['variant_info'] ?? '' ]; } } // Fetch seller payment information for all distinct stores in this checkout $sellerIds = array_unique(array_filter(array_map(function($i) { return $i['product']['seller_id'] ?? null; }, $items))); $sellersPaymentInfo = []; if (!empty($sellerIds)) { $inPlaceholders = implode(',', array_fill(0, count($sellerIds), '?')); $sSql = " SELECT sp.id as seller_id, sp.shop_name, sp.slug, sps.account_name, sps.bank_name, sps.bank_account, sps.promptpay_number, sps.qr_code FROM seller_profiles sp LEFT JOIN seller_payment_settings sps ON sp.id = sps.seller_id WHERE sp.id IN ({$inPlaceholders}) "; $sStmt = $db->prepare($sSql); $sStmt->execute(array_values($sellerIds)); $sellersPaymentInfo = $sStmt->fetchAll(); } $this->render('checkout/index', [ 'title' => 'ชำระเงินและยืนยันคำสั่งซื้อ', 'user' => $user, 'items' => $items, 'total' => $total, 'sellersPaymentInfo' => $sellersPaymentInfo ]); } public function process(): void { $this->validateCsrf(); $user = Auth::user(); $cart = Session::get('cart', []); if (empty($cart)) { Session::setFlash('danger', 'ไม่พบรายการสินค้าในตะกร้า'); $this->redirect('cart'); } $address = Security::sanitizeString($_POST['shipping_address'] ?? ''); $recipientName = Security::sanitizeString($_POST['recipient_name'] ?? $user['full_name']); $recipientPhone = Security::sanitizeString($_POST['recipient_phone'] ?? $user['phone']); $paymentMethod = Security::sanitizeString($_POST['payment_method'] ?? 'prepaid'); if (empty($address)) { Session::setFlash('danger', 'กรุณากรอกที่อยู่จัดส่งสินค้าให้ครบถ้วน'); $this->redirect('checkout'); } // Validate payment method if (!in_array($paymentMethod, ['prepaid', 'cod'])) { $paymentMethod = 'prepaid'; } $fullShippingAddress = "ผู้รับ: {$recipientName} (โทร: {$recipientPhone})\nที่อยู่: {$address}"; $db = Database::getInstance(); $productModel = new Product(); // 1. Calculate price & stock strictly on SERVER side (Never trust frontend) $sellerGroups = []; $stockErrors = []; // เช็คคอลัมน์ของ product_variants บนฐานข้อมูล $colsStmt = $db->query("SHOW COLUMNS FROM product_variants"); $variantCols = $colsStmt ? $colsStmt->fetchAll(PDO::FETCH_COLUMN) : []; $hasVarStock = in_array('stock', $variantCols); $hasVarPrice = in_array('price', $variantCols); $hasVarSalePrice = in_array('sale_price', $variantCols); foreach ($cart as $cartItem) { $productId = (int)$cartItem['product_id']; $variantId = (int)($cartItem['variant_id'] ?? 0); $qty = max(1, (int)$cartItem['quantity']); $product = $productModel->findById($productId); if (!$product || $product['status'] !== 'active') { Session::setFlash('danger', "สินค้า '{$product['title']}' ไม่พร้อมจำหน่ายในขณะนี้"); $this->redirect('cart'); } // Check stock $itemPrice = (float)($product['sale_price'] ?? $product['price']); $currentStock = (int)$product['stock_quantity']; if ($variantId > 0 && ($hasVarStock || $hasVarPrice || $hasVarSalePrice)) { $selectFields = []; if ($hasVarPrice) $selectFields[] = 'price'; if ($hasVarSalePrice) $selectFields[] = 'sale_price'; if ($hasVarStock) $selectFields[] = 'stock'; $vStmt = $db->prepare("SELECT " . implode(', ', $selectFields) . " FROM product_variants WHERE id = :vid AND product_id = :pid"); $vStmt->execute(['vid' => $variantId, 'pid' => $productId]); $vRow = $vStmt->fetch(); if ($vRow) { $vPrice = isset($vRow['sale_price']) && $vRow['sale_price'] !== null && $vRow['sale_price'] > 0 ? (float)$vRow['sale_price'] : (isset($vRow['price']) && $vRow['price'] !== null ? (float)$vRow['price'] : null); if ($vPrice !== null) { $itemPrice = $vPrice; } if ($hasVarStock && isset($vRow['stock'])) { $currentStock = (int)$vRow['stock']; } } } if ($currentStock < $qty) { $stockErrors[] = "สินค้า '{$product['title']}' มีสต็อกคงเหลือ {$currentStock} ชิ้น (คุณสั่ง {$qty} ชิ้น)"; } $sellerId = (int)$product['seller_id']; $subtotal = $itemPrice * $qty; $sellerGroups[$sellerId][] = [ 'product_id' => $productId, 'variant_id' => $variantId, 'variant_info' => $cartItem['variant_info'] ?? '', 'price' => $itemPrice, 'quantity' => $qty, 'subtotal' => $subtotal ]; } if (!empty($stockErrors)) { Session::setFlash('danger', implode('<br>', $stockErrors)); $this->redirect('cart'); } // 2. Perform Database Transaction for Atomic Order Creation & Inventory Deduction try { $db->beginTransaction(); // Check if orders table has payment columns (for backwards compatibility) $colsStmt = $db->query("SHOW COLUMNS FROM orders"); $existingCols = $colsStmt ? $colsStmt->fetchAll(PDO::FETCH_COLUMN) : []; $hasPaymentMethod = in_array('payment_method', $existingCols); $hasPaymentStatus = in_array('payment_status', $existingCols); $hasPaymentAmount = in_array('payment_amount', $existingCols); $hasPaidAt = in_array('paid_at', $existingCols); $hasCodAmount = in_array('cod_amount', $existingCols); $createdOrderIds = []; foreach ($sellerGroups as $sellerId => $groupItems) { $sellerTotal = array_sum(array_column($groupItems, 'subtotal')); $orderNumber = 'ORD-' . date('YmdHis') . '-' . rand(100, 999); // Set order & payment status based on chosen method if ($paymentMethod === 'prepaid') { $orderStatus = 'paid'; $paymentStatus = 'paid'; $paymentAmount = $sellerTotal; $paidAt = date('Y-m-d H:i:s'); $codAmount = 0.00; } else { // COD (Cash on Delivery) $orderStatus = 'pending'; $paymentStatus = 'pending'; $paymentAmount = 0.00; $paidAt = null; $codAmount = $sellerTotal; } // Construct dynamic insert $orderFields = ['order_number', 'user_id', 'seller_id', 'total_amount', 'shipping_address', 'status']; $orderValues = [':num', ':uid', ':sid', ':tot', ':addr', ':stat']; if ($hasPaymentMethod) { $orderFields[] = 'payment_method'; $orderValues[] = ':pmethod'; } if ($hasPaymentStatus) { $orderFields[] = 'payment_status'; $orderValues[] = ':pstatus'; } if ($hasPaymentAmount) { $orderFields[] = 'payment_amount'; $orderValues[] = ':pamount'; } if ($hasPaidAt) { $orderFields[] = 'paid_at'; $orderValues[] = ':paid_at'; } if ($hasCodAmount) { $orderFields[] = 'cod_amount'; $orderValues[] = ':cod_amount'; } $sqlOrder = "INSERT INTO orders (" . implode(', ', $orderFields) . ") VALUES (" . implode(', ', $orderValues) . ")"; $stmt = $db->prepare($sqlOrder); $params = [ 'num' => $orderNumber, 'uid' => $user['id'], 'sid' => $sellerId, 'tot' => $sellerTotal, 'addr' => $fullShippingAddress, 'stat' => $orderStatus ]; if ($hasPaymentMethod) $params['pmethod'] = $paymentMethod; if ($hasPaymentStatus) $params['pstatus'] = $paymentStatus; if ($hasPaymentAmount) $params['pamount'] = $paymentAmount; if ($hasPaidAt) $params['paid_at'] = $paidAt; if ($hasCodAmount) $params['cod_amount'] = $codAmount; $stmt->execute($params); $orderId = (int)$db->lastInsertId(); $createdOrderIds[] = $orderId; // Insert Order Items & Deduct Stock $itemStmt = $db->prepare("INSERT INTO order_items (order_id, product_id, variant_info, price, quantity, subtotal) VALUES (:oid, :pid, :vinfo, :price, :qty, :sub)"); $deductProdStock = $db->prepare("UPDATE products SET stock_quantity = GREATEST(0, stock_quantity - :qty) WHERE id = :pid"); $deductVarStock = $hasVarStock ? $db->prepare("UPDATE product_variants SET stock = GREATEST(0, stock - :qty) WHERE id = :vid") : null; foreach ($groupItems as $gItem) { $itemStmt->execute([ 'oid' => $orderId, 'pid' => $gItem['product_id'], 'vinfo' => $gItem['variant_info'], 'price' => $gItem['price'], 'qty' => $gItem['quantity'], 'sub' => $gItem['subtotal'] ]); // Deduct stock in DB $deductProdStock->execute(['qty' => $gItem['quantity'], 'pid' => $gItem['product_id']]); if ($gItem['variant_id'] > 0 && $deductVarStock) { $deductVarStock->execute(['qty' => $gItem['quantity'], 'vid' => $gItem['variant_id']]); } } // Record Initial Lifecycle in Status History if available if (class_exists('OrderStatusHistory')) { $initialNote = ($paymentMethod === 'cod') ? "สร้างคำสั่งซื้อใหม่ (เก็บเงินปลายทาง COD: ฿" . number_format($codAmount, 2) . ")" : "สร้างคำสั่งซื้อใหม่ (ชำระเงินต้นทางเรียบร้อยแล้ว)"; OrderStatusHistory::add($orderId, $orderStatus, null, $initialNote, 'Customer (' . Security::e($user['full_name']) . ')'); } } $db->commit(); // Clear Cart after success Session::remove('cart'); if ($paymentMethod === 'cod') { Session::setFlash('success', '🎉 สร้างคำสั่งซื้อแบบเก็บเงินปลายทาง (COD) สำเร็จแล้ว! ร้านค้าจะเริ่มจัดส่งสินค้าตามที่อยู่ที่ระบุ'); } else { Session::setFlash('success', '🎉 ยืนยันคำสั่งซื้อและชำระเงินต้นทางเรียบร้อยแล้ว! ขอบคุณที่ใช้บริการ'); } $this->redirect('orders'); } catch (Exception $e) { if ($db->inTransaction()) { $db->rollBack(); } error_log("[CheckoutProcess] Order creation failed: " . $e->getMessage()); Session::setFlash('danger', 'เกิดข้อผิดพลาดในการสร้างคำสั่งซื้อ กรุณาลองใหม่อีกครั้ง: ' . $e->getMessage()); $this->redirect('checkout'); } } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.24 |
proxy
|
phpinfo
|
Settings