File manager - Edit - /home/webapp69.cm.in.th/u69319090041/Shop41/public/checkout.php
Back
<?php ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); /** * public/checkout.php * หน้าชำระเงินและระบุที่อยู่จัดส่ง * ปรับปรุง: ใช้ระบบบัญชีธนาคารกลาง (Central Bank Account) สำหรับรับโอนเงินยอดรวมทั้งหมด (Grand Total) */ require_once __DIR__ . '/../config/config.php'; require_once __DIR__ . '/../includes/auth.php'; require_once __DIR__ . '/../includes/image_helper.php'; // Custom Fallback Function สำหรับ sanitize() if (!function_exists('safe_sanitize')) { function safe_sanitize($string) { if (function_exists('sanitize')) { return sanitize($string); } return htmlspecialchars((string)$string, ENT_QUOTES, 'UTF-8'); } } requireLogin(); try { $db = getDBConnection(); $userId = (int)($_SESSION['user']['id'] ?? 0); $sessionId = session_id(); // ดึงข้อมูลตะกร้าจากตาราง cart_items (Sync Database เข้า Session) $stmtCart = $db->prepare(" SELECT c.*, p.name, p.price, p.main_image as image, p.vendor_id FROM cart_items c JOIN products p ON c.product_id = p.id WHERE c.user_id = ? OR (c.session_id = ? AND c.user_id IS NULL) "); $stmtCart->execute([$userId, $sessionId]); $cartFromDb = $stmtCart->fetchAll(); if (!empty($cartFromDb)) { $_SESSION['cart'] = $cartFromDb; } $cart = $_SESSION['cart'] ?? []; if (empty($cart)) { header('Location: ' . (defined('SITE_URL') ? rtrim(SITE_URL, '/') : '') . '/cart.php'); exit; } $subtotal = 0; foreach ($cart as $item) { $subtotal += ($item['price'] ?? 0) * ($item['quantity'] ?? 0); } $shippingFee = 50.00; $grandTotal = $subtotal + $shippingFee; // ดึงข้อมูลบัญชีธนาคารกลางที่ Admin เปิดใช้งานอยู่ (lower_case SQL) $centralBanks = $db->query("select * from system_bank_accounts where is_active = 1 order by sort_order asc, id asc")->fetchAll(); // ตรวจสอบและแมป vendor_id สำหรับสินค้าในตะกร้า ป้องกันปัญหา Foreign Key Constraint $vendorIdsInCart = []; // หา default vendor เผื่อไว้กรณีฉุกเฉิน (lower_case SQL) $defaultVendorId = (int)($db->query("select id from vendors order by id asc limit 1")->fetchColumn() ?: 1); foreach ($cart as $k => $item) { $vId = !empty($item['vendor_id']) ? (int)$item['vendor_id'] : 0; // ตรวจสอบว่า vendor_id ปัจจุบันมีในตาราง vendors หรือไม่ $vCheckStmt = $db->prepare("select id from vendors where id = ?"); $vCheckStmt->execute([$vId]); $vendorExists = $vCheckStmt->fetchColumn(); if (!$vendorExists) { // หากไม่มี vendor_id นี้ ให้ลองค้นหาจากตาราง products $pStmt = $db->prepare("select vendor_id from products where id = ?"); $pStmt->execute([$item['product_id'] ?? 0]); $prodVendorId = $pStmt->fetchColumn(); if ($prodVendorId) { $vCheckStmt->execute([$prodVendorId]); if ($vCheckStmt->fetchColumn()) { $vId = (int)$prodVendorId; } else { $vId = $defaultVendorId; } } else { $vId = $defaultVendorId; } } $cart[$k]['vendor_id'] = $vId; $vendorIdsInCart[$vId] = $vId; } $_SESSION['cart'] = $cart; // ดึงข้อมูลที่อยู่ล่าสุดของสมาชิก หรือ ที่อยู่ที่ตั้งเป็นค่าเริ่มต้น $stmtAddrFetch = $db->prepare("select * from addresses where user_id = ? order by is_default desc, id desc limit 1"); $stmtAddrFetch->execute([$_SESSION['user']['id'] ?? 0]); $userAddress = $stmtAddrFetch->fetch(); if ($userAddress) { $userAddress = array_map(function($val) { return is_string($val) ? html_entity_decode($val, ENT_QUOTES, 'UTF-8') : $val; }, $userAddress); } $error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (function_exists('verifyCSRFToken')) { verifyCSRFToken($_POST['csrf_token'] ?? ''); } // ใช้ trim แทน sanitize ตอนบันทึกลง DB เพื่อป้องกันการแปลง HTML Entities ซ้ำซ้อน (Double Encoding) $recipient_name = trim($_POST['recipient_name'] ?? ''); $phone = trim($_POST['phone'] ?? ''); $address_line = trim($_POST['address_line'] ?? ''); $province = trim($_POST['province'] ?? ''); $amphoe = trim($_POST['amphoe'] ?? ''); $district = trim($_POST['district'] ?? ''); $zipcode = trim($_POST['zipcode'] ?? ''); $payment_method = trim($_POST['payment_method'] ?? 'bank_transfer'); if (empty($recipient_name) || empty($phone) || empty($address_line) || empty($province)) { $error = 'กรุณากรอกข้อมูลที่อยู่จัดส่งให้ครบถ้วน'; } else { $slipPath = null; // อัปโหลดสลิปการโอนเงิน (กรณีโอนเงิน หรือ PromptPay) if (in_array($payment_method, ['bank_transfer', 'promptpay'])) { if (isset($_FILES['payment_slip']) && $_FILES['payment_slip']['error'] === UPLOAD_ERR_OK) { if (function_exists('processAndUploadImage')) { $uploadResult = processAndUploadImage($_FILES['payment_slip'], 'slips'); if ($uploadResult['success']) { $slipPath = $uploadResult['main_path']; } else { $error = 'การอัปโหลดสลิปขัดข้อง: ' . ($uploadResult['error'] ?? 'Unknown Error'); } } else { $error = 'ระบบไม่พบฟังก์ชันสำหรับการอัปโหลดไฟล์'; } } else { $error = 'กรุณาแนบหลักฐานการโอนเงิน (สลิปชำระเงิน)'; } } if (empty($error)) { $db->beginTransaction(); try { // บันทึกที่อยู่ไว้ใช้ครั้งต่อไป if (isset($_POST['save_address'])) { $stmtCheckAddr = $db->prepare("select id from addresses where user_id = ? and recipient_name = ? and phone = ? and address_line = ? and province = ? and amphoe = ? and district = ? and zipcode = ? limit 1"); $stmtCheckAddr->execute([$_SESSION['user']['id'], $recipient_name, $phone, $address_line, $province, $amphoe, $district, $zipcode]); if (!$stmtCheckAddr->fetchColumn()) { $stmtAddr = $db->prepare("insert into addresses (user_id, recipient_name, phone, address_line, province, amphoe, district, zipcode) values (?, ?, ?, ?, ?, ?, ?, ?)"); $stmtAddr->execute([$_SESSION['user']['id'], $recipient_name, $phone, $address_line, $province, $amphoe, $district, $zipcode]); } } $orderNo = 'ORD' . date('YmdHis') . rand(100, 999); $fullShippingAddr = "$address_line ต.$district อ.$amphoe จ.$province $zipcode"; // บันทึกคำสั่งซื้อหลัก (orders) $stmtOrder = $db->prepare("insert into orders (order_no, user_id, total_amount, shipping_fee, payment_method, payment_status, order_status, payment_slip, shipping_name, shipping_phone, shipping_address) values (?, ?, ?, ?, ?, 'pending', 'pending', ?, ?, ?, ?)"); $stmtOrder->execute([$orderNo, $_SESSION['user']['id'], $grandTotal, $shippingFee, $payment_method, $slipPath, $recipient_name, $phone, $fullShippingAddr]); $orderId = $db->lastInsertId(); // Multi-Vendor Order Splitting Logic $groupedByVendor = []; foreach ($cart as $item) { $vId = (int)($item['vendor_id'] ?? 0); $groupedByVendor[$vId][] = $item; } $stmtVendorOrder = $db->prepare("insert into vendor_orders (order_id, vendor_id, subtotal, commission, vendor_earning, status) values (?, ?, ?, ?, ?, 'pending')"); $stmtItem = $db->prepare("insert into order_items (order_id, vendor_order_id, product_id, vendor_id, product_name, variant_info, price, quantity, subtotal) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"); foreach ($groupedByVendor as $vId => $vItems) { $vSubtotal = 0; foreach ($vItems as $vi) { $vSubtotal += ($vi['price'] ?? 0) * ($vi['quantity'] ?? 0); } $vCommStmt = $db->prepare("select commission_rate from vendors where id = ?"); $vCommStmt->execute([$vId]); $commRate = (float)($vCommStmt->fetchColumn() ?: 0.00); $commission = round($vSubtotal * ($commRate / 100), 2); $vendorEarning = $vSubtotal - $commission; $stmtVendorOrder->execute([$orderId, $vId, $vSubtotal, $commission, $vendorEarning]); $vendorOrderId = $db->lastInsertId(); foreach ($vItems as $item) { $variantInfo = "Size: " . ($item['size'] ?? '') . " | Color: " . ($item['color'] ?? ''); $itemSubtotal = ($item['price'] ?? 0) * ($item['quantity'] ?? 0); $stmtItem->execute([ $orderId, $vendorOrderId, $item['product_id'] ?? 0, $vId, $item['name'] ?? '', $variantInfo, $item['price'] ?? 0, $item['quantity'] ?? 0, $itemSubtotal ]); } } // ลบข้อมูลตะกร้าสินค้าในฐานข้อมูล $stmtClearCart = $db->prepare("DELETE FROM cart_items WHERE user_id = ? OR (session_id = ? AND user_id IS NULL)"); $stmtClearCart->execute([$_SESSION['user']['id'] ?? 0, session_id()]); $db->commit(); unset($_SESSION['cart']); $_SESSION['flash_success'] = "สั่งซื้อสินค้าสำเร็จ! หมายเลขคำสั่งซื้อ: $orderNo"; header('Location: ' . (defined('SITE_URL') ? rtrim(SITE_URL, '/') : '') . '/order_detail.php?id=' . $orderId); exit; } catch (Exception $e) { $db->rollBack(); $error = 'เกิดข้อผิดพลาดในการสร้างคำสั่งซื้อ: ' . $e->getMessage(); } } } } $pageTitle = "ชำระเงินและระบุที่อยู่"; require_once __DIR__ . '/../includes/header.php'; ?> <!-- Checkout Page Scoped Styles --> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> <style> /* ═══════════════════════════════════════════ CHECKOUT PAGE — Premium Dark Theme v2 ═══════════════════════════════════════════ */ .ck-page { font-family: 'Noto Sans Thai', 'Inter', sans-serif; } .ck-page *, .ck-page *::before, .ck-page *::after { box-sizing: border-box; } /* ── Breadcrumb ── */ .ck-breadcrumb { display: flex; align-items: center; gap: 8px; margin-bottom: 20px; font-size: .82rem; } .ck-breadcrumb a { color: var(--ck-muted, #94a3b8); text-decoration: none; transition: color .2s; } .ck-breadcrumb a:hover { color: #818cf8; } .ck-breadcrumb .ck-bc-sep { color: var(--ck-muted, #64748b); font-size: .7rem; } .ck-breadcrumb .ck-bc-current { color: var(--ck-text, #f1f5f9); font-weight: 600; } /* ── Step Indicator ── */ .ck-steps { display: flex; align-items: center; justify-content: center; gap: 0; margin-bottom: 28px; background: var(--ck-card, #182235); border: 1px solid var(--ck-border, rgba(99,102,241,.12)); border-radius: 14px; padding: 16px 24px; } .ck-step { display: flex; align-items: center; gap: 10px; font-size: .82rem; color: var(--ck-muted, #64748b); white-space: nowrap; } .ck-step.--active { color: var(--ck-text, #f1f5f9); font-weight: 600; } .ck-step.--done { color: #34d399; } .ck-step-num { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: .75rem; font-weight: 700; flex-shrink: 0; border: 2px solid var(--ck-border, #334155); color: var(--ck-muted, #64748b); transition: all .2s; } .ck-step.--done .ck-step-num { background: #34d399; border-color: #34d399; color: #fff; } .ck-step.--active .ck-step-num { background: #6366f1; border-color: #6366f1; color: #fff; box-shadow: 0 0 0 4px rgba(99,102,241,.2); } .ck-step-line { width: 48px; height: 2px; margin: 0 8px; background: var(--ck-border, #334155); border-radius: 1px; } .ck-step.--done + .ck-step-line { background: #34d399; } /* Layout */ .ck-grid { display: grid; grid-template-columns: 1fr 420px; gap: 28px; align-items: start; } @media(max-width:991px) { .ck-grid { grid-template-columns: 1fr; } } /* Section Card */ .ck-card { background: var(--ck-card, #182235); border: 1px solid var(--ck-border, rgba(99,102,241,.12)); border-radius: 16px; padding: 28px; transition: border-color .2s; } .ck-card:hover { border-color: rgba(99,102,241,.25); } /* Section Headers */ .ck-section-head { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; } .ck-section-icon { width: 44px; height: 44px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 1.15rem; flex-shrink: 0; } .ck-section-title { font-size: 1.1rem; font-weight: 700; color: var(--ck-text, #f1f5f9); margin: 0; } .ck-section-sub { font-size: .8rem; color: var(--ck-muted, #94a3b8); margin: 0; } /* Form Inputs */ .ck-label { font-size: .82rem; font-weight: 600; color: var(--ck-muted, #94a3b8); margin-bottom: 6px; display: block; } .ck-label .req { color: #f87171; } .ck-input, .ck-select, .ck-textarea { width: 100%; padding: 12px 14px; border-radius: 10px; border: 1px solid var(--ck-input-border, #2d3a4e); background: var(--ck-input-bg, #0f1a2e); color: var(--ck-text, #f1f5f9); font-size: .9rem; font-family: inherit; transition: border-color .2s, box-shadow .2s; outline: none; } .ck-input:focus, .ck-select:focus, .ck-textarea:focus { border-color: #6366f1; box-shadow: 0 0 0 3px rgba(99,102,241,.15); } .ck-input::placeholder, .ck-textarea::placeholder { color: #4a5568; } .ck-textarea { resize: vertical; min-height: 72px; } .ck-select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%2394a3b8' viewBox='0 0 16 16'%3E%3Cpath d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 14px center; padding-right: 36px; cursor: pointer; } .ck-select option { background: var(--ck-card, #182235); color: var(--ck-text, #f1f5f9); } .ck-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; } .ck-field { margin-bottom: 16px; } /* Form validation visuals */ .ck-input:invalid:not(:placeholder-shown):not(:focus), .ck-textarea:invalid:not(:placeholder-shown):not(:focus) { border-color: rgba(239,68,68,.4); } .ck-select:invalid:not(:focus) { border-color: rgba(239,68,68,.25); } /* Checkbox */ .ck-check-row { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; cursor: pointer; } .ck-check-row input[type="checkbox"] { width: 18px; height: 18px; accent-color: #6366f1; cursor: pointer; flex-shrink: 0; border-radius: 4px; } .ck-check-label { font-size: .82rem; color: var(--ck-muted, #94a3b8); cursor: pointer; } /* Divider */ .ck-divider { border: none; border-top: 1px solid var(--ck-border, rgba(99,102,241,.1)); margin: 28px 0; } /* ── Payment Method Cards ── */ .ck-pm-card { border: 1.5px solid var(--ck-border, rgba(99,102,241,.1)); border-radius: 14px; padding: 18px 20px; cursor: pointer; transition: all .2s ease; background: var(--ck-card, #182235); position: relative; margin-bottom: 12px; } .ck-pm-card:hover { border-color: rgba(99,102,241,.35); background: rgba(99,102,241,.04); } .ck-pm-card.--selected { border-color: #6366f1; background: rgba(99,102,241,.06); box-shadow: 0 0 0 3px rgba(99,102,241,.12), 0 4px 20px rgba(99,102,241,.08); } .ck-pm-header { display: flex; align-items: center; gap: 14px; } .ck-pm-radio { width: 20px; height: 20px; border-radius: 50%; border: 2px solid #4a5568; flex-shrink: 0; display: flex; align-items: center; justify-content: center; transition: border-color .2s, background .2s; } .ck-pm-card.--selected .ck-pm-radio { border-color: #6366f1; background: #6366f1; } .ck-pm-card.--selected .ck-pm-radio::after { content: ''; width: 8px; height: 8px; background: #fff; border-radius: 50%; } .ck-pm-icon { width: 42px; height: 42px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 1.1rem; flex-shrink: 0; } .ck-pm-icon.--bank { background: rgba(59,130,246,.12); color: #60a5fa; } .ck-pm-icon.--pp { background: rgba(16,185,129,.12); color: #34d399; } .ck-pm-icon.--cod { background: rgba(251,191,36,.12); color: #fbbf24; } .ck-pm-info { flex: 1; } .ck-pm-name { font-size: .92rem; font-weight: 600; color: var(--ck-text, #f1f5f9); } .ck-pm-desc { font-size: .78rem; color: var(--ck-muted, #94a3b8); margin-top: 2px; } /* Hidden radio — actual form element */ .ck-pm-card input[type="radio"] { position: absolute; opacity: 0; pointer-events: none; } /* ── Payment Details Panel ── */ .ck-pm-detail { margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--ck-border, rgba(99,102,241,.1)); animation: ckFadeIn .25s ease; } @keyframes ckFadeIn { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } } /* Amount Highlight */ .ck-amount-card { background: linear-gradient(135deg, rgba(251,191,36,.08), rgba(245,158,11,.04)); border: 1px solid rgba(251,191,36,.2); border-radius: 12px; padding: 16px 20px; display: flex; align-items: center; gap: 14px; margin-bottom: 18px; } .ck-amount-icon { font-size: 1.6rem; flex-shrink: 0; } .ck-amount-label { font-size: .8rem; color: #b45309; font-weight: 500; } .ck-amount-value { font-size: 1.35rem; font-weight: 800; color: #92400e; letter-spacing: -.02em; } .ck-amount-hint { font-size: .75rem; color: var(--ck-muted, #94a3b8); margin-top: 2px; } /* Dark mode amount text */ [data-theme="dark"] .ck-amount-label { color: #fbbf24; } [data-theme="dark"] .ck-amount-value { color: #fde68a; } /* Bank Account Card */ .ck-bank-card { background: var(--ck-input-bg, #0f1a2e); border: 1px solid var(--ck-border, rgba(99,102,241,.12)); border-radius: 12px; padding: 18px; display: flex; align-items: center; gap: 16px; margin-bottom: 12px; transition: border-color .2s; } .ck-bank-card:hover { border-color: rgba(59,130,246,.3); } .ck-bank-avatar { width: 52px; height: 52px; border-radius: 12px; object-fit: cover; cursor: pointer; flex-shrink: 0; border: 1px solid rgba(99,102,241,.15); } .ck-bank-avatar-placeholder { width: 52px; height: 52px; border-radius: 12px; background: rgba(99,102,241,.1); display: flex; align-items: center; justify-content: center; font-size: 1.3rem; flex-shrink: 0; } .ck-bank-info { flex: 1; min-width: 0; } .ck-bank-name { font-size: .85rem; font-weight: 600; color: var(--ck-text, #f1f5f9); } .ck-bank-acct { font-size: 1.15rem; font-weight: 700; color: #60a5fa; letter-spacing: .5px; margin: 4px 0; font-family: 'SF Mono', 'Fira Code', monospace; } .ck-bank-holder { font-size: .8rem; color: var(--ck-muted, #94a3b8); } .ck-copy-btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px; border-radius: 8px; background: rgba(59,130,246,.12); color: #60a5fa; border: 1px solid rgba(59,130,246,.2); font-size: .8rem; font-weight: 600; cursor: pointer; transition: all .2s; white-space: nowrap; flex-shrink: 0; font-family: inherit; } .ck-copy-btn:hover { background: rgba(59,130,246,.2); border-color: rgba(59,130,246,.4); } .ck-copy-btn.--copied { background: rgba(16,185,129,.15); color: #34d399; border-color: rgba(16,185,129,.3); } /* PromptPay QR */ .ck-qr-wrap { text-align: center; padding: 8px 0; } .ck-qr-img { width: 200px; height: 200px; border-radius: 14px; border: 1px solid var(--ck-border, rgba(99,102,241,.15)); background: #fff; padding: 8px; object-fit: contain; margin: 0 auto 14px; display: block; box-shadow: 0 4px 16px rgba(0,0,0,.15); } .ck-qr-label { font-size: .82rem; color: var(--ck-muted, #94a3b8); } .ck-qr-label strong { color: var(--ck-text, #f1f5f9); } /* ── Upload Slip ── */ .ck-upload-zone { border: 2px dashed rgba(99,102,241,.25); border-radius: 14px; padding: 32px 20px; text-align: center; cursor: pointer; transition: all .2s; position: relative; background: rgba(99,102,241,.03); } .ck-upload-zone:hover { border-color: rgba(99,102,241,.45); background: rgba(99,102,241,.06); } .ck-upload-zone.--has-file { border-color: rgba(16,185,129,.3); background: rgba(16,185,129,.04); } .ck-upload-zone.--drag-over { border-color: rgba(99,102,241,.7); background: rgba(99,102,241,.1); transform: scale(1.01); } .ck-upload-icon { font-size: 2rem; color: #6366f1; margin-bottom: 10px; } .ck-upload-text { font-size: .88rem; color: var(--ck-text, #f1f5f9); font-weight: 500; } .ck-upload-hint { font-size: .76rem; color: var(--ck-muted, #94a3b8); margin-top: 6px; } .ck-upload-zone input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; } /* Preview */ .ck-slip-preview { margin-top: 16px; text-align: center; animation: ckFadeIn .3s ease; } .ck-slip-img { max-height: 220px; border-radius: 12px; border: 1px solid var(--ck-border, rgba(99,102,241,.15)); box-shadow: 0 4px 20px rgba(0,0,0,.2); } .ck-slip-meta { font-size: .78rem; color: #34d399; font-weight: 500; margin-top: 10px; } .ck-slip-remove { display: inline-flex; align-items: center; gap: 4px; margin-top: 8px; padding: 6px 14px; border-radius: 8px; background: rgba(239,68,68,.1); color: #f87171; border: 1px solid rgba(239,68,68,.2); font-size: .78rem; cursor: pointer; transition: all .2s; font-family: inherit; } .ck-slip-remove:hover { background: rgba(239,68,68,.2); } /* ── CTA Button ── */ .ck-submit-btn { width: 100%; padding: 16px 24px; border: none; border-radius: 14px; font-size: 1rem; font-weight: 700; font-family: inherit; cursor: pointer; background: linear-gradient(135deg, #6366f1, #4f46e5); color: #fff; letter-spacing: -.01em; transition: all .2s; position: relative; box-shadow: 0 4px 20px rgba(99,102,241,.3); display: flex; align-items: center; justify-content: center; gap: 8px; } .ck-submit-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 28px rgba(99,102,241,.4); background: linear-gradient(135deg, #818cf8, #6366f1); } .ck-submit-btn:active { transform: translateY(0); } .ck-submit-btn:disabled { opacity: .5; cursor: not-allowed; transform: none; } .ck-submit-btn .ck-spinner { display: none; width: 20px; height: 20px; border: 2.5px solid rgba(255,255,255,.3); border-top-color: #fff; border-radius: 50%; animation: ckSpin .6s linear infinite; } .ck-submit-btn.--loading .ck-spinner { display: inline-block; } .ck-submit-btn.--loading .ck-btn-text { display: none; } @keyframes ckSpin { to { transform: rotate(360deg); } } /* ── Order Summary Sidebar ── */ .ck-summary { position: sticky; top: 80px; } .ck-summary-mobile-toggle { display: none; width: 100%; padding: 14px 18px; border-radius: 14px; background: var(--ck-card, #182235); border: 1px solid var(--ck-border, rgba(99,102,241,.12)); cursor: pointer; font-family: inherit; color: var(--ck-text, #f1f5f9); } .ck-summary-toggle-row { display: flex; justify-content: space-between; align-items: center; } .ck-summary-toggle-left { display: flex; align-items: center; gap: 10px; } .ck-summary-toggle-left i { color: #6366f1; } .ck-summary-toggle-label { font-weight: 600; font-size: .92rem; } .ck-summary-toggle-price { font-weight: 800; font-size: 1.05rem; color: #818cf8; } .ck-summary-toggle-arrow { color: var(--ck-muted, #94a3b8); transition: transform .2s; font-size: .85rem; } .ck-summary-toggle-arrow.--open { transform: rotate(180deg); } /* Product Items */ .ck-item { display: flex; align-items: center; gap: 14px; padding: 14px 0; border-bottom: 1px solid var(--ck-border, rgba(99,102,241,.08)); } .ck-item:last-child { border-bottom: none; } .ck-item-img { width: 56px; height: 56px; border-radius: 10px; object-fit: cover; flex-shrink: 0; border: 1px solid var(--ck-border, rgba(99,102,241,.12)); background: var(--ck-input-bg, #0f1a2e); } .ck-item-info { flex: 1; min-width: 0; } .ck-item-name { font-size: .85rem; font-weight: 600; color: var(--ck-text, #f1f5f9); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .ck-item-variant { font-size: .75rem; color: var(--ck-muted, #94a3b8); margin-top: 2px; } .ck-item-price { font-size: .88rem; font-weight: 700; color: var(--ck-text, #f1f5f9); white-space: nowrap; flex-shrink: 0; } .ck-item-qty { font-size: .72rem; color: var(--ck-muted, #94a3b8); background: rgba(99,102,241,.08); padding: 2px 8px; border-radius: 6px; margin-left: 6px; } /* Custom scrollbar for cart list */ .ck-scroll::-webkit-scrollbar { width: 4px; } .ck-scroll::-webkit-scrollbar-track { background: transparent; } .ck-scroll::-webkit-scrollbar-thumb { background: rgba(99,102,241,.2); border-radius: 4px; } .ck-scroll::-webkit-scrollbar-thumb:hover { background: rgba(99,102,241,.4); } .ck-scroll { scrollbar-width: thin; scrollbar-color: rgba(99,102,241,.2) transparent; } /* Price breakdown */ .ck-price-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; } .ck-price-label { font-size: .85rem; color: var(--ck-muted, #94a3b8); } .ck-price-val { font-size: .88rem; font-weight: 600; color: var(--ck-text, #f1f5f9); } .ck-price-divider { border: none; border-top: 1px dashed var(--ck-border, rgba(99,102,241,.15)); margin: 10px 0; } .ck-total-row { display: flex; justify-content: space-between; align-items: center; padding: 12px 0 4px; } .ck-total-label { font-size: .9rem; font-weight: 700; color: var(--ck-text, #f1f5f9); } .ck-total-val { font-size: 1.35rem; font-weight: 800; color: #818cf8; letter-spacing: -.02em; } /* Security badge */ .ck-secure { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 18px; padding: 12px; border-radius: 10px; background: rgba(16,185,129,.06); border: 1px solid rgba(16,185,129,.12); } .ck-secure i { color: #34d399; font-size: .9rem; } .ck-secure span { font-size: .76rem; color: #94a3b8; } /* Toast notification */ .ck-toast { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%) translateY(20px); padding: 14px 24px; border-radius: 12px; font-size: .88rem; font-weight: 600; z-index: 10000; opacity: 0; pointer-events: none; transition: all .3s ease; font-family: 'Noto Sans Thai', sans-serif; box-shadow: 0 8px 30px rgba(0,0,0,.3); } .ck-toast.--show { opacity: 1; transform: translateX(-50%) translateY(0); } .ck-toast.--success { background: #065f46; color: #d1fae5; border: 1px solid rgba(16,185,129,.3); } /* Error alert */ .ck-error { background: rgba(239,68,68,.08); border: 1px solid rgba(239,68,68,.2); border-radius: 12px; padding: 14px 18px; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; color: #fca5a5; font-size: .88rem; } .ck-error i { color: #f87171; font-size: 1rem; flex-shrink: 0; } /* ═══ Responsive ═══ */ /* Tablet 768–991 */ @media(max-width:991px) { .ck-summary-mobile-toggle { display: block; margin-bottom: 16px; } .ck-summary-body.--collapsed { display: none; } .ck-grid { gap: 16px; } .ck-card { padding: 22px; } .ck-summary { position: static; } .ck-col-left { order: 2; } .ck-col-right { order: 1; } .ck-steps { padding: 14px 16px; gap: 0; } .ck-step-line { width: 32px; } } /* Mobile ≤575 */ @media(max-width:575px) { .ck-row { grid-template-columns: 1fr; gap: 0; } .ck-card { padding: 18px; border-radius: 14px; } .ck-pm-card { padding: 14px 16px; } .ck-bank-card { flex-direction: column; align-items: stretch; gap: 12px; text-align: center; } .ck-bank-avatar, .ck-bank-avatar-placeholder { margin: 0 auto; } .ck-bank-info { text-align: center; } .ck-copy-btn { width: 100%; justify-content: center; } .ck-steps { flex-wrap: wrap; gap: 4px; justify-content: center; } .ck-step-line { width: 20px; } .ck-step span:not(.ck-step-num) { display: none; } .ck-breadcrumb { font-size: .76rem; } } /* Sticky mobile CTA */ @media(max-width:768px) { .ck-submit-wrap { position: sticky; bottom: 0; left: 0; right: 0; background: linear-gradient(to top, var(--ck-card, #182235) 60%, transparent); padding: 16px 0 4px; margin: 0 -18px -18px; padding-left: 18px; padding-right: 18px; z-index: 100; } } /* ═══ Light Theme Full Overrides ═══ */ [data-theme="light"] .ck-page { --ck-card: #ffffff; --ck-border: #e2e8f0; --ck-text: #1e293b; --ck-muted: #64748b; --ck-input-bg: #f8fafc; --ck-input-border: #e2e8f0; } [data-theme="light"] .ck-pm-card { background: #fff; } [data-theme="light"] .ck-pm-card:hover { background: #f8fafc; } [data-theme="light"] .ck-pm-card.--selected { background: rgba(99,102,241,.03); } [data-theme="light"] .ck-pm-radio { border-color: #cbd5e1; } [data-theme="light"] .ck-amount-card { background: linear-gradient(135deg, #fffbeb, #fef3c7); border-color: #fde68a; } [data-theme="light"] .ck-amount-label { color: #b45309; } [data-theme="light"] .ck-amount-value { color: #92400e; } [data-theme="light"] .ck-amount-hint { color: #78716c; } [data-theme="light"] .ck-bank-card { background: #f8fafc; } [data-theme="light"] .ck-bank-acct { color: #2563eb; } [data-theme="light"] .ck-bank-name { color: #1e293b; } [data-theme="light"] .ck-bank-holder { color: #64748b; } [data-theme="light"] .ck-copy-btn { background: rgba(59,130,246,.08); color: #2563eb; border-color: rgba(59,130,246,.15); } [data-theme="light"] .ck-total-val { color: #4f46e5; } [data-theme="light"] .ck-summary-toggle-price { color: #4f46e5; } [data-theme="light"] .ck-qr-img { box-shadow: 0 4px 16px rgba(0,0,0,.08); } [data-theme="light"] .ck-qr-label { color: #64748b; } [data-theme="light"] .ck-qr-label strong { color: #1e293b; } [data-theme="light"] .ck-upload-zone { background: rgba(99,102,241,.02); border-color: #cbd5e1; } [data-theme="light"] .ck-upload-zone:hover { border-color: #818cf8; background: rgba(99,102,241,.04); } [data-theme="light"] .ck-upload-icon { color: #4f46e5; } [data-theme="light"] .ck-toast.--success { background: #d1fae5; color: #065f46; border-color: #a7f3d0; } [data-theme="light"] .ck-error { background: #fef2f2; border-color: #fecaca; color: #b91c1c; } [data-theme="light"] .ck-error i { color: #dc2626; } [data-theme="light"] .ck-secure { background: #f0fdf4; border-color: #bbf7d0; } [data-theme="light"] .ck-secure span { color: #64748b; } [data-theme="light"] .ck-secure i { color: #16a34a; } [data-theme="light"] .ck-select option { background: #fff; color: #1e293b; } [data-theme="light"] .ck-step-num { border-color: #e2e8f0; color: #94a3b8; } [data-theme="light"] .ck-steps { background: #fff; border-color: #e2e8f0; } [data-theme="light"] .ck-step-line { background: #e2e8f0; } [data-theme="light"] .ck-item-img { background: #f1f5f9; } @media(max-width:768px) { [data-theme="light"] .ck-submit-wrap { background: linear-gradient(to top, #ffffff 60%, transparent); } } </style> <div class="ck-page"> <!-- Breadcrumb --> <div class="ck-breadcrumb"> <a href="<?php echo defined('SITE_URL') ? rtrim(SITE_URL, '/') : ''; ?>/index.php"><i class="fas fa-home"></i> หน้าหลัก</a> <span class="ck-bc-sep"><i class="fas fa-chevron-right"></i></span> <a href="<?php echo defined('SITE_URL') ? rtrim(SITE_URL, '/') : ''; ?>/cart.php">ตะกร้าสินค้า</a> <span class="ck-bc-sep"><i class="fas fa-chevron-right"></i></span> <span class="ck-bc-current">ชำระเงิน</span> </div> <!-- Step Indicator --> <div class="ck-steps"> <div class="ck-step --done"> <span class="ck-step-num"><i class="fas fa-check" style="font-size:.65rem"></i></span> <span>ตะกร้าสินค้า</span> </div> <div class="ck-step-line" style="background:#34d399"></div> <div class="ck-step --active"> <span class="ck-step-num">2</span> <span>ชำระเงิน</span> </div> <div class="ck-step-line"></div> <div class="ck-step"> <span class="ck-step-num">3</span> <span>สำเร็จ</span> </div> </div> <div class="ck-grid"> <!-- ═══════ LEFT COLUMN — Payment ═══════ --> <div class="ck-col-left"> <?php if (!empty($error)): ?> <div class="ck-error"> <i class="fas fa-exclamation-circle"></i> <span><?php echo htmlspecialchars($error); ?></span> </div> <?php endif; ?> <form method="POST" action="" enctype="multipart/form-data" id="checkoutForm"> <input type="hidden" name="csrf_token" value="<?php echo function_exists('generateCSRFToken') ? generateCSRFToken() : ''; ?>"> <!-- ── Shipping Address ── --> <div class="ck-card" style="margin-bottom: 20px;"> <div class="ck-section-head"> <div class="ck-section-icon" style="background: rgba(99,102,241,.1); color: #818cf8;"> <i class="fas fa-map-marker-alt"></i> </div> <div> <div class="ck-section-title">ที่อยู่จัดส่งสินค้า</div> <div class="ck-section-sub">ระบุที่อยู่สำหรับจัดส่งพัสดุของคุณ</div> </div> </div> <div class="ck-row"> <div class="ck-field"> <label class="ck-label">ชื่อ-นามสกุล ผู้รับ <span class="req">*</span></label> <input type="text" name="recipient_name" class="ck-input" required value="<?php echo safe_sanitize($_SESSION['user']['fullname'] ?? ''); ?>" placeholder="กรอกชื่อ-นามสกุล"> </div> <div class="ck-field"> <label class="ck-label">เบอร์โทรศัพท์ ผู้รับ <span class="req">*</span></label> <input type="tel" name="phone" class="ck-input" required placeholder="0812345678" value="<?php echo safe_sanitize($userAddress['phone'] ?? ''); ?>"> </div> </div> <div class="ck-field"> <label class="ck-label">ที่อยู่ (บ้านเลขที่, ถนน, ซอย) <span class="req">*</span></label> <textarea name="address_line" class="ck-textarea" required placeholder="เช่น 123/45 ถนนสุขุมวิท"><?php echo safe_sanitize($userAddress['address_line'] ?? ''); ?></textarea> </div> <div class="ck-row"> <div class="ck-field"> <label class="ck-label">จังหวัด <span class="req">*</span></label> <select id="provinceSelect" name="province" class="ck-select" required onchange="loadAmphoes(this.value)"> <option value="">-- เลือกจังหวัด --</option> </select> </div> <div class="ck-field"> <label class="ck-label">อำเภอ / เขต <span class="req">*</span></label> <select id="amphoeSelect" name="amphoe" class="ck-select" required onchange="loadDistricts(this.value)"> <option value="">-- เลือกอำเภอ --</option> </select> </div> </div> <div class="ck-row"> <div class="ck-field"> <label class="ck-label">ตำบล / แขวง <span class="req">*</span></label> <select id="districtSelect" name="district" class="ck-select" required onchange="updateZipcode(this)"> <option value="">-- เลือกตำบล --</option> </select> </div> <div class="ck-field"> <label class="ck-label">รหัสไปรษณีย์ <span class="req">*</span></label> <input type="text" id="zipcodeInput" name="zipcode" class="ck-input" required readonly value="<?php echo safe_sanitize($userAddress['zipcode'] ?? ''); ?>" style="background: rgba(99,102,241,.04);"> </div> </div> <label class="ck-check-row"> <input type="checkbox" name="save_address" id="save_address" checked> <span class="ck-check-label">บันทึกที่อยู่นี้ไว้ใช้ในการสั่งซื้อครั้งต่อไป</span> </label> </div> <!-- ── Payment Methods ── --> <div class="ck-card" style="margin-bottom: 20px;"> <div class="ck-section-head"> <div class="ck-section-icon" style="background: rgba(16,185,129,.1); color: #34d399;"> <i class="fas fa-credit-card"></i> </div> <div> <div class="ck-section-title">วิธีการชำระเงิน</div> <div class="ck-section-sub">เลือกวิธีการชำระเงินที่สะดวกสำหรับคุณ</div> </div> </div> <!-- Bank Transfer --> <label class="ck-pm-card --selected" id="pmCard_bank" onclick="selectPayment('bank_transfer')"> <input type="radio" name="payment_method" id="pay_bank" value="bank_transfer" checked onchange="togglePaymentUI()"> <div class="ck-pm-header"> <div class="ck-pm-radio"></div> <div class="ck-pm-icon --bank"><i class="fas fa-university"></i></div> <div class="ck-pm-info"> <div class="ck-pm-name">โอนเงินผ่านบัญชีธนาคารกลาง</div> <div class="ck-pm-desc">โอนเงินแล้วอัปโหลดสลิปเป็นหลักฐาน</div> </div> </div> <div id="bankDetails" class="ck-pm-detail"> <!-- Amount Highlight --> <div class="ck-amount-card"> <div class="ck-amount-icon">💰</div> <div> <div class="ck-amount-label">ยอดที่ต้องโอน</div> <div class="ck-amount-value">฿<?php echo number_format($grandTotal, 2); ?></div> <div class="ck-amount-hint">โอนเข้าบัญชีธนาคารด้านล่าง</div> </div> </div> <?php if (empty($centralBanks)): ?> <div class="ck-error" style="margin-bottom:0"> <i class="fas fa-info-circle"></i> <span>ยังไม่มีข้อมูลบัญชีธนาคารกลางในระบบ กรุณาติดต่อผู้ดูแลระบบ</span> </div> <?php else: ?> <?php foreach ($centralBanks as $cb): ?> <div class="ck-bank-card"> <?php if (!empty($cb['qr_code_image'])): ?> <img src="<?php echo (defined('UPLOAD_URL') ? rtrim(UPLOAD_URL, '/') : '') . '/' . ltrim($cb['qr_code_image'], '/'); ?>" class="ck-bank-avatar" onclick="event.preventDefault(); window.open(this.src, '_blank')" title="คลิกดูภาพขยาย"> <?php else: ?> <div class="ck-bank-avatar-placeholder">🏦</div> <?php endif; ?> <div class="ck-bank-info"> <div class="ck-bank-name"><?php echo htmlspecialchars($cb['bank_name'] ?? ''); ?></div> <div class="ck-bank-acct"><?php echo htmlspecialchars($cb['account_number'] ?? ''); ?></div> <div class="ck-bank-holder">ชื่อบัญชี: <?php echo htmlspecialchars($cb['account_name'] ?? ''); ?></div> </div> <button type="button" class="ck-copy-btn" onclick="event.preventDefault(); copyToClipboard('<?php echo htmlspecialchars($cb['account_number'] ?? ''); ?>', this)"> <i class="far fa-copy"></i> คัดลอก </button> </div> <?php endforeach; ?> <?php endif; ?> </div> </label> <!-- PromptPay --> <label class="ck-pm-card" id="pmCard_promptpay" onclick="selectPayment('promptpay')"> <input type="radio" name="payment_method" id="pay_promptpay" value="promptpay" onchange="togglePaymentUI()"> <div class="ck-pm-header"> <div class="ck-pm-radio"></div> <div class="ck-pm-icon --pp"><i class="fas fa-qrcode"></i></div> <div class="ck-pm-info"> <div class="ck-pm-name">พร้อมเพย์ (PromptPay QR Code)</div> <div class="ck-pm-desc">สแกน QR Code เพื่อชำระเงินทันที</div> </div> </div> <div id="promptpayDetails" class="ck-pm-detail" style="display:none;"> <?php $promptpayAccount = null; foreach ($centralBanks as $cb) { $bName = $cb['bank_name'] ?? ''; if (stripos($bName, 'PromptPay') !== false || stripos($bName, 'พร้อมเพย์') !== false) { $promptpayAccount = $cb; break; } } if (!$promptpayAccount && !empty($centralBanks)) { $promptpayAccount = $centralBanks[0]; } $ppNumber = $promptpayAccount ? preg_replace('/[^0-9]/', '', $promptpayAccount['account_number'] ?? '') : '0812345678'; ?> <!-- Amount Highlight for PromptPay --> <div class="ck-amount-card"> <div class="ck-amount-icon">📱</div> <div> <div class="ck-amount-label">ยอดที่ต้องชำระ</div> <div class="ck-amount-value">฿<?php echo number_format($grandTotal, 2); ?></div> </div> </div> <div class="ck-qr-wrap"> <?php if ($promptpayAccount && !empty($promptpayAccount['qr_code_image'])): ?> <img src="<?php echo (defined('UPLOAD_URL') ? rtrim(UPLOAD_URL, '/') : '') . '/' . ltrim($promptpayAccount['qr_code_image'], '/'); ?>" class="ck-qr-img" alt="PromptPay QR Code"> <?php else: ?> <img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=PROMPTPAY_<?php echo urlencode($ppNumber); ?>_AMOUNT_<?php echo $grandTotal; ?>" class="ck-qr-img" alt="PromptPay QR Code"> <?php endif; ?> <p class="ck-qr-label">เบอร์พร้อมเพย์ / เลขบัญชี: <strong><?php echo htmlspecialchars($promptpayAccount['account_number'] ?? $ppNumber); ?></strong></p> <p class="ck-qr-label">ชื่อบัญชี: <strong><?php echo htmlspecialchars($promptpayAccount['account_name'] ?? (defined('SITE_NAME') ? SITE_NAME : 'Shop')); ?></strong></p> </div> </div> </label> <!-- COD --> <label class="ck-pm-card" id="pmCard_cod" onclick="selectPayment('cod')"> <input type="radio" name="payment_method" id="pay_cod" value="cod" onchange="togglePaymentUI()"> <div class="ck-pm-header"> <div class="ck-pm-radio"></div> <div class="ck-pm-icon --cod"><i class="fas fa-truck"></i></div> <div class="ck-pm-info"> <div class="ck-pm-name">เก็บเงินปลายทาง (Cash on Delivery)</div> <div class="ck-pm-desc">ชำระเงินเมื่อได้รับพัสดุ</div> </div> </div> </label> </div> <!-- ── Upload Slip ── --> <div id="slipUploadSection" class="ck-card" style="margin-bottom: 20px;"> <div class="ck-section-head"> <div class="ck-section-icon" style="background: rgba(251,191,36,.1); color: #fbbf24;"> <i class="fas fa-receipt"></i> </div> <div> <div class="ck-section-title">แนบหลักฐานการโอนเงิน <span style="color:#f87171; font-size:.85rem;">*</span></div> <div class="ck-section-sub">อัปโหลดสลิปการโอนเพื่อยืนยันการชำระเงิน</div> </div> </div> <div class="ck-upload-zone" id="uploadZone"> <input type="file" name="payment_slip" id="payment_slip_input" accept="image/*" onchange="previewSlip(this)" required> <div class="ck-upload-icon"><i class="fas fa-cloud-upload-alt"></i></div> <div class="ck-upload-text">ลากไฟล์มาวางที่นี่ หรือคลิกเพื่อเลือกไฟล์</div> <div class="ck-upload-hint">รองรับไฟล์ JPG, PNG, WebP — บีบอัดอัตโนมัติ</div> </div> <div id="slipPreviewContainer" class="ck-slip-preview" style="display:none;"> <img id="slipPreviewImage" src="" class="ck-slip-img" alt="Slip Preview"> <div class="ck-slip-meta"><i class="fas fa-check-circle"></i> อัปโหลดสลิปสำเร็จ</div> <button type="button" class="ck-slip-remove" onclick="removeSlip()"> <i class="fas fa-times"></i> ลบไฟล์ </button> </div> </div> <!-- ── Submit Button ── --> <div class="ck-submit-wrap"> <button type="submit" class="ck-submit-btn" id="submitBtn"> <span class="ck-btn-text"><i class="fas fa-check-circle"></i> ยืนยันการสั่งซื้อสินค้า</span> <span class="ck-spinner"></span> </button> </div> </form> </div> <!-- ═══════ RIGHT COLUMN — Order Summary ═══════ --> <div class="ck-col-right"> <!-- Mobile Toggle --> <button type="button" class="ck-summary-mobile-toggle" onclick="toggleSummary()"> <div class="ck-summary-toggle-row"> <div class="ck-summary-toggle-left"> <i class="fas fa-shopping-bag"></i> <span class="ck-summary-toggle-label">สรุปคำสั่งซื้อ</span> </div> <div style="display:flex; align-items:center; gap:10px;"> <span class="ck-summary-toggle-price">฿<?php echo number_format($grandTotal, 2); ?></span> <i class="fas fa-chevron-down ck-summary-toggle-arrow" id="summaryArrow"></i> </div> </div> </button> <div class="ck-card ck-summary" id="summaryBody"> <div class="ck-section-head"> <div class="ck-section-icon" style="background: rgba(99,102,241,.1); color: #818cf8;"> <i class="fas fa-clipboard-list"></i> </div> <div> <div class="ck-section-title">สรุปรายการสั่งซื้อ</div> <div class="ck-section-sub"><?php echo count($cart); ?> รายการ</div> </div> </div> <!-- Cart Items --> <div class="ck-scroll" style="max-height: 320px; overflow-y: auto; margin-bottom: 16px;"> <?php foreach ($cart as $item): ?> <div class="ck-item"> <?php $itemImgUrl = ''; if (!empty($item['image'])) { $itemImgUrl = (function_exists('formatImageUrl')) ? formatImageUrl($item['image']) : $item['image']; } ?> <?php if ($itemImgUrl): ?> <img src="<?php echo htmlspecialchars($itemImgUrl); ?>" class="ck-item-img" alt="<?php echo safe_sanitize($item['name'] ?? ''); ?>" onerror="this.style.display='none'"> <?php endif; ?> <div class="ck-item-info"> <div class="ck-item-name"><?php echo safe_sanitize($item['name'] ?? 'Unknown'); ?></div> <div class="ck-item-variant"> ไซส์: <?php echo safe_sanitize($item['size'] ?? '-'); ?> · สี: <?php echo safe_sanitize($item['color'] ?? '-'); ?> <span class="ck-item-qty">x<?php echo (int)($item['quantity'] ?? 1); ?></span> </div> </div> <div class="ck-item-price">฿<?php echo number_format(($item['price'] ?? 0) * ($item['quantity'] ?? 1), 2); ?></div> </div> <?php endforeach; ?> </div> <!-- Price Breakdown --> <hr class="ck-price-divider"> <div class="ck-price-row"> <span class="ck-price-label">ราคารวมสินค้า</span> <span class="ck-price-val">฿<?php echo number_format($subtotal, 2); ?></span> </div> <div class="ck-price-row"> <span class="ck-price-label">ค่าจัดส่ง</span> <span class="ck-price-val">฿<?php echo number_format($shippingFee, 2); ?></span> </div> <hr class="ck-price-divider"> <div class="ck-total-row"> <span class="ck-total-label">ยอดสุทธิที่ต้องชำระ</span> <span class="ck-total-val">฿<?php echo number_format($grandTotal, 2); ?></span> </div> <!-- Security Badge --> <div class="ck-secure"> <i class="fas fa-shield-alt"></i> <span>การชำระเงินปลอดภัย — ข้อมูลถูกเข้ารหัสและปกป้อง</span> </div> </div> </div> </div><!-- /ck-grid --> </div><!-- /ck-page --> <!-- Toast Notification --> <div class="ck-toast --success" id="ckToast"></div> <script> const savedProvince = "<?php echo safe_sanitize($userAddress['province'] ?? ''); ?>"; const savedAmphoe = "<?php echo safe_sanitize($userAddress['amphoe'] ?? ''); ?>"; const savedDistrict = "<?php echo safe_sanitize($userAddress['district'] ?? ''); ?>"; const siteUrl = "<?php echo defined('SITE_URL') ? rtrim(SITE_URL, '/') : ''; ?>"; document.addEventListener('DOMContentLoaded', () => { fetch(siteUrl + '/ajax/get_location.php?type=provinces') .then(r => r.json()) .then(provinces => { const select = document.getElementById('provinceSelect'); provinces.forEach(p => { select.innerHTML += `<option value="${p}">${p}</option>`; }); if (savedProvince) { select.value = savedProvince; loadAmphoes(savedProvince, savedAmphoe); } }); }); let currentProvince = ''; function loadAmphoes(prov, autoSelectAmphoe = '') { currentProvince = prov; fetch(`${siteUrl}/ajax/get_location.php?type=amphoes&parent=${encodeURIComponent(prov)}`) .then(r => r.json()) .then(amphoes => { const select = document.getElementById('amphoeSelect'); select.innerHTML = '<option value="">-- เลือกอำเภอ --</option>'; amphoes.forEach(a => { select.innerHTML += `<option value="${a}">${a}</option>`; }); if (autoSelectAmphoe) { select.value = autoSelectAmphoe; loadDistricts(autoSelectAmphoe, savedDistrict); } }); } function loadDistricts(amphoe, autoSelectDistrict = '') { fetch(`${siteUrl}/ajax/get_location.php?type=districts&province=${encodeURIComponent(currentProvince)}&parent=${encodeURIComponent(amphoe)}`) .then(r => r.json()) .then(districts => { const select = document.getElementById('districtSelect'); select.innerHTML = '<option value="">-- เลือกตำบล --</option>'; window.districtData = districts; Object.keys(districts).forEach(d => { select.innerHTML += `<option value="${d}">${d}</option>`; }); if (autoSelectDistrict) { select.value = autoSelectDistrict; } }); } function updateZipcode(select) { const val = select.value; if (window.districtData && window.districtData[val]) { document.getElementById('zipcodeInput').value = window.districtData[val]; } } /* ── Payment Method Selection ── */ function selectPayment(method) { document.querySelectorAll('.ck-pm-card').forEach(c => c.classList.remove('--selected')); const map = { bank_transfer: 'pmCard_bank', promptpay: 'pmCard_promptpay', cod: 'pmCard_cod' }; const radioMap = { bank_transfer: 'pay_bank', promptpay: 'pay_promptpay', cod: 'pay_cod' }; document.getElementById(map[method]).classList.add('--selected'); document.getElementById(radioMap[method]).checked = true; togglePaymentUI(); } function togglePaymentUI() { const bank = document.getElementById('pay_bank').checked; const promptpay = document.getElementById('pay_promptpay').checked; const isSlipRequired = bank || promptpay; document.getElementById('bankDetails').style.display = bank ? 'block' : 'none'; document.getElementById('promptpayDetails').style.display = promptpay ? 'block' : 'none'; const slipSection = document.getElementById('slipUploadSection'); if (slipSection) slipSection.style.display = isSlipRequired ? 'block' : 'none'; const slipInput = document.getElementById('payment_slip_input'); if (slipInput) { if (isSlipRequired) { slipInput.setAttribute('required', 'required'); } else { slipInput.removeAttribute('required'); } } // Update card selection visual document.querySelectorAll('.ck-pm-card').forEach(c => c.classList.remove('--selected')); if (bank) document.getElementById('pmCard_bank').classList.add('--selected'); if (promptpay) document.getElementById('pmCard_promptpay').classList.add('--selected'); if (document.getElementById('pay_cod').checked) document.getElementById('pmCard_cod').classList.add('--selected'); } /* ── Slip Preview ── */ function previewSlip(input) { const container = document.getElementById('slipPreviewContainer'); const img = document.getElementById('slipPreviewImage'); const zone = document.getElementById('uploadZone'); if (input.files && input.files[0]) { const reader = new FileReader(); reader.onload = function(e) { img.src = e.target.result; container.style.display = 'block'; zone.classList.add('--has-file'); zone.style.display = 'none'; } reader.readAsDataURL(input.files[0]); } else { img.src = ''; container.style.display = 'none'; zone.classList.remove('--has-file'); zone.style.display = 'block'; } } function removeSlip() { const input = document.getElementById('payment_slip_input'); input.value = ''; document.getElementById('slipPreviewContainer').style.display = 'none'; document.getElementById('uploadZone').style.display = 'block'; document.getElementById('uploadZone').classList.remove('--has-file'); } /* ── Copy to Clipboard with Toast ── */ function copyToClipboard(text, btn) { navigator.clipboard.writeText(text).then(() => { // Toast notification const toast = document.getElementById('ckToast'); toast.innerHTML = '<i class="fas fa-check-circle" style="margin-right:8px"></i> คัดลอกเลขบัญชีเรียบร้อย'; toast.classList.add('--show'); setTimeout(() => toast.classList.remove('--show'), 2500); // Button state if (btn) { const orig = btn.innerHTML; btn.innerHTML = '<i class="fas fa-check"></i> คัดลอกแล้ว'; btn.classList.add('--copied'); setTimeout(() => { btn.innerHTML = orig; btn.classList.remove('--copied'); }, 2000); } }).catch(err => { const toast = document.getElementById('ckToast'); toast.innerHTML = '<i class="fas fa-copy" style="margin-right:8px"></i> เลขบัญชี: ' + text; toast.classList.add('--show'); setTimeout(() => toast.classList.remove('--show'), 3000); }); } /* ── Mobile Summary Toggle ── */ function toggleSummary() { const body = document.getElementById('summaryBody'); const arrow = document.getElementById('summaryArrow'); body.classList.toggle('--collapsed'); arrow.classList.toggle('--open'); } /* ── Form Submit Loading State ── */ document.getElementById('checkoutForm').addEventListener('submit', function(e) { const btn = document.getElementById('submitBtn'); btn.classList.add('--loading'); btn.disabled = true; }); /* ── Drag & Drop for Upload Zone ── */ const uploadZone = document.getElementById('uploadZone'); if (uploadZone) { ['dragenter', 'dragover'].forEach(evt => { uploadZone.addEventListener(evt, (e) => { e.preventDefault(); uploadZone.classList.add('--drag-over'); }); }); ['dragleave', 'drop'].forEach(evt => { uploadZone.addEventListener(evt, (e) => { e.preventDefault(); uploadZone.classList.remove('--drag-over'); }); }); uploadZone.addEventListener('drop', (e) => { e.preventDefault(); const input = document.getElementById('payment_slip_input'); if (e.dataTransfer.files.length > 0) { input.files = e.dataTransfer.files; previewSlip(input); } }); } </script> <?php require_once __DIR__ . '/../includes/footer.php'; ?> <?php } catch (PDOException $e) { // ดักจับ Error Database $pageTitle = "ระบบขัดข้องชั่วคราว (Database Error)"; @require_once __DIR__ . '/../includes/header.php'; ?> <div class="container my-5"> <div class="alert alert-danger shadow-sm border-0"> <h4 class="alert-heading fw-bold">⚠️ พบข้อผิดพลาดในการเชื่อมต่อฐานข้อมูล</h4> <hr> <p class="mb-0">ขออภัยในความไม่สะดวก ไม่สามารถโหลดหน้าชำระเงินได้ในขณะนี้ กรุณาลองใหม่อีกครั้งในภายหลัง</p> <!-- สามารถซ่อน Error Message ใน Production ได้ --> <p class="mt-3 mb-0 text-muted"><small><i>Details: <?php echo htmlspecialchars($e->getMessage()); ?></i></small></p> </div> <?php $siteUrl = defined('SITE_URL') ? rtrim(SITE_URL, '/') : ''; ?> <a href="<?php echo $siteUrl; ?>/index.php" class="btn btn-primary rounded-pill mt-3">กลับสู่หน้าหลัก</a> </div> <?php @require_once __DIR__ . '/../includes/footer.php'; } catch (Exception $e) { // ดักจับ Error ทั่วไป $pageTitle = "ระบบขัดข้อง (System Error)"; @require_once __DIR__ . '/../includes/header.php'; ?> <div class="container my-5"> <div class="alert alert-danger shadow-sm border-0"> <h4 class="alert-heading fw-bold">⚠️ เกิดข้อผิดพลาดของระบบ</h4> <hr> <p class="mb-0">ระบบเกิดข้อผิดพลาดบางประการ: <strong><?php echo htmlspecialchars($e->getMessage()); ?></strong></p> </div> <?php $siteUrl = defined('SITE_URL') ? rtrim(SITE_URL, '/') : ''; ?> <a href="<?php echo $siteUrl; ?>/index.php" class="btn btn-primary rounded-pill mt-3">กลับสู่หน้าหลัก</a> </div> <?php @require_once __DIR__ . '/../includes/footer.php'; } ?>
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.25 |
proxy
|
phpinfo
|
Settings