Tạo bởi Trần Văn Điêp|
Học PHP

[Video]Cookie: Xây dựng cart (giỏ hàng) + checkout (thanh toán) sử dụng Cookie & localStorage - Lập trình PHP/MySQL

Mở bài

Trong thương mại điện tử, chức năng giỏ hàng (cart)thanh toán (checkout) là trái tim của trải nghiệm mua sắm. Với lập trình viên mới học PHP/MySQL, một trong những cách đơn giản, nhanh chóng để triển khai giỏ hàng là tận dụng CookielocalStorage ở phía client — kết hợp với backend PHP để xử lý đặt hàng. Việc hiểu rõ khi nào nên lưu tạm dữ liệu trên trình duyệt (cookie/localStorage), khi nào cần đồng bộ lên server (PHP/MySQL), sẽ giúp bạn thiết kế hệ thống linh hoạt, an toàn và thân thiện với người dùng.

Bài viết này hướng dẫn từng bước để xây dựng cart + checkout sử dụng Cookie & localStorage: từ phân tích kiến trúc, thiết kế database, cài đặt front-end JavaScript đến xử lý backend bằng PHP và lưu dữ liệu vào MySQL. Ngoài kỹ thuật, bạn sẽ nhận được lời khuyên bảo mật, xử lý các tình huống thực tế (đồng bộ, tràn cookie, cross-device), và các mẹo tối ưu UX. Đây là tài liệu thực hành lý tưởng cho khóa học PHP/MySQL căn bản muốn mở rộng sang ứng dụng thương mại điện tử nhỏ.


Tại sao chọn Cookie và localStorage cho giỏ hàng?

Cookie và localStorage là hai cơ chế lưu trữ dữ liệu tại trình duyệt, có ưu/nhược khác nhau. Trước khi bắt tay vào xây dựng, hãy hiểu rõ mục tiêu dùng chúng cho cart.

  • Sử dụng Cookie: Cookie được trình duyệt gửi tự động kèm mỗi request đến server (tùy domain/path). Vì vậy cookie phù hợp để lưu một token hoặc dữ liệu nhỏ cần gửi đến server mà không cần logic client phức tạp. Cookie có hạn chế kích thước (~4KB) và nên dùng cho thông tin nhẹ như cart_id hoặc session token.

  • Sử dụng localStorage: localStorage cho phép lưu dữ liệu lớn hơn ( vài MB tùy browser) và dễ thao tác bằng JavaScript. Nó thích hợp để lưu nội dung giỏ hàng toàn bộ (các item, số lượng, giá tạm thời). Dữ liệu localStorage không tự gửi kèm request nên cần đồng bộ với server khi checkout/đăng nhập.

Lợi ích khi dùng client-side storage cho cart:

  1. Không cần đăng nhập: Khách hàng có thể thêm sản phẩm trước khi đăng nhập, trải nghiệm mượt mà.

  2. Tốc độ UI: Thao tác thêm/xóa cập nhật nhanh mà không cần gọi server.

  3. Giảm tải server: Truy vấn/ghi DB chỉ khi checkout, không cho mỗi thao tác thêm/xóa.

Nhưng cần lưu ý:

  • Cookie dễ bị gian lận nếu chứa dữ liệu nhạy cảm; cần mã hóa hoặc chỉ lưu token.

  • localStorage có thể bị xóa khi người dùng clear browser; cần cung cấp cơ chế restore (ví dụ đồng bộ khi login).

Tóm lại, chiến lược phổ biến: lưu “source of truth” tạm thời trên localStorage, lưu token/cart_id trên cookie để có thể đồng bộ với server khi cần.


Kiến trúc tổng quan: client + server interaction

Thiết kế hệ thống cart + checkout sử dụng Cookie & localStorage cần rõ ràng hai tầng: Front-end (JS) và Back-end (PHP/MySQL).

  1. Front-end (browser)

    • Quản lý giỏ hàng tạm thời bằng localStorage.cart (object chứa item id, qty, price, options).

    • Hiển thị cart, cập nhật số lượng, xóa item.

    • Khi user nhấn “Checkout”: validate trên client, gửi payload lên server bằng fetch() (POST JSON hoặc form).

    • Nếu có “remember cart”: gửi hoặc lưu cart_id vào document.cookie để server nhận biết cart đã tồn tại.

  2. Back-end (PHP/MySQL)

    • Endpoint API: /api/cart/sync (đồng bộ cart), /api/checkout (thực hiện checkout).

    • Nếu nhận cart_id từ cookie: load cart từ DB (server-side). Nếu không, tạo cart mới, lưu vào carts table, trả về cart_id và gợi ý client lưu cookie.

    • Khi checkout: validate stock/price, tạo ordersorder_items trong MySQL trong transaction, giảm tồn kho, trả về kết quả thành công/ lỗi.

  3. Bảng chính trong DB

    • carts (id, token, user_id nullable, created_at, updated_at)

    • cart_items (id, cart_id, product_id, qty, price_snapshot, options_json)

    • orders (id, user_id, total, status, created_at, address, payment_method)

    • order_items (order_id, product_id, qty, price)

Luồng cơ bản: người dùng thao tác trên localStorage → khi cần lưu server (login hoặc checkout), gửi cart JSON → server lưu vào DB, trả cart_id → client lưu cookie cart_id để phục hồi sau. Trường hợp người dùng vừa có cookie cart_id vừa localStorage, server sẽ hợp nhất (merge) 2 giỏ.


Thiết kế database và API backend bằng PHP/MySQL

Trước khi code, tạo schema cơ bản trong MySQL:

CREATE TABLE carts ( id INT AUTO_INCREMENT PRIMARY KEY, token VARCHAR(64) UNIQUE, user_id INT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE cart_items ( id INT AUTO_INCREMENT PRIMARY KEY, cart_id INT NOT NULL, product_id INT NOT NULL, qty INT NOT NULL, price DECIMAL(10,2) NOT NULL, options JSON NULL, FOREIGN KEY (cart_id) REFERENCES carts(id) ON DELETE CASCADE ); CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NULL, total DECIMAL(10,2), status VARCHAR(30), address TEXT, payment_method VARCHAR(50), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE order_items ( id INT AUTO_INCREMENT PRIMARY KEY, order_id INT NOT NULL, product_id INT NOT NULL, qty INT NOT NULL, price DECIMAL(10,2) NOT NULL, FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE );

API endpoints (quy ước):

  • POST /api/cart/sync — nhận cart JSON từ client (items[]), trả cart_id (tạo mới hoặc merge).

  • GET /api/cart/:id — trả cart hiện tại (dành cho AJAX render).

  • POST /api/checkout — nhận data thanh toán, validate, tạo order.

Ví dụ PHP - pseudo code cho sync:

// api/cart/sync.php require 'bootstrap.php'; // PDO init, helpers $payload = json_decode(file_get_contents('php://input'), true); $items = $payload['items'] ?? []; $cookieCartToken = $_COOKIE['cart_token'] ?? null; if ($cookieCartToken) { $cart = findCartByToken($cookieCartToken); if (!$cart) $cart = createCart($cookieCartToken); } else { $cart = createCart(generateToken()); setcookie('cart_token', $cart['token'], time()+30*24*3600, '/'); } // merge items: replace or add qty updateCartItems($cart['id'], $items); echo json_encode(['success'=>true, 'cart_token'=>$cart['token']]);

Lưu ý: generateToken() có thể là random 32 byte hex. Khi user đăng nhập, bạn có thể gán user_id cho cart để lưu lâu dài.


Lưu trữ cart: chi tiết code JavaScript (localStorage) và cookie

Dưới đây là ví dụ tập trung front-end để quản lý cart bằng localStorage và lưu cart_token vào cookie.

1. Thêm/xóa item trong localStorage

// cart.js function getLocalCart() { return JSON.parse(localStorage.getItem('cart') || '[]'); } function saveLocalCart(items) { localStorage.setItem('cart', JSON.stringify(items)); } function addToCart(product) { const items = getLocalCart(); const idx = items.findIndex(i => i.id === product.id); if (idx > -1) { items[idx].qty += product.qty; } else { items.push(product); } saveLocalCart(items); renderCart(); }

2. Đồng bộ local -> server (khi cần)

async function syncCartToServer() { const items = getLocalCart(); const res = await fetch('/api/cart/sync.php', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({items}) }); const data = await res.json(); if (data.success) { // server trả cart_token, lưu cookie nếu cần document.cookie = `cart_token=${data.cart_token};path=/;max-age=${30*24*3600}`; } }

3. Hợp nhất khi người dùng vừa có cookie vừa localStorage

Khi client mở trang, gọi GET /api/cart nếu cookie cart_token tồn tại, server trả cart; client merge với localStorage (cộng qty). Sau merge, sync lại server để có phiên bản cuối cùng.


Xử lý checkout: PHP side (validation, transaction, order insertion)

Bước quan trọng nhất là checkout: chuyển cart thành order trong DB, trừ tồn kho, xử lý thanh toán.

Kiểm tra nạp đồ: validation

  • Kiểm tra mỗi product_id còn đủ tồn kho (SELECT ... FOR UPDATE khi dùng InnoDB)

  • Kiểm tra price_snapshot khớp với giá hiện tại (ngăn thao túng client)

  • Kiểm tra thông tin giao hàng, phương thức thanh toán

Sử dụng transaction để đảm bảo nguyên tử

// api/checkout.php (pseudo) $db->beginTransaction(); try { $cart = findCartByToken($cookieCartToken); $items = getCartItems($cart['id']); // validate stock and price foreach ($items as $it) { $product = findProductForUpdate($it['product_id']); // SELECT ... FOR UPDATE if ($product['stock'] < $it['qty']) throw new Exception('Out of stock'); if ($product['price'] != $it['price_snapshot']) throw new Exception('Price changed'); // deduct stock updateProductStock($product['id'], $product['stock'] - $it['qty']); } // create order $orderId = createOrder($userId, $total, $address, $paymentMethod); foreach ($items as $it) { insertOrderItem($orderId, ...); } // clear cart clearCart($cart['id']); $db->commit(); echo json_encode(['success'=>true, 'order_id'=>$orderId]); } catch (Exception $e) { $db->rollBack(); echo json_encode(['success'=>false, 'message'=>$e->getMessage()]); }

Ghi nhớ: phải validate tất cả dữ liệu server-side. Không tin tưởng giá trị truyền từ client (localStorage/cookie).


Bảo mật, xử lý edge cases và best practices

Xây cart trên client mang lại tiện ích nhưng cũng nhiều rủi ro. Một số lưu ý quan trọng:

  1. Không lưu thông tin nhạy cảm trong cookie/localStorage (mật khẩu, thẻ tín dụng).

  2. Server phải luôn kiểm tra giá & tồn kho trước khi tạo order — dùng giá snapshot chỉ để hiển thị trước; so sánh với DB lúc checkout.

  3. Token hóa cart: cookie chỉ chứa cart_token, không chứa chi tiết items; server mới là nguồn dữ liệu cuối cùng khi checkout.

  4. Bảo vệ CSRF: dùng token cho form checkout nếu sử dụng form POST từ browser.

  5. Xử lý xung đột: nếu user mở nhiều tab, dùng locking hoặc transaction để tránh double checkout / oversell.

  6. Fallback khi cookie disabled: nếu cookie không bật, dựa vào localStorage và yêu cầu user đăng nhập để lưu cart server-side.

  7. Expiration & cleanup: tự động xóa cart cũ (ví dụ older than 30 days) để không chiếm DB.

  8. Rate limiting: chống spam checkout/requests.

Ngoài ra, hãy log các lỗi và hành vi bất thường để debug và theo dõi tấn công giả mạo.


Tối ưu UX và performance cho giỏ hàng & thanh toán

Một checkout tốt không chỉ an toàn mà còn phải nhanh và rõ ràng:

  • Hiển thị tiến trình: Cart → Shipping → Payment → Review → Confirm.

  • Lưu tự động: khi user thay đổi giỏ, lưu localStorage ngay và hiển thị tooltip “Đã lưu”.

  • Preview shipping cost & tax: tính ước lượng trên client hoặc gọi API nhanh để tính.

  • Minimize server calls: chỉ đồng bộ khi cần (login, checkout, explicit save).

  • Lazy load images: để danh sách cart tải nhanh.

  • Show price changes: nếu giá thay đổi trước khi checkout, thông báo rõ ràng và yêu cầu user confirm.

Về performance backend, tạo index cho cart_id, product_id, và tối ưu transaction để giảm lock contention. Sử dụng cache (Redis) cho produk catalog nhưng không cache stock/tính năng quan trọng.


Kết luận

Xây dựng cart (giỏ hàng) và checkout sử dụng Cookie & localStorage kết hợp với backend PHP/MySQL là giải pháp thực tế, phù hợp cho các dự án nhỏ và giai đoạn học tập. Chiến lược hợp lý là lưu tạm dữ liệu trên client với localStorage để có trải nghiệm mượt mà, lưu cart_token trong cookie để server có thể nhận diện và đồng bộ, rồi thực hiện checkout an toàn bằng transaction trên MySQL.

Trong khi cài đặt, bạn cần ưu tiên bảo mật: không tin dữ liệu client, luôn kiểm tra giá và tồn kho server-side, dùng prepared statements và transactions. Đồng thời chú trọng UX bằng cách tối ưu thời gian phản hồi, hiển thị thông báo rõ ràng khi có thay đổi giá/tồn kho, và cung cấp cơ chế khôi phục cart khi user đăng nhập trên thiết bị khác.

Bắt tay vào thực hành: bắt đầu với schema DB đơn giản, triển khai front-end localStorage, viết API sync và checkout bằng PHP, rồi mở rộng tính năng merge cart khi login, hỗ trợ guest checkout và tích hợp payment gateway khi cần. Với quy trình này, bạn sẽ có hệ thống giỏ hàng/checkout thực tế, an toàn và sẵn sàng cho mở rộng trong các dự án thương mại điện tử. Chúc bạn triển khai thành công!

Phản hồi từ học viên

5

Tổng 0 đánh giá

Đăng nhập để làm bài kiểm tra

Chưa có kết quả nào trước đó