# Thursday, 10 February 2011

One of my favorite features of uCommerce is the extensible framework lurking underneath the surface. Using the framework you have the opportunity to add, modify, or even completely the out of the box functionality of uCommerce. Shipping methods are no exception to this rule. Specifically you can build custom shipping method services to enable custom calculation of shipping costs during the checkout flow as discussed in Shipping Methods Explained.

This article covers adding a new shipping method service to uCommerce and having it show up the admin UI like so.

SNAGHTML9d935c

The Framework

Before you get into the nitty gritty of building your first custom shipping method service you’ll need a little background about the surrounding framework to understand what’s going on. Your shipping method service first needs to be configured in uCommerce via web.config (more on that later), once written the uCommerce pipelines will make sure and trigger your service when appropriate. By default shipping methods services are triggered in the Basket Pipeline by the task called CalculateShippingCostForShipments. If you’re interested in more information about pipeline please see the uCommerce Pipelines Explained article.

Notice that the tasks ends on Shipments plural. uCommerce supports multiple shipments per order and will call the proper shipping method service for each shipment based on the shipping method selected for it.

image

Pretty simple stuff. Now for the fun part: Writing your own shipping method service.

The IShippingMethodService Interface

image

This interface is what uCommerce uses to be able to calculate shipment costs. As you can see from the code it’s pretty simple. Basically you’ll receive the shipment as created using either the XSLT or .NET API and you’ll have the opportunity to loop through the relevant order lines, which are linked from the shipment. Lets see what’s involved to create a simple volume based IShippingMethodService.

Volume Based Shipping Method Service

Volume and weight based shipping pricing are probably the most common shipping scenarios for an online store so lets try and build one of them as an example. First you’ll need to create a new Visual Studio project and add a reference to the UCommerce.dll assembly. You will also need a reference to the Subsonic.Core.dll assembly. With that in place you’re ready to start implementing the service.

The Methods and Properties

image

As you can see you just need to implement two methods and a single property to get it going. The names of the methods should make it fairly obvious what they’re for. ValidateForShipping might require some explanation. As discussed in Shipping Method Explained each shipping method can be configured to available only to certain stores and for shipment only to certain countries. The ValidateForShipment enforces these rules. If you just need an unrestricted shipment you can go ahead and always return true, but be aware that users might be confused if they set up a shipping method in one way but it doesn’t actually check for the conditions thus behaving differently from what they expect.

CalculateShippingPrice Method

First up lets calculate the shipping price for the shipment:

public Money CalculateShippingPrice(Shipment shipment)
{
    // First sum up the total weight for the shipment.
    // We're assumning that a custom order line property 
    // was set on the order line prior when the product was added to the order line.
    decimal totalWeight = 0;
    foreach (OrderLine orderline in shipment.OrderLines)
        totalWeight += orderline.Quantity * Convert.ToDecimal(orderline["Weight"]);
 
    decimal shippingPrice = 0;
    if (totalWeight > 10)
        shippingPrice = 100;
    else if (totalWeight > 20)
        shippingPrice = 200;
    else
        shippingPrice = 300;
 
    // To instantiate a new Money object we need the currency,
    // which is set on the purchase order. To get the currency
    // we move through Shipment -> OrderLines -> PurchasrOrder -> Currency
    return new Money(shippingPrice, shipment.PurchaseOrder.BillingCurrency);
}

Of course the business rule in this particular case is very simplistic, but you get the idea.

ValidateForShipping Method

Next up is the ValidateForShippingMethod, which is actually called prior to CalculateShippingPrice to validate that the shipping method is validate for the current purchase order. The SinglePriceShippingMethod which comes out of the box has rules to ensure that the shipping method has a shipping address set and that the shipment is set to be delivered to one of the allowed countries for the shipping method.

public bool ValidateForShipping(Shipment shipment)
{
    if (shipment.ShipmentAddressId <= 0)
        throw new InvalidOperationException("Cannot validate shipment for country. Remember to set the shipping address for shipment.");
 
    var shippingMethod = shipment.ShippingMethod;
    if (shippingMethod == null)
        throw new InvalidOperationException(
            "Cannot validate destination country for shipment. It does not contain a shipping method. Remember to add a shipping method to your shipment before validating.");
 
    return ValidateShippingDestination(shipment.OrderAddress,
                                shippingMethod);
}
 
/// <summary>
/// Validates the order lines according to their desired destination and configured contries for shipping method
/// </summary>
/// <returns></returns>
protected virtual bool ValidateShippingDestination(OrderAddress shippingAddress, ShippingMethod shippingMethod)
{
    var eligibleCountries = shippingMethod.GetEligibleCountries();
 
    // No eligible countries exist - so the shipment isn't valid
    if (eligibleCountries == null) return false;
 
    return eligibleCountries.SingleOrDefault(x => x.Country == shippingAddress.Country) != null;
}

Name Property

I probably should have started out with the easier of the member to implement, but Name ended up with the shortest straw and got to go last. Name will be set externally based on the name used when the shipping method service is configured in web.config, which incidentally is the next topic of this article.

public virtual string Name
{
    get;
    set;
}

VolumeShippingMethodService

With the name property implemented our VolumeShippingMethodService looks like this:

public class VolumeShippingMethodService : IShippingMethodService
{
    public Money CalculateShippingPrice(Shipment shipment)
    {
        // First sum up the total weight for the shipment.
        // We're assumning that a custom order line property 
        // was set on the order line prior when the product was added to the order line.
        decimal totalWeight = 0;
        foreach (OrderLine orderline in shipment.OrderLines)
            totalWeight += orderline.Quantity * Convert.ToDecimal(orderline["Weight"]);
 
        decimal shippingPrice = 0;
        if (totalWeight > 10)
            shippingPrice = 100;
        else if (totalWeight > 20)
            shippingPrice = 200;
        else
            shippingPrice = 300;
 
        // To instantiate a new Money object we need the currency,
        // which is set on the purchase order. To get the currency
        // we move through Shipment -> OrderLines -> PurchasrOrder -> Currency
        return new Money(shippingPrice, shipment.PurchaseOrder.BillingCurrency);
    }
 
    public bool ValidateForShipping(Shipment shipment)
    {
        if (shipment.ShipmentAddressId <= 0)
            throw new InvalidOperationException("Cannot validate shipment for country. Remember to set the shipping address for shipment.");
 
        var shippingMethod = shipment.ShippingMethod;
        if (shippingMethod == null)
            throw new InvalidOperationException(
                "Cannot validate destination country for shipment. It does not contain a shipping method. Remember to add a shipping method to your shipment before validating.");
 
        return ValidateShippingDestination(shipment.OrderAddress,
                                    shippingMethod);
    }
 
    /// <summary>
    /// Validates the order lines according to their desired destination and configured contries for shipping method
    /// </summary>
    /// <returns></returns>
    protected virtual bool ValidateShippingDestination(OrderAddress shippingAddress, ShippingMethod shippingMethod)
    {
        var eligibleCountries = shippingMethod.GetEligibleCountries();
 
        // No eligible countries exist - so the shipment isn't valid
        if (eligibleCountries == null) return false;
 
        return eligibleCountries.SingleOrDefault(x => x.Country == shippingAddress.Country) != null;
    }
 
    public string Name
    {
        get; set;
    }
}

Registering Your ShippingMethodService with uCommerce

Your custom shipping method service needs to configured with uCommerce so users are able to set up new shipping methods which use your code. This happens in web.config. When uCommerce is rolled out a number of configuration sections are added to web.config one of which called ordersConfiguration.

<ordersConfiguration>
    <shippingMethodServices>
        <add name="SinglePriceService" type="UCommerce.Transactions.Shipping.SinglePriceShippingMethodService, UCommerce" />
        <add name="VolumeShippingService" type="MyUCommerceApp.VolumeShippingMethod, MyUCommerceApp" />
    </shippingMethodServices>
    <paymentMethodServices>
        <add name="DIBS" type="UCommerce.Transactions.Payments.DefaultPaymentMethodService, UCommerce" />
    </paymentMethodServices>
</ordersConfiguration>

With the web.config updated you’ll now be able to select your new shipping method service in the UI:

image

Summing It All Up

Creating your own shipping method service to handle custom calculations takes as little effort as implementing two methods and a property. Once it’s registered with uCommerce users take over and set up the shipping method to their liking. Becuase your shipping method service is rolled into a separate assembly it’s very straightforward to share between projects.

If you’d lie more information about Shipping Method please take a look at the article Shipping Methods Explained.

Comments are closed.