Web bán hàng có token thanh toán

<!DOCTYPE html> <html lang="vi"> <head> <meta charset="UTF-8" /> <title>Shop Điện cơ 247</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- PayPal JS SDK: NHỚ THAY client-id --> <script src="https://www.paypal.com/sdk/js?client-id=YOUR_PAYPAL_CLIENT_ID&currency=USD"></script> <style> * { box-sizing: border-box; margin: 0; padding: 0; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } body { background: linear-gradient(-45deg, #ff9a9e, #fad0c4, #fbc2eb, #ffd1ff); background-size: 400% 400%; animation: gradientShift 12s ease infinite; color: #111827; padding: 20px; min-height: 100vh; } @keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } h1, h2, h3 { margin-bottom: 10px; } .container { max-width: 1200px; margin: 0 auto; } .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .header-title { font-size: 24px; font-weight: 700; } .tagline { font-size: 14px; color: #374151; } .layout { display: grid; grid-template-columns: 3fr 2fr; gap: 20px; } @media (max-width: 900px) { .layout { grid-template-columns: 1fr; } } .card { background: #ffffffdd; backdrop-filter: blur(12px); border-radius: 16px; padding: 16px; box-shadow: 0 10px 25px rgba(15, 23, 42, 0.1); } .products-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; } .product-card { border-radius: 14px; padding: 12px; background: #f9fafb; border: 1px solid #e5e7eb; display: flex; flex-direction: column; justify-content: space-between; } .product-name { font-weight: 600; margin-bottom: 4px; } .product-price { font-size: 14px; color: #10b981; font-weight: 600; margin-bottom: 4px; } .product-stock { font-size: 12px; color: #6b7280; margin-bottom: 8px; } .button { border: none; outline: none; padding: 8px 10px; border-radius: 999px; cursor: pointer; font-size: 13px; font-weight: 600; display: inline-flex; align-items: center; justify-content: center; gap: 6px; background: #111827; color: #ffffff; transition: transform 0.1s ease, box-shadow 0.1s ease, background 0.1s ease; } .button.secondary { background: #e5e7eb; color: #111827; } .button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; } .button:not(:disabled):hover { transform: translateY(-1px); box-shadow: 0 8px 18px rgba(15, 23, 42, 0.2); } .button.small { padding: 4px 8px; font-size: 12px; } .pill { font-size: 11px; padding: 2px 8px; border-radius: 999px; background: #eef2ff; color: #4f46e5; } .section-title-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .section-subtitle { font-size: 12px; color: #6b7280; margin-bottom: 10px; } .badge { display: inline-flex; padding: 2px 8px; border-radius: 999px; font-size: 11px; background: #ecfdf3; color: #15803d; font-weight: 500; margin-left: 6px; } .badge.low { background: #fefce8; color: #b45309; } .badge.out { background: #fee2e2; color: #b91c1c; } table { width: 100%; border-collapse: collapse; font-size: 13px; } th, td { padding: 8px; text-align: left; border-bottom: 1px solid #e5e7eb; } th { font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; color: #6b7280; } .cart-total-row td { font-weight: 700; border-top: 2px solid #111827; } .cart-empty { font-size: 13px; color: #6b7280; padding: 8px 0; } .payment-methods { display: flex; flex-direction: column; gap: 12px; margin-top: 8px; } .payment-methods .button { width: 100%; justify-content: center; } .footer-note { margin-top: 16px; font-size: 11px; color: #9ca3af; } .status { font-size: 12px; margin-top: 6px; } .status.success { color: #15803d; } .status.error { color: #b91c1c; } .link-admin { font-size: 12px; color: #2563eb; text-decoration: none; } .link-admin:hover { text-decoration: underline; } </style> </head> <body> <div class="container"> <header class="header"> <div> <div class="header-title">Shop Điện cơ 247</div> <div class="tagline"> Cung cấp nhanh - Chất lượng bền - Hỗ trợ tận tâm </div> <a href="admin.html" class="link-admin">→ Trang quản lý hàng hóa (admin)</a> </div> <button class="button secondary small" onclick="resetData()"> Reset dữ liệu demo </button> </header> <main class="layout"> <!-- Cột trái: Sản phẩm --> <section> <div class="card"> <div class="section-title-row"> <h2>Sản phẩm</h2> <span class="pill" id="product-count-pill"></span> </div> <p class="section-subtitle">Nhấn “Thêm vào giỏ” để đặt mua.</p> <div class="products-grid" id="products-container"> <!-- Render bằng JS --> </div> </div> </section> <!-- Cột phải: Giỏ hàng + Thanh toán --> <section> <div class="card"> <div class="section-title-row"> <h2>Giỏ hàng</h2> <span class="pill" id="cart-count-pill">0 sản phẩm</span> </div> <div id="cart-container"></div> </div> <div class="card"> <div class="section-title-row"> <h3>Thanh toán</h3> </div> <p class="section-subtitle"> Thanh toán bằng PayPal hoặc MoMo (MoMo qua backend Node.js). </p> <div class="payment-methods"> <div> <div id="paypal-button-container"></div> <div id="paypal-status" class="status"></div> </div> <button class="button" onclick="payWithMomo()"> Thanh toán bằng MoMo </button> <div id="momo-status" class="status"></div> </div> <div class="footer-note"> ⚠️ Bản demo: dữ liệu lưu trong localStorage. Bản triển khai thật nên dùng backend + database. </div> </div> </section> </main> </div> <script> // ======= DỮ LIỆU SẢN PHẨM (CHUNG VỚI ADMIN) ======= const defaultProducts = [ { id: 1, name: "Áo phông basic", price: 199000, stock: 10 }, { id: 2, name: "Hoodie oversize", price: 399000, stock: 5 }, { id: 3, name: "Mũ lưỡi trai", price: 99000, stock: 20 }, { id: 4, name: "Tote bag canvas", price: 149000, stock: 15 } ]; const STORAGE_KEY = "shop_products"; function loadProducts() { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { try { return JSON.parse(stored); } catch { localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProducts)); return JSON.parse(JSON.stringify(defaultProducts)); } } else { localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProducts)); return JSON.parse(JSON.stringify(defaultProducts)); } } function saveProducts() { localStorage.setItem(STORAGE_KEY, JSON.stringify(products)); } let products = loadProducts(); let cartItems = []; // { productId, quantity } function formatCurrencyVND(amount) { return amount.toLocaleString("vi-VN") + " ₫"; } function findProduct(id) { return products.find(p => p.id === id); } function getCartTotal() { return cartItems.reduce((sum, item) => { const p = findProduct(item.productId); return sum + (p ? p.price * item.quantity : 0); }, 0); } // ======= RENDER SẢN PHẨM ======= function renderProducts() { const container = document.getElementById("products-container"); container.innerHTML = ""; products.forEach(product => { const card = document.createElement("div"); card.className = "product-card"; const badge = document.createElement("span"); if (product.stock === 0) { badge.className = "badge out"; badge.textContent = "Hết hàng"; } else if (product.stock < 5) { badge.className = "badge low"; badge.textContent = "Sắp hết"; } else { badge.className = "badge"; badge.textContent = "Còn hàng"; } const name = document.createElement("div"); name.className = "product-name"; name.textContent = product.name; name.appendChild(badge); const price = document.createElement("div"); price.className = "product-price"; price.textContent = formatCurrencyVND(product.price); const stock = document.createElement("div"); stock.className = "product-stock"; stock.textContent = "Tồn kho: " + product.stock; const button = document.createElement("button"); button.className = "button small"; button.textContent = "Thêm vào giỏ"; button.disabled = product.stock === 0; button.onclick = () => addToCart(product.id); card.appendChild(name); card.appendChild(price); card.appendChild(stock); card.appendChild(button); container.appendChild(card); }); document.getElementById("product-count-pill").textContent = products.length + " sản phẩm"; } // ======= GIỎ HÀNG ======= function addToCart(productId) { const product = findProduct(productId); if (!product || product.stock <= 0) { alert("Sản phẩm đã hết hàng!"); return; } product.stock -= 1; saveProducts(); const existing = cartItems.find(item => item.productId === productId); if (existing) { existing.quantity += 1; } else { cartItems.push({ productId, quantity: 1 }); } renderProducts(); renderCart(); } function updateCartItem(productId, delta) { const item = cartItems.find(i => i.productId === productId); const product = findProduct(productId); if (!item || !product) return; if (delta === 1) { if (product.stock <= 0) { alert("Không đủ tồn kho!"); return; } item.quantity += 1; product.stock -= 1; } else if (delta === -1) { item.quantity -= 1; product.stock += 1; if (item.quantity <= 0) { cartItems = cartItems.filter(i => i.productId !== productId); } } saveProducts(); renderProducts(); renderCart(); } function removeCartItem(productId) { const item = cartItems.find(i => i.productId === productId); const product = findProduct(productId); if (item && product) { product.stock += item.quantity; // trả lại kho } cartItems = cartItems.filter(i => i.productId !== productId); saveProducts(); renderProducts(); renderCart(); } function renderCart() { const container = document.getElementById("cart-container"); container.innerHTML = ""; if (cartItems.length === 0) { container.innerHTML = '<div class="cart-empty">Giỏ hàng trống.</div>'; document.getElementById("cart-count-pill").textContent = "0 sản phẩm"; return; } const table = document.createElement("table"); const thead = document.createElement("thead"); thead.innerHTML = "<tr><th>Sản phẩm</th><th>SL</th><th>Đơn giá</th><th>Thành tiền</th><th></th></tr>"; const tbody = document.createElement("tbody"); cartItems.forEach(item => { const product = findProduct(item.productId); if (!product) return; const row = document.createElement("tr"); const nameCell = document.createElement("td"); nameCell.textContent = product.name; const qtyCell = document.createElement("td"); const decBtn = document.createElement("button"); decBtn.className = "button small secondary"; decBtn.textContent = "-"; decBtn.onclick = () => updateCartItem(item.productId, -1); const qtySpan = document.createElement("span"); qtySpan.style.margin = "0 6px"; qtySpan.textContent = item.quantity; const incBtn = document.createElement("button"); incBtn.className = "button small secondary"; incBtn.textContent = "+"; incBtn.onclick = () => updateCartItem(item.productId, 1); qtyCell.appendChild(decBtn); qtyCell.appendChild(qtySpan); qtyCell.appendChild(incBtn); const priceCell = document.createElement("td"); priceCell.textContent = formatCurrencyVND(product.price); const totalCell = document.createElement("td"); totalCell.textContent = formatCurrencyVND(product.price * item.quantity); const actionsCell = document.createElement("td"); const removeBtn = document.createElement("button"); removeBtn.className = "button small secondary"; removeBtn.textContent = "Xóa"; removeBtn.onclick = () => removeCartItem(item.productId); actionsCell.appendChild(removeBtn); row.appendChild(nameCell); row.appendChild(qtyCell); row.appendChild(priceCell); row.appendChild(totalCell); row.appendChild(actionsCell); tbody.appendChild(row); }); const totalRow = document.createElement("tr"); totalRow.className = "cart-total-row"; const totalLabel = document.createElement("td"); totalLabel.colSpan = 3; totalLabel.textContent = "Tổng cộng"; const totalValue = document.createElement("td"); totalValue.textContent = formatCurrencyVND(getCartTotal()); const emptyCell = document.createElement("td"); totalRow.appendChild(totalLabel); totalRow.appendChild(totalValue); totalRow.appendChild(emptyCell); tbody.appendChild(totalRow); table.appendChild(thead); table.appendChild(tbody); container.appendChild(table); const totalItems = cartItems.reduce((sum, i) => sum + i.quantity, 0); document.getElementById("cart-count-pill").textContent = totalItems + " sản phẩm"; } // ======= PAYPAL (DEMO) ======= function initPayPal() { if (typeof paypal === "undefined") return; const paypalStatus = document.getElementById("paypal-status"); paypal.Buttons({ createOrder: function (data, actions) { const totalVND = getCartTotal(); if (totalVND <= 0) { paypalStatus.textContent = "Giỏ hàng đang trống, không thể thanh toán."; paypalStatus.className = "status error"; return; } const usdAmount = (totalVND / 25000).toFixed(2); return actions.order.create({ purchase_units: [ { amount: { value: usdAmount }, description: "Thanh toán đơn hàng Shop Điện cơ 247" } ] }); }, onApprove: function (data, actions) { return actions.order.capture().then(function (details) { paypalStatus.textContent = "Thanh toán thành công bởi " + details.payer.name.given_name + "."; paypalStatus.className = "status success"; cartItems = []; renderCart(); }); }, onError: function () { paypalStatus.textContent = "Có lỗi xảy ra khi thanh toán PayPal."; paypalStatus.className = "status error"; } }).render("#paypal-button-container"); } // ======= MoMo THẬT (qua backend) ======= async function payWithMomo() { const momoStatus = document.getElementById("momo-status"); const totalVND = getCartTotal(); if (totalVND <= 0) { momoStatus.textContent = "Giỏ hàng đang trống, không thể thanh toán."; momoStatus.className = "status error"; return; } momoStatus.textContent = "Đang tạo đơn thanh toán MoMo..."; momoStatus.className = "status"; try { const res = await fetch("http://localhost:3000/api/momo/create-payment", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ amount: totalVND, items: cartItems }) }); const data = await res.json(); if (!res.ok) { console.error(data); momoStatus.textContent = "Tạo đơn thanh toán thất bại."; momoStatus.className = "status error"; return; } momoStatus.textContent = "Đang chuyển sang MoMo..."; momoStatus.className = "status success"; // chuyển user sang trang thanh toán MoMo window.location.href = data.payUrl; } catch (err) { console.error(err); momoStatus.textContent = "Không gọi được server tạo thanh toán."; momoStatus.className = "status error"; } } // ======= RESET DEMO ======= function resetData() { if (!confirm("Reset dữ liệu demo (sản phẩm & giỏ hàng)?")) return; localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProducts)); products = loadProducts(); cartItems = []; renderProducts(); renderCart(); document.getElementById("paypal-status").textContent = ""; document.getElementById("momo-status").textContent = ""; } document.addEventListener("DOMContentLoaded", () => { products = loadProducts(); renderProducts(); renderCart(); initPayPal(); }); </script> </body> </html> flie admin quản lý đơn hàng( có thể thêm hàng hoặc xóa bớt hàng ): <!DOCTYPE html> <html lang="vi"> <head> <meta charset="UTF-8" /> <title>Admin – Quản lý hàng hóa</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <style> * { box-sizing: border-box; margin: 0; padding: 0; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } body { background: #0f172a; color: #e5e7eb; padding: 20px; } .container { max-width: 900px; margin: 0 auto; } h1 { font-size: 24px; margin-bottom: 10px; } .subtitle { font-size: 13px; color: #9ca3af; margin-bottom: 16px; } .card { background: #020617; border-radius: 16px; padding: 16px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); border: 1px solid #1f2937; } table { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 8px; } th, td { padding: 8px; border-bottom: 1px solid #1f2937; text-align: left; } th { font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; color: #9ca3af; } input { width: 100%; padding: 6px 8px; border-radius: 10px; border: 1px solid #4b5563; background: #020617; color: #e5e7eb; font-size: 13px; } input:focus { outline: 2px solid #6366f1; outline-offset: 1px; } .button { border: none; outline: none; padding: 6px 10px; border-radius: 999px; cursor: pointer; font-size: 12px; font-weight: 600; background: #6366f1; color: white; transition: transform 0.1s ease, box-shadow 0.1s ease; } .button.small { padding: 4px 8px; font-size: 11px; } .button.danger { background: #ef4444; } .button.secondary { background: #111827; } .button:hover { transform: translateY(-1px); box-shadow: 0 8px 18px rgba(15,23,42,0.6); } .top-actions { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } .muted { font-size: 11px; color: #9ca3af; margin-top: 8px; } .add-row { display: grid; grid-template-columns: 3fr 1fr 1fr auto; gap: 8px; margin-top: 14px; align-items: center; } a { color: #38bdf8; font-size: 12px; text-decoration: none; } a:hover { text-decoration: underline; } </style> </head> <body> <div class="container"> <h1>Admin – Quản lý hàng hóa</h1> <p class="subtitle"> Dữ liệu sản phẩm dùng chung với <strong>Shop Điện cơ 247</strong> thông qua <code>localStorage</code>. <a href="index.html">← Quay lại trang bán hàng</a> </p> <div class="card"> <div class="top-actions"> <span>Danh sách sản phẩm</span> <button class="button secondary" onclick="resetProducts()">Reset về mặc định</button> </div> <table id="products-table"> <thead> <tr> <th>ID</th> <th>Tên</th> <th>Giá (VND)</th> <th>Tồn kho</th> <th></th> </tr> </thead> <tbody> <!-- render bằng JS --> </tbody> </table> <div class="add-row"> <input id="new-name" placeholder="Tên sản phẩm mới" /> <input id="new-price" type="number" min="0" placeholder="Giá" /> <input id="new-stock" type="number" min="0" placeholder="Tồn kho" /> <button class="button" onclick="addProduct()">Thêm sản phẩm</button> </div> <p class="muted"> Gợi ý thực tế: Trang này nên đặt sau đăng nhập admin + kết nối API để lưu vào database thật. Ở đây dùng localStorage để Dylan test nhanh trên 1 máy. </p> </div> </div> <script> const defaultProducts = [ { id: 1, name: "Áo phông basic", price: 199000, stock: 10 }, { id: 2, name: "Hoodie oversize", price: 399000, stock: 5 }, { id: 3, name: "Mũ lưỡi trai", price: 99000, stock: 20 }, { id: 4, name: "Tote bag canvas", price: 149000, stock: 15 } ]; const STORAGE_KEY = "shop_products"; function loadProducts() { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { try { return JSON.parse(stored); } catch { localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProducts)); return JSON.parse(JSON.stringify(defaultProducts)); } } else { localStorage.setItem(STORAGE_KEY, JSON.stringify(defaultProducts)); return JSON.parse(JSON.stringify(defaultProducts)); } } function saveProducts() { localStorage.setItem(STORAGE_KEY, JSON.stringify(products)); renderTable(); } let products = loadProducts(); function renderTable() { const tbody = document.querySelector("#products-table tbody"); tbody.innerHTML = ""; products.forEach((p, index) => { const tr = document.createElement("tr"); const tdId = document.createElement("td"); tdId.textContent = p.id; const tdName = document.createElement("td"); const inputName = document.createElement("input"); inputName.value = p.name; inputName.onchange = e => { products[index].name = e.target.value || products[index].name; saveProducts(); }; tdName.appendChild(inputName); const tdPrice = document.createElement("td"); const inputPrice = document.createElement("input"); inputPrice.type = "number"; inputPrice.min = 0; inputPrice.value = p.price; inputPrice.onchange = e => { products[index].price = Number(e.target.value) || products[index].price; saveProducts(); }; tdPrice.appendChild(inputPrice); const tdStock = document.createElement("td"); const inputStock = document.createElement("input"); inputStock.type = "number"; inputStock.min = 0; inputStock.value = p.stock; inputStock.onchange = e => { products[index].stock = Number(e.target.value) || products[index].stock; saveProducts(); }; tdStock.appendChild(inputStock); const tdActions = document.createElement("td"); const btnDelete = document.createElement("button"); btnDelete.className = "button small danger"; btnDelete.textContent = "Xóa"; btnDelete.onclick = () => { if (confirm("Xóa sản phẩm này?")) { products.splice(index, 1); saveProducts(); } }; tdActions.appendChild(btnDelete); tr.appendChild(tdId); tr.appendChild(tdName); tr.appendChild(tdPrice); tr.appendChild(tdStock); tr.appendChild(tdActions); tbody.appendChild(tr); }); } function addProduct() { const nameInput = document.getElementById("new-name"); const priceInput = document.getElementById("new-price"); const stockInput = document.getElementById("new-stock"); const name = nameInput.value.trim(); const price = Number(priceInput.value); const stock = Number(stockInput.value); if (!name || !priceInput.value || !stockInput.value) { alert("Nhập đầy đủ tên, giá, tồn kho."); return; } const maxId = products.reduce((max, p) => Math.max(max, p.id), 0); products.push({ id: maxId + 1, name, price, stock }); nameInput.value = ""; priceInput.value = ""; stockInput.value = ""; saveProducts(); } function resetProducts() { if (!confirm("Reset toàn bộ sản phẩm về mặc định?")) return; products = JSON.parse(JSON.stringify(defaultProducts)); localStorage.setItem(STORAGE_KEY, JSON.stringify(products)); renderTable(); } document.addEventListener("DOMContentLoaded", () => { products = loadProducts(); renderTable(); }); </script> </body> </html>

Public Last updated: 2025-12-12 02:07:04 PM