For those who actually want people to pay to use their web applications, and don’t want the headache of accepting credit card payments themselves, the payment process can be outsourced with such services as Paypal Website Payments Standard and Amazon Flexible Payments Service (FPS). Amazon’s FPS looked very developer-oriented, and I like the customer experience they provide, so I went that route. Thankfully someone had already written a Ruby API for Amazon FPS, called Remit. It’s very well crafted, and I was able to glean everything I needed to know from the source code, but it still would have been nice to have a few examples to work from as I started using it to do the software-as-a-service dirtywork I needed to get done. So for those who follow in this path, here are a few tidbits. Note that a lot of the work in setting up a payment system is properly handling all the ways that payments can fail, which I’m not going into here.
Most Remit operations require an instance of Remit::API, so here’s how to set one up. The first two arguments for Remit::API.new are the access key and secret key that you can obtain when logged in to your Amazon Web Services account, and the third argument indicates whether or not to use sandbox mode:
require 'remit' api = Remit::API.new( "my access key", "my secret key", true )
Here’s the one-time process of creating and installing your recipient and caller tokens, which are needed to receive payments. The arguments for the request here are:
- payment_instruction – see the Amazon FPS docs for customizing this
- caller_reference – a reference ID that you may or may not want to store
- token_friendly_name – optional friendly name for the token
- token_type – see the Amazon FPS docs for customizing this
request = Remit::InstallPaymentInstruction::Request.new( :payment_instruction => "MyRole == 'Recipient' orSay 'Role does not match';", :caller_reference => Time.now.to_i.to_s, :token_friendly_name => "Friendly Name For Your Token", :token_type => "Unrestricted" ) install_recipient_response = api.install_payment_instruction(request) install_recipient_response.token_id # hold on to this request = Remit::InstallPaymentInstruction::Request.new( :payment_instruction => "MyRole == 'Caller' orSay 'Role does not match';", :caller_reference => Time.now.to_i.to_s, :token_friendly_name => "TriggerFood Caller Token", :token_type => "Unrestricted" ) install_caller_response = api.install_payment_instruction(request) install_caller_response.token_id # hold on to this
Now you can start directing users to Amazon to pay you. If you’re not really familiar with Amazon FPS yet, what happens is that you redirect your users to the URL this following code provides, where they have to log in or register for an Amazon account, and then specify their payment method. No actual payment is collected though, you just get a payment token back when the pipeline successfully redirects the user back to your site. Here are the arguments for getting the pipeline URL:
- caller_reference – a reference ID that you may or may not want to store
- recipient_token – the token you just produced above
- transaction_amount – per-month price
- recurring_period – length of recurring time period
- return_URL – the URL you want users to be sent back to after they’ve gone through the Amazon “pipeline”
- caller_key – same as access the key when creating the Remit::API instance
recurring_use_pipeline = api.get_recurring_use_pipeline( :caller_reference => Time.now.to_i.to_s, :recipient_token => install_recipient_response.token_id, :transaction_amount => price, :recurring_period => "1 Month", :return_URL => return_url, :caller_key => "my access key" ) recurring_use_pipeline.url # this is the URL you want to send your users to
When users are sent back to your site at the :return_URL you provide, you’ll need to process that URL to get the information about how what happened in the pipeline. You’ll need to provide both the URL from Amazon when they redirected the user to your site, as well as your secret yet (the same as used above in the API step).
pipeline_response = Remit::PipelineResponse.new( url, "my secret key" )
There are 3 essential pieces of information you’ll be able to glean from the pipeline response:
- pipeline_response.valid? – if this isn’t true, then the URL isn’t signed properly, so you don’t want to use it
- pipeline_response.success? – make sure this is also true before trying to request payment
- pipeline_response.tokenID – the token for the sender–you’ll use this when requesting payment.
Now to make the user actually pay. For a web app with monthly billing, you’ll probably request payment immediately after running the pipeline or after some trial period, and then again once every month. Setting a temporary decline policy of implicit retry here will make Amazon do the work of retrying the transaction until it is either successful or permanently failed. This requires handling instant payment notification requests, which we’ll get to next. Here are the arguments you’ll need to provide and an example:
- caller_token_id – install_caller_response.token_id from above
- recipient_token_id – install_recipient_response.token_id from above
- sender_token_id – the pipeline_response.tokenID from the last step
- transaction_amount – a Remit::RequestTypes::Amount object, which takes an :amount and :currency_code (see example)
- charge_fee_to – should be “Recipient”, assuming you want Amazon’s fee to come out of the charge to the user
- caller_reference – a reference ID that you may or may not want to store
- temporary_decline_policy – needs to specify implicit retry if you want Amazon to do the dirty work (see example)
request = Remit::Pay::Request.new(
:caller_token_id => "my caller token",
:recipient_token_id => "my recipient token",
:sender_token_id => pipeline_response.tokenID,
:transaction_amount => Remit::RequestTypes::Amount.new(
:amount => price,
:currency_code => "USD"
),
:charge_fee_to => "Recipient",
:caller_reference => Time.now.to_i.to_s,
:temporary_decline_policy => Remit::RequestTypes::TemporaryDeclinePolicy.new(
:temporary_decline_policy_type => Remit::TemporaryDeclinePolicyType::IMPLICIT_RETRY
)
)
pay_response = api.pay(request)
The pay response returns the following essential information:
- pay_response.successful? – false indicates that the request failed, true indicates that the request was successful, but not necessarily the transaction
- pay_response.transaction_response.success? – true if the payment transaction was a success
- pay_response.transaction_response.initiated? – true if payment has been initiated, but needs to be handled asynchronously
- pay_response.transaction_response.reinitiated? – true if transaction was temporarily declined, but has been initiated again
- pay_response.transaction_response.temporary_decline? – true if the transaction is temporarily declined
- pay_response.transaction_response.failure? – true if it’s a complete failure
For transactions that are of ‘initiated’, ‘reinitiated’ or ‘temporary_decline’ status, Amazon will keep trying the transactions until they complete, assuming the temporary decline policy has been set to implicit retry. I handle these by using the Amazon FPS Instant Payment Notification (IPN) service, which can be enabled and set to hit a URL of your choice in your Amazon Payments settings. In order to process these notifications, you just need to provide Remit with the request parameters and your secret key:
ipn_request = Remit::IpnRequest.new( params, "my secret key" )
The IPN request has the following useful properties:
- ipn_request.valid? – true if the IPN request was properly signed and had all the necessary info
- ipn_request.status – if this is “SUCCESS”, then the payment went through, otherwise it has permanently failed.
So that’s the bare bones of what’s needed to use Amazon FPS to handle recurring payments. There’s a lot missing here, such as (assuming a Ruby on Rails app) the actions that will send the user to your Amazon FPS pipeline, receive the responses from the Amazon FPS pipeline and IPN service, and the code to handle recurring payments. Writing lots of tests for these models and controllers is highly recommended, as well as quite a bit of subsequent integration testing in sandbox mode. Enjoy!