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¤cy=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
