23 November 2009
To follow all the steps in this tutorial you will need general familiarity with Flex and ActionScript 3.0 as well as PHP. Some knowledge of the Amazon FPS API will also be helpful.
All
You will also need a web server with PHP support and an Amazon developer account.
This article describes a technique for integrating Amazon Flexible Payment Service (Amazon FPS) with a Flex application running in Flash Player and in Adobe AIR. I will cover security and UI considerations and the architectural approach, and then show you how you can implement the whole thing. You can also download working samples to see how everything works together.
For server-side scripting I have used PHP but the techniques demonstrated here can be extended to any server language.
This article focuses on Amazon FPS, but many of the same ideas apply to PayPal Express Checkout as well. For more information on integrating with the PayPal payment service, read Integrating PayPal Express Checkout with Flex and Adobe AIR.
Amazon FPS is designed to integrate with standard, request-based web applications. While this request-based paradigm is successfully used by many current web applications, Rich Internet Applications (RIAs) using a single page paradigm have proved to be better suited for many e-commerce workflows and use cases. The challenge of integrating payment services into RIAs stems from the fact that currently, payment services such as Amazon FPS are designed to work in a request-response paradigm, while RIAs are, of course, stateful.In this article, I describe an approach for integrating Amazon FPS with RIAs to handle the following scenario:
John visits MitiOnDemand.tv, a new on demand video site. He chooses to watch The Matrix. Just when Neo is asked to choose between the red and blue pill, the movie pauses and John is asked for $1, the fee for watching premium content on MitiOnDemand.tv. John, being already caught up in the action, decides to pay the amount using Amazon Payments. After the transaction is completed John happily enters The Matrix.
One reason for the success of e-commerce is that the Internet has proven to be a secure medium for transferring money and making payments. Amazon FPS uses several security mechanisms to make sure that all payments processed through the service are as secure as possible:
Looking at these security elements of Amazon FPS a very important observation can be made regarding protecting the Amazon secret key. Specifically, because Flex is a client technology (and even though the code is compiled into bytecode), hardcoding sensitive information into a Flex application is a highly insecure practice. This means that any signature related computation should not be made in Flex and the Amazon secret key should not, under any circumstance, be put into a Flex application.
Summing this up, the architectural solution has to comply with two concurrent demands:
To meet these requirements, I propose the following approach:
From the buyer’s perspective the Amazon FPS Basic Quick Start workflow using Flex will include the following steps:
Note: From this point forward all requests are performed through HTTPS.
returnURL parameter in the co-branded service request) contains a button labeled “Return to Movie”. The URL contains additional information including the status of the authorization, and a reference, called the TokenId, to the token stored on Amazon servers. That token is used in Amazon FPS transaction actions (such as pay) to actually initiate the transfer of money.Note: The payment transaction is not initiated by Amazon FPS. The MitiOnDemand.tv company must make a pay web service request with the TokenId that the co-branded service returned.
Because the Amazon secret key needs to stay on the server, it makes sense to perform the signature processing on the server as well. For this article, I chose PHP as the server language and also the Amazon FPS PHP SDK. You can use another server language, the principles and techniques highlighted here remain the same.
To call the Amazon CBUI I start by opening the pop-up window from Flex.
//Open the Pop-Up Window first. Using the
//ExternalInterface call we can control the window appereance
ExternalInterface.call('window.open','about:blank','amazonWindow','height=500,width=900,toolbar=no,scrollbars=yes');
The code then sends a request containing the user’s selection to a server page, startPaymentFlex.php, in the newly opened window.
var url:URLRequest = new URLRequest("https://miti.pricope.com/testAmazon/startPaymentFlex.php");
url.data = new URLVariables();
var obj:URLVariables = new URLVariables();
url.data.movieId = moviePick.selectedItem.value;
url.data.paymentReason = 'Enter The Matrix';
url.method = "GET";
navigateToURL(url, "amazonWindow");
Using navigateToURL() assures that if the window.open call doesn’t work (because of a drastic pop-up blocker, for example) the user can still continue the workflow.
On the server page, I generate the Amazon CBUI request and redirect the browser to that request:
session_start();
function getMovieAmount($movieId) {
//you can replace this function with a more sophisticated one
return 1;
}
$obj = new Amazon_FPS_CBUIUtils(AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY);
$obj->setMandatoryParams("SingleUse", "https://" . $_SERVER['HTTP_HOST'] . "/testAmazon/return.php");
//The refernce ID is unique to your business
//You can replace the standard UID php function with more suitable function
$ref = uniqid("amazon");
$obj->setCallerReference($ref);
$obj->setTransactionAmount(getMovieAmount($_GET['movieId']));
$obj->setPaymentReason($_GET['paymentReason']);
$qs = $obj->getURL() ;
//We use session data to store the state of the application between requests
//The amount will be used later on (in return.php) to invoke the FPS Pay method
//We also hold the status of the transaction. This will be requested
//by the Flex App
$_SESSION['status'] = 'START';
$_SESSION['transaction_amount'] = getMovieAmount($_GET['movieId']);
$_SESSION['movieId'] = $_GET['movieId'];
header("Location:$qs")
Note that the code also stores some data in session variables, including the status, transaction amount, and movie ID. These values will be used in the return page to actually invoke the Amazon FPS pay method.
In a real e-commerce site, I strongly suggest that you also log the application state in a database, which will ensure access to all transaction steps later on.
After the user completes the Amazon CBUI workflow and authorizes the transaction he is redirected to the return page that was specified in the returnURL parameter of the co-branded service request. In this case, he is redirected to return.php, which verifies that the returning request is valid and then calls the pay method in the Amazon FPS to initiate the money transfer.
For this step, I again used the Amazon FPS PHP SDK.
function validateQueryString()
{
echo "validing the query string now\n";
$querystring = $_SERVER['QUERY_STRING'];
echo $_GET['signature'];
$obj = new Amazon_FPS_CBUIUtils(AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY);
//Original signature received in response from Amazon FPS should be specified.
$signatureMatched = $obj->validateQueryString($querystring,$_GET['signature']);
if ($signatureMatched) {
echo "signature matched \n";
$request = new Amazon_FPS_Model_PayRequest();
//set the proper senderToken here.
$request->setSenderTokenId($_GET['tokenID']);
$amount = new Amazon_FPS_Model_Amount();
$amount->setCurrencyCode("USD");
//set the transaction amount here;
$amount->setValue($_SESSION['transaction_amount']);
$request->setTransactionAmount($amount);
//set the unique caller reference here.
$request->setCallerReference($_GET['callerReference']);
$service = new Amazon_FPS_Client(AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY);
invokePay($service, $request);
}
else
echo "Signature did not match \n";
}
validateQueryString();
I have used the invokePay function that can be found in the Amazon FPS PHP SDK, with one small change. When the transaction is successful, the code stores the status in a session variable.
function invokePay(Amazon_FPS_Interface $service, $request)
{
try {
$response = $service->pay($request);
echo ("Service Response\n");
echo ("===============================================\n");
echo(" PayResponse\n");
if ($response->isSetPayResult()) {
echo(" PayResult\n");
$payResult = $response->getPayResult();
if ($payResult->isSetTransactionId())
{
echo("TransactionId\n");
echo("" . $payResult->getTransactionId() . "\n");
}
if ($payResult->isSetTransactionStatus())
{
echo("TransactionStatus\n");
echo("" . $payResult->getTransactionStatus() . "\n");
//CHECK Transaction Status is Success
$_SESSION['status'] = 'OK';
}
}
if ($response->isSetResponseMetadata()) {
echo(" ResponseMetadata\n");
$responseMetadata = $response->getResponseMetadata();
if ($responseMetadata->isSetRequestId())
{
echo("RequestId\n");
echo("" . $responseMetadata->getRequestId() . "\n");
}
}
} catch (Amazon_FPS_Exception $ex) {
echo("Caught Exception: " . $ex->getMessage() . "\n");
echo("Response Status Code: " . $ex->getStatusCode() . "\n");
echo("Error Code: " . $ex->getErrorCode() . "\n");
echo("Error Type: " . $ex->getErrorType() . "\n");
echo("Request ID: " . $ex->getRequestId() . "\n");
echo("XML: " . $ex->getXML() . "\n");
}
}
The only thing left to do now is notify the Flex application and close the pop-up window.
First, I need to prepare the Flex application. I create a payment notification function to be called by the pop-up window using the ExternalInterface API. Because the JavaScript call can be overwritten (by using Firebug for instance) the ExternalInterface call is not secure. So I use the call only to notify the Flex application that the Amazon workflow has ended. The Flex application then retrieves the status from the server through HTTPS.
private function paymentNotification():void {
var srv:HTTPService = new HTTPService();
srv.url = "https://miti.pricope.com/testAmazon/paymentStatus.php";
srv.addEventListener(ResultEvent.RESULT,function (event:ResultEvent):void {
Alert.show("Status: " + event.result.status);
});
srv.send();
}
In this case paymentStatus.php is a simple server-side script that returns the status packed in a simple XML format.
session_start();
echo '<status>' . $_SESSION['status'] . '</status>'
The paymentNotification() function must be explicitly exposed through ExternalInterface to be available to JavaScript calls. The best time to add the call back is when the Flex application finishes initializing, and the applicationComplete event handler is a good place for it.
//This will be used by return.php to notify Flex App that the payment has been made
ExternalInterface.addCallback('paymentNotification',paymentNotification);
Back in return.php, the only thing left to do is to close the pop-up window and notify the Flex application.
<script type="text/javascript">
function gotoflex() {
window.opener.window.document.getElementById('testAmazon').paymentNotification();
window.close();
}
</script>
<form>
<input type="button" value="Close This Window and Return to Flex APP" onclick="gotoflex()"/>
</form>
Follow these steps to install the sample files:
define('AWS_ACCESS_KEY_ID', 'YOUR KEY');
define('AWS_SECRET_ACCESS_KEY', 'YOUR SECRET KEY');
Replace the values with your own Amazon access key ID and secret access key.
Note: Because the Flash Player and Adobe AIR implementations described in this article both use Flex, parts of the following discussion repeat concepts introduced earlier.
Although Adobe AIR applications run on the desktop and the local security constraints are different from those in the browser, from a payment gateway point of view things don’t change at all. Because AIR is a client technology, hardcoding sensitive information into an AIR application is a highly insecure practice (even though the code is compiled into bytecode). This means that any signature related computation should not be made in the AIR application and the Amazon secret key should not be put into an AIR application.
As with the Flex application, the architectural solution has to comply with concurrent demands:
To meet these requirements, I propose the following approach:
Note: Although technically it might be possible to use the HTML container of the AIR runtime to access the co-branded page, this is an insecure practice because the end user cannot visually verify that he is entering his credentials on the Amazon site. In the browser he can check the URL and the security certificate, which provides an effective anti-phishing measure.
Here is how to open the Amazon CBUI in a new browser window from the AIR application.
var url:URLRequest = new URLRequest("http://localhost/amazonAIR/startPayment.php");
url.data = new URLVariables();
var obj:URLVariables = new URLVariables();
url.data.movieId = 1;
url.data.paymentReason = 'Enter The Matrix';
url.method = "GET";
navigateToURL(url, "new");
The remaining elements for this stage and the following stage of the workflow are similar to those used to call the Amazon CBUI from a Flex application that runs in Flash Player. See Calling the Amazon Co-Branded User Interface from Flex and Returning from Amazon and making the payment for more details.
The only thing left to do now is notify the AIR application and bring it to front.
This requires communication between the browser application and the AIR application. To accomplish this, the code uses the LocalConnection mechanism. LocalConnection objects can communicate among files that are running on the same client computer, but may be running in different applications—for example, SWF content running in a browser and SWF content running in Adobe AIR.
Because LocalConnection can be tricked using techniques such as DNS rewriting, it should not be used to pass sensitive information. In addition, because the AIR application is independent from the browser, they will not share the same server session. A simple notification is not enough; I also need to pass the session id. This is not sensitive information, but it will allow the AIR application to retrieve from the server any sensitive information that the browser application has set.
For simplicity, the returnAir.php page has a link to a little Flex application that will communicate with the AIR application. This Flex application gets the cookie information and sends it through LocalConnection to the AIR application.
private var outbound:LocalConnection = new LocalConnection();
private function gotoAIR():void {
//get the cookie string
ExternalInterface.call('eval','window.cookieStr = function () {return document.cookie};')
var cookieStr:String = ExternalInterface.call('cookieStr');
outbound.connect("paymentSample");
outbound.send("app#amazonAIR:paymentSample","notifyPayment",cookieStr);
//outbound.send("app#testAmazonAir.F0B3F68E1857B8A07069FED1D0638CAF200F76EB.1:paymentSample","notifyPayment",cookieStr);
outbound.close();
}
When launching from Flash Builder 4, the AIR application has no Publisher ID so the connection name is app#amazonAIR:paymentSample. After packaging and installation, the AIR application will get its own Publisher ID so the connection name becomes something like the following:
app#amazonAIR.F0B3F68E1857B8A07069FED1D0638CAF200F76EB.1:paymentSample.
You can determine the publisher ID of an installed AIR application by looking at the META-INF/AIR/publisherid file within the application install directory.
Back in the AIR application, I expose a function to be available through LocalConnection.
//This will be used by return.php to notify the AIR App
//that the payment has been made
private var inbound:LocalConnection = new LocalConnection();
private function initApp():void {
//only allow connections from localhost
//you need to replace "localhost" with the final domain
//where your application will be hosted
inbound.allowDomain("localhost");
inbound.client = new Object();
//this is the function that will be called by the Browser App
inbound.client.notifyPayment = paymentNotification;
inbound.connect("paymentSample");
}
In the code above, paymentNotification() is a function that receives the cookie string as a parameter and queries the server to check the transaction status.
public function paymentNotification(cookieStr:String):void {
var srv:HTTPService = new HTTPService();
srv.headers.Cookie = cookieStr;
srv.url = "http://localhost/amazonAIR/paymentStatus.php";
srv.addEventListener(ResultEvent.RESULT,function (event:ResultEvent):void {
nativeApplication.activate();
if (event.result.status == 'OK') {
currentState = 'Succes';
} else {
currentState = 'Fail';
}
});
srv.send();
}
Follow these steps to install the sample files for the Adobe AIR implementation:
define('AWS_ACCESS_KEY_ID', 'YOUR KEY');
define('AWS_SECRET_ACCESS_KEY', 'YOUR SECRET KEY');
Replace the values with your own Amazon access key ID and secret access key.
In this article I have demonstrated an approach for securely implementing a payment workflow using Amazon Flexible Payment System and Flex, running both in Flash Player and in Adobe AIR.
The following resources provide more details on the technologies used in this article:
Amazon FPS developer resources

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License