How to work with Live Shipping Rates

We return Live Shipping Rates on Shopify and WooCommerce for now (April 2019)

Methods:

/basket.live_shipping_service.list.json

/basket.live_shipping_service.create.json

/basket.live_shipping_service.delete.json

 

How it works?

Create new shipping service on the store

/basket.live_shipping_service.create.json

      On WooCommerce stores there can be an unlimited amount of services, but on Shopify you can create only one sevice (Shopify limit).

      To create the service you’ll have to specify the name of the service and callback url, that will return us shipping rates. We validate the callback when it is being created by sending test request to it. Test requests contain header X-Shipping-Service: 1.  

      To check it the request was really sent by API2Cart, compare header signature X-Shipping-Service-Signature

      The signature is built based on all headers that start with X-Shipping-Service, except for X-Shipping-Service-Signature.

 

The algorithm:

      1. Create array of headers, let the header name be its key, its value - the header value string.

      2. Sort the headers by the name.  

      3. Create the string for signature, that is made of headers. For that, encode the array of headers in JSON and concatenate from request body.

      4. Calculate sha256 signature in binary format

      5. Convert the binary signature to base64

 

Php example:

      $headersToSign = [

      'X-Shipping-Service-Test-Request' => '1',

      'X-Shipping-Service-Request-Timestamp' => '1553177278'

      ];

      ksort($headersToSign);

      $headersJson = json_encode($headers);

      $sign = base64_encode(hash_hmac('sha256', $headersJson . $body, $signingKey, true));

 

Test request example (from API2Cart)

 

REQUEST: POST /rates.php

Host: www.stores.local

Connection: close

User-Agent: Zend_Http_Client

accept-encoding: identity

content-type: application/json

accept: application/json

X-Shipping-Service-Signature: Q4jaFbiUKJW2gjTHqvlRrWMUOx65CaepT0BaKFiw6a0=

X-Shipping-Service-Test-Request: 1

X-Shipping-Service-Request-Timestamp: 1553609265

Content-Length: 1880

{

   "packages": [

       {

           "id": "1",

           "currency_code": "USD",

           "origin": {

               "first_name": null,

               "last_name": null,

               "postcode": "35004",

               "address1": "Test, 1\/1",

               "address2": "",

               "phone": "",

               "city": "Moody",

               "country": {

                   "code2": "US",

                   "code3": "USA",

                   "name": "United States of America"

               },

               "state": {

                   "code": "AL",

                   "name": "Alabama"

               },

               "company": null

           },

           "destination": {

               "first_name": null,

               "last_name": null,

               "postcode": "35005",

               "address1": "Test, 1\/1",

               "address2": "",

               "phone": "",

               "city": "Adamsville",

               "country": {

                   "code2": "US",

                   "code3": "USA",

                   "name": "United States of America"

               },

               "state": {

                   "code": "AL",

                   "name": "Alabama"

               },

               "company": null

           },

           "items": [

               {

                   "product_id": "1",

                   "model": "t1",

                   "name": "Test Product 1",

                   "price": 100.99,

                   "quantity": 5.5,

                   "discount_amount": null,

                   "total_price": 555.45,

                   "tax_percent": 21,

                   "tax_value": 116.64,

                   "variant_id": null,

                   "weight_unit": "kg",

                   "weight": 5.5

               }

           ]

       },

       {

           "id": "2",

           "currency_code": "USD",

           "origin": {

               "first_name": null,

               "last_name": null,

               "postcode": "35004",

               "address1": "Test, 1\/1",

               "address2": "",

               "phone": "",

               "city": "Moody",

               "country": {

                   "code2": "US",

                   "code3": "USA",

                   "name": "United States of America"

               },

               "state": {

                   "code": "AL",

                   "name": "Alabama"

               },

               "company": null

           },

           "destination": {

               "first_name": null,

               "last_name": null,

               "postcode": "35005",

               "address1": "Test address",

               "address2": "",

               "phone": "",

               "city": "Adamsville",

               "country": {

                   "code2": "US",

                   "code3": "USA",

                   "name": "United States of America"

               },

               "state": {

                   "code": "AL",

                   "name": "Alabama"

               },

               "company": null

           },

           "items": [

               {

                   "product_id": "1",

                   "model": "t1",

                   "name": "Test Product 1",

                   "price": 100.99,

                   "quantity": 5.5,

                   "discount_amount": null,

                   "total_price": 555.45,

                   "tax_percent": 21,

                   "tax_value": 116.64,

                   "variant_id": null,

                   "weight_unit": "kg",

                   "weight": 5.5

               },

               {

                   "product_id": "1",

                   "model": "t2",

                   "name": "Test product 2",

                   "price": 100.99,

                   "quantity": 5.5,

                   "discount_amount": null,

                   "total_price": 555.45,

                   "tax_percent": 21,

                   "tax_value": 116.64,

                   "variant_id": null,

                   "weight_unit": "kg",

                   "weight": 5.5,

                   "additional_fields": {

                       "dimensions_unit": "cm",

                       "height": 3.5,

                       "width": 2.15,

                       "length": 10.36

                   }

               }

           ]

       }

   ]

}

 

Once you get test request, your endpoint have to respond in JSON format

The response will differ for different shopping carts

For instance, from Shopify response structure will look like the following:

{  

  "packages_rates":[  

     {  

        "package_id":"1",

        "rates":[  

           {  

              "name":"Some name",

              "description":"Tax included an duties",

              "code":"some_name",

              "currency":"USD",

              "total_cost":10.56

           },

           {  

              "name":"Some name 2",

              "description":"Tax included an duties",

              "code":"some_name2",

              "currency":"USD",

              "total_cost":20.59,

              "min_delivery_timestamp":1558177278,

              "max_delivery_timestamp":1559177278

           }

        ]

     }

  ]

}

 

For WooCommerce the response structure will look like that:

{  

  "packages_rates":[  

     {  

        "package_id":"1",

        "rates":[  

           {

     "name": "Some name",

     "code": "some_name",

     "total_cost": 10.56,

"taxable" : true,

"tax_value": 2.12/*pass tax value*/

   },

   {

     "name": "Some name",

     "code": "some_name",

     "total_cost": 10.56,

"taxable" : false/*disable tax*/

   },

   {

     "name": "Some name",

     "code": "some_name",

     "total_cost": 10.56,

     "taxable" : true /*tax calculated using store tax rates*/

   },

...

        ]

     }

  ]

}

When we send a certain amount of packages, we expect to get the same amount of packages_rates objects. The property rates can be empty, which means there can be no rates for a certain package.

If the structure is not valid, we return an error. For example, you will get error when you pass a string in total_cost.

{

   "return_code": 109,

   "return_message": "Bad Response. Field \"rates->0->total_cost\" has wrong type. It must be decimal",

   "result": {}

}

If the callback returns 404 error code, we will not try again

If the callback returns 200 error code, we will check response validity and then return it to the store in the format that fits the store.

If the store returns 200 error code, butwith empty or incorrect response, we will wait 2 seconds and will try again.

If the callback don’t respond in 15 seconds, we will throw an error and will not try again.

{

   "return_code": 109,

   "return_message": "Callback http://www.stores.local/rates.php did not respond within 15 sec",

   "result": {}

}

If everything is ok, the new service will be created.

 

2) When the store requests rates, we unify the request and send it to callback specified when the service was created. Also, we send X-Shipping-Service-Id header.

 

If the response is not valid or the callback didn’t answer, we will log the error and increment error count of the shipping service by 1. If the next request will be successful, the count will be reduced to zero. Soon we will add the functionality that will turn off the service when the error limit is exceeded.

Posted in: General Service Questions

Need help or advice?

Schedule a call