Business Scenario

Customer wants to have capability to call UBER from a Fiori Launchpad after he/she books ticket online and needs go to cinema. Call UBER would be a good option to get to cinema.

Solution

A PoC is build up for this purpose. Assume the current customer position (source location) and cinema location (destination) is known. We will focus on how to consume UBER APIs to call ride request from SAP UI5 application.

Customer will have visibility of available UBER product and estimated price. Then customer can select which kind of product and request an UBER service.

Technical Detail

In order to consume UBER API, it is necessary to register UBER development account. https://developer.uber.com/dashboard/

Create an app on UBER development account. In the app there are some important information to consume UBER API:

Finding more detail technical document here: https://developer.uber.com/docs/riders/introduction

In our example application, following activities with UBER are implemented:

  • Get UBER user profile that has authorized with the application
  • Get UBER available product
  • Get price for product
  • Request estimation for specific product
  • Send UBER request for specific product and estimation

Get User Profile

Reference: https://developer.uber.com/docs/riders/references/api/v1.2/me-get

We can test web APIs with different tools. In my example, the tool “Postman” is used.

The “Access Token” of the UBER app can be used here as header parameter “Authorization”. This request returns general information of registered UBER user.

Get Products

Reference: https://developer.uber.com/docs/riders/references/api/v1.2/products-get

The current location (latitude&longitude) is passed to request URL to get available products on this position. The service will return detail information of available products.

Get Prices

Reference: https://developer.uber.com/docs/riders/references/api/v1.2/estimates-price-get

In this request, both source position and destination positon are passed to URL. System will return estimated prices based on available products.

Request Estimation

Reference: https://developer.uber.com/docs/riders/references/api/v1.2/requests-estimate-post

This request will return the estimated information for specific product and location information (source & destination location). The result of estimation will be used in the next step to create ride request.

Create Ride Request

Reference: https://developer.uber.com/docs/riders/references/api/v1.2/requests-post

This service will create ride request of selected product and fare_id which was created in previous step. After service is posted, the request will be processed and waiting for response from UBER drivers. The status will be updated accordingly based on rider interaction.

Note: the access token which is generated in the UBER app configuration page cannot be used to create ride request. Refer to next section for access token generation.

User Access Token Creation

Reference: https://developer.uber.com/docs/riders/guides/authentication/user-access-token

The Uber API uses OAuth 2.0 to allow developers to get a user access token to access a single user’s data or do actions on their behalf. There are following steps to get user access token:

  1. Go to UBER app admin screen. On the tab “Auth” we can select what scope will be available. In this step, make sure the scope “Request” is selected:
  2. UBER provides an authorization page to grant permission to your UBER app. Use following URL to get authorization code:
    https://login.uber.com/oauth/v2/authorize?client_id=<YOUR_CLIENT_ID>&response_type=code&redirect_uri=<YOUR_REDIRECT_URI>

    replace all <PLACEHOLDER> in the URL. The placeholder <YOUR_CLIENT_ID> can be derived from UBER app admin page. You can leave placeholder <YOUR_REDIRECT_URL> blank and system will return authorization code in URL.Note: in the UBER app admin page, the “redirect URI” is configured as “http://localhost/”. So here the authorization code redirect to localhost.

    Copy the authorization code from returned URL. This code will be used to generate user access token in next step.

  3. Use the Token Exchange endpoint to exchange the authorization code for an access_token which will allow you to make requests on behalf of the user.
    In this step, send request “https://login.uber.com/oauth/v2/token” with following parameter to get user access token:The access token has expiry time. So we have to refresh access token after it is expired.
  4. When the user’s access_token has expired, you can obtain a new access_token by exchanging the refresh_token associated with the access_token using the Token Exchange endpoint. Refreshing the user access token means that you don’t need to ask the user to authorize your app for the same permissions again. According to the API document, use following request to refresh access token:

Handle CORS Issue

Reference of CORS concept: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

UBER API will reject request if the origin URIs are not defined in the UBER app admin page. To solve CORS issue, it is necessary to define origin URIs in the tab “Settings” of UBER app admin page.

Here is xml file of main view:

<mvc:View controllerName="sap.uber.controller.Main" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m" xmlns:f="sap.ui.layout.form" xmlns:l="sap.ui.layout" xmlns:core="sap.ui.core" xmlns:semantic="sap.m.semantic" xmlns:table="sap.ui.table"> <App id="idApp"> <pages> <semantic:FullscreenPage title="{i18n>title}"> <semantic:content> <Panel id="idPanel" class="sapUiResponsivePadding"> <content> <f:SimpleForm editable="true" layout="ResponsiveGridLayout" title="Route Information" labelSpanL="4" labelSpanM="4" emptySpanL="4" emptySpanM="4" columnsL="2" columnsM="2"> <f:content> <core:Title text="Home Location"/> <Label text="User Name"/> <Text text="{userapi>/name}"/> <Label text="First Name"/> <Text text="{userapi>/firstName}"/> <Label text="Last Name"/> <Text text="{userapi>/lastName}"/> <Label text="Home Address"/> <Text text="1455 Market St, San Francisco"/> <core:Title text="Cinema Location"/> <Label text="Cinema Name"/> <Text text="Landmark Theaters Embarcadero Center Cinema"/> <Label text="Cinema Address"/> <Text text="37.7752415, -122.518075"/> <core:Title text="Time Information"/> <Label text="Estimated Arrival Time"/> <Text id="txtETA" text=""/> </f:content> </f:SimpleForm> <f:SimpleForm editable="false" layout="ResponsiveGridLayout" title="Uber Information" labelSpanL="4" labelSpanM="4" emptySpanL="4" emptySpanM="4" columnsL="2" columnsM="2"> <f:content> <Label text="Fist Name"/> <Text text="{profile>/data/first_name}"/> <Label text="Last Name"/> <Text text="{profile>/data/last_name}"/> <Label text="Ride Request Status"/> <ObjectStatus class="sapUiSmallMarginBottom" text="{rideStatus>/data/status}" state="Success"/> </f:content> </f:SimpleForm> <table:Table id="productTable" rows="{product>/data}" title="Available Products" selectionMode="Single" visibleRowCount="5"> <table:columns> <table:Column width="auto"> <Label text="Product Name"/> <table:template> <Text text="{product>display_name}"/> </table:template> </table:Column> <table:Column width="auto"> <Label text="Product Group"/> <table:template> <Text text="{product>product_group}"/> </table:template> </table:Column> <table:Column width="auto"> <Label text="Description"/> <table:template> <Text text="{product>description}"/> </table:template> </table:Column> <table:Column width="auto"> <Label text="Capacity"/> <table:template> <Text text="{product>capacity}"/> </table:template> </table:Column> <table:Column width="auto"> <Label text="Image"/> <table:template> <Image src="{product>image}" tooltip="Vehicle Image"/> </table:template> </table:Column> </table:columns> </table:Table> <table:Table id="priceTable" rows="{prices>/data/prices}" title="Available Prices" selectionMode="Single" visibleRowCount="5"> <table:columns> <table:Column width="auto"> <Label text="Name"/> <table:template> <Text text="{prices>display_name}"/> </table:template> </table:Column> <table:Column width="auto"> <Label text="Distance"/> <table:template> <ObjectNumber number="{prices>distance}" unit="KM"/> </table:template> </table:Column> <table:Column width="auto"> <Label text="Highest Estimation"/> <table:template> <ObjectNumber number="{prices>high_estimate}" unit="{prices>currency_code}"/> </table:template> </table:Column> <table:Column width="auto"> <Label text="Lowest Estimation"/> <table:template> <ObjectNumber number="{prices>low_estimate}" unit="{prices>currency_code}"/> </table:template> </table:Column> <table:Column width="auto"> <Label text="Duration"/> <table:template> <ObjectNumber number="{prices>duration}" unit="Seconds"/> </table:template> </table:Column> <table:Column width="auto"> <Label text="Estimated Price"/> <table:template> <ObjectNumber number="{prices>estimate}" unit="{prices>currency_code}"/> </table:template> </table:Column> </table:columns> </table:Table> </content> </Panel> </semantic:content> <semantic:customFooterContent> <Button id="btnRideRequest" icon="sap-icon://taxi" tooltip="Call Uber" press="onRideRequest"/> <Button id="btnRideStatus" icon="sap-icon://process" tooltip="Show Status" press="onRideStatus"/> </semantic:customFooterContent> </semantic:FullscreenPage> </pages> </App> </mvc:View>

And the relevant controller class shows below:

sap.ui.define([ "sap/ui/core/mvc/Controller", "sap/m/MessageToast" ], function(Controller, MessageToast) { "use strict"; return Controller.extend("sap.uber.controller.Main", { onInit: function(){ this.SERVER_TOKEN = "<SERVER_TOKEN>"; this.CLIENT_ID = "<CLIENT_ID>"; this.CLIENT_SECRET = "<CLIENT_SECRET>"; this.ACCESS_TOKEN = "<ACCESS_TOKEN>"; this.oSampleRequest = { "sourceLat": "37.7752315", "sourceLng": "-122.418075", "destinationLat": "37.7752415", "destinationLng": "-122.518075", "seat_count": "2" }; this.oSampleProduct = { "sourceLat": "37.7752315", "sourceLng": "-122.418075", "destinationLat": "37.7899886", "destinationLng": "-122.4021253", "seat_count": "2" }; this.oDialogRequest = sap.ui.xmlfragment("dialogRequest", "sap.uber.view.RideRequest", this.getView().getController()); this.oDialogStatus = sap.ui.xmlfragment("dialogStatus", "sap.uber.view.RideStatus", this.getView().getController()); var meModel = new sap.ui.model.json.JSONModel({ "data": {} }); var productModel = new sap.ui.model.json.JSONModel({ "data": {} }); var priceModel = new sap.ui.model.json.JSONModel({ "data": {} }); var estimateModel = new sap.ui.model.json.JSONModel({ "data": {} }); var statusModel = new sap.ui.model.json.JSONModel({ "data": {} }); this.getView().setModel(meModel, "profile"); this.getView().setModel(productModel, "product"); this.getView().setModel(priceModel, "prices"); this.getView().setModel(estimateModel, "estimate"); this.getView().setModel(statusModel, "rideStatus"); this.getMe(); this.getProducts(); this.getPrices(); }, // get uber application data getMe: function(){ var sUrl = "https://api.uber.com/v1.2/me"; var oModel = this.getView().getModel("profile"); // call uber api  $.ajax({ url: sUrl, type: "get", async: true, headers: { "Authorization": "Bearer " + this.ACCESS_TOKEN, "Content-Type": "application/json" } }).done(function(results){ $.sap.log.info(results); oModel.setProperty("/data", results); }).fail(function(xhr, textStatus, errorThrown){ MessageToast.show("Get Uber User Error"); $.sap.log.error(xhr); }); }, // get available ride types  getProducts: function(){ var oData = { "sourceLat": "37.7752315", "sourceLng": "-122.418075", "destinationLat": "37.7899886", "destinationLng": "-122.4021253", "seat_count": "2" }; var sUrl = "https://api.uber.com/v1.2/products?latitude=" + oData.sourceLat + "&longitude=" + oData.sourceLng; var oModel = this.getView().getModel("product"); // call uber api to get available products $.ajax({ url: sUrl, type: "GET", async: true, headers: { "Authorization": "Bearer " + this.ACCESS_TOKEN, "Content-Type": "application/json", "Accept-Language": "en_EN" } }).done(function(results){ $.sap.log.info("get products successfully"); oModel.setProperty("/data", results.products); }).fail(function(xhr, textStatus, errorThrown){ $.sap.log.error(xhr); MessageToast.show("Cannot get Uber Products"); }); }, // get available prices  getPrices: function(){ var oData = { "sourceLat": "37.7752315", "sourceLng": "-122.418075", "destinationLat": "37.7752415", "destinationLng": "-122.518075", "seat_count": "2" }; var sUrl = "https://api.uber.com/v1.2/estimates/price?start_latitude=" + oData.sourceLat + "&start_longitude=" + oData.sourceLng + "&end_latitude=" + oData.destinationLat + "&end_longitude=" + oData.destinationLng; var oModel = this.getView().getModel("prices"); var that = this; // call uber api to get estimation $.ajax({ url: sUrl, type: "GET", async: true, headers:{ "Authorization": "Bearer " + this.ACCESS_TOKEN, "Content-Type": "application/json" } }).done(function(results){ $.sap.log.info("get estimated prices"); oModel.setProperty("/data", results); }).fail(function(xhr, textStatus, errorThrown){ $.sap.log.error("xhr"); MessageToast.show("Cannot get Price Information"); }); }, // create a ride request onRideRequest: function(oEvent){ // for test var oSampleRequest = { "sourceLat": "37.7752315", "sourceLng": "-122.418075", "destinationLat": "37.7752415", "destinationLng": "-122.518075", "seat_count": "2" }; var oTable = this.byId("productTable"); var selectedIndex = oTable.getSelectedIndex(); var oModelProduct = this.getView().getModel("product"); var oModelEstimate = this.getView().getModel("estimate"); var that   // get selected product if(oModelProduct.getData()){ var oProduct = oModelProduct.getData().data[selectedIndex]; var sUrl = "https://api.uber.com/v1.2/requests/estimate"; // build up the POST data var oData = { "product_id": oProduct.product_id, "start_latitude": oSampleRequest.sourceLat, "start_longitude": oSampleRequest.sourceLng, "end_latitude": oSampleRequest.destinationLat, "end_longitude": oSampleRequest.destinationLng, "seat_count": oSampleRequest.seat_count }; // call uber api to request a ride estimation $.ajax({ url: sUrl, type: "POST", async: true, headers: { "Authorization": "Bearer " + this.ACCESS_TOKEN, "Content-Type": "application/json" }, data: JSON.stringify(oData) }).done(function(data){ $.sap.log.info("Post ride request is successful"); // update the estimate model oModelEstimate.setProperty("/data", data); // open the dialog box to display ride estimation data if (that.oDialogRequest){ that.oDialogRequest.setModel(oModelEstimate, "estimate"); that.oDialogRequest.open(); } }).fail(function(xhr, textStatus, errorThrown){ $.sap.log.error("Error during post ride request"); MessageToast.show("Error during post ride request"); }); } }, // close dialog box onConfirm: function(oEvent){ // confirm the ride request var sUrl = "https://sandbox-api.uber.com/v1.2/requests"; //for sandbox only var modelEstimate = this.getView().getModel("estimate"); var modelProduct = this.getView().getModel("product"); var modelStatus = this.getView().getModel("rideStatus"); var that = this; // get selected index var oTable = this.byId("productTable"); var selectedIndex = oTable.getSelectedIndex(); var oPostData = { "product_id": modelProduct.getData().data[selectedIndex].product_id, "start_latitude": this.oSampleProduct.sourceLat, "start_longitude": this.oSampleProduct.sourceLng, "end_latitude": this.oSampleProduct.destinationLat, "end_longitude": this.oSampleProduct.destinationLng, "seat_count": this.oSampleProduct.seat_count, "fare_id": modelEstimate.getData().data.fare.fare_id }; $.ajax({ url: sUrl, type: "POST", async: false, headers: { "Authorization": "Bearer " + this.ACCESS_TOKEN, "Content-Type": "application/json" }, data: JSON.stringify(oPostData) }).done(function(data, statusText, xhr){ switch (statusText){ case "202": //Accepted // Your Request is successfully being processed MessageToast.show("Your Request is successfully being processed"); break; case "409": //Conflict // An error has occurred, possibly due to no drivers available MessageToast.show("An error has occurred, possibly due to no drivers available"); break; case "422": //Unprocessable Entity MessageToast.show("An error has occurred, most likely due to an issue with the user’s Uber account."); break; } modelStatus.setProperty("/data", data); // update the ETA var oText = that.getView().byId("txtETA"); if (oText){ oText.setText("In " + data.pickup_estimate + " Minutes"); } $.sap.log.info("Request is processed"); }).fail(function(xhr, textStatus, errorThrown){ $.sap.log.info("Error during post ride request"); // show message var oResponseText = JSON.parse(xhr.responseText); var sMsg = oResponseText.errors[0].title; MessageToast.show(sMsg); }); this.oDialogRequest.close(); }, onCancel: function(oEvent){ this.oDialogRequest.close(); }, onRideStatus: function(oEvent){ if (!this.oDialogStatus){ this.oDialogStatus = sap.ui.xmlfragment("dialogStatus", "sap.uber.view.RideStatus", this.getView().getController()); } var sUrl = "https://api.uber.com/v1.2/requests/current"; var oModel = this.getView().getModel("status"); // call uber api to get current trip information $.ajax({ url: sUrl, type: "GET", async: true, headers: { "Authorization": "Bearer " + this.ACCESS_TOKEN } }).done(function(data, statusText, xhr){ oModel.setProperty("/data", data); this.oDialogStatus.setModel(oModel, "status"); this.oDialogStatus.open(); }).fail(function(xhr, statusText, errorThrown){ MessageToast.show("User is not currently on a trip"); }); } }); });