How to Set up a Booking Process for an eCommerce

This section describes step by step how the SDK should be used to integrate RECShipping service with an eCommerce platform.

During the user checkout process, RECShipping proposes two different shipping services. They will be operating as two complementary carriers in the eCommerce platforms:

  • RECS Last Mile Delivery is the servicrequeste that allows the shopper to select when he wants to receive his order.
  • RECS Standard Delivery is a standard shipping service, and is designed to cover the areas where the Last Mile Delivery service is not available yet.

The booking of a shipping service needs to go through the following steps:

  1. Initialize carrier availabilities
  2. Display carrier and retrieve user selection (for last mile).
  3. Confirm Selected Delivery
  4. Confirm Payment

The Payment confirmation is common to both shipping solutions. The initialization and the confirmation processes, however, are distict. Thus, we will decline these sections in "Standard Delivery" and "Last Mile Delivery".

Initialization

In the following code snippets, we consider that the Client repository has previously been initialized as follow:

Client::getInstance()->setApiKey('dGVzdDp0ZXN0');

As soon as the service number has been provided by the creation request ($service_number = $response->getServiceNumber()):

Client::getInstance()->setServiceNumber($service_number);

1. Initialize carriers availabilities

For any delivery request with a destination address lying within the countries covered by our service*, one of the two services we propose will be available: Standard delivery or Last Mile delivery. Each of the following sections describes how this is performed.

* at the moment, Spain Mainland

1.1 Standard Delivery

When the available carrier is the Standard carrier, the initialization steps go as follow:

  1. Consult Service Availability (HOOK_GATHER_CARRIERS)
  2. Get the pricing of the Standard shipping method.

Initialize Carrier Availabilities

1.1.1 Consult Service Availability

In order to know which services are available, run the SERVICE_AVAILABILITY request, and use the response object available methods to check for availability for the requested services, like so:

$response = Client::getInstance()
                ->request( Client::SERVICE_AVAILABILITY,
                    [
                        'destination_address' => $basket->getShopperAddress(),
                        'products' => $basket->getProductsDetails(),
                        'services' => ['last-mile', 'classic'],
                        'shopper' => $basket->getShopperDetails()
                    ]
                );

$response->hasStandardService();  //returns boolean
$response->hasLastMileService();  //returns boolean

destination_address is the address of the shopper

services is an array of service types you want to request availability for

products is an array with the list and details of the products

( i ) The data formats for each array are described in the section deticated to data formats.

1.1.2. Get Standard Shipping Cost

When there is standard availability, you can create the service and get the cost like this:

$response = Client::getInstance()
                ->request( Client::STANDARD_CREATION,
                    [
                        'session_timeout' => 600,
                        'currency_code' => 'EUR',
                        'total_price' => 95.79,
                        'shopper" => $basket->getShopperDetails(),
                        'destination_address' => $basket->getShopperAddress(),
                        'products' => $basket->getProductsDetails()
                    ]
                );

if($response->isAvailable()) 
{
    $price = $response->getPrice();
    $priceAmount = $response->getPrice()->getAmount();
    $service_number = $response->getServiceNumber();
}

The request admits the same destination_address and products information as the 1.1.1 Consult Service Availability request. To these two parameters, we add the following:

shopper is an array containing the shopper information and contact details

session_timeout (optional) session duration of the eCommerce, in seconds.

currency_code (optional) 3 letters ISO4217 currency code that specifies the currency that will be used througout this service.

total_price (optional) is the price of the full shopping basket whithout transportation. It is used to find out if any discount should be applied. If not informed, no discount will be applied.

The response provides the service number and the standard price for the created service.

1.2 Last Mile Availability

When the available carrier is the Standard carrier, the initialization steps go as follow:

  1. Consult Service Availability (HOOK_GATHER_CARRIERS)
  2. Get the pricing of the Last Mile shipping method.
  3. Retrieve the resources to display the widget (HOOK_SHOW_LM_CARRIER)
  4. Find price to be displayed for the last mile option (optional: if we need to display this price before we know the shopper address).

Initialize Carrier Availabilities

1.2.1 Consult Service Availability

The first step is common to the Classic and the Last Mile scenarios: only the reponse differs. When the service availability response specifies that we have availability for the Last Mile Service, it comes as follow:

$response = Client::getInstance()   ->request( Client::SERVICE_AVAILABILITY,
        [
            'destination_address' => $basket->getShopperAddress(),
            'products' => $basket->getProductsDetails()
            'services' => ['last-mile', 'classic'],
            'shopper' => $basket->getShopperDetails()
        ]);

if($response->hasLastMileService())
{
    //do something
}

The format of the request is the one specified in the 1.1.1 Consult Service Availability section.

1.2.2. Get Last Mile Shipping Cost

The cost for the Last Mile Service come as a calendar of shipping costs. The best price will be shown along the Last Mile shipping method with a 'from' label, and will change to the actual user selection once it has been done.


$response = Client::getInstance()->request( Client::LASTMILE_CREATION,[
        'session_timeout' => 600,
        'currency_code' => 'EUR',
        'total_price' => 95.79,
        'shopper' => $basket->getShopperDetails(),
        'destination_address' => $basket->getShopperAddress(),
        'products' => $basket->getProductsDetails()
]);

if($response->isAvailable()) 
{
    $service_number = $response->getServiceNumber();
    $bestPrice = $response->getBestPrice();
    $bestPriceAmount = $response->getBestPrice()->getAmount();
    $priceMatrix = $response->getPriceMatrixJson();
    $defaultUserSelection = $response->getDefaultSelections();
}

The format of the request is the same as for the 1.1.2 Get Standard Shipping Cost section. The response provides a service number, the best price available and a calendar of service prices.

1.2.3. Get the resources

The Last Mile Shipping method needs to interract with the shopper. Here is the method that retrieves the resources that we will use in the view in order to manage this interraction. Get the list of resources (HOOK_SHOW_LM_CARRIER):

$response = Client::getInstance()   ->request(
    Client::LASTMILE_ASSETS,
    ['version' => 2, 'locale' => 'es_ES']
);

$css_url  = $response->getCssUrl();
$js_url   = $response->getJsUrl();

//Returns the HTML Contents
$html = $response->getHtml(); 

//If you really want to fetch the html content yourself you can use this
$html_url = $response->getHtmlUrl();

version (optional) is the version of the assets that has to be retrieved. This offers a mechanism to freeze the assets that are returned by the api. It is not taken into account yet.

locale (optional) is the locale parameter that should be used within the widget

1.2.4. Last Mile Best Price

If at any time you need to get an estimate of the best price for a last mile delivery before you know the destination address, you can use the last mile best price request.


$response = Client::getInstance()
                ->request( Client::LASTMILE_BESTPRICE,
                    [
                        "currency_code" => "EUR",
                        "total_price" => 95.79
                    ]);

$best_price = $response->getPrice();

1.3 Full example for Standard & Last Mile usage

<?php

use Nektria\Recs\MerchantApi\Client;
use Nektria\Recs\MerchantApi\Exceptions\ApiClientException;

$dataAvailability = [(
    'destination_address' => getShopperAddress(),
    'products' => getProductsDetails(),
    'services' => ['last-mile', 'classic'],
    'shopper' => $this->getShopperDetails()
];

$dataService = array_merge($dataAvailability, [
    'session_timeout' => $cookie_lifetime,
    'currency_code' => $currencyIso3,
    'total_price' => getOrderTotal()
]);

$client = Client::getInstance();

try {

    $response = $client->request(Client::SERVICE_AVAILABILITY, $data_availability);

    if($response->hasLastMileService())
    {
        try {
            $response = $client->request(Client::LASTMILE_CREATION, $dataService);

            if ($response->isAvailable())
            {
                $lastMileMatrix = $response->getPriceMatrix();
                $serviceNumber = $response->getServiceNumber();
                $bestPrice = $response->getBestPrice()->getAmount();
                $defaultUserSelection = $response->getDefaultSelections();
            }

        } catch (ApiClientException $e) {
            echo "ERROR: " . $e->getMessage();
        }
    }

    if($response->hasStandardService())
    {
       try {
            $response = $client->request(Client::STANDARD_CREATION, $dataService);

            if ($response->isAvailable())
            {
                $serviceNumber = $response->getServiceNumber();
                $price = $response->getPrice()->getAmount();
            }

        } catch (ApiClientException $e) {
             echo "ERROR: " . $e->getMessage();
        }
    }

} catch (ApiClientException $e) {
    echo "ERROR: " . $e->getMessage();
} catch (\Exception $e) {
    echo "Network Error: " . $e->getMessage();
}

2.Display carrier options and retrieve user selection

A list of shipping options is presented for the user to choose. One of the two options will appear: Standard delivery or Last Mile delivery. Standard delivery behaves like other usual delivery methods. Last Mile delivery needs some more work, as described further down.

2.1. Standard

The Standard shipping method will return the price of the delivery, which can be displayed in the same way other carrier display the price of their service. No additional logic needs to be taken into account.

2.2. Last Mile Shipping: initialize the widget

Whenever selected, the Last Mile Delivery Service offers a list of options for the shopper to choose: the exact delivery time. The SDK offers a widget to display the prices of each delivery hour, and retrieve the shopper selection. The flow of information that navigates between the widget and the checkout page needs to be set up as follow:

  1. Place each of these resources in the layout a. js b. css c. html

  2. Setup javascript to interract with the widget: a. Set up event handler to initalize the widget after it has been (re)loaded. b. When last mile carrier selection changes. c. Set up event handler for shopping cart updates. d. Set up event handler for user selection updates.

2.2.1. Display the resources

The resources retrieved in the controller should be displayed as follow in the view:

    <head>
    ....
    <!-- 2.2.a. javascript -->
    <script src="<?php print $js_url; ?>"></script>
    <!-- 2.2.b. stylesheet -->
    <link rel="stylesheet" href="<?php print $css_url; ?>" />
    </head>
    <body>
    ....

    <!-- 2.3.c. place html -->
    <?php print file_get_contents($html_url); ?>
    ....
    </body>

2.2.2. Setup widget data flow

The communication between the widget and the shop is managed via a set of document events and via the corresponding event handlers. Selection or unselection of Last Mile Shipping service, widget load and reload, user selection changes and confirmation are all monitored:

a. Every time the widget is (re)loaded, the SDK triggers the recs_widget_loaded event. The eCommerce has to set up an event handler to initialize recs variables with nektria_recs.setPriceMatrix and nektria_recs.setUserSelection

  • price_matrix is the matrix of prices returned by the SDK in the LastMileAvailability Request.
  • my_session.user_selection is the user selection that has been retrieved by the recs_update_user_selection event handler (It can be empty or null if the user has made no selection so far).

b. When the Last Mile carrier selection changes, the eCommerce has to trigger the following events:

  • lastmile_selected when it is selected
  • lastmile_unselected when it is unselected

c. When the user confirms his selection of time windows for the last mile shipping method, the shopping cart should be updated. This happens via the recs_update_shopping_cart event that is triggered by the SDK, and will be handled by the eCommerce to perform the following tasks:

  • Update of the shopping cart with information provided about delivery windows
  • Update of the total cost corresponding to selected shipping option

The data provided to the event comes under the "detail" key, and looks as follow:

    {
        "total_price": 5.50,
        "currency_code": "EUR",
        "validation_windows":
        [
            { 
                "start_time": "2015-05-15T11:00:00+02:00", 
                "end_time": "2015-05-15T13:00:00+02:00" 
            },
            { 
                "start_time": "2015-05-16T18:00:00+02:00", 
                "end_time": "2015-05-16T19:00:00+02:00" 
            }
        ],
        "delivery_windows":
        [
            { 
                "start_time": "2015-05-16T19:00:00+02:00", 
                "end_time": "2015-05-16T20:00:00+02:00" 
            }
        ]
    }   

Note: the object ShowUserSelectionHelper can help you display information about the selection made by the user.

d. The eCommerce registers at all time the latest user selection in the widget, so that it is maintained when moving back and forth. Each time a selection of time windows changes in the widget, the SDK triggers a recs_update_user_selection event. The eCommerce has to listen to this event, and store the data about selected time windows which comes under the detail key.

Here is a sample implementation

<head>
....
    <script>    

        function recs_user_selection_is_set()
        {
            element = my_session.user_selection;
            if(typeof element == "undefined") return false;
            if(element == null) return false;
            if(element == "") return false;
            return true;
            my_session.user_selection = default_user_selection;
        }

        function recs_set_user_selection(value)
        {
            my_session.user_selection = value;
        }

        price_matrix = <?php print $priceMatrix; ?>;
        default_user_selection = "<?php print $defaultUserSelection; ?>";       if(!recs_user_selection_is_set()
            recs_set_user_selection(default_user_selection);

         /**
          * 2.3. Set up event handler for
          *     a.  Init widget
          *         &
          *     c. shopping cart updates 
          *         &
          *     d. user selection updates.
          */
         init_nektria_recs = function()
         {
             nektria_recs.listen('widget_loaded', widgetLoadedHandler);
             nektria_recs.listen('update_shopping_cart', updateCartHandler);
             nektria_recs.listen('update_user_selection', updateUserSelectionHandler);
         }

        if (window.addEventListener)
            window.addEventListener('load', init_nektria_recs, false);
        else if (window.attachEvent)
            window.attachEvent('onLoad', init_nektria_recs);

        function widgetLoadedHandler()
        {
            nektria_recs.setPriceMatrix(price_matrix);
            nektria_recs.setUserSelection(my_session.user_selection);
        }

        function updateCartHandler(e)
        { 
             // Add to the basket: user_calendar_selection;
             my_basket.user_confirmed_selection =  e.detail;
             (...) 

             // update_total_price
             my_basket.shipping_price = e.detail.total_price;
             (...)
        }   

        function updateUserSelectionHandler(e)
        { 
             my_session.user_selection =  e.detail;
        }           
    </script>
</head>
<body>
      <li>    
         <!-- 2.3.b. show price grid when last mile selected -->
         <input name="shipping_method" type="radio" value="nektria" id="s_matrixrate" 
                class="radio validate" onclick="nektria_recs.dispatch('lastmile_selected');">
         <label for="s_matrixrate">ReCS                                                                                  <span class="price">8,16 €</span>                                                       </label>
      </li>
      <li>
        <!-- 2.3.b. hide price grid when last mile unselected -->
        <input name="shipping_method" type="radio" value="nacex" id="s_matrixrate2" 
                class="radio" onclick="nektria_recs.dispatch('lastmile_unselected');">
        <label for="s_matrixrate2">NACEX                                                                                    <span class="price">8,12 €</span>                                                
        </label>
      </li>     

....

</body>

3. Confirm Selected Delivery

If the shopper finally chooses to execute the delivery with RECS, he will have selected one of the two options we propose:

  1. RECS Standard Delivery Service
  2. RECS Last Mile Delivery Service

Each of these services has a slightly different confirmation process. Here is how you can execute them.

3.1. Shopper chooses RECS Standard Delivery

If the shopper has selected RECS Standard Delivery as shipping Service, we need to confirm it. This has to take place after the user has made the payment (HOOK_ORDER_CONFIRMED).

// 3. Order Confirmed hook
$response = Client::getInstance()
                ->request( Client::STANDARD_CONFIRMATION,
                                    [
                                        "order_number" => $basket->getOrderNumber(),
                                        "shopper" => $shopper_details,
                                        "destination_address" => $shopper_address
                                    ]);

Note: It is compulsory that shopper details and destination_address are resubmitted. This is to avoid any situation where the cache may interfere with information that has changed during the booking process.

3.2. Shopper chooses RECS Last Mile Delivery

If the shopper has selected RECS Last Mile as shipping Service, we need to confirm it. This has to take place after the user has made the payment (HOOK_ORDER_CONFIRMED).

  1. !! Make sure the price provided in the user selection for the API matches the price in the shopper basket
  2. Validate last mile request with information retrieved from final selection.
  3. After the payment, confirm the shipping order. (HOOK_ORDER_CONFRIMED)

Last Mile Shipping Confirmation

3.2.1. Data Verification

Check basket shipping price matches the one provided to the API

$user_selection = your_get_from_basket();
$a = json_decode($user_selection ,true);
$shipping_price = $a["total_price"];
basket_shipping_price = your_pickup_basket_shipping_price();
if($shipping_price != $basket_shipping_price)
    throw new BookingException("Price mismatch - the price in the basket is incorrect");

In order to detect any error as soon as possible in the checkout process, we verify that the price that has been shown in the shopping basked matches the price that is submitted in the validation request. Any mismatch should trigger a refresh of the delivery options, and show an error inviting the shopper to reselect his delivery options.

3.2.2. Shipping validation

(this is optional: the last mile service may be confirmed directly after the payment.)

When the user confirms the shipping method, before he provides his payment information, the shipping order will be validated:


$response = Client::getInstance()
                ->request(Client::LASTMILE_VALIDATION, 
                    [
                        "user_selection" => $user_selection,
                        "shopper" => $shopper_details,
                        "destination_address" => $shopper_address
                    ]
                );

user_selection is the json string returned by the widget about the delivery windows that have been chosen by the shopper.

shopper specifies contact information about the shopper, in the same format as in the SERVICE_CREATION request.

destination_address specifies where the shopper's products should be delivered. It comes in the same format as in the SERVICE_CREATION request.

shopper and destination_address fields are the same as in the SERVICE_CREATION request, but may have been modified during the selection of the delivery options. As a result, the final information is transmitted in the validation request.

3.2.3. Shipping confirmation

After the payment information has been provided, the order is confirmed. Then the shipping also has to be confirmed

$response = Client::getInstance()
                ->request(Client::LASTMILE_CONFIRMATION, 
                        [
                            "order_number" => $basket->getOrderNumber(),
                            "user_selection" => $user_selection,
                            "shopper" => $shopper_details,
                            "destination_address" => $shopper_address
                        ]);

order_number is the order reference in the eCommerce platform.

user_selection (optional) is the json string returned by the widget about the delivery windows that have been chosen by the shopper.

shopper (optional) specifies contact information about the shopper, in the same format as in the SERVICE_CREATION request.

destination_address (optional) specifies where the shopper's products should be delivered. It comes in the same format as in the SERVICE_CREATION request.

user_selection, shopper, destination_address will be accepted only if there has been no previous validation.

4. Confirm Payment

Some payment methods do not give an immediate confirmation after the user has provided his payment information. There can be a delay from a few minutes up to a few hours before the payment is actually confirmed by the financial entity.

Payment Confirmation

As a result, a confirmed shipping is still waiting for the payment to be confirmed. This is done with the following method:

Client::getInstance()
        ->setServiceNumber('UJS-9745')
        ->request(Client::PAYMENT_CONFIRMATION);

This method will be triggered by the order status change in the eCommerce platform.