In this “How-to” guide, we will show you how to test MLE (Message Level Encryption) enabled APIs using PHP.
Important Links:
Encryption Guide https ://developer.visa.com/pages/encryption_guide
Working with Two Way SSL https://developer.visa.com/pages/working-with-visa-apis/two-way-ssl
Login to your Visa Developer Dashboard and go to your project, you should see something like this:
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:
You can obtain your project credentials by browsing the left side navigation menu of your project and click on “Credentials”.
Next step we will create a Key-ID by clicking on the Generate Key-ID button.
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
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.
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.
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.
To be able to make an API call with MLE, you need to have the following
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
{
"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
$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";
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.
Solved! Go to Solution
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.
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.