Article

Getting started with Milefy API v3.0

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:

  1. Elementary knowledge of server-side programming language such as PHP,
  2. Apache 2.0 server or similar for running the flight search engine app with installed PHP5,
  3. 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.

Successfully cloned application ran in the browser.

Try to select different flight searches from the dropdown and display results using Search button.

These are real flight search results, but without mileage earnings. We are going to fix it.

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?

  1. User selects one of available searches,
  2. User hits Search button,
  3. Browser sends request to load flight search results using /api/search/single.php script,
  4. Request is passed to external flight search API,
  5. Response from external flight search API containing results is loaded,
  6. Parameters for mileage calculation are collected from the received flight results,
  7. Milefy API Calculate frequent flyer info method is invoked along with 2 other supplimentary methods,
  8. Response from Milefy API is parsed and combined with flight information received in step 5,
  9. Flight results including mileage earnings are embeded into HTML template partials/results.php,
  10. Ready HTML with flight results is sent back to the browser,
  11. 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:

  1. Number of earned award miles,
  2. Name of these award miles as branded by the applicable frequent flyer program,
  3. 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.

Ready flight search result page with mileage earnings.

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.