This tutorial shows how to integrate Milefy API into your flight search app in just a few minutes. Our goal is to display number of award miles, that traveler is going to earn by taking particular flight. You may treat it as 30K’s “Hello world” example.
Prerequisites
This tutorial uses as a base simple flight search engine project, that is written primarily in PHP. Because of that you need:
- Elementary knowledge of server-side programming language such as PHP,
- Apache 2.0 server or similar for running the flight search engine app with installed PHP5,
- Installed Git to clone the project.
1. Setup
Clone Getting starged with 30K API project:
git clone git@github.com:30kcom/getting-started-30k-api.git .
To verify, that your copy is working, launch your web server and access index.html
file in your browser. You should see loaded application just like on a screenshot below.
Try to select different flight searches from the dropdown and display results using Search button.
2. API credentials
In order to use Milefy API, you need first to request the credentials: API key and user data encryption key. In this tutorial we are going to use only API key. If you haven’t done this before, request Milefy API testing credentials:
Request Milefy API testing credentials
3. Workflow
We will keep things simple and add only one piece of functionality — number of award miles for every flight result. How would that work?
- User selects one of available searches,
- User hits Search button,
- Browser sends request to load flight search results using
/api/search/single.php
script, - Request is passed to external flight search API,
- Response from external flight search API containing results is loaded,
- Parameters for mileage calculation are collected from the received flight results,
- Milefy API Calculate frequent flyer info method is invoked along with 2 other supplimentary methods,
- Response from Milefy API is parsed and combined with flight information received in step 5,
- Flight results including mileage earnings are embeded into HTML template
partials/results.php
, - Ready HTML with flight results is sent back to the browser,
- Results are injected into the web page.
Looks complicated? In fact everything, but steps 6-8 are already done, so let’s focus just on these missing bits.
4. HTML template
First of all, we’re going to modify the HTML template, that is used to present flight search results. We will add there placeholder for mileage earnings.
Open partials/results.php
file and search for the part including code:
<div class="flight-header">
<span class="flight-price"><?php echo $this->_getPrice($flight); ?></span>
<!-- FREQUENT FLYER INFO PLACEHOLDER -->
</div>
Replace the FREQUENT FLYER INFO PLACEHOLDER
comment with:
<!-- FREQUENT FLYER INFO PLACEHOLDER -->
<?php if(isset($flight['frequentFlyer'])): ?>
<span class="flight-freq-flyer">
<strong><?php echo $flight['frequentFlyer']['program']; ?>:</strong>
<span class="flight-miles"><?php echo $flight['frequentFlyer']['awardMilesValue']; ?></span>
<span class="flight-mile-type"><?php echo $flight['frequentFlyer']['awardMilesName']; ?></span>
</span>
<?php else: ?>
<span class="flight-no-miles">No earnings :(</span>
<?php endif; ?>
<!-- FREQUENT FLYER INFO PLACEHOLDER -->
This code will be responsible to display three things in our template:
- Number of earned award miles,
- Name of these award miles as branded by the applicable frequent flyer program,
- Name of a frequent flyer program, that is default for the marketing airline of a particular flight.
This information is going to be displayed only if flight earns some miles. Otherwise we’ll write No earnings :( message.
5. Injecting frequent flyer info
Now, when we have ready HTML template, we need to provide data for it. There is already FlightResults
class, that does that for flight results information. Let’s open api/FlightResults.php
file and modify it to include there frequent flyer data too.
public function __construct($response){
$this->_helper = new Helper();
$this->_response = $response;
}
Constructor of the FlightResults
class takes as argument response with flight information. This is later used in toHtml
method, that produces final markup. We’re going to re-write constructor logic to inject frequent flyer information into _response
property:
public function __construct($response){
$this->_helper = new Helper();
// appends frequent flyer information to flight search results data
$this->_response = $this->_appendFrequentFlyerInfo($response);
}
We need to add _appendFrequentFlyerInfo
method too:
protected function _appendFrequentFlyerInfo($response){
// validates flight search results
if(!is_array($response['flights']) || count($response['flights']) <= 0)="" return="" $response;="" creates="" milefy="" api="" client="" $milefyapiclient="new" milefyapiclient($response['flights']);="" fetches="" array="" of="" flights="" containing="" frequent="" flyer="" info="" $milefyflights="$milefyApiClient-">getFlights();
if(!$milefyFlights) return $response;
// for every flight append frequent flyer info if exists
foreach($response['flights'] as &$flight){
// find flight with frequent flyer info
$milefyFlight = $this->_helper->find($milefyFlights, $flight['flightId'], 'id');
// no frequent flyer info?
if(!$milefyFlight) continue;
// append frequent flyer info to the flight
$flight['frequentFlyer'] = $milefyFlight;
}
return $response;
}
=>
This method simply merges each flight from result set with computed mileage earnings referenced by $milefyApiClient
object. Each flight has its own ID, that is used to match it with its mileage earnings returned by Milefy API.
Finally the frequent flyer information is stored under frequentFlyer
key for every flight to be used in HTML template.
Since we’re going to use MilefyApiClient
class, we need to include it into the script. Add this line just after PHP opening tag:
require('MilefyApiClient.php');
6. Milefy API client
In the step before we used MilefyApiClient
class, that does not exist yet. We’re going to add it right now. It’s going to perform communication with Milefy API and produce array of flight IDs with their respective mileage earnings.
Create a file named MilefyApiClient.php
inside api
folder and copy the code below:
<?php
class MilefyApiClient{
public function __construct($flightResults){
session_start();
// Read Milefy API credentials stored as environmental variables
$this->_apiBaseUrl = getenv('MILEFY_BASE_URL');
$this->_apiKey = getenv('MILEFY_KEY');
// Store flight search results info
$this->_flightResults = $flightResults;
$this->_helper = new Helper();
}
public function getFlights(){
}
// Flight search results - list of flights
protected $_flightResults = null;
// A helper object
protected $_helper = null;
// Milefy API access credentials
protected $_apiBaseUrl;
protected $_apiKey;
// Default error
const DEFAULT_FAILURE_MESSAGE = 'Unknown processing error.';
// Default timeout
const REQUEST_TIMEOUT = 120000;
}
MilefyApiClient
class contains just a few lines of code for the initialization: creates PHP session, copies API credentials from environmental variables to local ones and stores flight results for further reference.
Now we need to create a factory method, that will return us object capable of sending HTTP requests to Milefy API. We’ll name it _createHttpClient
and specify two parameters it uses: HTTP method and API endpoint.
protected function _createHttpClient($method, $endpoint){
$client = EasyRequest::create($this->_apiBaseUrl . $endpoint, $method);
$client
// Basic authentication using API credentials
->withQuery('apiKey', $this->_apiKey)
// Headers sent with every request
->withHeader(self::$_DEFAULT_HEADERS)
// Timeout to prevent request termination
->withTimeout(self::REQUEST_TIMEOUT);
return $client;
}
This way we already set up authentication for the Milefy API using apiKey
query string parameter. Additionally we set request headers to default values:
protected static $_DEFAULT_HEADERS = array(
'Accept-Language' => 'en-US,en;q=1',
'Accept' => 'application/hal+json;q=1, application/json;q=0.8',
'Content-Type'=> 'application/json;charset=UTF-8',
'X-Api-Version' => 'v3.0'
);
By setting Accept
header to application/hal+json
you will receive responses including HAL hypermedia links. This is especially useful to navigate between different endpoints without a need to hard-code them.
Make sure to set properly X-Api-Version
header, because if absent, you will receive as fallback, eldest version of our API.
7. Create a traveler
Now when you can send API calls using client generated in _createHttpClient
method, you can start with the first request, that creates a traveler.
Traveler ID is a required parameter for the Calculate frequent flyer info method. Having traveler profile assinged to your user allows you to personalize calculations depending on traveler’s frequent flyer program memberships. This is fully explained in Milefy API integration guide. In this tutorial we won’t use this functionality and return only mileage earnings for the default frequent flyer program. In other words, our traveler profile will remain empty without any memberships.
How to create a traveler? In the simpliest case you need to send POST
request to /travelers
endpoint (see documentation). That’s what we’re going to do in the new method added to the MilefyApiClient
:
protected function _createTraveler(){
$client = $this->_createHttpClient('POST', '/travelers');
// Empty object is just enough if you don't know country of traveler's residence
$client->withBody('{}');
$client->send();
// HTTP status code validation
if($client->getResponseStatus() < 400){
$response = json_decode($client->getResponseBody());
// response body validation
if(isset($response) && isset($response->id)){
// success
return $response;
}else{
// failure
return false;
}
}else{
// failure
return false;
}
}
There is no need to create a new traveler every time you need to calculate miles. One traveler can make many flight searches. He can even leave your website or application and return later. We recommend to identify returning users and re-use their existing traveler profiles. For this purpose we’ll add another method to the MilefyApiClient
class:
protected function _getTravelerId(){
// for demo purposes our user id is stored only in cookie
if(!isset($_COOKIE['travelerId'])){
$traveler = $this->_createTraveler();
setcookie('travelerId', $traveler->id, time() + 3600 * 24 * 365);
}
return $_COOKIE['travelerId'];
}
The _getTravelerId
method serves as a proxy, trying first to identify existing user with his traveler ID stored in the cookie
and then creating new traveler if there is none available.
8. Calculate mileage earnings
The Calculate frequent flyer info method requires only two parameters: traveler ID and flight itineraries to compute miles for. We have both of them, so we can extend content getFlights
method using following code:
public function getFlights(){
// creates HTTP client to communicate with Milefy API
// CalculateMiles API method returns mileage earnings for specified list of flights
$client = $this->_createHttpClient('POST', '/calculate');
// Traveler ID for individual user can be stored in database with user account or in cookie for temporary visitors.
// It's required and needs to be created with Create traveler method first.
$client->withQuery('traveler', $this->_getTravelerId());
// limits response size returning only mileage earnings (optimizes performance)
$client->withQuery('fields', 'id,flights(id,programs(code,statusTiers(code,mileageEarnings)))');
// generates CalculateMiles request body
$body = $this->_getCalculateRequestBody();
// appends request body to the request
$client->withJson($body);
// sends CalculateMiles request to Milefy API
$client->send();
// response HTTP status code validation
if($client->getResponseStatus() < 400){
$response = json_decode($client->getResponseBody());
// response body validation
if(isset($response) && is_array($response->flights)){
// success
// returns award miles for calculated flights
return $this->_getFlightsAwardMiles($response->flights);
}else{
// failure
return false;
}
}else{
// failure
return false;
}
}
The method sends POST
request to the /calculate
method with two query parameters set: travelerId
and fields
as well as request body containing flight itineraries returned by _getCalculateRequestBody
method.
We added fields
parameter to limit response size and improve performance. Our goal is to display only mileage earnings, so we can exclude other API features such as status benefits or cabin upgrades. You can refer to the API documentation to see how to construct value for this parameter.
Finally successful response is processed by _getFlightsAwardMiles
method to return mileage earnings in the simple form of flat array.
Request body
How to prepare request body for the Calculate frequent flyer info method? This code snippet should be self-explainatory:
protected function _getCalculateRequestBody(){
$body = array(
// List of flights to calculate miles for. Required.
'flights' => array()
);
// iterate flights to calculate miles for...
foreach($this->_flightResults as $flight){
$bodyFlight = array(
'id' => $flight['flightId'], // Flight ID. Required.
'price' => array( // Price. Required.
'currency' => $flight['price']['currencyCode'], // Currency code (3 letters). Required.
'total' => $flight['price']['total'], // Total price. Required.
'baseFare' => $flight['price']['fare'], // Base fare. Recommended.
'taxes' => $flight['price']['taxes'], // Taxes. Recommended.
'airlineSurcharges' => $flight['price']['surcharges'] // Airline surcharges. Recommended.
),
'legs' => array() // Flight legs. Round trip has two, one-way one. Required.
);
// iterate throught flight legs
foreach($flight['legs'] as $leg){
$bodyLeg = array(
'id' => $leg['legId'], // Flight leg id. Required.
'segments' => array() // Flight segments - every pair of departure and landing. Required.
);
// iterate through flight segments
foreach($leg['segments'] as $segment){
$bodySegment = array(
'id' => $segment['segmentId'], // Segment Id. Required
'marketingAirline' => $segment['marketingAirlineCode'], // Marketing airline IATA code. Required.
'operatingAirline' => $segment['operatingAirlineCode'], // Operating airline IATA code. Required.
'departureAirport' => $segment['deptCode'], // Departure airport IATA code. Required.
'arrivalAirport' => $segment['destCode'], // Destination airport IATA code. Required.
'departureDate' => $segment['deptDate'], // Departure date in format: YYYY-MM-DD. Required.
'bookingClass' => $segment['fareCode'], // Booking class code, a single letter. Required.
'flightNumber' => $segment['flightNumber'], // Flight number, just digits. Required.
'fareBasisCode' => $segment['fareBasisCode'], // Fare basis code. Recommended.
'distance' => $segment['distance'] // Distance in miles. Recommended.
);
$bodyLeg['segments'][] = $bodySegment;
}
$bodyFlight['legs'][] = $bodyLeg;
}
$body['flights'][] = $bodyFlight;
}
return $body;
}
9. Format mileage earnings
There is only one task left — to transform response from Calculate frequent flyer info method into simple array with itineraries containing number of award miles, name for this mile type as well as program name. That’s what _getFlightsAwardMiles
method is responsible for.
The API response, that we got in the previous step doesn’t contain frequent flyer program name, neither name of the award miles for this program. In order to get these, we need to use another API method — Get program collection. It returns list of all supported frequent flyer programs. This list can (and should) be cached on client. Recommended period to store this data is 24 hours. In this tutorial we’ll use just simple cache in PHP session:
public function getPrograms(){
if(!isset($_SESSION['programs']) || !is_array($_SESSION['programs'])){
// create HTTP client to connect with Milefy API
// using Get program collection method
$client = $this->_createHttpClient('GET', '/programs');
$client->send();
// HTTP status code validation
if($client->getResponseStatus() < 400){
$response = json_decode($client->getResponseBody());
// response body validation
if(isset($response) && $response->_embedded && is_array($response->_embedded->programs)){
// success
// list of programs stored in cache
$_SESSION['programs'] = json_encode($response->_embedded->programs);
}else{
// failure
return false;
}
}else{
// failure
return false;
}
}
// from cache
return json_decode($_SESSION['programs']);
}
Now, when we have access to list of supported frequent flyer programs, we should define which type of mile do we want to display to the user. In Milefy API we use constants to identify types of miles (see documentation). Let’s use them to identify award miles in our code:
// Code of award miles in Milefy API
const AWARD_MILES_CODE = 1;
Finally we can return mileage earnings with necessary metadata using _getFlightsAwardMiles
method:
protected function _getFlightsAwardMiles($responseFlights){
// result
$flights = [];
// returns list of supported frequent flyer programs in Milefy API
$programs = $this->getPrograms();
if(!is_array($programs)) return false;
// for every flight in CalculateMiles mehtod response
foreach($responseFlights as $responseFlight){
// Skip flights with no frequent flyer program specified.
// There might be more than one program earning miles to one flight, but
// for the sake of simplicity we will display just one.
if(!is_array($responseFlight->programs) || count($responseFlight->programs) <= 0)="" continue;="" $flight="array(" 'id'=""> $responseFlight->id
);
// skip flights with no mileage earnings
$responseProgram = $responseFlight->programs[0];
// ensure program has at least one status tier for which calculations has been made
if(count($responseProgram->statusTiers) <= 0)="" continue;="" $responsestatus="$responseProgram-">statusTiers[0];
// skip programs without mileage earnings
if(!is_array($responseStatus->mileageEarnings) || count($responseStatus->mileageEarnings) <= 0)="" continue;="" skip="" flights="" with="" no="" award="" miles="" earned="" $responseawardmiles="$this-">_helper->find($responseStatus->mileageEarnings, self::AWARD_MILES_CODE, 'code');
if(!$responseAwardMiles) continue;
// fetch frequent flyer program details
$program = $this->_helper->find($programs, $responseProgram->code, 'code');
if(!$program || !is_array($program->mileTypes)) continue;
// fetch award miles name specific for the program
$awardMiles = $this->_helper->find($program->mileTypes, self::AWARD_MILES_CODE, 'code');
if(!$awardMiles) continue;
// for majority of programs mileage earnings are integer values,
// but sometimes there might be decimals too!
$decimal = ($responseAwardMiles->value * 100) % 100;
$precision = $decimal > 0 ? strlen(strval($decimal)) : 0;
// save number of earned award miles, name of this type of miles and name of default frequent flyer program
$flight['awardMilesValue'] = number_format($responseAwardMiles->value, $decimal);
$flight['program'] = $program->name;
$flight['awardMilesName'] = $awardMiles->name;
$flights[] = $flight;
}
return $flights;
}
=>=>=>
10. Ready!
Now you can test your integration with Milefy API. Run the server and open index.html
in your browser. If everything is all right, you should see mileage earnings on every flight result.
What’s next?
Discover how to use other features included into Milefy API and how you can enchance the user experience with different levels of personalization.