Sana Assistant (online)
Table of Contents

Implementing New Shipping Service Provider

From this article you will learn how to create custom shipping extension.

Please use the following reference articles to find more details of extensions infrastructure:

Start with a new project

Create a new add-on project named "Sana.Extensions.CustomShippingService" as described in the add-on development tutorial.

The "CustomShippingService" constant is the name which will be used in this tutorial, but in real life add-ons, it should be replaced by the name of the shipping service which the add-on integrates with.

Implement the extension class

Create a new class CustomShippingServiceExtension inherited from ShippingExtension. More information about ShippingExtension you can find in ShippingExtension reference article.

public class CustomShippingServiceExtension : ShippingExtension
{
    public override bool WeightRequired => false;

    public override bool BasketTotalsRequired => false;

    public override IList<ShippingRate> CalculateShippingRates(ShippingContext shippingContext)
    {
        throw new NotImplementedException();
    }
}

Specify shipping module's unique identifier

Add ShippingModuleIdAttribute to the previously created class and specify the unique identifier of the shipping extension.

[ShippingModuleId("CustomShippingService")]
public class CustomShippingServiceExtension : ShippingExtension
{
    // Properties and methods
}

Implement configuration class

Create a new class CustomShippingServiceConfiguration inherited from the ExtensionConfiguration and decorate it with ConfigurationKey attribute. This class will be used by Sana as a view-model to configure shipping extension in Sana Admin. More details about extension configuration class you can find in Configuration article.

[ConfigurationKey("CustomShippingService")]
public class CustomShippingServiceConfiguration : ExtensionConfiguration
{
}

Let's add properties which are needed to configure the shipping extension to the CustomShippingServiceConfiguration. You can decorate the properties with data annotation attributes since this class is a model for a view.

[ConfigurationKey("CustomShippingService")]
public class CustomShippingServiceConfiguration : ExtensionConfiguration
{
    [Required]
    [Display(Name = "Total parcel weight limit (pounds)")]
    public decimal TotalWeightLimit { get; set; }
}

For the sake of simplicity in our example we will use only one configuration value which is the total allowed weight for the shipment.

Implement IConfigurable<TConfiguration> interface in CustomShippingServiceExtension. Put CustomShippingServiceConfiguration class as a generic type parameter for IConfigurable<TConfiguration>, it will indicate that our shipping extension should be configured with this class.

[ShippingModuleId("CustomShippingService")]
public class CustomShippingServiceExtension : ShippingExtension, IConfigurable<CustomShippingServiceConfiguration>
{
    [AllowNull]
    public CustomShippingServiceConfiguration Configuration { get; set; }

    // Other properties and methods
}

Sana will initialize Configuration property with configuration settings entered in Sana Admin on the extension configuration page. This page will be accessible when you go to all installed extensions page in Sana Admin and click "Configure" button of our "Custom shipping service example" extension once it gets built and installed in Sana Admin:

Extension Configure Button

Configuration page: Extension Configuration Page

Implement shipping method settings class

This step is not mandatory. Every shipping method that you configure in Sana Admin that uses our shipping extension will already have basic settings. If those settings are enough for your scenario, you can skip this step and proceed with the following one.

Create a new class named "CustomShippingMethodSettings" as described in the Settings Per Shipping Method article.

Implement WeightRequired property

Implement WeightRequired property in CustomShippingServiceExtension class. If your shipping extension calculates shipping rates based on the weight of items in the shopping cart, then return true from this property. It will tell Sana to validate whether all items in the shopping cart have weight before proceeding with your shipping extension, and in case weight is not specified for some of the items Sana will not show shipping methods from your extension to the user.

If your extension does not use weight of items in the shopping cart to calculate shipping rate, return false from this property. This will tell Sana to skip validating whether weights are specified for shopping cart items.

[ShippingModuleId("CustomShippingService")]
public class CustomShippingServiceExtension : ShippingExtension, IConfigurable<CustomShippingServiceConfiguration>
{
    [AllowNull]
    public CustomShippingServiceConfiguration Configuration { get; set; }

    public override bool WeightRequired => true;

    public override bool BasketTotalsRequired => false;

    public override IList<ShippingRate> CalculateShippingRates(ShippingContext shippingContext)
    {
        throw new NotImplementedException();
    }
}

Implement CalculateShippingRates method.

Implement CalculateShippingRates method in CustomShippingServiceExtension class. This is the method that does the actual shipping rates calculation.

The method returns the list of ShippingRate entities each representing calculated shipping rate. Each of these rates will be shown to the user during shopping cart checkout in the Sana web store.

The method receives ShippingContext instance as parameter. This context object contains all relevant information about the shopping cart and configuration which you can use in your calculation.

public override IList<ShippingRate> CalculateShippingRates(ShippingContext shippingContext)
{
    if (shippingContext.TotalGrossWeight.Pounds > Configuration.TotalWeightLimit)
        return new List<ShippingRate>(0);

    // Here you can make a call to the shipping provider's server to get the shipping rates
    // or calculate the shipping rates in your code. We will simply hard-code the values
    // here in the CreateShippingRate private method for simplicity.
    return shippingContext.Methods.Select(CreateShippingRate).ToList();
}

ShippingRate CreateShippingRate(ShippingMethod method)
{
    var rate = new ShippingRate
    {
        ShippingMethodId = method.Id
    };
    switch (((CustomShippingMethodSettings)method.Settings).ServiceType)
    {
        case CustomShippingServiceType.Ground:
            rate.Cost = 10m;
            break;
        case CustomShippingServiceType.Air:
            rate.Cost = 12m;
            break;
        case CustomShippingServiceType.Freight:
            rate.Cost = 15m;
            break;
    }
    return rate;
}

Here in this small example we do not provide the actual implementation of calculations, it is up to you, the extension developer, to decide on exact implementation.

Note that you can access custom settings (If you followed the steps in the Settings Per Shipping Method article) of each shipping method by casting its Settings property to your shipping method settings type which in our case is CustomShippingMethodSettings:

Next steps

After the extension is implemented, follow the regular add-on development guide to test the implemented extension.

Check the "Shipping methods" page in Sana Admin for the newly provided shipping methods: Admin Shipping Methods Configured

And also go through the checkout process on the webstore frontend to see the extension in action: Frontend Shipping Methods In Checkout

When the add-on works as expected, then follow below:

See also