Creating a booking
At the other side of the line, there should be a Transporter who is responsible for responding to the bookings, and to carry out trips according to the bookings. How to handle bookings is described in detail in the tutorial Handling a booking
Creating a booking is one of the easiest real-world examples of using CCP. The tutorial assumes the reader has basic knowledge about CCP, OData and REST based communication in general.
Adding a booking to the system is as easy as a HTTP POST to /Bookings. The payload of de request consist of the booking properties in one of the supported formats. CCP supports three OData wire-formats: Atom and two JSON variants (verbose and light). The provided examples are in the JSON light format for readability.
Versioning
The examples use OData version "3.0" and an API version of "17.0", the most current versions at the time of writing.
Tools
The tutorial is platform, language, framework and IDE independent. For testing purposes or to simply follow along the tutorial you can use a HTTP request tools like HttpRequester for Firefox, Postman for Google Chrome, Fiddler or any other REST client.
Authentication
In this example we use Basic Authentication. The username and password should be combined like username:password, for example test@test.com:test123. This string should be converted to bytes (using UTF8-encoding) and converted back to string using Base64, and preceded by the term "Basic ". When applied to the example, it should look like "Basic dGVzdEB0ZXN0LmNvbTp0ZXN0MTIz". In every request to CCP, the header "Authorization" should be present having this value. which you will see in the request headers. The following roles have rights to create a booking: Transporter, Contractor, Agent and Traveler.
Our first Booking
Let’s create our first Booking. There are quite a lot of properties in the Booking entity. Most of these are optional and mostly used in a specific context. For our first Booking we keep things simple. We create a booking where we want an immediate pickup at our home address of "Wilhelminapark 36 TILBURG" without a destination address.
First we need to add the appropriate headers to the request
POST /CCPService/DataServiceCCP.svc/Bookings HTTP/1.1
Authorization: Basic dGVzdEB0ZXN0LmNvbTp0ZXN0MTIz
Accept: application/json
Content-Type: application/json
DataServiceVersion: 3.0
MaxDataServiceVersion: 3.0
CabmanCloudPlatformVersion: 17.0
MaxCabmanCloudPlatformVersion: 17.0
Then add the fields in json format to the body
{
"BookingDateTimeTypeName" : "Immediately",
"BookingScopeName" : "To",
"VehicleKindName" : "Taxi",
"StatusName" : "New",
"PickUpAddress" : {
"Street" : "Wilhelminapark",
"Number" : 36,
"Town" : "TILBURG"
}
}
There are a few extra fields required for our simple booking to complete successfully. These are the fields:
- StatusName. Just fill it with a value of "New".
- BookingScopeName. Fill it with either "To" or "Return".
- VehicleKindName. Fill it with either "Taxi" or "Van". CCP needs to know if the user wants a normal taxi or a larger vehicle.
- PickupAddress. An AddressFragment complex type used in many entities.
Execute the request. The result should have an HTTP responsecode 201 (created) and the body should contain the created booking. The following headers are also part of the response:
CabmanCloudPlatformVersion: 17.0
Cache-Control: no-cache
Content-Length: 1155
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
DataServiceVersion: 3.0;
Date: Wed, 03 Dec 2014 14:43:54 GMT
Location: https://www.cabmanonline.nl/CCPService/DataServiceCCP.svc/Bookings(3198)
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Content-Type-Options: nosniff
X-Powered-By: ASP.NET
Most headers are self-explanatory and shows information about the server and the size and content-type of the returned data. The most interesting header is de location header. This header contains the fully qualified URL that points to the new resource. You can use this URL to request or update the booking we just created.
If you don't want the newly created booking to be returned in the response (e.g. to conserve bandwidth), just add HTTP request header "Prefer" with the value "return-no-content" to POST-request. In other cases the response body should look like:
{
"odata.metadata" : "https://www.cabmanonline.nl/CCPService/DataServiceCCP.svc/$metadata#Bookings/@Element",
"BookingId" : 3198,
"BookingDateTime" : "2014-12-03T15:43:54.6982187+01:00",
"BookingDateTimeTypeName" : "Immediately",
"StatusName" : "New",
"LastUpdated" : "2014-12-03T14:43:54.9482187Z",
"TravelerCount" : 1,
"Deleted" : false,
"PhoneNumber" : null,
"EmailAddress" : null,
"NotificationLanguage" : null,
"Remark" : null,
"CostCenterId" : null,
"Reference" : null,
"FlightNumberPickUp" : null,
"FlightNumberDropOff" : null,
"PaymentTypeName" : null,
"DayCode" : "D0001",
"Amount" : null,
"PlannedDistance" : null,
"PlannedDuration" : null,
"VehicleKindName" : "Taxi",
"TravelerId" : null,
"ContractorId" : null,
"AgentId" : null,
"TransporterId" : 1,
"AssignedTransporterId" : null,
"PickUpLocationId" : null,
"DropOffLocationId" : null,
"PickUpAddress" : {
"Street" : "Wilhelminapark",
"Number" : 36,
"Addition" : null,
"PostalCode" : null,
"State" : null,
"Town" : "TILBURG",
"CountryId" : null,
"Coordinates" : {
"Latitude" : null,
"Longitude" : null,
"FormatName" : null
}
},
"DropOffAddress" : {
"Street" : null,
"Number" : null,
"Addition" : null,
"PostalCode" : null,
"State" : null,
"Town" : null,
"CountryId" : null,
"Coordinates" : {
"Latitude" : null,
"Longitude" : null,
"FormatName" : null
}
}
}
Locating the booking in Cabman Online shows the same information
The response has a number of extra fields that are automatically filled by CCP. The most important ones are "BookingId", "DayCode" and "TransporterId". "BookingId" contains the unique identification CCP has assigned to the booking. After the POST the Booking can be requested using a GET on /Bookings(<bookingid>). In our example the "BookingId" is 3198 so the request would be /Bookings(3198). "DayCode" is a property that is automatically generated by the server. This property can be used as an identification that is easier to remember. Finally "TransporterId" contains the id of the transporter that owns the booking. Even if other roles create a booking, the corresponding "TransporterId" is assigned. This way the booking can be routed to the right transporter inside CCP. Also note that the BookingDateTime field is automatically filled with the time that the booking is created in CCP. This is because the BookingDateTimeTypeName property had a value of "Immediately".
Canceling or modifying a booking
It can happen that a booking has to be cancelled or details have to be changed on the booking, after the booking has been created. The following rules apply:
- As long as the StatusName of the booking is "New", the booking can be updated of cancelled without further consequences. Examples how to execute a request to update or cancel a booking are detailed below.
The software that is used to let the Transporter Accept or Reject the booking should always present a refreshed booking, so the possible changes are reflected as well. - Bookings that have the StatusName "Declined" cannot be updated nor cancelled.
- Bookings that have the StatusName "Confirmed" can only be modified if none the possible TripRules are being violated. The Transporter who will execute the Trip can specify one or more TripRules to limit the possibities of updating or cancelling. For more details about TripRules, see the tutorial Handling a booking
- It is not allowed to modify the StatusName-field of the Booking. The StatusName changes automatically as a result of the Actions that will be executed.
Update example
Let’s assume it’s allowed to change the booking (no TripRules are being violated). From our POST we received the unique BookingId of our booking so we can use that to update the entity. There are two ways to update an entity using OData. PUT and MERGE/PATCH. The main difference is that PUT is a replacement update (should contain all fields) and MERGE/PATCH a differential update (containing just the modified fields). MERGE/PATCH is recommended because the request will be more compact, and it avoids the risk of reverting changes to properties that were updated in the meantime.
In this example we’ll update a field in the booking. We’ll change the TravelerCount property from 1 to 2. The following request is sent to the server. As you can see the URI is not /Bookings but pointed directly at the specific booking 3198.
PATCH /CCPService/DataServiceCCP.svc/Bookings(3198) HTTP/1.1
Host: www.cabmanonline.nl
Authorization: Basic dGVzdEB0ZXN0LmNvbTp0ZXN0MTIz
Accept: application/json
Content-Type: application/json
DataServiceVersion: 3.0
MaxDataServiceVersion: 3.0
CabmanCloudPlatformVersion: 17.0
MaxCabmanCloudPlatformVersion: 17.0
{
"TravelerCount": 2
}
When successful, the server returns a HTTP status code 204 (no content) and without anything in the body. When we request the booking using a GET you can see that the TravelerCount has changed to 2 and the other fields are not changed. The only other field that’s changed is "LastUpdated". This field is very important when synchronizing bookings with your own data-store. This is outside the scope of this tutorial. There are other tutorials that use synchronization. Also note that the coordinates are calculated for our pickup address. This is done automatically by CCP.
{
"odata.metadata" : "https://www.cabmanonline.nl/CCPService/DataServiceCCP.svc/$metadata#Bookings/@Element",
"BookingId" : 3198,
"BookingDateTime" : "2014-12-03T15:43:54.697",
"BookingDateTimeTypeName" : "Immediately",
"StatusName" : "New",
"LastUpdated" : "2014-12-03T16:13:13.23",
"TravelerCount" : 2,
"Deleted" : false,
"PhoneNumber" : null,
"EmailAddress" : null,
"NotificationLanguage" : null,
"Remark" : null,
"CostCenterId" : null,
"Reference" : null,
"FlightNumberPickUp" : null,
"FlightNumberDropOff" : null,
"PaymentTypeName" : null,
"DayCode" : "D0001",
"Amount" : null,
"PlannedDistance" : null,
"PlannedDuration" : null,
"VehicleKindName" : "Taxi",
"TravelerId" : null,
"ContractorId" : null,
"AgentId" : null,
"TransporterId" : 1,
"AssignedTransporterId" : null,
"PickUpLocationId" : null,
"DropOffLocationId" : null,
"PickUpAddress" : {
"Street" : "Wilhelminapark",
"Number" : 36,
"Addition" : null,
"PostalCode" : null,
"State" : null,
"Town" : "TILBURG",
"CountryId" : null,
"Coordinates" : {
"Latitude" : 51.565360000000005,
"Longitude" : 5.0777600000000005,
"FormatName" : "GeoDecimal"
}
},
"DropOffAddress" : {
"Street" : null,
"Number" : null,
"Addition" : null,
"PostalCode" : null,
"State" : null,
"Town" : null,
"CountryId" : null,
"Coordinates" : {
"Latitude" : null,
"Longitude" : null,
"FormatName" : null
}
}
}
Cancel example
CCP has an Action for cancelling a booking: CancelBooking. When looking at the reference of this function this needs to be in a POST request and has 1 parameter called bookingId.
To cancel our booking we need the same headers as when creating the booking
POST /CCPService/DataServiceCCP.svc/CancelBooking HTTP/1.1
Host: www.cabmanonline.nl
Authorization: Basic dGVzdEB0ZXN0LmNvbTp0ZXN0MTIz
Accept: application/json
Content-Type: application/json
DataServiceVersion: 3.0
MaxDataServiceVersion: 3.0
CabmanCloudPlatformVersion: 17.0
MaxCabmanCloudPlatformVersion: 17.0
Include the following body
{
"bookingId": 3198
}
Execute the request and the server responds with a HTTP result 204 (no content). This confirms that the booking has been canceled. If it’s not allowed to cancel the booking, an error is returned by use of the standard http response codes depending on the type of error (response codes 300 and up).
What happens next?
After creating the booking, it needs to be processed by the Transporter, either by accepting the Booking or rejecting the booking. This process is described in the next tutorial Handling a booking.
For the creator of the Booking it might be interesting to follow these events:
- When a Booking is rejected, its StatusName changes to "Declined". This is a final state, the Booking cannot be altered anymore.
- When a Booking is accepted, its StatusName changes to "Confirmed", and one or more Trips are being created. Those Trips and their underlaying Movements are interesting to use as feedback to the user, as they contain a StatusName, a PlannedDistance [km] the planned pickup- and dropofftime, etc. Especially the "InTransit"-state of Trip is interesting, which indicates that the driver is on its way.