This article target developers looking to connect the new method GetShipAdvise to another API.
While we will use the Shopify e-commerce platform in this example, the approach would likely be similar on other platforms.
Note: This is only to show a basic way to convert requests/responses between the two API.
You need to apply your own business logic and error handling.
You should not raise questions to nShift Support regarding Shopify.
But use the Shopify community forum here instead.
Content in this article:
- Register a Carrier Service on a development store
- Exchanging data between Shopify and ShipAdvisor
- Shipping Rules
- Carrier Performance
Requirements
-
The Shipment Server API
On the API we will use the method GetShipAdvise -
The Shopify Carrier Service API
The Carrier Service API allows Shopify stores to incorporate custom shipping methods. It does so by making an external API request to the app and retrieves the carrier/shipping descriptions and prices.When a user initiates a checkout, a request will be sent to the URL that the Carrier Service points towards. The request will contain information about the items, receiver & sender.
To learn more about the Shopify Carrier Service API visit the official docs here:
https://help.shopify.com/en/api/reference/shipping-and-fulfillment/carrierservice -
Shipping Rules
An XML file containing the products that will be displayed on the store - Carrier Performance
An XML file containing the calculation of the ETA on the different products - A Shopify development store
https://www.shopify.com/partners
Register a Carrier Service on a Shopify store
Create a new private app.
The app needs to have Read and Write permission on the Shipping rate, countries and provinces section.
Using the method described here to register a new service.
Example script uses Node.js and node-fetch to POST the data to the Carrier API.
fetch('https://YOUR_STORE.myshopify.com/admin/api/2019-07/carrier_services.json',{
headers: {
'X-Shopify-Access-Token':'YOUR_PASSWORD_FROM_THE_PRIVATE_APP',
'Content-Type':'application/json'
},
body: JSON.stringify({
"carrier_service": {
"name": "SA Shipping Rate Provider",
"callback_url": "URL_TO_YOUR_WEB_SERVICE_THAT_THE_CARRIER_SERVICE_CONNECTS_TO",
"service_discovery": true
}
}),
method: 'POST'
})
After this, a new rate provider should be visible in the store.
Exchanging data between Shopify and Ship Advisor
The request from the Carrier Service needs to be translated into a format that the GetShipAdvise method can understand. The data is expected to be formatted as a Shipment Object
To see an example on the request object from the Carrier API click here
The example function below will use this as input and return an object with a Receiver, Sender & Lines.
The .filter(x => x.requires_shipping) is used to filter out Products from the Shopify store than is not marked to require a shipment. You can remove this part if you don't need it.
There are a few ways you can construct the Lines array so that it matches the Shopping Cart content.
In this example, it's hard-coded that each item represents one package on the shipment.
const translateShopifyRequest = ({origin, destination, items}, output = {}) => {
let tmpLineObject = items.filter(x => x.requires_shipping).map((item) => {
return {
"References": [{
"Kind": 23,
"Value": item.vendor || ""
}],
"PkgWeight": item.grams,
"Number": 1
}
})
output = {
"Addresses": [{
"Kind": 1,
"Name1": destination.name,
"Street1": destination.address1,
"PostCode": destination.postal_code,
"City": destination.city,
"CountryCode": destination.country
}, {
"Kind": 2,
"Name1": origin.name,
"Street1": origin.address1,
"PostCode": origin.postal_code,
"City": origin.city,
"CountryCode": origin.country
}],
"Lines": tmpLineObject
}
return output
}
The Shopify Carrier Service API expects that the response to be a rates object.
The example function below will take the response from GetShipAdvise and return the expected object.
Note: The CustomFields can hold any custom value that you also might need to be returned with the products.
const translateShipAdvisorResponse = ({Products}, output = {"rates": []}) => {
output.rates = Products.map((product) => {
let startDate = new Date(Date.parse(product.DeliveryDate))
let serviceName = (product.CustomFields && product.CustomFields.type) ? product.CustomFields.type : "",
let serviceCode = (product.CustomFields && product.CustomFields.servicecode) ? product.CustomFields.servicecode : "",
return {
"service_name": serviceName,
"service_code": serviceCode,
"total_price": product.Price,
"description": product.ProdName,
"currency": product.PriceCurrency,
"min_delivery_date": startDate.toISOString(),
"max_delivery_date": startDate.toISOString()
}
}) || []
return output
}
Shipping Rules
The CSR will handle the filtering of products.
The attribute carriervalidation on the CSR node tells the engine if it should use the default carrier validation.
In this example the default validation is disabled and custom validation is used directly on the products, both can be used at the same time if needed.
<?xml version="1.0" encoding="utf-8"?>
<CSR carriervalidation="0" defaultservicelevel="Service_Level_Configured_For_SA">
<Prices>
<Price name="fixed59" currency="DKK">
<Exceptions>
<Exception price="5900">
<ValidationRules>
</ValidationRules>
</Exception>
</Exceptions>
<Fees>
<Fee price="1900">
<ValidationRules>
</ValidationRules>
</Fee>
</Fees>
</Price>
<Price name="fixed39" currency="DKK">
<Exceptions>
<Exception price="3900">
<ValidationRules>
</ValidationRules>
</Exception>
</Exceptions>
</Price>
</Prices>
<ServiceLevels>
<ServiceLevel name="Service_Level_Configured_For_SA">
<Products>
<Product adviseprice="fixed39" name="GLS DK - Pakkeshop med Drop Points" conceptid="1551">
<ProductGoodsType goodstypeid="0"/>
<CustomFields type="Pickup Point" servicecode="P1"/>
<ValidationRules>
<ValidationRule name="country" allowcountries="DK" field="fld_AdrCountry"/>
</ValidationRules>
</Product>
<Product adviseprice="fixed59" name="GLS DK - Normal erhvervspakke" conceptid="113">
<ProductGoodsType goodstypeid="0"/>
<CustomFields type="Home Delivery" servicecode="P2"/>
<ValidationRules>
<ValidationRule name="country" allowcountries="DK" field="fld_AdrCountry"/>
</ValidationRules>
<Services>
<Service serviceid="11020" name="Private Delivery Service"/>
</Services>
</Product>
<Product adviseprice="fixed59" name="GLS DK - Export" conceptid="189">
<ProductGoodsType goodstypeid="0"/>
<CustomFields type="Home Delivery" servicecode="P3"/>
<ValidationRules>
<ValidationRule name="country" allowcountries="SE" field="fld_AdrCountry"/>
</ValidationRules>
</Product>
</Products>
</ServiceLevel>
</ServiceLevels>
</CSR>
Carrier Performance
This is a very basic example showing the ETA calculations for the Products used in the CSR configuration.
<?xml version="1.0" encoding="utf-8"?>
<ConsignorETA>
<Areas>
<Area name="Nordic" type="countries">
<AreaItem countries="DK,SE"/>
</Area>
</Areas>
<PickupSchedules>
<PickupSchedule name="default" pickingtime="00:10" weekstartsonsunday="0">
<Pickup time="11:00" days="1,2,3,4,5"/>
</PickupSchedule>
</PickupSchedules>
<TransitTimes>
<TransitTime name="profile1" pickupschedule="default" trigger="submit" initiatedays="1,2,3,4,5" deliverydays="1,2,3,4,5" transportdays="1,2,3,4,5" weekstartsonsunday="0">
<Products>
<Product id="1551" name="GLS DK - Pakkeshop med Drop Points"/>
</Products>
<TimeTable>
<From area="Nordic">
<To area="Nordic" days="1" earliest="07:00" latest="18:00"/>
</From>
</TimeTable>
</TransitTime>
<TransitTime name="profile2" pickupschedule="default" trigger="submit" initiatedays="1,2,3,4,5" deliverydays="1,2,3,4,5" transportdays="1,2,3,4,5" weekstartsonsunday="0">
<Products>
<Product id="113" name="GLS DK - Normal erhvervspakke"/>
</Products>
<TimeTable>
<From area="Nordic">
<To area="Nordic" days="2" earliest="07:00" latest="18:00"/>
</From>
</TimeTable>
</TransitTime>
<TransitTime name="profile3" pickupschedule="default" trigger="submit" initiatedays="1,2,3,4,5" deliverydays="1,2,3,4,5" transportdays="1,2,3,4,5" weekstartsonsunday="0">
<Products>
<Product id="189" name="GLS DK - Export"/>
</Products>
<TimeTable>
<From area="Nordic">
<To area="Nordic" days="3" earliest="07:00" latest="18:00"/>
</From>
</TimeTable>
</TransitTime>
</TransitTimes>
</ConsignorETA>
The expected result when an address from Denmark is used.
The expected result when an address from Sweden is used.