Insite Twilio Integration : Mobile Commerce Beyond Responsive Design
written by hchakrabarti
|August 2016
Insite is a market leading ecommerce platform for B2B commerce and Twilio is cloud based communication platform for building messaging and voice applications based on Twilio open APIs. In this article, I will show how an Insite application can communicate with customer and accept order from customer via SMS.
One of the most important aspects of digital strategy for modern websites is to make the applications or websites accessible in mobile devices. So, every application we build these days has to follow responsive design. But, mobile strategy is not or should not be confined to only responsive or native mobile application. Mobile strategy should look beyond that. For example, when I check-in for a flight, I get the boarding pass as a text message in my phone. That is mobile application too.
B2B customers’ shopping process and need is very different from B2C customers. B2B customers shop for their living and sometime they are too busy even to open up the website in the mobile device and order something. I think, in that kind of situation, a SMS communication with the ecommerce application will be very effective. In this article, I will show how that is possible using Insite and Twilio integration.
Let’s put it in the form of a story to understand what problem we are solving here. John Doe has a construction business and his company buys construction material from ABC Wholesale. Few days, before John starting his new project, he buys everything he needs except one very important item that is out of stock. Luckily, ABC Wholesale site allows John to ‘Watch’ that item and when that item is available, ABC sends a SMS message to John, saying that, the item he was watching is available now. If John wants to buy the item, he can SMS back to ABC with the item number and that will notify the customer service representative, who will place the order for him.
Following video shows how this scenario is working for John.
Twilio Setup:
The SMS communication between the customer and Insite application happens via Twilio. Twilio is a cloud based communication platform for building messaging and voice application. They have RESTful APIs to send and receive messages from application to mobile phone. For this blog, I created a Twilio account and I had to buy a phone number from Twilio so that Insite can send SMS to the customer phone number via Twilio phone number and when customer reply to Insite, he replies to the Twilio phone number which in turn calls Insite to send the message. Following is the Twilio setup.
Insite Customizations:
I have added a ‘Watch It’ button on the product detail page as shown in the video. To do this I had to add the following code in the ProductDetailView.cshtml in my application’s theme folder InSiteCommerce.Web\Themes\MyExperiments\Views\Directives\Catalog.
<div class="small-12 primary-button">
<button ng-show="vm.product.availability.messageType == 2"
id="displayAddToCart" ng-click="vm.addToWatch(vm.product, '@(Url.ContentPage<SignInPage>())')"
role="button" class="btn primary btn-add-to-cart" ng-disabled="!vm.product.qtyOrdered || vm.product.qtyOrdered == 0">
@T("Add To Watch")
</button>
</div>
Next step was to customize the ProductDetailController to add the ‘addToWatch’ method in the controller.
import account = insite.account; module insite.catalog { "use strict"; export class CustomProductDetailController extends ProductDetailController { session: any; customerNumber: string; public static $inject = ["$scope", "$filter", "$window", "coreService", "cartService", "productService", "customerService", "sessionService"]; constructor( protected $scope: ng.IScope, protected $filter: ng.IFilterService, protected $window: ng.IWindowService, protected coreService: core.ICoreService, protected cartService: cart.ICartService, protected productService: IProductService, protected customerService: customers.ICustomerService, protected sessionService: account.ISessionService) { super($scope, $filter, coreService, cartService, productService); this.init(); } init() { this.$scope.$on("sessionLoaded", (event: ng.IAngularEvent, session: SessionModel) => { this.session = session; }); super.init(); } addToWatch(product: ProductDto, signInUri: string) { if (!this.isAuthenticated()) { this.$window.location.href = signInUri + "?returnUrl=" + this.$window.location.href; return; } this.customerService.getBillTo("").success(billTo => { billTo.properties["Source"] = "AddToWatch"; billTo.properties["ProductId"] = product.id.toString(); //JSON.stringify(product); billTo.properties["Quantity"] = product.qtyOrdered.toString(); this.customerService.updateBillTo(billTo); alert("This item is added to your watch list now."); }); } isAuthenticated(): boolean { return this.session && this.session.isAuthenticated && this.sessionService.isAuthenticated(); } } angular .module("insite") .controller("ProductDetailController", CustomProductDetailController); }
As you can see ‘addToWatch’ is calling updateBillTo, which calls the BillTo update API and in turn calls the UpdateBillToHandler. I added following custom handler to add the Watch Item information in a Custom WatchItem table I created to store the data for this purpose.
using System.Collections.Generic; using Insite.Core.Services.Handlers; using Insite.Customers.Services.Parameters; using Insite.Customers.Services.Results; using InSite.Model.Interfaces; using InSite.Model.Attributes.Dependency; using Insite.Customers.Services.Handlers.Helpers; using MyExperiments.Models; using System; namespace MyExperiments.Handlers { [DependencyName("UpdateBillToHandler_AddToWatch")] class UpdateBillToHandler_AddToWatch : HandlerBase<updatebilltoparameter> { protected readonly IContextProvider ContextProvider; protected readonly IDefaultCustomerProvider DefaultCustomerProvider; protected readonly ICustomerHelper CustomerHelper; public UpdateBillToHandler_AddToWatch(IContextProvider contextProvider, IDefaultCustomerProvider defaultCustomerProvider, ICustomerHelper customerHelper) { this.ContextProvider = contextProvider; this.DefaultCustomerProvider = defaultCustomerProvider; this.CustomerHelper = customerHelper; } public override int Order { get { return 600; } } public override UpdateBillToResult Execute(IUnitOfWork unitOfWork, UpdateBillToParameter parameter, UpdateBillToResult result) { try { var billTo = result.BillTo; if (parameter.Properties["Source"] == "AddToWatch") { var watchItem = new WatchItem(); watchItem.ProductId = Guid.Parse(parameter.Properties["ProductId"]) watchItem.Quantity = Convert.ToInt32(parameter.Properties["Quantity"]); watchItem.BillToId = billTo.Id; watchItem.ShipToId = ContextProvider.CurrentContext.ShipTo.Id; unitOfWork.GetRepository<watchitem>().Insert(watchItem); } } catch (Exception ex) { throw ex; } return this.NextHandler.Execute(unitOfWork, parameter, result); } } }
Since WatchItem is a custom table, I needed to create a WatchItem model class and corresponding XML mapper to work with Nhibernate.
using System; using System.Runtime.Serialization; using InSite.Model; namespace MyExperiments.Models { [Serializable] [DataContract(IsReference = true)] public class WatchItem : ModelBase<watchitem> { public virtual Guid WatchItemId { get; set; } public virtual Guid ProductId { get; set; } public virtual Guid BillToId { get; set; } public virtual Guid ShipToId { get; set; } public virtual int Quantity { get; set; } public virtual DateTime? SentOn { get; set; } public virtual DateTime? ReceivedOn { get; set; } public virtual string CustomerResponse { get; set; } } }
Next I need to send the SMS to the customer when the Watched item is available. As you have seen in the video, I used an integration job to look at the available watched items and sent the SMS. Following in the integration job PreProcessor does that. Here I have used TwilioRestClient to message to customer via Twilio. You can find out the Twilio Rest API Helper library package from NuGet.
using System.Configuration; using InSite.Model using InSite.Model.Integration.Interfaces; using InSite.Model.Attributes.Dependency; using Microsoft.Practices.ServiceLocation; using InSite.Model.Interfaces; using System.Linq; using MyExperiments.Models; using Twilio; using System; namespace MyExperiments.Integration.PreProcessors { [DependencyName("WatchItemPreProcessor")] public class WatchItemPreProcessor : IJobPreprocessor { public IntegrationJob IntegrationJob { get; set; } public IJobLogger JobLogger { get; set; } public IntegrationJob Execute() { var watchItems = ServiceLocator.Current.GetInstance<iunitofwork>().GetRepository<watchitem>().GetTable().Where(w => w.SentOn == null).ToList(); foreach (var watchItem in watchItems) { var customer = ServiceLocator.Current.GetInstance<iunitofwork>().GetRepository<customer>().GetTable().Where(c => c.Id == watchItem.BillToId).ToList().FirstOrDefault(); var product = ServiceLocator.Current.GetInstance<iunitofwork>().GetRepository<product>().GetTable().Where(c => c.Id == watchItem.ProductId).ToList().FirstOrDefault(); if(!string.IsNullOrEmpty(customer.Phone) && product.IsInStock(watchItem.Quantity)) { var messageSid = SendMessage(customer.Phone, product.ShortDescription, watchItem.WatchItemId.ToString()); watchItem.SentOn = DateTime.Now; ServiceLocator.Current.GetInstance<iunitofwork>().GetRepository<watchitem>().Insert(watchItem); ServiceLocator.Current.GetInstance<iunitofwork>().GetRepository<watchitem>().DataProvider.Save(); } } return IntegrationJob; } private string SendMessage(string customerPhone, string productShortDescription, string watchItemId) { // Find your Account Sid and Auth Token at twilio.com/user/account string AccountSid = ConfigurationManager.AppSettings["TwilioAccountSid"]; string AuthToken = ConfigurationManager.AppSettings["TwilioAccountToken"]; string twilioPhone= ConfigurationManager.AppSettings["TwilioPhone"]; var twilio = new TwilioRestClient(AccountSid, AuthToken); var message = twilio.SendMessage(twilioPhone, customerPhone, string.Format("{0} is available now. Please text back with {1} to BUY.", productShortDescription, watchItemId)); return message.Sid; } } }Once the customer get the SMS, he replies to the message with the WatchItem Id. That message goes to Twilio phone number and Twilio calls the configured web service to the phone number and following code executes to save customer response. The code returns response back to Twilio which Twilio sends to the customer.
using InSite.Model; using InSite.Model.Interfaces; using Microsoft.Practices.ServiceLocation; using MyExperiments.Models; using System; using System.Linq; using System.Web.Mvc; namespace InSiteCommerce.Web.Controllers { public class WatchItemController : Controller { // GET: WatchItem [HttpPost] public ActionResult Index() { var watchItemId = Request.Params["body"]; var watchItem = ServiceLocator.Current.GetInstance<iunitofwork>().GetRepository<watchitem>().GetTable() .Where(w => w.WatchItemId.ToString() == watchItemId).ToList().FirstOrDefault(); if(watchItem!=null) { watchItem.CustomerResponse = "Yes"; watchItem.ReceivedOn = DateTime.Now; ServiceLocator.Current.GetInstance<iunitofwork>().GetRepository<watchitem>().Insert(watchItem); ServiceLocator.Current.GetInstance<iunitofwork>().GetRepository<watchitem>().DataProvider.Save(); return Content("Final Thoughts:"); } return Content(" Thanks, your order is being placed. "); } } } Sorry, I didn't understand that. Failed to place the order.
Insite as a platform is very flexible and extensible. I used the Insite Integration to communicate with Twilio and piggy back customer’s Watch Item action to the UpdateBillTo API. I could also write a set of API endpoints for the Watch Item. But, that would take more work and I took a shortcut for the proof of concept. This exercise proved that it is not at all difficult to integrate Insite with unconventional mobile tools if a right communication protocol is available. The next big thing that industry is buzzing with is, IoT (Internet of Things). I think Insite platform is ready to integrate with IoT. A lot of B2B businesses are around manufacturing where IoT is going to dominate the technology very soon. Looking forward to that excitement.
Disclaimer:
The code I provided with this article are not production quality code. I wrote the code for the proof of concept. Please modify them as your need.