How to run PHP Sample Code for MLE

Solved! Go to solution
shameem
Visa Employee

How to run PHP Sample Code for MLE

In this “How-to” guide, we will show you how to test MLE (Message Level Encryption) enabled APIs using PHP.

 

Important Links:

 

Enable MLE for the API(s) you are interested in.

 

Login to your Visa Developer Dashboard and go to your project, you should see something like this:

 

2020-11-11_09-45-12.png

 

Enable the APIs for which MLE needs to be active in VDP by toggling the API for which MLE needs to be enforced.

 

In this example, we will enable MLE for Funds Transfer API And Query API as below:

 

2020-11-11_09-46-41.png

 

 

How to get credentials

 

You can obtain your project credentials by browsing the left side navigation menu of your project and click on “Credentials”.

 

2020-11-11_11-25-10.png

 

Next step we will create a Key-ID by clicking on the Generate Key-ID button.

 

2020-11-11_11-29-02.png

 

After you have clicked the button, you will get Key-ID. Copy the Key-ID for your reference.

The Key-ID will look like this: 41d9f2a1-xxxx-4xxx-b40c-a0480c2xxxxx

 

2020-11-11_11-30-49.png

 

The next step is to add a CSR (Certificate Signing Request). Click on the link "Add CSR" .   

 

You will be prompt to submit a Certificate Signing Request. 

 

2020-11-11_11-32-46.png

 

We have option to Generate a CSR for me (default) or submit your own. In this example we will use the Generate a CSR for me (default) and Click Confirm button.

 

After submitting the request, you will be prompt to download the Certificate/Copy Private Key. 

 

2020-11-11_11-34-26.png

 

After you have downloaded the private key, check the box "I confirm that I've downloaded my certificate key" and click continue. You will see the Status change to "Active".

 

 

Expand the Key-ID and you will see the Server Encryption Certificate and Client Encryption Certificate.

Download both certificates and save it.

 

2020-11-11_11-36-33.png

To be able to make an API call with MLE, you need to have the following

  • Server Encryption Certificate
  • Key-ID
  • Certificate Private Key

 

How to run PHP Sample Code for MLE

 

 

Step 1 - Create a new project on PhpStorm

 

  • Launch PhpStorm, on the WebStorm Welcome Screen, click Create New Project.
  • In the New Project wizard, select Composer Project from the list on the left.
  • Provide the project Location
  • Select your composer executable or download the composer.phar and add it to PhpStorm
  • Select your PHP Interpreter,  click Create.

 

Refer to How to run PHP Sample Code using the Hello World API and Mutual SSL for how to create a project with WebStorm and testing VISA APIs using Mutual SSL

 

 

Step 2 - Adding the project/composer dependencies

 

  • Edit the composer.json file
  • Add the below required dependencies

 

 

 

 

 

 

{
 "require": {
    "ext-curl": "*",
    "ext-json": "*",
    "web-token/jwt-framework": "^2.2"
  }
}

 

 

 

 

 

 

PhpStorm will prompt to "Update" if no click "Update:", to update and install the composer dependencies 

 

 

Step 3 - Create a New PHP file "mle_oct.php" and copy the below sample code to the file

 

  • Set the below parameters:

$username = '<YOUR USER ID>';
$password = '<YOUR PASSWORD>';

$client_cert = '<YOUR MUTUAL SSL CLIENT CERTIFICATE PATH>';
$private_key = '<YOUR MUTUAL SSL PRIVATE KEY PATH>';

$mleClientPrivateKeyPath = '<YOUR MLE CLIENT PRIVATE KEY PATH>';
$mleClientPublicCertificatePath = '<YOUR MLE SSL CLIENT CERTIFICATE PATH>';
$mleServerPublicCertificatePath = '<YOUR MLE SERVER CERTIFICATE PATH>';
$keyId = '<YOUR KEY ID>';

 

 

 

 

 

 

<?php

include_once __DIR__ . '/vendor/autoload.php';

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A128GCM;
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256;
use Jose\Component\Encryption\Compression\CompressionMethodManager;
use Jose\Component\Encryption\Compression\Deflate;
use Jose\Component\Encryption\JWEBuilder;
use Jose\Component\Encryption\JWEDecrypter;
use Jose\Component\Encryption\JWELoader;
use Jose\Component\Encryption\Serializer\CompactSerializer;
use Jose\Component\Encryption\Serializer\JWESerializerManager;
use Jose\Component\KeyManagement\JWKFactory;

$username = '<YOUR USER ID>';
$password = '<YOUR PASSWORD>';

$client_cert = '<YOUR MUTUAL SSL CLIENT CERTIFICATE PATH>';
$private_key = '<YOUR MUTUAL SSL PRIVATE KEY PATH>';

$mleClientPrivateKeyPath = '<YOUR MLE CLIENT PRIVATE KEY PATH>';
$mleClientPublicCertificatePath = '<YOUR MLE SSL CLIENT CERTIFICATE PATH>';
$mleServerPublicCertificatePath = '<YOUR MLE SERVER CERTIFICATE PATH>';
$keyId = '<YOUR KEY ID>';


/**
 * This method will encrypt the payload and create a JWE token
 *
 * @param $payload
 * @param $keyId
 * @param $mleServerPublicCertificatePath
 * @return false|string
 */
function encryptPayload($payload, $keyId, $mleServerPublicCertificatePath)
{

    // The key encryption algorithm manager with the RSA-OAEP-256 algorithm.
    $keyEncryptionAlgorithmManager = new AlgorithmManager([new RSAOAEP256(),]);

    // The content encryption algorithm manager with the A128GCM algorithm.
    $contentEncryptionAlgorithmManager = new AlgorithmManager([new A128GCM(),]);

    // The compression method manager with the DEF (Deflate) method.
    $compressionMethodManager = new CompressionMethodManager([new Deflate(),]);

    // We instantiate our JWE Builder.
    $jweBuilder = new JWEBuilder($keyEncryptionAlgorithmManager, $contentEncryptionAlgorithmManager, $compressionMethodManager);

    // Our key.
    $jwk = JWKFactory::createFromCertificateFile($mleServerPublicCertificatePath);

    $milliseconds = round(microtime(true) * 1000);

    $jwe = $jweBuilder
        ->create()              // We want to create a new JWE
        ->withPayload($payload) // We set the payload
        ->withSharedProtectedHeader([
            'alg' => 'RSA-OAEP-256',  // Key Encryption Algorithm
            'enc' => 'A128GCM',       // Content Encryption Algorithm
            'iat' => $milliseconds,   // Current Time Stamp in milliseconds
            'kid' => $keyId
        ])
        ->addRecipient($jwk)    // We add a recipient (a shared key or public key).
        ->build();              // We build it

    $serializer = new CompactSerializer();
    $token = $serializer->serialize($jwe, 0);
    return json_encode(['encData' => $token], JSON_PRETTY_PRINT);
}

/**
 * This method will decrypt the given JWE token.
 *
 * @param $encryptedPayload - JWE Token
 * @param $mleClientPrivateKeyPath
 * @return string|null
 */
function decryptJwe($encryptedPayload, $mleClientPrivateKeyPath)
{
    // The key encryption algorithm manager with the RSA-OAEP-256 algorithm.
    $keyEncryptionAlgorithmManager = new AlgorithmManager([new RSAOAEP256(),]);

    // The content encryption algorithm manager with the A128GCM algorithm.
    $contentEncryptionAlgorithmManager = new AlgorithmManager([new A128GCM(),]);

    // The compression method manager with the DEF (Deflate) method.
    $compressionMethodManager = new CompressionMethodManager([new Deflate(),]);

    // We instantiate our JWE Decrypter.
    $jweDecrypter = new JWEDecrypter($keyEncryptionAlgorithmManager, $contentEncryptionAlgorithmManager, $compressionMethodManager);

    // Our key.
    $jwk = JWKFactory::createFromKeyFile($mleClientPrivateKeyPath);
    $encryptedPayload = json_decode($encryptedPayload, true);

    $token = $encryptedPayload['encData'];

    $serializerManager = new JWESerializerManager([new CompactSerializer(),]);

    $jwe = $serializerManager->unserialize($token);

    $success = $jweDecrypter->decryptUsingKey($jwe, $jwk, 0);
    if ($success) {
        $jweLoader = new JWELoader(
            $serializerManager,
            $jweDecrypter,
            null
        );
        $jwe = $jweLoader->loadAndDecryptWithKey($token, $jwk, $recipient);
        return $jwe->getPayload();
    } else {
        throw new RuntimeException('Error Decrypting JWE');
    }
}

function invokeAPI($url, $method, $username, $password, $keyId, $clientCertPath, $clientPrivateKey, $mleClientPrivateKeyPath, $payload = '')
{
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 2);

    if ($method == 'POST') {
        curl_setopt($curl, CURLOPT_POST, 1);
    }

    if ($payload != '') {
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
    }

    curl_setopt($curl, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json',
        'Accept: application/json',
        'keyId: ' . $keyId
    ));

    curl_setopt($curl, CURLOPT_PORT, 443);
    curl_setopt($curl, CURLOPT_VERBOSE, 0);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 2);
    curl_setopt($curl, CURLOPT_SSLVERSION, 1);
    curl_setopt($curl, CURLOPT_SSLCERT, $clientCertPath);
    curl_setopt($curl, CURLOPT_SSLKEY, $clientPrivateKey);

    curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
    curl_setopt($curl, CURLOPT_USERPWD, $username . ":" . $password);

    //enable headers
    curl_setopt($curl, CURLOPT_HEADER, 1);

    curl_setopt($curl, CURLOPT_URL, $url);

    $response = curl_exec($curl);
    $response_info = curl_getinfo($curl);

    // close curl resource to free up system resources
    $http_header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
    curl_close($curl);

    $http_header = httpParseHeaders(substr($response, 0, $http_header_size));

    // Print all headers as array
    echo "<pre>";
    print_r($http_header);
    echo "</pre>";

    if ($response_info['http_code'] === 0) {
        $curl_error_message = curl_error($curl);
        // curl_exec can sometimes fail but still return a blank message from curl_error().
        if (!empty($curl_error_message)) {
            $error_message = "<br/><br/>API call to $url failed: $curl_error_message";
        } else {
            $error_message = "<br/><br/>API call to $url failed, but for an unknown reason. " .
                "This could happen if you are disconnected from the network.";
        }
        echo $error_message;
    } elseif ($response_info['http_code'] >= 200 && $response_info['http_code'] <= 299) {
        echo "<br/><br/>Your call returned with a success code - " . $response_info['http_code'] . "";
    } else {
        echo "<br/><br/>[HTTP Status: " . $response_info['http_code'] . "]\n[" . $response . "]";
        echo "<br/><br/>Error connecting to the API ($url)";
    }
    return substr($response, $http_header_size);
}

function httpParseHeaders($raw_headers)
{
    $headers = [];
    $key = '';

    foreach (explode("\n", $raw_headers) as $h) {
        $h = explode(':', $h, 2);

        if (isset($h[1])) {
            if (!isset($headers[$h[0]])) {
                $headers[$h[0]] = trim($h[1]);
            } elseif (is_array($headers[$h[0]])) {
                $headers[$h[0]] = array_merge($headers[$h[0]], [trim($h[1])]);
            } else {
                $headers[$h[0]] = array_merge([$headers[$h[0]]], [trim($h[1])]);
            }

            $key = $h[0];
        } else {
            if (substr($h[0], 0, 1) === "\t") {
                $headers[$key] .= "\r\n\t" . trim($h[0]);
            } elseif (!$key) {
                $headers[0] = trim($h[0]);
            }
            trim($h[0]);
        }
    }

    return $headers;
}

$acquiringBin = '408999';
$localTransactionDateTime = date_format(new DateTime(), "Y-m-d\TH:i:s");;

$payload = '{
  "amount": "124.05",
  "senderAddress": "901 Metro Center Blvd",
  "localTransactionDateTime": "' . $localTransactionDateTime . '",
  "pointOfServiceData": {
    "panEntryMode": "90",
    "posConditionCode": "00",
    "motoECIIndicator": "0"
  },
  "recipientPrimaryAccountNumber": "4957030420210496",
  "colombiaNationalServiceData": {
    "addValueTaxReturn": "10.00",
    "taxAmountConsumption": "10.00",
    "nationalNetReimbursementFeeBaseAmount": "20.00",
    "addValueTaxAmount": "10.00",
    "nationalNetMiscAmount": "10.00",
    "countryCodeNationalService": "170",
    "nationalChargebackReason": "11",
    "emvTransactionIndicator": "1",
    "nationalNetMiscAmountType": "A",
    "costTransactionIndicator": "0",
    "nationalReimbursementFee": "20.00"
  },
  "cardAcceptor": {
    "address": {
      "country": "USA",
      "zipCode": "94404",
      "county": "San Mateo",
      "state": "CA"
    },
    "idCode": "CA-IDCode-77765",
    "name": "Visa Inc. USA-Foster City",
    "terminalId": "TID-9999"
  },
  "senderReference": "",
  "transactionIdentifier": "381228649430015",
  "acquirerCountryCode": "840",
  "acquiringBin": "' . $acquiringBin . '",
  "retrievalReferenceNumber": "412770451018",
  "senderCity": "Foster City",
  "senderStateCode": "CA",
  "systemsTraceAuditNumber": "451018",
  "senderName": "Mohammed Qasim",
  "businessApplicationId": "AA",
  "settlementServiceIndicator": "9",
  "merchantCategoryCode": "6012",
  "transactionCurrencyCode": "USD",
  "recipientName": "rohan",
  "senderCountryCode": "124",
  "sourceOfFundsCode": "05",
  "senderAccountNumber": "4653459515756154"
}';

echo '.................Testing MLE With OCT + Query API...........................' . "\xA";

echo '.................START Push Funds Transaction API Call.......................' . "\xA";
$encryptedData = encryptPayload($payload, $keyId, $mleServerPublicCertificatePath);
$url = 'https://sandbox.api.visa.com/visadirect/fundstransfer/v1/pushfundstransactions';
$body = invokeAPI($url, 'POST', $username, $password, $keyId, $client_cert, $private_key, $mleClientPrivateKeyPath, $encryptedData);
$responsePayload = decryptJwe($body, $mleClientPrivateKeyPath);

echo 'Response Payload:' . "\xA";
$pushFundsResponse = json_decode($responsePayload, 1);
print_r($pushFundsResponse);
echo '.................END Push Funds Transaction API Call.........................' . "\xA";

$acquiringBin = '408999';
$transactionIdentifier = $pushFundsResponse['transactionIdentifier'];
$queryString = '?acquiringBIN=' . $acquiringBin . '&transactionIdentifier=' . $transactionIdentifier;
$url = 'https://sandbox.api.visa.com/visadirect/v1/transactionquery' . $queryString;

echo '.................START Query API Call.........................' . "\xA";
$body = invokeAPI($url, 'GET', $username, $password, $keyId, $client_cert, $private_key, $mleClientPrivateKeyPath);
$responsePayload = decryptJwe($body, $mleClientPrivateKeyPath);
echo 'Response Payload:' . "\xA";
print_r(json_decode($responsePayload));
echo '.................END Query API Call.........................' . "\xA";

 

 

 

 

 

 

 

Step 4 - Compile Your Code 

 

  • Simply right click and run “mle_oct.php (PHP Script)"
  • If you are prompt with the “Edit Configuration Screen”,  click on the “Fix” button to configure your PHP Interpreter
  • Once the above step is completed, right click and click Run mle_oct.php (PHP Script)

 

Want more? Join the Visa Developer Community to get alerts on the latest tutorials, guides and new developer resources. Stay tuned for more in the series. 

2 REPLIES 2
dev_api
Helper

Re: How to run PHP Sample Code for MLE

Which php version should the code run on? I run this on php7.3. It seems not working (Type error on the line:

 

$keyEncryptionAlgorithmManager = new AlgorithmManager([new RSAOAEP256(),]);

 

And there are many versions of Jose library. Could not find one that made it work.

Thanks.

shameem
Visa Employee

Re: How to run PHP Sample Code for MLE

Hi @dev_api ,

 

Thank you for trying out the PHP Sample Code for MLE. We ran sample code with PHP v7.3.27,  with the libraries mentioned, and found no issues. 

 

Kindly verify that all the dependencies were downloaded successfully. We are using composer as dependency manager and if you are using the same, you should see under the "vendor" folder, "web-token" --> "jwt-framework" where the files have been downloaded.

 

"ext-curl": "*",
"ext-json": "*",
"web-token/jwt-framework": "^2.2"

 

Let us know if you have any further queries.

 

Thank you.