Click here to Skip to main content
Click here to Skip to main content

nopCommerce - An open-source shopping cart framework

, 12 Dec 2008
Rate this:
Please Sign up or sign in to vote.
Implementing a website with e-commerce features in ASP.NET.

Introduction

There are many shipping carts on the internet, but there are no articles that deal with the creation of a powerful shopping cart from scratch. The main objective of this article is to show how to create an ASP.NET-based shopping cart. To keep the size of this article reasonable, I'm going to concentrate on implementing the basic shopping cart features. This article is inspired and based on the work and code by developers who created the nopCommerce shopping cart. The decision to develop an online store can involve a lot of time, just trying to assess what you need. For the sake of space, I won’t show much on the user interface, because I’m sure most folks have visited an online store.

At the end of this article, you'll have a fully functioning shopping cart.

This sample is ASP.NET 3.5 based, so you need MS Visual Studio 2008. And, of course, SQL Server 2005 database is required for storing the shopping cart items.

Using the Code

Using the nopCommerce source code is quite straightforward. The shopping cart can be installed locally on your development server or on your production server.

  • Download and install Enterprise Library 3.1 - May 2007. Enterprise Library is a library of application blocks designed to assist developers with common enterprise development challenges.
  • Unzip the file your have downloaded (nopCommerce 1.05).
  • Create an empty store database.
  • Once the database is created, execute the following SQL scripts from the \install\ folder on your database:
    • nopCommerce_createDatabase.sql
    • nopCommerce_createData.sql
  • Open your web.config file. You have to modify the database connection string.

The admin area should be the same URL + /administration/. The default administrator email is admin@yourstore.com. The default administrator password is admin.

Products

Products are the merchandise that is for sale in your store. All products need to be listed under a category or subcategory. However, keep in mind that products are not things that are directly orderable in nopCommerce, Product Variants (SKUs) are. A simple example will make this clear. Consider the product “Creative Sound Card”. A customer cannot order a “Creative Sound Card” directly, they have to order an OEM or a Retail version of the “Creative Sound Card”. So, in our case, the product is “Creative Sound Card”, and there would be two variants for this product: “OEM” and “Retail”, each with potentially different prices.

Shown below is the screenshot for managing products:

Some products may have additional properties that customers must choose before placing orders. For example, a cloth may have different sizes (X, M, L), or colors (red, white, black). All attributes must be defined before adding to the inventory. Product attributes (e.g., color, size) can be applied to the product variant only. You can add product attributes after the product variant is created.

The BLL manager classes provide many methods for managing the products. Here are some classes related to product management:

ASP.NET shopping cart

Here’s an example usage of some of these classes. This code is used to display a simple product info:

Product product = ProductManager.GetByProductID(ProductID);
if (product != null)
{
    lProductName.Text = Server.HtmlEncode(product.Name);
    lFullDescription.Text = product.FullDescription;
}

And, one more example. This code can be used to display category info and products attached to a category:

Category category = CategoryManager.GetByCategoryID(CategoryID);

rptrCategoryBreadcrumb.DataSource= CategoryManager.GetBreadCrumb(CategoryID);
rptrCategoryBreadcrumb.DataBind();

lDescription.Text = category.Description;

CategoryCollection subCategoryCollection = 
    CategoryManager.GetAllCategories(category.CategoryID);
if (subCategoryCollection.Count > 0)
{
    dlSubCategories.DataSource = subCategoryCollection;
    dlSubCategories.DataBind();
}
else
    dlSubCategories.Visible = false;

ProductCollection productCollection = new ProductCollection();
ProductCategoryCollection productCategoryCollection = 
  ProductCategoryManager.GetProductCategoriesByCategoryID(CategoryID);
foreach (ProductCategory productCategory in productCategoryCollection)
productCollection.Add(productCategory.Product);

if (productCollection.Count > 0)
{
    dlCatalog.DataSource = productCollection;
    dlCatalog.DataBind();
}
else
    dlCatalog.Visible = false;

International Support

Internationalization and localization means making your site usable in more than one language. With the introduction of ASP.NET, Microsoft aims to make it easier to localize your website for individual users, no matter where they hail from. Almost every shopping cart supports multiple languages. The shopping cart localization is managed by the LocalizationManager class. Every page in the source code that you can download is inherited from BaseNopPage, which has the GetLocaleResourceString(string ResourceName) method. Here’s an example of localization of two buttons named btnAddBillingAddress and btnAddShippingAddress:

private void ApplyLocalization()
{
    btnAddBillingAddress.Text = 
      GetLocaleResourceString("Account.AddBillingAddress");
    btnAddShippingAddress.Text = 
      GetLocaleResourceString("Account.AddShippingAddress");
}

nopCommerce comes with two predefined languages – English and Russian. Each page had a dropdown list to choose the language of the application. So, a user can select a preferred language for display.

Almost every shopping cart has multi-currency support. Also, a good shopping cart uses an exchange rate to calculate the amounts for published currencies. The exchange rate is entered when a currency is added or edited. Or, you can use real-time exchange rates (ECB: European Central Bank). To calculate the amount, the price of the product is multiplied by the exchange rate provided. Keep in mind that exchange rates fluctuate on a daily basis. You can edit the exchange rate as often as you need in order to stay current. Actual transactions are only handled in your store's primary currency. On credit card transactions, banks will usually make exchanges automatically based on the most current currency values. You can get live currency rates by using the GetCurrencyLiveRates method of the CurrencyManager class:

public static void GetCurrencyLiveRates(string ExchangeRateCurrencyCode, 
              out DateTime UpdateDate, out DataTable Rates)
{
    if (String.IsNullOrEmpty(ExchangeRateCurrencyCode) || 
                 ExchangeRateCurrencyCode.ToLower() != "eur")
        throw new Exception("You can use our \"CurrencyLiveRate\" service" + 
                            " only when exchange rate currency code is set to EURO");

    UpdateDate = DateTime.MinValue;
    Rates = new DataTable();
    Rates.Columns.Add("CurrencyCode", typeof(string));
    Rates.Columns.Add("Rate", typeof(decimal));
    HttpWebRequest request = WebRequest.Create("http://www.ecb.int/" + 
                             "stats/eurofxref/eurofxref-daily.xml") as HttpWebRequest;
    using (WebResponse response = request.GetResponse())
    {
        XmlDocument document = new XmlDocument();
        document.Load(response.GetResponseStream());
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(document.NameTable);
        nsmgr.AddNamespace("ns", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");
        nsmgr.AddNamespace("gesmes", "http://www.gesmes.org/xml/2002-08-01");
        XmlNode node = document.SelectSingleNode("gesmes:Envelope/ns:Cube/ns:Cube", nsmgr);
        UpdateDate = DateTime.ParseExact(node.Attributes["time"].Value,
            "yyyy-MM-dd", null);
        NumberFormatInfo provider = new NumberFormatInfo();
        provider.NumberDecimalSeparator = ".";
        provider.NumberGroupSeparator = "";
        foreach (XmlNode node2 in node.ChildNodes)
            Rates.Rows.Add(new object[] {node2.Attributes["currency"].Value, 
                           double.Parse(node2.Attributes["rate"].Value, provider) });
    }
}

Shipping Support

Nearly all software offer a shipping tool that calculates shipping costs for customers based on parameters that you set; some can even link up to common shipping methods like FedEx.

Shipping methods are ways in which you decide how to ship the products to the customers. The customer can then pick what type of shipping they want, and the cost for that shipping method is attached to the order. Typically, shipping methods would be things like: “By Ground”, “By Air”, “Next Day”, etc… You are free to choose any shipping method name that you want.

Today, many carts come with several shipping rate computation methods. Currently, nopCommerce provides four shipping rate computation methods.

  • Free Shipping. Shipping is zero cost.
  • Shipping By Order Weight. Shipping rates are calculated based upon the total weight of a shipment.
  • Shipping By Order Total. Shipping rates are calculated based upon the order total of a shipment.
  • Shipping By Country & Order Weight. Shipping rates are calculated based upon the country and the total weight of a shipment.

Let’s look at the Shipping By Order Weight computation method. It will calculate a shipping fee based on how much the shipment weighs. This is the recommended shipping calculation for companies that have products that vary a great deal in weight. The ability to charge different costs depending on the weight of the shipment helps to keep the company's shipping costs down when heavy items are shipped, yet keep the cost reasonable for customers who purchase products that are light in weight.

If you decide to use the matrix by weight, you will set up weight brackets and how much shipping will cost if the shipment falls within that bracket. For example: 1 pound up to 5 pounds will cost $3.00, 6 pounds up to 12 pounds will cost $8.00 etc. You can set up multiple shipping fees depending on the shipping method. Using the first weight bracket above, 1 pound up to 5 pounds will cost $3.00 using Ground, and 1 pound up to 5 pounds will cost $18.00 using Next Day.

Here’s the implementation of the ShippingByWeightComputationMethod class:

public class ShippingByWeightComputationMethod : IShippingRateComputationMethod
{
    public decimal? GetShippingRate(ShoppingCartCollection Cart, 
           ShippingInfo shippingInfo, bool includeDiscounts, 
           Country CountryFrom, StateProvince StateProvinceFrom, 
           string ZipPostalCodeFrom)
    {
        decimal shippingTotal = decimal.Zero;

        if (shippingInfo == null)
            return null;
        if (shippingInfo.ShippingMethod == null)
            return null;

        decimal subTotal = decimal.Zero;
        foreach (ShoppingCart shoppingCartItem in Cart)
        {
            if (shoppingCartItem.IsFreeShipping || 
                       !shoppingCartItem.IsShipEnabled)
                continue;
            if (includeDiscounts)
                subTotal += shoppingCartItem.SubTotalWithDiscount;
            else
                subTotal += shoppingCartItem.SubTotalWithoutDiscount;
        }

        decimal orderWeight = ShoppingCartManager.GetShoppingCartTotalWeigth(Cart);
        ShippingByWeight shippingByWeight = null;
        ShippingByWeightCollection shippingByWeightCollection = 
          ShippingByWeightManager.GetAllByShippingMethodID(
          shippingInfo.ShippingMethod.ShippingMethodID);
        foreach (ShippingByWeight shippingByWeight2 in shippingByWeightCollection)
        {
            if ((orderWeight >= shippingByWeight2.From) && 
                (orderWeight <= shippingByWeight2.To))
            {
                shippingByWeight = shippingByWeight2;
                break;
            }
        }
        if (shippingByWeight == null)
            return decimal.Zero;
        if (shippingByWeight.UsePercentage && 
               shippingByWeight.ShippingChargePercentage <= decimal.Zero)
            return decimal.Zero;
        if (!shippingByWeight.UsePercentage && 
               shippingByWeight.ShippingChargeAmount <= decimal.Zero)
            return decimal.Zero;
        if (shippingByWeight.UsePercentage)
            shippingTotal = Math.Round((decimal)((((float)subTotal) * 
               ((float)shippingByWeight.ShippingChargePercentage)) / 100f), 2);
        else
            shippingTotal = shippingByWeight.ShippingChargeAmount * orderWeight;

        if (shippingTotal < decimal.Zero)
            shippingTotal = decimal.Zero;
        return shippingTotal;
    }
}

Payment Gateways

Most ecommerce sites want to accept credit cards. A payment method is how a customer pays for the order. nopCommerce allows for both online and offline transactions. For the online methods, nopCommerce supports integration with several third party payment gateways so that customer credit card information will automatically be sent through the gateway (as either an authorization, or as an authorization and charge) upon completion of an order. You can have multiple payment methods active at one time. The user can select how he wants to pay at checkout.

As a rule, two transaction modes are supported by payment methods:

  • Authorize – authorizes the charge, but does not capture or transfer funds. Just verifies the card.
  • Authorize and capture – authorizes and captures the transaction all at once.

If you don’t want to charge the customer until you ship, then use Authorize. For charges that come in as Authrorized only, you can later capture them via the admin area. There will be a Capture button on the order.

Every payment method in nopCommerce should implement the IPaymentMethod interface.

public interface IPaymentMethod
{
    /// Provides an interface for creating payment gateways
    void ProcessPayment(PaymentInfo paymentInfo, Customer customer, 
         Guid OrderGuid, ProcessPaymentResult processPaymentResult);
    
    /// Post process payment (payment gateways that require redirecting)
    string PostProcessPayment(Order order);
}

Here’s an implementation of the Authorize.Net payment processor:

public class AuthorizeNetPaymentProcessor : IPaymentMethod
{
    private bool useSandBox = true;
    private string loginID;
    private string transactionKey;
    
    public AuthorizeNetPaymentProcessor()
    {

    }

    public static TransactMode GetCurrentTransactionMode()
    {
        TransactMode transactionModeEnum = TransactMode.Authorize;
        string transactionMode = SettingManager.GetSettingValue(
               "PaymentMethod.AuthorizeNET.TransactionMode");
        if (!String.IsNullOrEmpty(transactionMode))
            transactionModeEnum = (TransactMode)
                Enum.Parse(typeof(TransactMode), transactionMode);
        return transactionModeEnum;
    }

    private void InitSettings()
    {
        useSandBox = SettingManager.GetSettingValueBoolean(
                        "PaymentMethod.AuthorizeNET.UseSandbox");
        transactionKey = SettingManager.GetSettingValue(
                           "PaymentMethod.AuthorizeNET.TransactionKey");
        loginID = SettingManager.GetSettingValue(
                     "PaymentMethod.AuthorizeNET.LoginID");

        if (string.IsNullOrEmpty(transactionKey))
            throw new Exception("Authorize.NET API transaction key is not set");

        if (string.IsNullOrEmpty(loginID))
            throw new Exception("Authorize.NET API login ID is not set");
    }

    private string GetAuthorizeNETUrl()
    {
        return useSandBox ? "https://test.authorize.net/gateway/transact.dll" :
            "https://secure.authorize.net/gateway/transact.dll";
    }

    public void ProcessPayment(PaymentInfo paymentInfo, Customer customer, 
                Guid OrderGuid, ProcessPaymentResult processPaymentResult)
    {
        InitSettings();
        TransactMode transactionMode = GetCurrentTransactionMode();

        WebClient webClient = new WebClient(); 
        NameValueCollection form = new NameValueCollection();
        form.Add("x_login", loginID);
        form.Add("x_tran_key", transactionKey);
        if (useSandBox)
            form.Add("x_test_request", "TRUE");
        else
            form.Add("x_test_request", "FALSE");

        form.Add("x_delim_data", "TRUE");
        form.Add("x_delim_char", "|");
        form.Add("x_encap_char", "");
        form.Add("x_version", APIVersion);
        form.Add("x_relay_response", "FALSE");
        form.Add("x_method", "CC");
        //TODO populate currency code
        form.Add("x_currency_code", "USD");
        if (transactionMode == TransactMode.Authorize)
            form.Add("x_type", "AUTH_ONLY");
        else if (transactionMode == TransactMode.AuthorizeAndCapture)
            form.Add("x_type", "AUTH_CAPTURE");
        else
            throw new Exception("Not supported transaction mode");

        form.Add("x_amount", paymentInfo.OrderTotal.ToString("####.00", 
                 new CultureInfo("en-US", false).NumberFormat));
        form.Add("x_card_num", paymentInfo.CreditCardNumber);
        form.Add("x_exp_date", paymentInfo.CreditCardExpireMonth.ToString("D2") + 
                 paymentInfo.CreditCardExpireYear.ToString());
        form.Add("x_card_code", paymentInfo.CreditCardCVV2);
        form.Add("x_first_name", paymentInfo.BillingAddress.FirstName);
        form.Add("x_last_name", paymentInfo.BillingAddress.LastName);
        if (string.IsNullOrEmpty(paymentInfo.BillingAddress.Company))
            form.Add("x_company", paymentInfo.BillingAddress.Company);
        form.Add("x_address", paymentInfo.BillingAddress.Address1);
        form.Add("x_city", paymentInfo.BillingAddress.City);
        if (paymentInfo.BillingAddress.StateProvince != null)
        form.Add("x_state", paymentInfo.BillingAddress.StateProvince.Name);
        form.Add("x_zip", paymentInfo.BillingAddress.ZipPostalCode);
        if (paymentInfo.BillingAddress.Country != null)
        form.Add("x_country", paymentInfo.BillingAddress.Country.TwoLetterISOCode);
        form.Add("x_invoice_num", OrderGuid.ToString());
        form.Add("x_customer_ip", HttpContext.Current.Request.UserHostAddress);

        string reply = null;
        Byte[] responseData = webClient.UploadValues(GetAuthorizeNETUrl(), form);
        reply = Encoding.ASCII.GetString(responseData);

        DateTime nowDT = DateTime.Now;

        if (null != reply)
        {
            string[] responseFields = reply.Split('|');
            switch (responseFields[0])
            {
                case "1":                        
                    processPaymentResult.AuthorizationCode = 
                      string.Format("{0},{1}", 
                      responseFields[6], responseFields[4]);
                    processPaymentResult.AuthorizationResult = 
                      string.Format("Approved ({0}: {1})", 
                      responseFields[2], responseFields[3]);
                    processPaymentResult.AVSResult = responseFields[5];
                    processPaymentResult.AuthorizationTransID = responseFields[38];
                    if (transactionMode == TransactMode.Authorize)
                    {
                        processPaymentResult.TransactionState = TransactState.Authorized;
                        processPaymentResult.AuthorizationDate =nowDT;
                    }
                    else
                    {
                        processPaymentResult.TransactionState = TransactState.Captured;
                        processPaymentResult.AuthorizationDate = nowDT;
                    }
                    break;
                case "2":
                    processPaymentResult.Error = string.Format("Declined ({0}: {1})", 
                                                 responseFields[2], responseFields[3]);
                    processPaymentResult.FullError = string.Format("Declined ({0}: {1})", 
                                                     responseFields[2], responseFields[3]);
                    break;
                case "3":
                    processPaymentResult.Error = reply;
                    processPaymentResult.FullError = reply;
                    break;
            }
            processPaymentResult.AuthorizationDate = DateTime.Now;
        }
        else
        {
            processPaymentResult.Error = "Authorize.NET unknown error";
            processPaymentResult.FullError = "Authorize.NET unknown error";
        }
    }
    public string PostProcessPayment(Order order)
    {
        return string.Empty;
    }

    public string APIVersion
    {
        get
        {
            return "3.1";
        }
    }
}

Managing Orders

During the process of creating an ecommerce website, you will run into the fact that you need some way to take and process orders. You will also have to set up a way to make this easy for customers. After a customer completes a transaction, a new order will appear in the orders page.

Shown below is the screenshot for a new order:

ASP.NET shopping cart

You cam manage orders using the OrderManager class:

ASP.NET shopping cart

Conclusion

As you have seen, building your own shopping cart isn't very difficult. The great thing about building your own cart is that it is completely customizable - and you don't have to spend hundreds of dollars for an extension suite that you end up having to learn anyway.

References

  • nopCommerce community site
  • This is a forum of nopCommerce developers
  • This page contains the latest version of the implementation of the examples described in the article

License

This article, along with any associated source code and files, is licensed under The Mozilla Public License 1.1 (MPL 1.1)

Share

You may also be interested in...

About the Author

Andrey Mazoulnitsyn
Architect nop Solutions
Russian Federation Russian Federation
No Biography provided

Comments and Discussions

 
QuestionVery professional code! PinmemberUricano30-Jul-12 5:35 
Questionasp.net Pinmemberkano louis3-Jun-12 3:23 
Generalthanks Pinmemberkano louis3-Jun-12 3:16 
QuestionnopV2.30 Pinmemberthammu tham2-Dec-11 19:06 
GeneralMy vote of 4 Pinmembersachinjha0720-Aug-11 1:53 
GeneralMy vote of 5 Pinmemberaicha200830-Jun-10 0:52 
GeneralNice Pinmemberali_reza_zareian4-Mar-09 7:22 
GeneralVery useful PinmemberHemant.Kamalakar17-Feb-09 2:53 
GeneralWebsite Folder Structure PinmemberGary In SD15-Jan-09 19:33 
Generalgreat web shop solution PinmemberRadu Chirila12-Jan-09 7:38 
GeneralRe: great web shop solution PinmemberAndrey Mazoulnitsyn12-Jan-09 8:42 
GeneralUseful... PinmemberRajesh Pillai12-Dec-08 16:38 
GeneralNice Introduction! PinmemberSamnang Chhun7-Dec-08 22:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 12 Dec 2008
Article Copyright 2008 by Andrey Mazoulnitsyn
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid