Dev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunitySubmit a ticketLog In
GitHubNuGetDev CommunitySubmit a ticket

ICartOrderProvider

Describes how the cart order provider retrieves user carts on the Storefront.

This provider is used in several places on the Storefront, including on the Cart page and during the checkout process.

Methods

CustomerOrder GetCartOrder();

Retrieves the cart for the current user.

CustomerOrder GetOrCreateCartOrder();

Retrieves the cart for the current session. If a cart does not exist for the current session, a cart will be created and associated with the session.

void SetCartOrder(CustomerOrder customerOrder);

Associates the cart (customer order) with the current session. This method is called by the GetOrCreateCartOrder if and after a cart is created.

  • customerOrder – The cart to associate with the current session.

void ClearCache();

If the cart provider stores the current cart in a cache (like using the PerRequestCacheManager), this will clear the current cart from that cache.

Remarks

Commerce (SaaS) provides three standard implementations: CartOrderProviderByCustomer, CartOrderProviderByShipTo, and CartOrderProviderByUser. The CartOrderProviderByCustomer associates a cart with the user that created the cart (added products) and the Bill To customer being used at the time the cart was created. The CartOrderProviderByShipTo associates a cart with the user that created the cart and the Ship To customer being used at the time the cart was created. The CartOrderProviderByUserassociates a cart with the user that created the cart.  Depending on the implementation being used, the current session could include the user who created the cart or the bill-to or ship-to customer being used when the cart was created. Usually these customers are selected immediately after logging into the Storefront.

Only one provider is used at a time. The cart order provider can be configured in the Admin Console using the Site Configurations > Providers > Cart Provider setting.

746

Implementations of the ICartOrderProvider must be decorated with the [DependencyName] attribute. The DependencyName attribute takes a name as an argument. The name will be used to register the implementation in the IOC container. In the Admin Console, the name will appear in the dropdown when selecting a cart provider. This is how the application will know which cart provider to use during runtime.

Example: Associate the cart with the current user

[DependencyName("ByUser")]

public class CartOrderProviderByUser : ICartOrderProvider
{
    private int? cartRetentionDays;

    /// <summary>The customer order key.</summary>
    protected const string CustomerOrderKey = "CurrentCustomerOrder";

    /// <summary>The cookie manager.</summary>
    protected readonly ICookieManager CookieManager;

    /// <summary>The customer order utilities.</summary>
    protected readonly ICustomerOrderUtilities CustomerOrderUtilities;

    /// <summary>The per request cache manager.</summary>
    protected readonly IPerRequestCacheManager PerRequestCacheManager;

    /// <summary>The unit of work.</summary>
    protected readonly IUnitOfWork UnitOfWork;

    /// <summary>The cart settings.</summary>
    protected readonly CartSettings CartSettings;

    /// <summary>Gets the cart cookie name.</summary>
    protected virtual string CartCookieName => SiteContext.Current.WebsiteDto.Id + "_Cart";

    /// <summary>Number of days to retain shopping cart.</summary>
    protected virtual int CartRetentionDays => this.cartRetentionDays ?? (this.cartRetentionDays = this.CartSettings.RetentionDays).Value;

    /// <summary>Keep cart from browser session after login.</summary>
    protected virtual bool KeepCartAfterLogin => true;

    /// <summary>Base filter for getting customer order.</summary>
    protected virtual IQueryable<CustomerOrder> ApplyCustomerOrderBaseFilter(IQueryable<CustomerOrder> query)
    {
        var minValidOrderDate = this.CartRetentionDays > 0
            ? DateTimeProvider.Current.Now.AddDays(-this.CartRetentionDays)
            : DateTimeOffset.MinValue;
        var currentUserProfile = SiteContext.Current.UserProfile ?? SiteContext.Current.RememberedUserProfile;

        return query.Where(co => co.WebsiteId == SiteContext.Current.WebsiteDto.Id
            && co.InitiatedByUserProfileId == currentUserProfile.Id
            && (co.Status == CustomerOrder.StatusType.Cart || co.Status == CustomerOrder.StatusType.PunchOut)
            && co.OrderDate > minValidOrderDate);
    }

    /// <summary>Additional filter for getting customer order.</summary>
    protected virtual IQueryable<CustomerOrder> ApplyCustomerOrderAdditionalFilter(IQueryable<CustomerOrder> query)
    {
        return query;
    }

    /// <summary>Initializes a new instance of the <see cref="CartOrderProviderByUser"/> class.</summary>
    /// <param name="cookieManager">The cookie Manager.</param>
    /// <param name="perRequestCacheManager">The per Request Cache Manager.</param>
    /// <param name="unitOfWorkFactory">The unit Of Work Factory.</param>
    /// <param name="customerOrderUtilities">The customer order utilities.</param>
    /// <param name="cartSettings">The cart settings.</param>
    public CartOrderProviderByUser(
        ICookieManager cookieManager,
        IPerRequestCacheManager perRequestCacheManager,
        IUnitOfWorkFactory unitOfWorkFactory,
        ICustomerOrderUtilities customerOrderUtilities,
        CartSettings cartSettings)
    {
        this.CookieManager = cookieManager;
        this.PerRequestCacheManager = perRequestCacheManager;
        this.UnitOfWork = unitOfWorkFactory.GetUnitOfWork();
        this.CustomerOrderUtilities = customerOrderUtilities;
        this.CartSettings = cartSettings;
    }

    /// <summary>The get cart order.</summary>
    /// <returns>The <see cref="CustomerOrder"/>.</returns>
    public virtual CustomerOrder GetCartOrder()
    {
        var customerOrder = this.LookupCustomerOrder();
        if (customerOrder != null && customerOrder.Status == CustomerOrder.StatusType.AwaitingApproval)
        {
            return customerOrder;
        }

        if (customerOrder == null || (customerOrder.Status != CustomerOrder.StatusType.Cart && customerOrder.Status != CustomerOrder.StatusType.PunchOut))
        {
            return null;
        }

        var currentUserProfile = SiteContext.Current.UserProfile ?? SiteContext.Current.RememberedUserProfile;
        if (customerOrder.PlacedByUserProfile != currentUserProfile)
        {
            customerOrder.InitiatedByUserProfileId = currentUserProfile?.Id;
            customerOrder.InitiatedByUserProfile = currentUserProfile;
            customerOrder.PlacedByUserProfileId = currentUserProfile?.Id;
            customerOrder.PlacedByUserProfile = currentUserProfile;
            customerOrder.PlacedByUserName = currentUserProfile?.UserName ?? string.Empty;
        }

        this.SetCartOrder(customerOrder);
        return customerOrder;
    }

    /// <summary>The get or create cart order.</summary>
    /// <returns>The <see cref="CustomerOrder"/>.</returns>
    public virtual CustomerOrder GetOrCreateCartOrder()
    {
        var customerOrder = this.GetCartOrder();
        if (customerOrder != null)
        {
            return customerOrder;
        }

        customerOrder = this.CreateCartOrder();

        this.SetCartOrder(customerOrder);
        return customerOrder;
    }

    /// <summary>The set cart order.</summary>
    /// <param name="customerOrder">The customer order.</param>
    public virtual void SetCartOrder(CustomerOrder customerOrder)
    {
        this.PerRequestCacheManager.Add(CustomerOrderKey, customerOrder);
        var userProfile = SiteContext.Current.UserProfile ?? SiteContext.Current.RememberedUserProfile;
        if (userProfile == null)
        {
            if (customerOrder == null)
            {
                this.CookieManager.Remove(this.CartCookieName);
            }
            else
            {
                var expires = this.CartRetentionDays > 0 ? customerOrder.OrderDate.AddDays(this.CartRetentionDays) : (DateTimeOffset?)null;
                this.CookieManager.Add(this.CartCookieName, customerOrder.Id.ToString(), expires);
            }
        }
    }

    /// <summary>Clears the cache.</summary>
    public virtual void ClearCache()
    {
        this.PerRequestCacheManager.Remove(CustomerOrderKey);
    }

    /// <summary>The create cart order.</summary>
    /// <returns>The <see cref="CustomerOrder"/>.</returns>
    protected virtual CustomerOrder CreateCartOrder()
    {
        var customerOrderRepository = this.UnitOfWork.GetRepository<CustomerOrder>();

        var customerOrder = customerOrderRepository.Create();
        customerOrder.Status = CustomerOrder.StatusType.Cart;
        customerOrder.OrderNumber = Guid.NewGuid().ToString();

        var currentCustomer = SiteContext.Current.BillTo;
        if (currentCustomer == null)
        {
            customerOrder.Customer = this.GetAbandonedCartCustomer();
            this.CustomerOrderUtilities.SetBillTo(customerOrder, customerOrder.Customer);
        }
        else
        {
            customerOrder.Customer = currentCustomer;
            this.CustomerOrderUtilities.SetBillTo(customerOrder, customerOrder.Customer);
            customerOrder.ShipTo = SiteContext.Current.ShipTo;
            this.CustomerOrderUtilities.SetShipTo(customerOrder, customerOrder.ShipTo);
        }

        var currentUserProfile = SiteContext.Current.UserProfile ?? SiteContext.Current.RememberedUserProfile;
        customerOrder.InitiatedByUserProfile = currentUserProfile;
        customerOrder.InitiatedByUserProfileId = currentUserProfile?.Id;
        customerOrder.PlacedByUserProfile = currentUserProfile;
        customerOrder.PlacedByUserProfileId = currentUserProfile?.Id;
        customerOrder.PlacedByUserName = currentUserProfile?.UserName ?? string.Empty;
        customerOrder.Type = CustomerOrder.OrderType.Order;
        customerOrder.Website = this.UnitOfWork.GetRepository<Website>().Get(SiteContext.Current.WebsiteDto.Id);
        customerOrder.WebsiteId = SiteContext.Current.WebsiteDto.Id;

        var currency = SiteContext.Current?.CurrencyDto;
        if (currency != null)
        {
            this.CustomerOrderUtilities.SetCurrency(customerOrder, currency.Id);
        }

        customerOrderRepository.Insert(customerOrder);
        this.UnitOfWork.Save();

        return customerOrder;
    }

    /// <summary>The get abandoned cart customer.</summary>
    /// <returns>The <see cref="Customer"/>.</returns>
    protected virtual Customer GetAbandonedCartCustomer()
    {
        var customerRepository = this.UnitOfWork.GetTypedRepository<ICustomerRepository>();
        var customer = customerRepository.GetBillToByNumber(Customer.AbandonedCartNumber);
        if (customer == null)
        {
            customer = customerRepository.Create();
            customer.CustomerNumber = Customer.AbandonedCartNumber;
            customer.IsBillTo = true;
            customerRepository.Insert(customer);
            this.UnitOfWork.Save();
        }

        return customer;
    }

    /// <summary>The lookup customer order.</summary>
    /// <returns>The <see cref="CustomerOrder"/>.</returns>
    protected virtual CustomerOrder LookupCustomerOrder()
    {
        var customerOrder = this.PerRequestCacheManager.Get<CustomerOrder>(CustomerOrderKey) ?? this.GetCustomerOrder();
        customerOrder = this.ReplaceWithApprovalOrderIfNeeded(customerOrder);
        this.PerRequestCacheManager.Add(CustomerOrderKey, customerOrder);

        return customerOrder;
    }

    /// <summary>The get customer order.</summary>
    /// <returns>The <see cref="CustomerOrder"/>.</returns>
    protected virtual CustomerOrder GetCustomerOrder()
    {
        var currentUserProfile = SiteContext.Current.UserProfile ?? SiteContext.Current.RememberedUserProfile;
        return currentUserProfile == null
            ? this.GetCustomerOrderForNotLoggedUser()
            : this.GetCustomerOrderForLoggedInUser();
    }

    /// <summary>The get customer order for not logged user.</summary>
    /// <returns>The <see cref="CustomerOrder"/>.</returns>
    protected virtual CustomerOrder GetCustomerOrderForNotLoggedUser()
    {
        var customerOrderId = this.CookieManager.Get(this.CartCookieName);
        return customerOrderId.IsBlank()
            ? null
            : this.UnitOfWork.GetRepository<CustomerOrder>().Get(customerOrderId);
    }

    /// <summary>The get customer order for logged in user.</summary>
    /// <returns>The <see cref="CustomerOrder"/>.</returns>
    protected virtual CustomerOrder GetCustomerOrderForLoggedInUser()
    {
        if (this.KeepCartAfterLogin)
        {
            var customerOrderId = this.CookieManager.Get(this.CartCookieName);
            if (!customerOrderId.IsBlank())
            {
                var customerOrder = this.UnitOfWork.GetRepository<CustomerOrder>().Get(customerOrderId);
                if (customerOrder == null)
                {
                    this.CookieManager.Remove(this.CartCookieName);
                }
                else
                {
                    var currentUserProfile = SiteContext.Current.UserProfile ?? SiteContext.Current.RememberedUserProfile;
                    if (customerOrder.InitiatedByUserProfileId == currentUserProfile.Id)
                    {
                        this.CookieManager.Remove(this.CartCookieName);

                        var voidOldCarts = this.ApplyCustomerOrderBaseFilter(this.UnitOfWork.GetRepository<CustomerOrder>().GetTable());

                        this.ApplyCustomerOrderAdditionalFilter(voidOldCarts)
                            .Where(co => co.Id != customerOrder.Id)
                            .Each(co => co.Status = CustomerOrder.StatusType.Void);
                    }

                    return customerOrder;
                }
            }
        }

        var cart = this.ApplyCustomerOrderBaseFilter(this.UnitOfWork.GetRepository<CustomerOrder>().GetTable());
        cart = this.ApplyCustomerOrderAdditionalFilter(cart);
        return cart.OrderByDescending(co => co.CreatedOn).FirstOrDefault();
    }

    /// <summary>The replace with approval order if needed.</summary>
    /// <param name="customerOrder">The customer order.</param>
    /// <returns>The <see cref="CustomerOrder"/>.</returns>
    protected virtual CustomerOrder ReplaceWithApprovalOrderIfNeeded(CustomerOrder customerOrder)
    {
        var cookieKey = SiteContext.Current.WebsiteDto.Id + "_CustomerOrderForApproval";
        var customerOrderForApprovalId = this.CookieManager.Get(cookieKey);
        if (customerOrderForApprovalId.IsBlank() || customerOrderForApprovalId.EqualsIgnoreCase(customerOrder?.Id.ToString()))
        {
            return customerOrder;
        }

        var customerOrderForApproval = this.UnitOfWork.GetRepository<CustomerOrder>().Get(customerOrderForApprovalId);

        var userProfile = SiteContext.Current.UserProfile;
        if (customerOrderForApproval == null || customerOrderForApproval.Status != CustomerOrder.StatusType.AwaitingApproval || userProfile == null)
        {
            this.CookieManager.Remove(cookieKey);
            return customerOrder;
        }

        if (customerOrderForApproval.ApproverUserProfileId == userProfile.Id)
        {
            return customerOrderForApproval;
        }

        // if the user is not an Administrator, then they can only approve orders assigned to themselves
        if (!SiteContext.Current.IsUserInRole(BuiltInRoles.Administrator))
        {
            return customerOrder;
        }

        // if the user is an Administrator, but does not have this billto assigned, return initial order
        if (customerOrderForApproval.Customer.UserProfiles.All(m => m.Id != userProfile.Id))
        {
            return customerOrder;
        }

        // if the shipto is the billto, then the Administrative user has access
        if (customerOrderForApproval.Customer.Id.Equals(customerOrderForApproval.ShipTo.Id))
        {
            return customerOrderForApproval;
        }

        // if the user is an Administrator and has no assigned shiptos, then they can approve any order for their billtos
        var shipTos = this.UnitOfWork.GetTypedRepository<ICustomerRepository>().GetAssignedShipTos(customerOrderForApproval.Customer, userProfile.Id);
        if (!shipTos.Any())
        {
            return customerOrderForApproval;
        }

        // if the user is an Administrator and has assigned shiptos, then they can only approve orders for their assigned shiptos
        if (shipTos.Any(s => s.Id == customerOrderForApproval.ShipTo.Id))
        {
            return customerOrderForApproval;
        }

        return customerOrder;
    }
}