CMS & Content Platforms

Triển khai Wishlist trong Optimizely Commerce 14 bằng ICart

By Ginbok8 min read

Giới thiệu

Việc thêm tính năng danh sách yêu thích (wishlist) giúp nâng cao trải nghiệm người dùng bằng cách cho phép khách hàng lưu lại sản phẩm để mua sau. Trong Optimizely Commerce, bạn có thể tận dụng cơ sở hạ tầng giỏ hàng (cart) hiện có để triển khai wishlist mà không cần tạo thêm các bảng cơ sở dữ liệu hoặc logic phức tạp.

Những gì bạn sẽ học:

Điều kiện tiên quyết:


Phương pháp tiếp cận

Thay vì xây dựng một hệ thống wishlist riêng biệt, chúng ta sẽ sử dụng chức năng giỏ hàng của Optimizely Commerce với một tên giỏ hàng duy nhất để phân biệt các mặt hàng trong wishlist với các mặt hàng trong giỏ hàng mua sắm thực tế.

Khái niệm chính:

Shopping Cart → Cart Name: "Default"
Wishlist      → Cart Name: "Wishlist"

Phương pháp này mang lại:


Bước 1: Tạo Service Wishlist

Tạo một service để quản lý các thao tác wishlist sử dụng API giỏ hàng của Commerce.

// File: Business/Services/IWishlistService.cs
using Mediachase.Commerce.Orders;

namespace YourProject.Business.Services
{
    public interface IWishlistService
    {
        ICart GetWishlist();
        bool AddToWishlist(string code, decimal quantity = 1);
        bool RemoveFromWishlist(string code);
        bool MoveToCart(string code);
        int GetWishlistItemCount();
    }
}

Bước 2: Triển khai Wishlist Service

// File: Business/Services/WishlistService.cs
using EPiServer.Commerce.Order;
using Mediachase.Commerce;
using Mediachase.Commerce.Orders;
using System.Linq;

namespace YourProject.Business.Services
{
    public class WishlistService : IWishlistService
    {
        private const string WishlistCartName = "Wishlist";
        
        private readonly IOrderRepository _orderRepository;
        private readonly IOrderGroupFactory _orderGroupFactory;
        private readonly ICurrentMarket _currentMarket;

        public WishlistService(
            IOrderRepository orderRepository,
            IOrderGroupFactory orderGroupFactory,
            ICurrentMarket currentMarket)
        {
            _orderRepository = orderRepository;
            _orderGroupFactory = orderGroupFactory;
            _currentMarket = currentMarket;
        }

        public ICart GetWishlist()
        {
            var market = _currentMarket.GetCurrentMarket();
            
            return _orderRepository.LoadOrCreateCart<ICart>(
                market.MarketId,
                WishlistCartName);
        }

        public bool AddToWishlist(string code, decimal quantity = 1)
        {
            var wishlist = GetWishlist();
            
            // Check if item already exists
            var existingItem = wishlist.GetAllLineItems()
                .FirstOrDefault(x => x.Code == code);

            if (existingItem != null)
            {
                // Update quantity if already in wishlist
                existingItem.Quantity += quantity;
            }
            else
            {
                // Add new item
                var lineItem = _orderGroupFactory.CreateLineItem(code, wishlist);
                lineItem.Quantity = quantity;
                wishlist.AddLineItem(lineItem);
            }

            return _orderRepository.Save(wishlist);
        }

        public bool RemoveFromWishlist(string code)
        {
            var wishlist = GetWishlist();
            var lineItem = wishlist.GetAllLineItems()
                .FirstOrDefault(x => x.Code == code);

            if (lineItem == null)
                return false;

            wishlist.RemoveLineItem(lineItem);
            return _orderRepository.Save(wishlist);
        }

        public bool MoveToCart(string code)
        {
            var wishlist = GetWishlist();
            var lineItem = wishlist.GetAllLineItems()
                .FirstOrDefault(x => x.Code == code);

            if (lineItem == null)
                return false;

            // Get shopping cart
            var cart = _orderRepository.LoadOrCreateCart<ICart>(
                _currentMarket.GetCurrentMarket().MarketId,
                "Default");

            // Add to cart
            var cartLineItem = _orderGroupFactory.CreateLineItem(code, cart);
            cartLineItem.Quantity = lineItem.Quantity;
            cart.AddLineItem(cartLineItem);

            // Remove from wishlist
            wishlist.RemoveLineItem(lineItem);

            // Save both
            _orderRepository.Save(cart);
            return _orderRepository.Save(wishlist);
        }

        public int GetWishlistItemCount()
        {
            var wishlist = GetWishlist();
            return wishlist.GetAllLineItems().Count();
        }
    }
}

Bước 3: Đăng ký Service

// File: Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    // Other services...
    
    services.AddTransient<IWishlistService, WishlistService>();
}

Bước 4: Tạo Wishlist Controller

// File: Controllers/WishlistController.cs
using Microsoft.AspNetCore.Mvc;
using YourProject.Business.Services;

namespace YourProject.Controllers
{
    public class WishlistController : Controller
    {
        private readonly IWishlistService _wishlistService;

        public WishlistController(IWishlistService wishlistService)
        {
            _wishlistService = wishlistService;
        }

        [HttpGet]
        public IActionResult Index()
        {
            var wishlist = _wishlistService.GetWishlist();
            return View(wishlist);
        }

        [HttpPost]
        public IActionResult Add(string code, decimal quantity = 1)
        {
            var success = _wishlistService.AddToWishlist(code, quantity);
            
            if (success)
            {
                return Json(new { 
                    success = true, 
                    message = "Added to wishlist",
                    count = _wishlistService.GetWishlistItemCount()
                });
            }

            return Json(new { success = false, message = "Failed to add" });
        }

        [HttpPost]
        public IActionResult Remove(string code)
        {
            var success = _wishlistService.RemoveFromWishlist(code);
            
            return Json(new { 
                success = success,
                count = _wishlistService.GetWishlistItemCount()
            });
        }

        [HttpPost]
        public IActionResult MoveToCart(string code)
        {
            var success = _wishlistService.MoveToCart(code);
            
            return Json(new { 
                success = success,
                message = success ? "Moved to cart" : "Failed to move"
            });
        }
    }
}

Bước 5: Tạo Wishlist View

@* File: Views/Wishlist/Index.cshtml *@
@model Mediachase.Commerce.Orders.ICart

<div class="wishlist-page">
    <h1>Danh sách yêu thích của tôi</h1>

    @if (!Model.GetAllLineItems().Any())
    {
        <p class="empty-message">Danh sách yêu thích của bạn đang trống</p>
    }
    else
    {
        <div class="wishlist-items">
            @foreach (var item in Model.GetAllLineItems())
            {
                <div class="wishlist-item" data-code="@item.Code">
                    <img src="@item.GetEntryContent()?.GetDefaultAsset()?.Url" 
                         alt="@item.DisplayName" />
                    
                    <div class="item-details">
                        <h3>@item.DisplayName</h3>
                        <p class="price">@item.PlacedPrice.ToString("C")</p>
                        <p class="quantity">SL: @item.Quantity</p>
                    </div>

                    <div class="item-actions">
                        <button class="btn-move-to-cart" 
                                data-code="@item.Code">
                            Thêm vào Giỏ hàng
                        </button>
                        
                        <button class="btn-remove" 
                                data-code="@item.Code">
                            Xóa
                        </button>
                    </div>
                </div>
            }
        </div>
    }
</div>

<script>
// Add to Cart from Wishlist
$('.btn-move-to-cart').on('click', function() {
    const code = $(this).data('code');
    
    $.post('/wishlist/movetocart', { code: code })
        .done(function(data) {
            if (data.success) {
                location.reload();
            }
        });
});

// Remove from Wishlist
$('.btn-remove').on('click', function() {
    const code = $(this).data('code');
    
    $.post('/wishlist/remove', { code: code })
        .done(function(data) {
            if (data.success) {
                $(`[data-code="${code}"]`).fadeOut();
                updateWishlistCount(data.count);
            }
        });
});
</script>

Bước 6: Thêm Nút Wishlist vào Sản phẩm

@* File: Views/Product/Index.cshtml (excerpt) *@
<button class="btn-add-to-wishlist" 
        data-code="@Model.Code">
    <i class="icon-heart"></i> Thêm vào Danh sách yêu thích
</button>

<script>
$('.btn-add-to-wishlist').on('click', function() {
    const code = $(this).data('code');
    
    $.post('/wishlist/add', { code: code, quantity: 1 })
        .done(function(data) {
            if (data.success) {
                alert('Đã thêm vào danh sách yêu thích!');
                updateWishlistCount(data.count);
            }
        });
});

function updateWishlistCount(count) {
    $('.wishlist-count').text(count);
}
</script>

Các vấn đề thường gặp

Vấn đề 1: Sản phẩm không được lưu trữ

Vấn đề: Các mặt hàng trong wishlist biến mất sau khi phiên làm việc (session) kết thúc

Nguyên nhân: Giỏ hàng của người dùng ẩn danh (anonymous users) có thể không được lưu trữ đúng cách

Giải pháp:

// Ensure user identity is set
public ICart GetWishlist()
{
    var market = _currentMarket.GetCurrentMarket();
    var customerId = GetCurrentCustomerId(); // Get from authentication
    
    return _orderRepository.LoadOrCreateCart<ICart>(
        market.MarketId,
        WishlistCartName,
        customerId);
}

private Guid GetCurrentCustomerId()
{
    // Return authenticated user ID or anonymous ID
    if (User.Identity.IsAuthenticated)
        return Guid.Parse(User.Identity.Name);
    
    return GetAnonymousCustomerId();
}

Vấn đề 2: Các mặt hàng trùng lặp

Vấn đề: Cùng một sản phẩm được thêm nhiều lần vào wishlist

Giải pháp:

public bool AddToWishlist(string code, decimal quantity = 1)
{
    var wishlist = GetWishlist();
    var existingItem = wishlist.GetAllLineItems()
        .FirstOrDefault(x => x.Code == code);

    if (existingItem != null)
    {
        // Don't increase quantity for wishlist
        return true; // Already in wishlist
    }

    var lineItem = _orderGroupFactory.CreateLineItem(code, wishlist);
    lineItem.Quantity = 1; // Always set to 1 for wishlist
    wishlist.AddLineItem(lineItem);

    return _orderRepository.Save(wishlist);
}

Vấn đề 3: Số lượng Wishlist không cập nhật

Vấn đề: Bộ đếm trên giao diện người dùng (UI counter) không phản ánh trạng thái wishlist hiện tại

Giải pháp:

// Create a ViewComponent for dynamic wishlist count
public class WishlistCountViewComponent : ViewComponent
{
    private readonly IWishlistService _wishlistService;

    public WishlistCountViewComponent(IWishlistService wishlistService)
    {
        _wishlistService = wishlistService;
    }

    public IViewComponentResult Invoke()
    {
        var count = _wishlistService.GetWishlistItemCount();
        return View(count);
    }
}
@* Use in layout *@
<a href="/wishlist">
    <i class="icon-heart"></i>
    @await Component.InvokeAsync("WishlistCount")
</a>

Các thực hành tốt nhất

1. Sử dụng Tên Giỏ hàng Riêng biệt

// ✅ NÊN: Sử dụng tên rõ ràng, nhất quán
private const string WishlistCartName = "Wishlist";
private const string DefaultCartName = "Default";

// ❌ KHÔNG NÊN: Sử dụng tên mơ hồ
private const string WishlistCartName = "Cart2";

2. Xử lý Người dùng Khách (Guest Users)

// ✅ NÊN: Hợp nhất wishlist khi người dùng đăng nhập
public void MergeAnonymousWishlist(Guid anonymousId, Guid customerId)
{
    var anonymousWishlist = _orderRepository.LoadCart<ICart>(
        anonymousId, WishlistCartName);
    
    var customerWishlist = _orderRepository.LoadCart<ICart>(
        customerId, WishlistCartName);

    foreach (var item in anonymousWishlist.GetAllLineItems())
    {
        AddToWishlist(item.Code, item.Quantity);
    }
    
    _orderRepository.Delete(anonymousWishlist.OrderLink);
}

3. Thêm Kiểm tra Trạng thái Wishlist

// ✅ NÊN: Cung cấp phương thức kiểm tra xem mặt hàng có trong wishlist không
public bool IsInWishlist(string code)
{
    var wishlist = GetWishlist();
    return wishlist.GetAllLineItems()
        .Any(x => x.Code == code);
}

Sử dụng trong view:

@if (await WishlistService.IsInWishlist(Model.Code))
{
    <button class="btn-in-wishlist" disabled>
        <i class="icon-heart-filled"></i> Đã có trong Wishlist
    </button>
}
else
{
    <button class="btn-add-to-wishlist" data-code="@Model.Code">
        <i class="icon-heart"></i> Thêm vào Danh sách yêu thích
    </button>
}

Tham chiếu Nhanh

Các Thao tác Chính

Thao tác Phương thức Trả về
Lấy wishlist GetWishlist() ICart
Thêm mặt hàng AddToWishlist(code, qty) bool
Xóa mặt hàng RemoveFromWishlist(code) bool
Chuyển sang giỏ hàng MoveToCart(code) bool
Lấy số lượng GetWishlistItemCount() int

Tên Giỏ hàng

Shopping Cart: "Default"
Wishlist:      "Wishlist"
Save for Later: "SavedForLater" // Tùy chọn

Xác minh

Sau khi triển khai, hãy kiểm tra:

  1. Thêm vào Wishlist

    • Nhấp vào "Thêm vào Danh sách yêu thích" trên trang sản phẩm
    • Xác minh mặt hàng xuất hiện trong trang wishlist
    • Kiểm tra bộ đếm trên tiêu đề được cập nhật
  2. Xóa khỏi Wishlist

    • Nhấp vào nút xóa
    • Xác minh mặt hàng biến mất
    • Kiểm tra bộ đếm giảm
  3. Chuyển sang Giỏ hàng

    • Nhấp vào "Thêm vào Giỏ hàng" từ wishlist
    • Xác minh mặt hàng được chuyển sang giỏ hàng mua sắm
    • Xác minh mặt hàng bị xóa khỏi wishlist
  4. Lưu trữ (Persistence)

    • Thêm các mặt hàng vào wishlist
    • Đăng xuất và đăng nhập lại
    • Xác minh các mặt hàng vẫn còn trong wishlist

Kết luận

Việc triển khai chức năng wishlist sử dụng hệ thống giỏ hàng của Optimizely Commerce là hiệu quả và dễ bảo trì. Bằng cách sử dụng tên giỏ hàng duy nhất, bạn tận dụng được cơ sở hạ tầng hiện có mà không cần thêm sự phức tạp nào.

Lợi ích chính:

...
#Optimizely Ecommerce DotNet Wishlist Backend
← Back to Articles
Triển khai Wishlist trong Optimizely Commerce 14 bằng ICart - Ginbok