Skip to content

Instantly share code, notes, and snippets.

@ohammersmith
Created October 8, 2009 16:01
Show Gist options
  • Save ohammersmith/205127 to your computer and use it in GitHub Desktop.
Save ohammersmith/205127 to your computer and use it in GitHub Desktop.
Index: test/unit/integrations/helpers/amazon_paynow_helper_test.rb
===================================================================
--- test/unit/integrations/helpers/amazon_paynow_helper_test.rb (revision 0)
+++ test/unit/integrations/helpers/amazon_paynow_helper_test.rb (revision 0)
@@ -0,0 +1,31 @@
+require File.dirname(__FILE__) + '/../../../test_helper'
+
+class AmazonPaynowHelperTest < Test::Unit::TestCase
+ include ActiveMerchant::Billing::Integrations
+
+ def setup
+ @helper = AmazonPaynow::Helper.new('order-500',nil, :aws_access_key_id => 'JDAKJHKASKDJAHSKDJHA', :aws_secret_access_key => 'askdjhdueHAKSUENajskshdyYWJANSKDJ')
+ end
+
+
+ def test_form_fields
+ @helper.description 'Product 500'
+ @helper.amount 'USD 10.11'
+ @helper.immediate_return false
+ @helper.return_url 'http://localhost:3000/amazon_paynow/done'
+ @helper.cancel_return_url 'http://localhost:3000/amazon_paynow/cancel'
+
+ # calling this function as only then signature gets computed
+ form_fields = @helper.form_fields
+
+ assert_field 'description', 'Product 500'
+ assert_field 'amount', 'USD 10.11'
+ assert_field 'referenceId', 'order-500'
+ assert_field 'accessKey', 'JDAKJHKASKDJAHSKDJHA'
+ assert_field 'immediateReturn', '0'
+ assert_field 'accessKey', 'JDAKJHKASKDJAHSKDJHA'
+ assert_field 'returnUrl', 'http://localhost:3000/amazon_paynow/done'
+ assert_field 'abandonUrl', 'http://localhost:3000/amazon_paynow/cancel'
+ assert_field 'signature', 'GQAXF+ww53KnTBOGTapsY+hP12Y='
+ end
+end
Index: test/unit/integrations/amazon_paynow_module_test.rb
===================================================================
--- test/unit/integrations/amazon_paynow_module_test.rb (revision 0)
+++ test/unit/integrations/amazon_paynow_module_test.rb (revision 0)
@@ -0,0 +1,52 @@
+require File.dirname(__FILE__) + '/../../test_helper'
+
+class AmazonPaynowModuleTest < Test::Unit::TestCase
+ include ActiveMerchant::Billing::Integrations
+
+ def test_test_mode
+ ActiveMerchant::Billing::Base.integration_mode = :test
+ assert_equal 'https://authorize.payments-sandbox.amazon.com/pba/paypipeline', AmazonPaynow.service_url
+ end
+
+ def test_production_mode
+ ActiveMerchant::Billing::Base.integration_mode = :production
+ assert_equal 'https://authorize.payments.amazon.com/pba/paypipeline', AmazonPaynow.service_url
+ end
+
+ def test_invalid_mode
+ ActiveMerchant::Billing::Base.integration_mode = :preprod
+ assert_raise(StandardError){ AmazonPaynow.service_url }
+ end
+
+ def test_ui_return
+ query_string = 'status=PS&referenceId=orderid85&transactionId=12ROQMTAFV1V348RVSJTQNPTN48TQ64K9HH&signature=oHK0/MU8coqRkygmMURTHp9Ekzc='
+
+ resp = ActiveMerchant::Billing::Integrations::AmazonPaynow::Return.new(query_string)
+
+ assert_equal resp.reference_id, 'orderid85'
+ assert_equal resp.transaction_id, '12ROQMTAFV1V348RVSJTQNPTN48TQ64K9HH'
+ assert_equal resp.status, 'PS'
+ assert_equal resp.acknowledge('dummykey'), true
+ end
+
+ def test_ui_merchant_error
+ query_string = 'status=ME&errorMessage=Merchant+Input+Error%3A+The+following+input+parameters+are+either+invalid+or+absent%3A+%5Bamount%5D&referenceId=orderid904'
+
+ resp = ActiveMerchant::Billing::Integrations::AmazonPaynow::Return.new(query_string)
+
+ assert_equal resp.reference_id, 'orderid904'
+ assert_equal resp.status, 'ME'
+ assert_equal resp.error_message, 'Merchant Input Error: The following input parameters are either invalid or absent: [amount]'
+ end
+
+
+ def test_ui_return_ack_failure
+
+ query_string = 'status=PS&referenceId=orderid85&transactionId=12ROQMTAFV1V348RVSJTQNPTN48TQ64K9HH&signature=oHK0/MU8coqRkygmMURTHp9zc='
+
+ resp = ActiveMerchant::Billing::Integrations::AmazonPaynow::Return.new(query_string)
+
+ assert_equal resp.acknowledge('dummy_key'), false
+ end
+
+end
Index: lib/active_merchant/billing/integrations/amazon_paynow/helper.rb
===================================================================
--- lib/active_merchant/billing/integrations/amazon_paynow/helper.rb (revision 0)
+++ lib/active_merchant/billing/integrations/amazon_paynow/helper.rb (revision 0)
@@ -0,0 +1,81 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ module Integrations #:nodoc:
+ module AmazonPaynow
+ class Helper < ActiveMerchant::Billing::Integrations::Helper
+
+ mapping :amount, 'amount'
+ mapping :reference_id, "referenceId"
+ mapping :immediate_return, "immediateReturn"
+ mapping :aws_access_key_id, "accessKey"
+ mapping :return_url, "returnUrl"
+ mapping :cancel_return_url, "abandonUrl"
+ mapping :description, "description"
+
+ def initialize(order, account, options = {})
+ @aws_access_key_id = options.delete(:aws_access_key_id)
+ @aws_secret_access_key = options.delete(:aws_secret_access_key)
+ super
+ self.aws_access_key_id = @aws_access_key_id
+ self.reference_id = order
+ end
+
+ # This method accepts a boolean
+ def immediate_return(immediate_return_flag)
+ if immediate_return_flag
+ value = 1
+ else
+ value = 0
+ end
+ add_field mappings[:immediate_return], value
+ end
+
+ def immediate_return=(immediate_return_flag)
+ immediate_return(immediate_return_flag)
+ end
+
+ # Format amount in format "#{currency} #{amount}"
+ def amount(amt)
+ unless amt.nil?
+ if amt.is_a?(String)
+ value = amt
+ elsif amt.is_a?(Money)
+ value = sprintf("%s %.2f", amt.currency, amt.cents.to_f/100)
+ else
+ raise ArgumentError, 'money amount must be either a Money object
+ or a string like "USD 50"'
+ end
+ add_field mappings[:amount], value
+ end
+ end
+
+ def amount=(amt)
+ amount(amt)
+ end
+
+ def form_fields
+ add_field("signature", Helper::calculate_signature(
+ Helper::get_string_to_sign(super),
+ @aws_secret_access_key))
+ return super
+ end
+
+ # Computes RFC 2104-compliant HMAC signature given
+ # given data to sign
+ def self.calculate_signature(data, secret_key)
+ digest = OpenSSL::Digest::Digest.new('sha1')
+ hmac = OpenSSL::HMAC.digest(digest, secret_key, data)
+ Base64.encode64(hmac).chomp
+ end
+
+ # Computes the string to sign from the form parameters
+ def self.get_string_to_sign(field_values)
+ sorted_fields = field_values.sort_by { |field, value| field.downcase }
+ sorted_fields.collect {|field, value| field+value}.join
+ end
+
+ end
+ end
+ end
+ end
+end
Index: lib/active_merchant/billing/integrations/amazon_paynow/return.rb
===================================================================
--- lib/active_merchant/billing/integrations/amazon_paynow/return.rb (revision 0)
+++ lib/active_merchant/billing/integrations/amazon_paynow/return.rb (revision 0)
@@ -0,0 +1,71 @@
+require 'net/http'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ module Integrations #:nodoc:
+ module AmazonPaynow
+ class Return < ActiveMerchant::Billing::Integrations::Return
+
+# def initialize(response_string)
+# super
+# @params.each_key do |key|
+# @params[key] = CGI::unescape(@params[key])
+# end
+# end
+
+ def params
+ @params
+ end
+
+ def reference_id
+ params['referenceId']
+ end
+
+ def transaction_id
+ params['transactionId']
+ end
+
+ def error_message
+ params['errorMessage']
+ end
+
+ def signature
+ params['signature']
+ end
+
+ #Status of transaction. List of possible values:
+ #<tt>PS</tt>::Indicates that the payment transaction was successful.
+ #<tt>PF</tt>::Indicates that the payment transaction has failed and the money was not transferred.
+ # You can redirect your customer to the Amazon Payments Payment Authorization page
+ # to select a different payment method.
+ #<tt>PI</tt>::Indicates the payment has been initiated. It will take between five seconds
+ # and 48 hours to complete, based on the availability of external payment networks and
+ # the riskiness of the transaction.
+ #<tt>A</tt>::Indicates that your customer abandoned the transaction by clicking on the Cancel
+ # link during the payment pipeline.
+ #<tt>ME</tt>::Indicates that the HTML form received was not constructed properly. Please refer to
+ # the Implementation Guide for instructions on constructing Amazon Payments buttons.
+ #<tt>SE</tt>::Indicates a temporary system unavailable error and that the payment was not processed.
+ def status
+ params['status']
+ end
+
+ def success
+ raise NotImplementedError.new("Use the method status instead")
+ end
+
+ # Validate that the response is in fact from Amazon Pay now pipeline
+ # by verifying the signature
+ def acknowledge(aws_secret_access_key)
+ return_info = params.clone
+ return_info.delete("signature")
+ string_to_sign = Helper.get_string_to_sign(return_info)
+ calculated_signature = Helper.calculate_signature(string_to_sign,
+ aws_secret_access_key)
+ signature == calculated_signature
+ end
+ end
+ end
+ end
+ end
+end
Index: lib/active_merchant/billing/integrations/amazon_paynow.rb
===================================================================
--- lib/active_merchant/billing/integrations/amazon_paynow.rb (revision 0)
+++ lib/active_merchant/billing/integrations/amazon_paynow.rb (revision 0)
@@ -0,0 +1,88 @@
+require File.dirname(__FILE__) + '/amazon_paynow/helper.rb'
+require File.dirname(__FILE__) + '/amazon_paynow/return.rb'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ module Integrations #:nodoc:
+
+ # To start with Amazon Pay now, follow the instructions for installing
+ # ActiveMerchant as a plugin, as described on
+ # http://www.activemerchant.org/.
+ #
+ # The AmazonPaynow helper facilitates creating the Amazon FPS Pay now
+ # button/link dymanically for any product discription/price price/dercription
+ #
+ # Here is the relevent documentation about Amazon Pay now
+ # http://docs.amazonwebservices.com/AmazonFPS/2007-01-08/PayNowWidgetImplementationGuide/
+ #
+ # The following code renders a html form that redirect to Amazon FPS
+ # Pay Now pipeline
+ #
+ # The syntax of the helper is as follows:
+ #
+ # <% payment_service_for 'order11', nil,
+ # :aws_access_key_id => 'KJAJJJAKSUEKAHCNEKRU',
+ # :aws_secret_access_key => 'saskjdhfLKJlkjasdfasfasdksjdfhdksjfha',
+ # :service => :amazon_paynow
+ # do |service| %>
+ #
+ # <% service.description "Purchase at Foo bar store"
+ # <% service.amount "USD 400" %>
+ # <% service.immediate_return false %>
+ # <% service.return_url url_for(:only_path => false, :action => 'done') %>
+ # <% service.cancel_return_url url_for(:only_path => false, :action => 'cancel') %>
+ # <p>Proceed to make payment<p>
+ # <%=image_submit_tag 'https://authorize.payments.amazon.com/pba/images/payNowButton.png' %>
+ # <% end %>
+ #
+ #
+ # The following code is a quick example (not complete) of how the the response from Amazon FPS
+ # Pay Now pipeline is handled using the AmazonPaynow::Return class
+ #
+ # <% resp = ActiveMerchant::Billing::Integrations::AmazonPaynow::Return.new(request.query_string)
+ #
+ # status = resp.status
+ #
+ # if (status == 'PS' || status == 'PI' || status == 'PF') && !resp.acknowledge(@aws_secret_access_key)
+ # msg = 'There was a problem while returning back to our web site. There could be a malware in your browser changing the data returned to our website'
+ # elsif status == 'PS'
+ # msg = 'Thank You for your payment'
+ # elsif resp.status == 'PI'
+ # msg = 'Your payment is initiated. We will process the order once the payment is complete.'
+ # elsif status == 'PF'
+ # msg = 'Your payment has failed. Please try again using a different payment method'
+ # elsif status == 'SE'
+ # msg = 'There is a temporary problem with the payment system. Please try again after some time'
+ # elsif status == 'ME'
+ # msg = 'There is a problem processing the payment.'
+ # error_message = resp.error_message
+ # elsif status == 'A'
+ # msg = 'You have aborted the payment'
+ # end
+ # %>
+ # <h2><%=msg%></h2>
+ #
+
+ module AmazonPaynow
+
+ mattr_accessor :test_url
+ self.test_url = 'https://authorize.payments-sandbox.amazon.com/pba/paypipeline'
+
+ mattr_accessor :production_url
+ self.production_url = 'https://authorize.payments.amazon.com/pba/paypipeline'
+
+ def self.service_url
+ mode = ActiveMerchant::Billing::Base.integration_mode
+ case mode
+ when :production
+ self.production_url
+ when :test
+ self.test_url
+ else
+ raise StandardError, "Integration mode set to an invalid value: #{mode}"
+ end
+ end
+ end
+ end
+ end
+end
Index: lib/active_merchant/billing/integrations.rb
===================================================================
--- lib/active_merchant/billing/integrations.rb (revision 497)
+++ lib/active_merchant/billing/integrations.rb (working copy)
@@ -8,6 +8,7 @@
require 'active_merchant/billing/integrations/gestpay'
require 'active_merchant/billing/integrations/two_checkout'
require 'active_merchant/billing/integrations/hi_trust'
+require 'active_merchant/billing/integrations/amazon_paynow'
# make the bogus gateway be classified correctly by the inflector
Inflector.inflections do |inflect|
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment