Introduction
Amazon Web Services is a set of services that developers can use to build applications that have good performance, scalability, and are cost-effective. Amazon FPS (Flexible Payments Service) Limited Beta is one of these Web Services that has been built specifically for developers. It allows transfer of money between any two entities, and is built on top of Amazon's payment infrastructure. The sample application in this article is an example of a small store that implements Amazon FPS in C#. Rather than the traditional credit card signature for payment, users authorize payment by signing into their Amazon account.
Background
Amazon Flexible Payments Service (FPS) Limited Beta is an online payment method that has been built specifically for developers. It is built on top of Amazon's reliable and scalable payment infrastructure. It supports payment methods such as credit cards, bank account debits, and Amazon Balance Payment transfers. There is a different fee associated with each of these payment methods. The sender and recipient of funds declare payment instructions initially. Amazon FPS also allows micro-payments, so that many transactions could be accumulated into a single transaction. And unlike credit cards, transaction amounts can be very minimal. When a lot of single transactions are aggregated into a single transaction, then transaction processing costs can be saved. The sender of funds as such does not pay any transaction fees. For the receiver, however, the transaction processing fees are charged according to whether it is by credit card, bank account debit, or Amazon's Balance Payment transfer method. Existing Amazon.com customers can use their login details with Amazon Payments when buying anything from a site that is powered by Amazon Payments. This way, they would not have to key in all the payment information every time they buy something, as is the case with credit cards.
What you need to do to use this application
I. Register for an Amazon Web Services Developer Account
First, go to this link: https://aws-portal.amazon.com/gp/aws/developer/registration/index.html?.
- You must follow the first three steps to create an Amazon.com account, as it is explained. After filling in all the details and successfully creating an account, an email will be sent to your account with a confirmation link, and the steps need to be followed as mentioned in the email. After clicking on the link, you will be redirected to the Amazon.com website home page. And the account details are shown as below:
- Click the Your Web Services Account tab on top, and then click Access Identifiers, to see your AWS Access Key ID and Secret Access Key, as shown below:
- Your access key ID and secret access key will be shown as below:
Please click the Show link to view your secret access key.
- Now, click on the Create New tab under X.509 certificate to create a new certificate. The following screen will show:
- Click on the Yes tab to create a new certificate, and the following screen will appear:
Download the private key file and the X.509 certificate. The files will be in .PEM format, and then save both the files in the same folder. The files need to be encrypted using OpenSSL to be encrypted in the P12 format. So to download OpenSSL, go to http://www.openssl.org and download the latest version of OpenSSL and extract the contents of the .tar file to be in the same folder as the saved private key file and the saved certificate file. And then, open the Command Prompt and enter this command:
openssl pkcs12 -export -inkey your_private_key_file.pem
-in your_X.509_certificate_file.pem
-name "YOUR KeyStorePassword" -out afps-sdk-keystore.p12
Here, your_private_key_file and your_X.509_certificate_file are the names of the private key and the certificate that you downloaded. Now, go to app.config.
<!---->
<add key="AWSAccessKeyID" value="YOUR AWSAccessKeyID" />
<add key="AWSSecretAccessKey" value="YOUR AWSSecretAccessKey" />
<!---->
<add key="KeyStoreLocation" value="YOUR afps-sdk-keystore.p12 Location" />
<add key="KeyStorePassword" value="YOUR KeyStorePassword" />
II. Sign up for an Amazon FPS Sandbox Environment
To create an Amazon Payments Sandbox account, go to this link: https://aws-portal.amazon.com/gp/aws/payments/sandbox/index.htm.
Sign in using the Amazon.com account that you created previously. And an account is created for you.
III. Install Visual Studio 2005
IV. Install ASP.NET 2.0
V. Install Web Services Enhancement 3.0
VI. Make sure IIS is installed on your computer
For further details, please visit: http://developer.amazonwebservices.com/connect/entry.jspa?entryID=913&ref=featured.
How the application works
The following section show, with the help of sequence diagrams, the step-by-step process of how the application works:
Setting up caller and recipient tokens
The above diagram shows the process of installing a caller and a recipient token. The AmazonFPSUI
class, which is the user interface class, tells the AmazonFPSManager
class that the caller and recipient tokens need to be set up. The AmazonFPSManager
class then sends a web request in the InstallPaymentInstruction
method, which:
- establishes the token required for Web Service calls, and
- establishes the payment instructions by indicating as either "caller" or "recipient".
In this code sample, the store plays the role of the caller. After that, the recipient token needs to be setup in order to receive payments. In this case, the store is also the recipient. The recipient token is set up using the InstallRecipientInstruction
method. This is saved in the application configuration file (app.config). The following source code shows an example:
static class AmazonFPSManager
{
#region Propertie
private static AmazonFPSInfo m_AccountFPSInfo = null;
private static AmazonFPSInfo AccountFPSInfo
{
get
{
if (m_AccountFPSInfo == null)
{
m_AccountFPSInfo = new AmazonFPSInfo();
m_AccountFPSInfo.CallerToken =
ComfigurationUtillity.GetAppSettings("CallerTokenID");
m_AccountFPSInfo.RecipientToken =
ComfigurationUtillity.GetAppSettings("RecipientTokenID");
m_AccountFPSInfo.AWSAccessKeyID =
ComfigurationUtillity.GetAppSettings("AWSAccessKeyID");
m_AccountFPSInfo.AWSSecretAccessKey =
ComfigurationUtillity.GetAppSettings("AWSSecretAccessKey");
m_AccountFPSInfo.RecipientName = Settings.TOKENFRIENDLYNAME;
}
return m_AccountFPSInfo;
}
}
#endregion
#region Setup
public static AmazonFPSInfo Setup(string keyStoreLocation, string keyStorePassword)
{
string callerToken = InstallPaymentInstruction(Roles.Caller,
keyStoreLocation, keyStorePassword);
string recipientToken = InstallPaymentInstruction(Roles.Recipient,
keyStoreLocation, keyStorePassword);
AmazonFPSInfo retInfo = new AmazonFPSInfo();
AccountFPSInfo.CallerToken = callerToken;
AccountFPSInfo.RecipientToken = recipientToken;
return retInfo;
}
private static string InstallPaymentInstruction(Roles role,
string keyStoreLocation, string keyStorePassword)
{
InstallPaymentInstructionRequest paymentReq =
new InstallPaymentInstructionRequest();
paymentReq.TokenType = TokenType.Unrestricted;
paymentReq.TokenFriendlyName = Settings.TOKENFRIENDLYNAME;
paymentReq.CallerReference = Settings.CallerReference;
if (role.Equals(Roles.Caller))
paymentReq.PaymentInstruction = "MyRole == 'Caller';";
else
paymentReq.PaymentInstruction = "MyRole == 'Recipient';";
AmazonFPSWse clientAgent =
AmazonFPSProxy.GetInstance(ComfigurationUtillity.GetAppSettings(
"WebserviceEndpoint"), keyStoreLocation, keyStorePassword);
InstallPaymentInstructionResponse paymentRes =
if (paymentRes == null)
{
throw new ApplicationException("InstallPaymentInstruction Call failed");
}
else if (paymentRes.Status == ResponseStatus.Failure)
{
ApplicationException ex =
new ApplicationException("Caller Token Creation Failed.");
ex.Data.Add("Is Retriable", paymentRes.Errors[0].IsRetriable);
ex.Data.Add("Type", paymentRes.Errors[0].ErrorType);
ex.Data.Add("Code", paymentRes.Errors[0].ErrorCode);
ex.Data.Add("Reason", paymentRes.Errors[0].ReasonText);
throw ex;
}
return paymentRes.TokenId;
}
#endregion
class ComfigurationUtillity
{
public static void UpdateAppSettings(string key, string value)
{
Configuration config =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings[key].Value = value;
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
}
public static string GetAppSettings(string key)
{
System.Configuration.Configuration config =
return config.AppSettings.Settings[key].Value;
}
}
Signing in process of sender
The above diagram shows the process of setting up a token for the sender (buyer) when he wants to buy something from the store. AmazonFPSUI
sends the sign-in method, with the parameters PaymentReason
and PaymentMethod
to the AmazonFPSManager
. Here, payment reason could be any product that the buyer is buying, and payment method is either by credit card or cash. AmazonFPSManager
manages all the transactions that take place. The AMazonFPSManager
class sends the caller and the recipient token ID (as set above), the recipient name, and the access key of the recipient, to the AmazonFPSInfo
class. The AWS access key is for identifying the recipient. After the recipient is authenticated, a URL is sent back to the User Interface and the user is directed to the Amazon Sign-in page. Here, the user signs in with the Amazon account, and after the user (buyer) is authenticated, the token ID of the buyer (that is, the SenderToken
ID) is sent to be stored in the AmazonFPSSenderInfo
class. Then, the buyer is redirected back to the website using the signed URL. The following is an example of a signed URL and its parameters:
#region SignIn
public static string SignIn(string paymentReason, double amout, string paymentMethod)
{
string returnUrl = ComfigurationUtillity.GetAppSettings("ReturnUrl");
string cbuiPipelineUrl = ComfigurationUtillity.GetAppSettings("CBUIPipelineUrl");
string pipelineName = Pipeline.SINGLE_USE;
SortedDictionary<string> parameters = new SortedDictionary<string,>();
parameters.Add("transactionAmount", amout.ToString());
parameters.Add("callerKey", AccountFPSInfo.AWSAccessKeyID);
parameters.Add("callerReference", Settings.CallerReference);
parameters.Add("pipelineName", pipelineName);
parameters.Add("recipientToken", AccountFPSInfo.RecipientToken);
parameters.Add("paymentReason", paymentReason);
parameters.Add("paymentMethod", paymentMethod);
parameters.Add("returnURL", returnUrl);
string signature =
BuildSignature(AccountFPSInfo.AWSSecretAccessKey, parameters);
parameters.Add("awsSignature", signature);
return ConstructSignedURL(cbuiPipelineUrl, parameters);
}
public static string ConstructSignedURL(string cbuiPipelineUrl,
SortedDictionary<string, > parameters)
{
StringBuilder unsignedUrl = new StringBuilder();
unsignedUrl.AppendFormat("{0}", cbuiPipelineUrl);
bool first = true;
foreach (KeyValuePair<string, /> var in parameters)
{
unsignedUrl.AppendFormat("{2}{0}={1}", var.Key,
var.Value, (first ? "?" : first = false;
}
return unsignedUrl.ToString();
}
private static string PrepareUrlEncode(string val)
{
if (HttpUtility.UrlEncode(val).IndexOf('%') != -1)
{
String[] strs = HttpUtility.UrlEncode(val.Trim()).Split(new char[] { '%' StringBuilder retEncodedVAl = new StringBuilder(strs[0]);
for (int k = 1; k < strs.Length; k++)
{
retEncodedVAl.Append('%');
retEncodedVAl.Append(strs[k].Substring(0, 2).ToUpper() +
strs }
return retEncodedVAl.ToString();
}
else
{
return HttpUtility.UrlEncode(val.Trim());
}
}
private static string BuildSignature(string secretKey,
SortedDictionary<string,> parameters)
{
StringBuilder sortedParams =
new StringBuilder(@"/cobranded//-ui/actions/start");
bool first = true;
foreach (KeyValuePair<string, /> var in parameters)
{
sortedParams.AppendFormat("{2}{0}={1}", var.Key.Trim(),
first = false;
}
return CalculateHMAC(secretKey, sortedParams.ToString());
}
internal static string CalculateHMAC(String secretKey, String parametersToHash)
{
ASCIIEncoding ae = new ASCIIEncoding();
HMACSHA1 signature = new HMACSHA1(ae.GetBytes(secretKey));
return Convert.ToBase64String(signature.ComputeHash(
ae.GetBytes(parametersToHash.ToCharArray())));
}
#endregion
Payment process
The above diagram first shows that after getting the details of the sender token ID from the previous diagram and the email address of the Amazon account of the buyer, a request is then made to the WebRequest
class to process the transaction. The results of the transaction are stored in the AmazonPaymentInfo
class. The AmzonFPSManager
class then checks the payResponse
object to see if the transaction was successful. payResponse
is an instance of the class PayResponse
. PayResponse
is a proxy class which defines TransactionResponse
, ResponseStatus
, ServiceError
, and requestIdField
. The response is returned accordingly to the browser. The TransactionId
is then stored in the AmazonPaymentInfo
class.
Pay request
The following code used in the sample application shows the logic behind how a pay request is processed, the various parameters that are involved, and the transaction response details from Amazon FPS:
#region Pay Method
internal static AmazonPaymentInfo ProcessPayment(
AmazonFPSSenderInfo amazonFPSSenderInfo, double amountRaw)
{
PayRequest payRequest = new PayRequest();
payRequest.CallerTokenId = AccountFPSInfo.CallerToken;
payRequest.RecipientTokenId = AccountFPSInfo.RecipientToken;
payRequest.SenderTokenId = amazonFPSSenderInfo.SenderToken;
Amount amount = new Amount();
amount.Amount1 = amountRaw.ToString();
payRequest.TransactionAmount = amount;
payRequest.ChargeFeeTo = amazonFPSSenderInfo.ChargeFeeTo;
payRequest.CallerReference = Settings.CallerReference;
payRequest.TransactionDate = System.DateTime.Now;
payRequest.CallerDescription = Settings.TOKENFRIENDLYNAME;
payRequest.RecipientDescription =
"Tiny Point of Sale: Amazon Flexible Payments Service (Amazon FPS)";
payRequest.RecipientReference = "keng@mycosservices.com";
payRequest.SenderDescription = string.Format("Custome name : {0}, Email address {1}",
amazonFPSSenderInfo.CustomerName,
amazonFPSSenderInfo.EmailAddress);
payRequest.SenderReference = amazonFPSSenderInfo.EmailAddress;
AmazonFPSWse clientAgent = AmazonFPSProxy.GetInstance();
PayResponse payResponse = clientAgent.Pay(payRequest);
TransactionResponse transactionResponse = payResponse.TransactionResponse;
AmazonPaymentInfo amazonPaymentInfo = new AmazonPaymentInfo();
amazonPaymentInfo.TransectionId = transactionResponse.TransactionId;
amazonPaymentInfo.TransectionStatus = transactionResponse.Status.ToString();
if (payResponse.Status.Equals(ResponseStatus.Success))
{
amazonPaymentInfo.IsSuccess = true;
amazonPaymentInfo.Message = string.Format("Status detail : {0}",
transactionResponse.StatusDetail);
}
else if (payResponse.Status.Equals(ResponseStatus.Failure))
{
amazonPaymentInfo.IsSuccess = false;
amazonPaymentInfo.Message = string.Format("Error message : {0}",
string.Concat(payResponse.Errors));
}
return amazonPaymentInfo;
}
#endregion
Using the code
This application is a Windows application. So in order to implement it as an ASP.NET web application, you can download the code (a single class) and have a web form call the class.