Requirements
     
Prerequisite knowledge
Required products Sample files  

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.

Flash Builder (Download trial)

 

AmazonAIRFiles.zip (1851 KB)

AmazonFlexFiles.zip (1847 KB)

 
User level      
All
     

 
Additional Requirements

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.
 

 
Starting an Amazon FPS workflow from Flash Player

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.
 
 
Security considerations
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:
 
  • Usage of HTTPS for communication with Amazon FPS web services ensures that the communication is protected from third party access.
  • A signature mechanism based on access keys and secret keys ensures that each seller is uniquely identified.
  • A co-branded page hosted on Amazon servers ensures that the users enter their credentials and pre-approve all the amounts only on the Amazon domain.
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.
 
 
Architectural approach and UI solutions
Summing this up, the architectural solution has to comply with two concurrent demands:
 
  • It must incorporate a Flex RIA front end that is stateful and is built following the single page application paradigm
  • To ensure security, the Flex application must not handle the Amazon security key.
  • Further, the Amazon co-branded page will be used to authenticate users, enabling them to see exactly where they are logging in.
To meet these requirements, I propose the following approach:
 
  • All payment-related processing will be performed on the server to protect the Amazon secret key.
  • Access to the co-branded page will be from a pop-up page so that the Flex application will stay in the single page paradigm.
From the buyer’s perspective the Amazon FPS Basic Quick Start workflow using Flex will include the following steps:
 
  1. John, the buyer, decides he want to make a purchase. He clicks the Pay Now button in the Flex application (see Figure 1).
The Pay Now button starts the workflow.
Figure 1. The Pay Now button starts the workflow.
 
Note: From this point forward all requests are performed through HTTPS.
 
2. A new pop-up window opens and displays the Amazon Co-Branded User Interface (CBUI). This is a standard HTML-based web application and it is hosted on the Amazon servers. John signs into his Amazon Payments account using his e-mail ID and password (see Figure 2).
Figure 2. The buyer logs in to Amazon Payments in the browser
 
3. After signing in, John views the Payment Method page. This page enables him to select a personal payment instrument, such as his credit card, for the transaction. John selects his Amazon Payments account balance (ABT) as the payment instrument and clicks Continue (see Figure 3).
Figure 3. The user selects a payment method for the transaction.
 
4. After he clicks Continue, John views the Payment Summary page. He reviews the payment details and clicks Confirm (see Figure 4).
Figure 4. This payment summary page allows the buyer to review the transaction.
 
5. John is then redirected to the MitiOnDemand.tv web site. The web page he’s redirected to (which was specified in the 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.
 
6. When John clicks Return to Movie, the pop-up window closes and the Flex application is notified that the authorization process has ended. Now it can call the server logic to perform the actual payment and resume the movie so that John can finally enter The Matrix
 
Calling the Amazon Co-Branded User Interface from Flex
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.
 
 
amazonFlex.mxml

//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.
 
 
amazonFlex.mxml

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:
 
 
startPaymentFlex.php

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.
 
 
Returning from Amazon and making the payment
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.
 
 
return.php

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.
 
 
return.php

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"); } }
 
Using ExternalInterface to notify the Flex application
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.
 
 
amazonFlex.mxml

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.
 
 
paymentStatus.php

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.
 
 
amazonFlex.mxml

//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.
 
 
return.php

<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>
 
Installing the sample files
Follow these steps to install the sample files:
 
  1. Unpack the archive and put the files in a temporary location; for example, /tmp.
  2. Copy the amazonFlex folder into your web root folder; for example, /work/www.
  3. Edit amazonFlex/amazon-fps/src/Amazon/FPS/Samples/.config.inc.php and find the following lines:
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.
 
  1. In Flash Builder 4 choose File >Import and select Flash Builder Project.
  2. Select the sample file amazonFlex.fxp.
  3. Specify the Output folder location, Web root, and Root URL with values appropriate for your setup. The settings I used are shown in Figure 5; your settings may be different.

 
Starting an Amazon FPS workflow from Adobe AIR

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.
 
 
Architectural approach and UI solutions
As with the Flex application, the architectural solution has to comply with concurrent demands:
 
  • It requires an AIR front end that is stateful and is built following the single page application paradigm.
  • To ensure security, the AIR application must not handle the Amazon security key.
  • The Amazon co-branded page will be used to authenticate users.
To meet these requirements, I propose the following approach:
 
  • All payment related processing should be done on the server side to protect the Amazon secret key. Of course, this means that the AIR application must be online when the payment takes place.
  • Access to the co-branded page should be from a browser page.
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.
 
 
Calling the Amazon Co-Branded User Interface from AIR
Here is how to open the Amazon CBUI in a new browser window from the AIR application.
 
 
amazonAir.mxml
 
 
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.
 
 
Using LocalConnection to notify the AIR application
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.
 
 
amazonAIRReturn.mxml
 
 
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.
 
 
amazonAIR.mxml

//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.
 
 
amazonAIR.mxml

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(); }
 
Installing the sample files
Follow these steps to install the sample files for the Adobe AIR implementation:
 
  1. Unpack the archive and put the files in a temporary location; for example, /tmp.
  2. In Flash Builder 4 choose File > Import and select Flash Builder Project.
  3. Select the AmazonAIR.fxp sample file.
  4. Copy amazonAIR into your Web Root folder; for example, /work/www.
  5. Edit amazonAIR/amazon-fps/src/Amazon/FPS/Samples/.config.inc.php and find the following lines:
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.
 
  1. In Flash Builder 4 choose File > Import once more and select Flash Builder Project.
  2. Select amazonAIRReturn.fxp.
  3. Specify the Output folder location (where you have amazonAIR in your web root), Web root, and Root URL with values appropriate for your setup. The settings I used are shown in Figure 6; your settings may be different.

 
Where to go from here

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: