Stidner Shipping Widget

Last updated: Nov 20th, 2017

General notes

Stidner Shipping Widget is a handy UI-wrapper for Stidner Shipping API, that takes care of shipping stage in e-commerce checkout.

Widget is developed very raw in its core, so that it can support almost all types of checkout: it can communicate with different Payment Widgets (for example Payson, Klarna) and it also can be used "standalone", when no payment stage is needed.









Widget lifecycle

The general widget lifecycle consists of the following stages:

1. Create, store and update shipping order

Shipping Widget is an UI-wrapper of Order object, so you need to create on Order on get Widget instance.

When customer is navigated the the checkout page of your store (having Items added to cart), you use POST /order endpoint to create Order object based on given items - in response you'll get Order object.

After that you'll need to store uuid of Order somewhere in your system, bound to current checkout. The place of storage is up to you, but we recommend Cookies storage with the lifespan depending on the lifespan of your checkout. More complex e-commerce systems (like WooCommerce) store Order uuid inside own storage, less complex use Cookies.

If user updates cart, you need to update Items list in Order performing POST /order/{order_uuid} request, providing uuid of Order stored as explained above.

It's important to update existing Order, not create new one, on cart update - this way is more consistent and doesn't lead to creation 'blank' Orders on Stidner side. We monitor 'blank' Orders from each integration and if see that cart updates result in new Order creation - we contact the developer and advise yo optimise his integration.

2. Display widget on the checkout page

Order object has two attributes, that concern Widget

  • widget_url - url of Widget, including language parameter (specified on Order creation)
  • widget_embed - full code of iFrame and correspondent Javascript files, ready to be placed on the checkout page

You need to just place widget_embed contents on the checkout page. widget_embed contains not only iFrame with Widget, but it also connects Javascript files with helper functions for better Widget performance (for example, adjusting height of the Widget to its contents). Due to iFrame -> parent relations, some of the code should be located outside of the iFrame.

3. React to callbacks from Widget

When customer performs actions inside the Widget (changes Shipping Option, selects Service Point, provides Address) - some of them are notified using Javascript-callbacks to parent window.

Depending on specific integration, you may want to catch these callbacks and react to them.

4. Finalize order

Because Widget is rarely used standalone, it doesn't have any means to finalize Order from inside. We also don't offer any Javascript-handler for this purpose due to security reasons (so no hacker can use browser console to force-finalize Order).

What you need to do is perform PUT /order/{order_uuid} request, setting attribute order_status=completed. After that (if needed) finalize checkout in your e-commerce platform and either redirect customer to success page, or reload checkout page - depending on the architecture, you are using.

On that result page display Widget again. Once Order enters the completed stage, its widget will have the confirmation message displayed instead of Shipping Options list.

Javascript callbacks

On specific actions Widget iFrame sends to parent window Javascript callbacks and expects you to catch them and react, if needed.

1. Order is updated

order_updated

This event is sent every time, when Order is updated inside widget (for example, customer changed ShippingOption). It also has the changes array attached to the event, that shows, what essential changes happened to Order on current update.

Attribute Description
shipping_price Is included in 'changes' array if as a result of Order update shipping price was changed. Monitoring this attribute may be useful if Stidner Shipping Widget is operating in pair with Payment widget, and we need to update shipping price in correspondent Payment order.
recipient_address Is included in 'changes' array if as a result of Order update recipient address was changed.

Example of callback handler

window.addEventListener(
    "message",
    function (messageEvent) {
        var event_contents = null;
        if (__event_is_object(messageEvent.data)) {
            event_contents = messageEvent.data;
        } else if (__event_is_json(messageEvent.data)) {
            event_contents = $.parseJSON(messageEvent.data);
        }

        if (event_contents === null
            || event_contents.event_type === undefined
                || event_contents.event_type !== '__stidner_event') {
            return;
        }

        //do something depending on 'event_contents.action' here

    },
    true
);

__event_is_object = function(data){
    return typeof data === 'object';
};

__event_is_json = function(data) {
    var isJson = false;
    try {
        var json = $.parseJSON(data);
        isJson = typeof json === 'object' ;
    } catch (ex) {

    }

    return isJson;
};

Payment providers

Widget doesn't handle payments, it also is rarely used standalone (because shipping + payment go together in 100% of the cases). But it is designed that way to be able to be easily integrated with almost every popular payment solution.

The core of communication between Widget and Payment System is fast and correct data exchange between two systems in the following cases:

1. Updating shipping price in Payment System

The total sum to pay in Payment System is composed of two parts:

  • items prices - total cost of all items in cart
  • shipping price - cost of selected in Widget shipping option

When Order is created - the correspondent Payment Order should be created with the specified shipping price. When shipping option, service point or product is changed (and that leads to shipping price changing) - price should be updated in correspondent Payment Order, and Payment Widget should be refreshed.

2. Updating customer address in Payment System

When customer provides his address in Widget, it needs to be instantly updated in Payment Order. After update Payment Widget should be refreshed.

This is necessary to reduce customer actions during the checkout - if he has already provided some data, checkout should not demand it again.

3. Updating customer address in Widget

The same as the previous, only in the opposite direction. When customer provides his address in Payment Widget, it should be instantly updated in Widget (and Widget should be refreshed).

4. Finalizing Widget after successfull payment

keeping customer address in Widget up to date when it's changed in Payment System

1 and 2 are handled by Javascript callbacks (see above) of Shipping Widget. 3 should be handled on Payment solution side (via the similar callbacks), and all major Payment solutions send these callbacks.

Example of integration

Below we will describe the full example of integration for the following case:

  • E-commerce store (Integration)
  • Stidner Shipping Widget and Stidner Shipping API
  • Payson Payment Widget and Payson API (tech.payson.se)

Integration flow in scheme

Below scheme shows the visualization of integration process.

1. Shopping Items pages

Customer surfs the e-commerce store, adds desired items to cart. Adding to cart does not differ from standard checkout of any platform and needs no modifying.

Pay attention to storing items in e-commerce - they should be fully described in order to use Stidner Shipping functionality to a full scale. We recommend you to always specify the following attributes of item:

  • Name. Name of item is printed on waybill and on customs invoice (in case of shipping outside EU).
  • Dimensions: weight, length, width, height. To get the exact shipping price we recommend to specify all the dimensions. However in some e-commerce areas it may be complicated to specify length, width and height of all products. It's possible to get the quote only based on weight, but in that case you may be charged extra cost after carrier scans your shipment and calculates both gross weight and dimensional weight.
  • Unique products identifiers: article number, sku, ean. With at least one of the identifiers specified it's easier to navigate through order items in Stidner Dashboard, and it also gives more power in Warehouse UI
  • Warehouse location and if item is in stock. If you are planning to use Warehouse UI, specifying these attributes will ease its usage.

2. Create ShippingOrder Checkout page

To register ShippingOrder perform POST /order request to Stidner API:

  • items - array with all Items from the cart. Make sure to correctly map all Item fields, based on what's said in above section.
  • async - set it to 'true' for a faster response. Since we are performing specific actions before Widget display, the options will be processed in background and will be ready at the moment of Widget display

$request = new HttpRequest();
$request->setUrl('https://gateway.test.stidner.com/api/v1/order');
$request->setMethod(HTTP_METH_POST);

$request->setHeaders(array(
  'authorization' => 'Basic MjAwOnN0aWRuZXJfc2FuZGJveF8zMGQ2NWI5Ni0zMWI4LTRmOTQtYmU4NC02YjE2ZDI1YjFiZDM=',
  'content-type' => 'application/json'
));

$request->setBody('{
    "async": true,
	"source": "__stidner_widget",
	"external_reference": "434",
	"payment_system": "payson",
    "currency": "SEK",
    "items": [
        {
            "article_number": "sku_50",
            "name": "X-box 2009",
            "description": "A gaming pod",
            "quantity": 2,
            "unit_price": 2000,
            "weight": 300
        }
    ],
    "addresses": [
        {
            "type": "recipient",
            "customer_type": "person",
            "name": null,
            "country_code": "SE",
            "postal_code": "46157",
            "city": null,
            "address_line": null,
            "contact_name": null,
            "contact_phone": null,
            "contact_email": null
        }
    ],
    "test_mode": true,
    "notification_url": "https://wordpress-site.loc/notify/434"
}');

$response = $request->send();

and get ShippingOrder object from response data attribute.

$order = $response->data

After that take uuid of Order and save it into Cookies to bind Order to current Checkout

setcookie('stidner_order_uuid', $order->uuid)

3. Create PaysonOrder Checkout page

Perform POST /Checkouts request to Payson, in request provide shipping_price of Shipping Order


$request = new HttpRequest();
$request->setUrl('https://api.payson.se/2.0/Checkouts');
$request->setMethod(HTTP_METH_POST);

$request->setHeaders(array(
  'authorization' => 'Basic 1234:kTIACC8ITL5WYDpTjnjRRtsn9AuIBEhUKxaV4TOuZRs=',
  'content-type' => 'application/json'
));

$request->setBody('{
"order": {
    "currency": "SEK",
    "items": [
        {
            "name": "X-box 2009",
            "quantity": 2,
            "taxRate": 0.2,
            "unitPrice": 2000
        },
        {
            "name": "Stidner Shipping",
            "quantity": 1,
            "taxRate": 0.25,
            "unitPrice": 5600
        }
        ]
    },
    "merchant": {
        "checkoutUri": "http://www.adress.xyz/Checkout?id=123",
        "confirmationUri": "http://www.adress.xyz/Confirmation?id=123",
        "notificationUri": "http://www.addres.xyz/Notification?id=123",
        "termsUri": "http://www.addres.xyz/Terms"
    }
}');

$response = $request->send();

and get Payment Order object from response data attribute

$payson_order = $response->data

4. Connect Shipping Order to Payment Order Checkout page

Perform PUT /order/{order_uuid} request to Stidner and update ShippingOrder with payment_reference. It is important to connect the orders because if any non-standard behaviour happens during the checkout process, that will lead to page reload, you will need to get both ShippingOrder and PaymentOrder by their identifiers. Since ShippingOrder is master in this communication, you need to make sure, it has the connection to slave PaymentOrder.

$order->payment_reference = $payson_order->id;

/**
* Perform order update request to Stidner API here
**/

5. Widgets display Checkout page

Append the code of both widgets to the checkout page while rendering it.

<!-- Stidner Widget -->
<script
    src="https://widget.test.stidner.com/js/integration.js?v=94"
    type="text/javascript">
</script>
<iframe
    src="https://widget.test.stidner.com/order/429b6e40-affc-11e7-a644-39ac3e691588"
    id="__stidner_shipping_iframe"
    width="375"
    frameborder="0">
</iframe>

<!-- Payson Widget -->
<iframe
    id="checkoutIframe"
    name="checkoutIframe"
    src="http://embedded.payson.se/checkout?id=264d3bfd-f7a1-4122-8556-a56700f71eaa"
    style="width:100%; height: 100%"
    frameborder="0"
    scrolling="no">
</iframe>

6. React to Javascript callbacks from both Widgets Checkout page

When customer performs any notable action inside ShippingWidget or PaymentWidget, they send the javascript event to parent page of the iframe (in other words - to Integration). Integration should react correspondently to these events, as described below.

Listen to order_updated event from ShippingWidget, paying attention to the changes array in it. If any of the below values is present in that array, you should react as described:

  • shipping_price - you need to update the PaymentOrder with new shipping price.
    • perform ajax-request to backend
    • at backend perform GET /order/{order_uuid} request to Stidner API
    • take shipping_price from response Order object
    • update PaymentOrder with shipping_price
  • recipient_address - you need to update PaymentOrder with new recipient address.
    • perform ajax-request to backend
    • at backend perform GET /order/{order_uuid} request to Stidner API
    • take address with type 'recipient' from response Order object
    • update PaymentOrder with new customer address
window.addEventListener(
    "message",
    function (messageEvent) {
        if (messageEvent.data.action === 'order_updated') {

            /**
             * Transform contents of 'changes' array into integration action.
             */
            var integration_action = null;
            if (messageEvent.data.attributes.changes.indexOf('shipping_price') !== -1) {
                integration_action = 'shipping_option_updated';
            }else if(messageEvent.data.attributes.changes.indexOf('recipient_address') !== -1){
                integration_action = 'stidner_widget_address_updated';
            }

            if(action !== null){

                /**
                 * Lock Payson iframe to prevent from customer action there while updating.
                 * How to lock Payson iframe read at tech.payson.se
                 */
                __payson_iframe_lock();

                /**
                 * Make ajax request to backend to perform correspondent action
                 */
                $.get(stidner.ajax_url, {action: integration_action})
                    .done(function (data) {
                        /**
                         * Unlock Payson iframe when request is finished
                         */
                        __payson_iframe_unlock();
                    })
                    .fail(function (data) {
                        __payson_iframe_unlock();
                    });
            }
        }
    },
    true
);

Listen to PaysonEmbeddedAddressChanged event from PaysonWidget. When it is fired it means customer provided (or changed) his address at PaysonWidget - so you need to update the StidnerWidget with the same address.

  • perform ajax-request to backend
  • at backend perform GET /Checkout/{payment_order_id} request to Payson API
  • take customer address from response PaymentOrder object
  • update ShippingOrder with new recipient address
window.addEventListener("PaysonEmbeddedAddressChanged", function (evt) {

    /**
     * Lock Stidner Widget to prevent customer from performing action there while update
     */
    __stidner_shipping_iframe_lock();

    /**
     * Make ajax request to backend to perform correspondent action
     */
    $.post(stidner.ajax_url, {action: 'payson_address_updated'})
        .done(function () {
            /**
             * Unlock Stidner Widget after successful update
             */
            __stidner_shipping_iframe_unlock();
        })
        .fail(function (data) {
            __stidner_shipping_iframe_unlock();
        });
});

7. Finalize ShippingOrder after successful payment Checkout page

Customer performs payment process in PaysonWidget. After PaysonOrder is successfully paid, it is automatically finalized on Payson side. After finalization Payson sends HTTP-notification to Integration - it should react with the following:

  • notification contains PaysonOrder identifier - get PaysonOrder object by that identifier and make sure it is paid
  • get ShippingOrder identifier from PaysonOrder
  • update correspondent ShippingOrder with order_status = 'completed'
$order->order_status = 'completed';

/**
* Perform order update request to Stidner API here
**/

8. Display orders confirmation Confirmation page

On this stage PaysonOrder is paid, ShippingOrder is completed - you need to display customer the confirmation of both orders. Both widgets are designed in the way, that when their orders enter the final stage, they automatically display the confirmation information of the Order instead of the checkout form.

So what you need to do is automatically redirect customer to confirmation page, where display both widgets (as you already displayed them above at step 5). Depending on how your e-commerce store is designed, you may reload the widgets using javascript - they will display confirmation after the reload.