From ee1a3ffb5098538e780392f6796d80b004f40ef9 Mon Sep 17 00:00:00 2001 From: "W. Justin Bedard" Date: Wed, 2 Oct 2013 15:22:45 -0400 Subject: [PATCH 001/104] During the certification process, found out that CurrencyCode and CurrencyExponent were not being set while performing transactions with saved profiles. This commit fixes that. --- .../billing/gateways/orbital.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/active_merchant/billing/gateways/orbital.rb b/lib/active_merchant/billing/gateways/orbital.rb index c26ebbac4a9..cee7b17c7cb 100644 --- a/lib/active_merchant/billing/gateways/orbital.rb +++ b/lib/active_merchant/billing/gateways/orbital.rb @@ -146,7 +146,7 @@ def initialize(options = {}) # A – Authorization request def authorize(money, creditcard, options = {}) order = build_new_order_xml(AUTH_ONLY, money, options) do |xml| - add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn] + add_creditcard(xml, creditcard, options[:currency]) add_address(xml, creditcard, options) if @options[:customer_profiles] add_customer_data(xml, options) @@ -159,7 +159,7 @@ def authorize(money, creditcard, options = {}) # AC – Authorization and Capture def purchase(money, creditcard, options = {}) order = build_new_order_xml(AUTH_AND_CAPTURE, money, options) do |xml| - add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn] + add_creditcard(xml, creditcard, options[:currency]) add_address(xml, creditcard, options) if @options[:customer_profiles] add_customer_data(xml, options) @@ -327,8 +327,10 @@ def add_customer_address(xml, options) end def add_creditcard(xml, creditcard, currency=nil) - xml.tag! :AccountNum, creditcard.number - xml.tag! :Exp, expiry_date(creditcard) + unless creditcard.nil? + xml.tag! :AccountNum, creditcard.number + xml.tag! :Exp, expiry_date(creditcard) + end xml.tag! :CurrencyCode, currency_code(currency) xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen. @@ -342,10 +344,12 @@ def add_creditcard(xml, creditcard, currency=nil) # Null-fill this attribute OR # Do not submit the attribute at all. # - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf - if %w( visa discover ).include?(creditcard.brand) - xml.tag! :CardSecValInd, (creditcard.verification_value? ? '1' : '9') + unless creditcard.nil? + if %w( visa discover ).include?(creditcard.brand) + xml.tag! :CardSecValInd, (creditcard.verification_value? ? '1' : '9') + end + xml.tag! :CardSecVal, creditcard.verification_value if creditcard.verification_value? end - xml.tag! :CardSecVal, creditcard.verification_value if creditcard.verification_value? end def add_refund(xml, currency=nil) From 32ca90803b69dc1969a5030b53b5fc121d1d16da Mon Sep 17 00:00:00 2001 From: "W. Justin Bedard" Date: Wed, 2 Oct 2013 15:39:52 -0400 Subject: [PATCH 002/104] tests for orbital_mandatory_field_fix ee1a3ff --- test/unit/gateways/orbital_test.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/unit/gateways/orbital_test.rb b/test/unit/gateways/orbital_test.rb index 667b054ab22..2225d0bcba5 100644 --- a/test/unit/gateways/orbital_test.rb +++ b/test/unit/gateways/orbital_test.rb @@ -259,6 +259,30 @@ def test_send_customer_data_when_customer_ref_is_provided assert_success response end + def test_currency_code_and_exponent_are_set_for_profile_purchase + @gateway.options[:customer_profiles] = true + response = stub_comms do + @gateway.purchase(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) + end.check_request do |endpoint, data, headers| + assert_match(/<ABC/, data) + assert_match(/124/, data) + assert_match(/2/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_currency_code_and_exponent_are_set_for_profile_authorizations + @gateway.options[:customer_profiles] = true + response = stub_comms do + @gateway.authorize(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) + end.check_request do |endpoint, data, headers| + assert_match(/<ABC/, data) + assert_match(/124/, data) + assert_match(/2/, data) + end.respond_with(successful_purchase_response) + assert_success response + end + # K1C2N6 # 1234 My Street # Apt 1 From 3245a50e9400ec7073c15088ddef51668a052391 Mon Sep 17 00:00:00 2001 From: "W. Justin Bedard" Date: Thu, 3 Oct 2013 16:12:03 -0400 Subject: [PATCH 003/104] i missed a syntax error... --- test/unit/gateways/orbital_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/gateways/orbital_test.rb b/test/unit/gateways/orbital_test.rb index 2225d0bcba5..84e1fce2cc2 100644 --- a/test/unit/gateways/orbital_test.rb +++ b/test/unit/gateways/orbital_test.rb @@ -264,7 +264,7 @@ def test_currency_code_and_exponent_are_set_for_profile_purchase response = stub_comms do @gateway.purchase(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) end.check_request do |endpoint, data, headers| - assert_match(/<ABC/, data) + assert_match(/ABC/, data) assert_match(/124/, data) assert_match(/2/, data) end.respond_with(successful_purchase_response) @@ -276,7 +276,7 @@ def test_currency_code_and_exponent_are_set_for_profile_authorizations response = stub_comms do @gateway.authorize(50, nil, :order_id => 1, :customer_ref_num => @customer_ref_num) end.check_request do |endpoint, data, headers| - assert_match(/<ABC/, data) + assert_match(/ABC/, data) assert_match(/124/, data) assert_match(/2/, data) end.respond_with(successful_purchase_response) From 35db85e7a913a9bc4d03934c60f038d0f944eca0 Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Mon, 7 Oct 2013 14:57:55 -0400 Subject: [PATCH 004/104] Ipay88: Use the amount in dollars instead of cents to generate the signature. --- .../billing/integrations/ipay88/helper.rb | 8 ++++++-- .../integrations/helpers/ipay88_helper_test.rb | 16 +++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/active_merchant/billing/integrations/ipay88/helper.rb b/lib/active_merchant/billing/integrations/ipay88/helper.rb index 123f660358b..1a889802502 100644 --- a/lib/active_merchant/billing/integrations/ipay88/helper.rb +++ b/lib/active_merchant/billing/integrations/ipay88/helper.rb @@ -46,12 +46,16 @@ def initialize(order, account, options = {}) add_field mappings[:signature], signature end + def amount_in_dollars + sprintf("%.2f", @amount_in_cents.to_f/100) + end + def amount=(money) @amount_in_cents = money.respond_to?(:cents) ? money.cents : money if money.is_a?(String) or @amount_in_cents.to_i < 0 raise ArgumentError, "money amount must be either a Money object or a positive integer in cents." end - add_field mappings[:amount], sprintf("%.2f", @amount_in_cents.to_f/100) + add_field mappings[:amount], amount_in_dollars end def currency(symbol) @@ -103,7 +107,7 @@ def sig_components components = [merchant_key] components << fields[mappings[:account]] components << fields[mappings[:order]] - components << amount_in_cents.to_s.gsub(/[.,]/, '') + components << amount_in_dollars.to_s.gsub(/0+$/, '').gsub(/[.,]/, '') components << fields[mappings[:currency]] components.join end diff --git a/test/unit/integrations/helpers/ipay88_helper_test.rb b/test/unit/integrations/helpers/ipay88_helper_test.rb index 675ba816728..8fb50ff2c2f 100644 --- a/test/unit/integrations/helpers/ipay88_helper_test.rb +++ b/test/unit/integrations/helpers/ipay88_helper_test.rb @@ -74,7 +74,7 @@ def test_unsupported_payment end def test_signature - assert_field "Signature", "vDwWN/XHvYnlReq3f1llHFCxDTY=" + assert_field "Signature", "sz25/58PfRuHloIGafsscRjk3H4=" end def test_valid_amount @@ -95,9 +95,19 @@ def test_invalid_amount_as_negative_integer_in_cents end def test_sig_components_amount_doesnt_include_decimal_points - @helper.amount = 0.5 + @helper.amount = 50 assert_equal "abcipay88merchcodeorder-50005MYR", @helper.send(:sig_components) - @helper.amount = 12.34 + @helper.amount = 1234 assert_equal "abcipay88merchcodeorder-5001234MYR", @helper.send(:sig_components) + @helper.amount = 1000 + assert_equal "abcipay88merchcodeorder-50010MYR", @helper.send(:sig_components) + @helper.amount = Money.new(90) + assert_equal "abcipay88merchcodeorder-50009MYR", @helper.send(:sig_components) + @helper.amount = Money.new(1000) + assert_equal "abcipay88merchcodeorder-50010MYR", @helper.send(:sig_components) + end + + def test_sign_method + assert_equal "rq3VxZp9cjkiqiw4mHnZJH49MzQ=", Ipay88::Helper.sign("L3mn6Bpy4HM0605613619416109MYR") end end From 280b13692c0c33f198fcf537926856306f4b6037 Mon Sep 17 00:00:00 2001 From: Wesley Ellis Date: Fri, 11 Oct 2013 10:34:56 -0400 Subject: [PATCH 005/104] Add bitpay return, fix return_url mapping --- lib/active_merchant/billing/integrations/bit_pay.rb | 5 +++++ .../billing/integrations/bit_pay/helper.rb | 2 +- .../billing/integrations/bit_pay/return.rb | 10 ++++++++++ test/unit/integrations/bit_pay_module_test.rb | 4 ++++ 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 lib/active_merchant/billing/integrations/bit_pay/return.rb diff --git a/lib/active_merchant/billing/integrations/bit_pay.rb b/lib/active_merchant/billing/integrations/bit_pay.rb index 53f0b31c39a..05609b7679a 100644 --- a/lib/active_merchant/billing/integrations/bit_pay.rb +++ b/lib/active_merchant/billing/integrations/bit_pay.rb @@ -1,5 +1,6 @@ require File.dirname(__FILE__) + '/bit_pay/helper.rb' require File.dirname(__FILE__) + '/bit_pay/notification.rb' +require File.dirname(__FILE__) + '/bit_pay/return.rb' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -19,6 +20,10 @@ def self.notification(post) def self.helper(order, account, options = {}) Helper.new(order, account, options) end + + def self.return(query_string, options = {}) + Return.new(query_string) + end end end end diff --git a/lib/active_merchant/billing/integrations/bit_pay/helper.rb b/lib/active_merchant/billing/integrations/bit_pay/helper.rb index b6753d8cb35..e5afcdd0f65 100644 --- a/lib/active_merchant/billing/integrations/bit_pay/helper.rb +++ b/lib/active_merchant/billing/integrations/bit_pay/helper.rb @@ -34,7 +34,7 @@ def initialize(order_id, account, options) :country => 'buyerCountry' mapping :notify_url, 'notificationURL' - mapping :return_url, 'returnURL' + mapping :return_url, 'redirectURL' mapping :id, 'id' def generate_invoice_id diff --git a/lib/active_merchant/billing/integrations/bit_pay/return.rb b/lib/active_merchant/billing/integrations/bit_pay/return.rb new file mode 100644 index 00000000000..f7641a80dcb --- /dev/null +++ b/lib/active_merchant/billing/integrations/bit_pay/return.rb @@ -0,0 +1,10 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module BitPay + class Return < ActiveMerchant::Billing::Integrations::Return + end + end + end + end +end diff --git a/test/unit/integrations/bit_pay_module_test.rb b/test/unit/integrations/bit_pay_module_test.rb index 0596d60e6c0..8ca4e227578 100644 --- a/test/unit/integrations/bit_pay_module_test.rb +++ b/test/unit/integrations/bit_pay_module_test.rb @@ -6,4 +6,8 @@ class BitPayModuleTest < Test::Unit::TestCase def test_notification_method assert_instance_of BitPay::Notification, BitPay.notification('name=cody') end + + def test_return_method + assert_instance_of BitPay::Return, BitPay.return('name=cody', {}) + end end From ba75017209a979c0863ca4e835abb954e51ad147 Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Fri, 18 Oct 2013 12:13:20 -0400 Subject: [PATCH 006/104] Revert "Paymill: Add support for specifying the :customer" This reverts commit 75744b7b2302055f74a286344b5508ead33cce59. Conflicts: CHANGELOG --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/paymill.rb | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4185d018aa6..ec03962427a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ = ActiveMerchant CHANGELOG + == Version 1.39.2 (October 10th, 2013) * Eway Rapid: Fix a bug with access codes that have equal signs in them [odorcic] diff --git a/lib/active_merchant/billing/gateways/paymill.rb b/lib/active_merchant/billing/gateways/paymill.rb index 12738d5ef78..7d1c816a18f 100644 --- a/lib/active_merchant/billing/gateways/paymill.rb +++ b/lib/active_merchant/billing/gateways/paymill.rb @@ -116,7 +116,6 @@ def purchase_with_token(money, card_token, options) add_amount(post, money, options) post[:token] = card_token post[:description] = options[:description] - post[:client] = options[:customer] commit(:post, 'transactions', post) end From 05085af59d5e1de28c9d2be92a4e456224bfe7a5 Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Fri, 18 Oct 2013 12:14:06 -0400 Subject: [PATCH 007/104] Bump for release v1.40.0 --- CHANGELOG | 5 +++++ lib/active_merchant/version.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index ec03962427a..743f5f91028 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,10 @@ = ActiveMerchant CHANGELOG +== Version 1.40.0 (October 18th, 2013) + +* Paymill: Revert Add support for specifying the :customer [melari] +* Quickpay: Make v7 of the API default [kvs] +* Bitpay: Add return [tahnok] == Version 1.39.2 (October 10th, 2013) diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb index 5a00f3ccb4b..041cc018b1b 100644 --- a/lib/active_merchant/version.rb +++ b/lib/active_merchant/version.rb @@ -1,3 +1,3 @@ module ActiveMerchant - VERSION = "1.39.2" + VERSION = "1.40.0" end From 3ba3d2c1dc3a575202d83ec02531bc4982ea5235 Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Mon, 21 Oct 2013 15:36:27 -0400 Subject: [PATCH 008/104] Stripe: Ignore tthe customer option when paying with a creditcard. --- lib/active_merchant/billing/gateways/stripe.rb | 6 +++--- lib/active_merchant/billing/gateways/webpay.rb | 4 ++-- test/remote/gateways/remote_stripe_test.rb | 7 +++++++ test/unit/gateways/stripe_test.rb | 2 +- test/unit/gateways/webpay_test.rb | 6 +++--- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index 0e06a2bd4d9..3073fa72164 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -127,7 +127,7 @@ def create_post_for_auth_or_purchase(money, creditcard, options) post = {} add_amount(post, money, options) add_creditcard(post, creditcard, options) - add_customer(post, options) + add_customer(post, creditcard, options) add_customer_data(post,options) post[:description] = options[:description] || options[:email] add_flags(post, options) @@ -189,8 +189,8 @@ def add_creditcard(post, creditcard, options) end end - def add_customer(post, options) - post[:customer] = options[:customer] if options[:customer] + def add_customer(post, creditcard, options) + post[:customer] = options[:customer] if options[:customer] && !creditcard.respond_to?(:number) end def add_flags(post, options) diff --git a/lib/active_merchant/billing/gateways/webpay.rb b/lib/active_merchant/billing/gateways/webpay.rb index b08e8aa090f..1f8ec4f2e68 100644 --- a/lib/active_merchant/billing/gateways/webpay.rb +++ b/lib/active_merchant/billing/gateways/webpay.rb @@ -48,8 +48,8 @@ def add_amount(post, money, options) post[:amount] = localized_amount(money, post[:currency].upcase) end - def add_customer(post, options) - post[:customer] = options[:customer] if options[:customer] && post[:card].blank? + def add_customer(post, creditcard, options) + post[:customer] = options[:customer] if options[:customer] && !creditcard.respond_to?(:number) end def json_error(raw_response) diff --git a/test/remote/gateways/remote_stripe_test.rb b/test/remote/gateways/remote_stripe_test.rb index 06c737cd1dc..c2c0ef2374d 100644 --- a/test/remote/gateways/remote_stripe_test.rb +++ b/test/remote/gateways/remote_stripe_test.rb @@ -173,4 +173,11 @@ def test_refund_partial_application_fee assert refund = @gateway.refund(@amount - 20, response.authorization, { :refund_fee_amount => 10 }) assert_success refund end + + def test_creditcard_purchase_with_customer + assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:customer => '1234')) + assert_success response + assert_equal "charge", response.params["object"] + assert response.params["paid"] + end end diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb index 03898d4b2f5..5a590e22ef6 100644 --- a/test/unit/gateways/stripe_test.rb +++ b/test/unit/gateways/stripe_test.rb @@ -182,7 +182,7 @@ def test_add_creditcard_with_token_and_track_data def test_add_customer post = {} - @gateway.send(:add_customer, post, {:customer => "test_customer"}) + @gateway.send(:add_customer, post, @creditcard, {:customer => "test_customer"}) assert_equal "test_customer", post[:customer] end diff --git a/test/unit/gateways/webpay_test.rb b/test/unit/gateways/webpay_test.rb index 1e6d9406c9c..98fc8749852 100644 --- a/test/unit/gateways/webpay_test.rb +++ b/test/unit/gateways/webpay_test.rb @@ -88,13 +88,13 @@ def test_invalid_raw_response def test_add_customer post = {} - @gateway.send(:add_customer, post, {:customer => "test_customer"}) + @gateway.send(:add_customer, post, 'card_token', {:customer => "test_customer"}) assert_equal "test_customer", post[:customer] end def test_doesnt_add_customer_if_card - post = { :card => 'foo' } - @gateway.send(:add_customer, post, {:customer => "test_customer"}) + post = {} + @gateway.send(:add_customer, post, @credit_card, {:customer => "test_customer"}) assert !post[:customer] end From a9d06f7176cfd0dd236556ed85b16bdde383cfb5 Mon Sep 17 00:00:00 2001 From: Jack London Date: Tue, 1 Oct 2013 15:47:21 -0500 Subject: [PATCH 009/104] Add Conekta gateway https://conekta.io Closes #863. --- CHANGELOG | 2 + CONTRIBUTORS | 4 + README.md | 1 + .../billing/gateways/conekta.rb | 233 +++++++++++++++ test/fixtures.yml | 3 + test/remote/gateways/remote_conekta_test.rb | 119 ++++++++ test/unit/gateways/conekta_test.rb | 280 ++++++++++++++++++ 7 files changed, 642 insertions(+) create mode 100644 lib/active_merchant/billing/gateways/conekta.rb create mode 100644 test/remote/gateways/remote_conekta_test.rb create mode 100644 test/unit/gateways/conekta_test.rb diff --git a/CHANGELOG b/CHANGELOG index 743f5f91028..9eb001038f3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ = ActiveMerchant CHANGELOG +* Add Conekta gateway [leofischer] + == Version 1.40.0 (October 18th, 2013) * Paymill: Revert Add support for specifying the :customer [melari] diff --git a/CONTRIBUTORS b/CONTRIBUTORS index c3fd6412c43..2a362d6f984 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -420,3 +420,7 @@ MoneyMovers (September 2013) Be2Bill (September 2013) * Michaël Hoste (MichaelHoste) + +Conekta (October 2013) + +* Leo Fischer (leofischer) diff --git a/README.md b/README.md index 420f6573c1b..0a184b43613 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta * [CardSave](http://www.cardsave.net/) - GB * [CardStream](http://www.cardstream.com/) - GB * [CertoDirect](http://www.certodirect.com/) - BE, BG, CZ, DK, DE, EE, IE, EL, ES, FR, IT, CY, LV, LT, LU, HU, MT, NL, AT, PL, PT, RO, SI, SK, FI, SE, GB +* [Conekta](https://conekta.io) - MX * [CyberSource](http://www.cybersource.com) - US, BR, CA, CN, DK, FI, FR, DE, JP, MX, NO, SE, GB, SG * [DataCash](http://www.datacash.com/) - GB * [Efsnet](http://www.concordefsnet.com/) - US diff --git a/lib/active_merchant/billing/gateways/conekta.rb b/lib/active_merchant/billing/gateways/conekta.rb new file mode 100644 index 00000000000..2389106acff --- /dev/null +++ b/lib/active_merchant/billing/gateways/conekta.rb @@ -0,0 +1,233 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class ConektaGateway < Gateway + self.live_url = 'https://api.conekta.io/' + + self.supported_countries = ['MX'] + self.supported_cardtypes = [:visa, :master] + self.homepage_url = 'https://conekta.io/' + self.display_name = 'Conekta Gateway' + self.money_format = :cents + self.default_currency = 'MXN' + + def initialize(options = {}) + requires!(options, :key) + options[:version] ||= '0.2.0' + super + end + + def purchase(money, payment_source, options = {}) + post = {} + + add_order(post, money, options) + add_payment_source(post, payment_source, options) + add_details_data(post, options) + + commit(:post, 'charges', post) + end + + def authorize(money, payment_source, options = {}) + post = {} + + add_order(post, money, options) + add_payment_source(post, payment_source, options) + add_details_data(post, options) + + post[:capture] = false + commit(:post, "charges", post) + end + + def capture(identifier, money, options = {}) + post = {} + + post[:order_id] = identifier + add_order(post, money, options) + + commit(:post, "charges/#{identifier}/capture", post) + end + + def refund(identifier, money, options) + post = {} + + post[:order_id] = identifier + add_order(post, money, options) + + commit(:post, "charges/#{identifier}/refund", post) + end + + def store(creditcard, options = {}) + post = {} + add_payment_source(post, creditcard, options) + post[:name] = options[:name] + post[:email] = options[:email] + + path = if options[:customer] + "customers/#{CGI.escape(options[:customer])}" + else + 'customers' + end + + commit(:post, path, post) + end + + def unstore(customer_id, options = {}) + commit(:delete, "customers/#{CGI.escape(customer_id)}", nil) + end + + private + + def add_order(post, money, options) + post[:description] = options[:description] + post[:reference_id] = options[:order_id] + post[:amount] = amount(money) + end + + def add_details_data(post, options) + details = {} + details[:name] = options[:customer] + details[:email] = options[:email] + details[:phone] = options[:phone] + details[:device_fingerprint] = options[:device_fingerprint] + details[:ip] = options[:ip] + add_billing_address(details, options) + add_line_items(details, options) + add_shipment(details, options) + + post[:details] = details + end + + def add_shipment(post, options) + shipment = {} + shipment[:carrier] = options[:carrier] + shipment[:service] = options[:service] + shipment[:tracking_number] = options[:tracking_number] + shipment[:price] = options[:price] + add_shipment_address(shipment, options) + post[:shipment] = shipment + end + + def add_shipment_address(post, options) + address = {} + address[:street1] = options[:address1] + address[:street2] = options[:address2] + address[:street3] = options[:address3] + address[:city] = options[:city] + address[:state] = options[:state] + address[:country] = options[:country] + address[:zip] = options[:zip] + post[:address] = address + end + + def add_line_items(post, options) + post[:line_items] = (options[:line_items] || []).collect do |line_item| + line_item + end + end + + def add_billing_address(post, options) + address = {} + address[:street1] = options[:address1] + address[:street2] = options[:address2] + address[:street3] = options[:address3] + address[:city] = options[:city] + address[:state] = options[:state] + address[:country] = options[:country] + address[:zip] = options[:zip] + address[:company_name] = options[:company_name] + address[:tax_id] = options[:tax_id] + address[:name] = options[:name] + address[:phone] = options[:phone] + address[:email] = options[:email] + post[:billing_address] = address + end + + def add_address(post, options) + address = {} + address[:street1] = options[:address1] + address[:street2] = options[:address2] + address[:street3] = options[:address3] + address[:city] = options[:city] + address[:state] = options[:state] + address[:country] = options[:country] + address[:zip] = options[:zip] + post[:address] = address + end + + def add_payment_source(post, payment_source, options) + if payment_source.kind_of?(String) + post[:card] = payment_source + elsif payment_source.respond_to?(:number) + card = {} + card[:name] = payment_source.name + card[:cvc] = payment_source.verification_value + card[:number] = payment_source.number + card[:exp_month] = "#{sprintf("%02d", payment_source.month)}" + card[:exp_year] = "#{"#{payment_source.year}"[-2, 2]}" + post[:card] = card + add_address(post[:card], options) + end + end + + def parse(body) + return {} unless body + JSON.parse(body) + end + + def headers(meta) + @@ua ||= JSON.dump({ + :bindings_version => ActiveMerchant::VERSION, + :lang => 'ruby', + :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", + :platform => RUBY_PLATFORM, + :publisher => 'active_merchant' + }) + + { + "Accept" => "application/vnd.conekta-v#{options[:version]}+json", + "Authorization" => "Basic " + Base64.encode64("#{options[:key]}:"), + "RaiseHtmlError" => "false", + "User-Agent" => "Conekta ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + "X-Conekta-Client-User-Agent" => @@ua, + "X-Conekta-Client-User-Metadata" => meta.to_json + } + end + + def commit(method, url, parameters, options = {}) + success = false + begin + raw_response = parse(ssl_request(method, live_url + url, (parameters ? parameters.to_query : nil), headers(options[:meta]))) + success = (raw_response.key?("object") && (raw_response["object"] != "error")) + rescue ResponseError => e + raw_response = response_error(e.response.body) + rescue JSON::ParserError + raw_response = json_error(raw_response) + end + + Response.new( + success, + raw_response["message"], + raw_response, + :test => test?, + :authorization => raw_response["id"] + ) + end + + def response_error(raw_response) + begin + parse(raw_response) + rescue JSON::ParserError + json_error(raw_response) + end + end + + def json_error(raw_response) + msg = 'Invalid response received from the Conekta API.' + msg += " (The raw response returned by the API was #{raw_response.inspect})" + { + "message" => msg + } + end + end + end +end + diff --git a/test/fixtures.yml b/test/fixtures.yml index 377ee656e79..6e10ecea744 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -85,6 +85,9 @@ cyber_source: login: X password: Y +conekta: + key: 1tv5yJp3xnVZ7eK67m4h + data_cash: login: X password: Y diff --git a/test/remote/gateways/remote_conekta_test.rb b/test/remote/gateways/remote_conekta_test.rb new file mode 100644 index 00000000000..e6cdf914817 --- /dev/null +++ b/test/remote/gateways/remote_conekta_test.rb @@ -0,0 +1,119 @@ +require 'test_helper' + +class RemoteConektaTest < Test::Unit::TestCase + def setup + @gateway = ConektaGateway.new(fixtures(:conekta)) + + @amount = 300 + + @credit_card = ActiveMerchant::Billing::CreditCard.new( + number: "4111111111111111", + verification_value: "183", + month: "01", + year: "2018", + first_name: "Mario F.", + last_name: "Moreno Reyes" + ) + + @declined_card = ActiveMerchant::Billing::CreditCard.new( + number: "4000000000000002", + verification_value: "183", + month: "01", + year: "2018", + first_name: "Mario F.", + last_name: "Moreno Reyes" + ) + + @options = { + description: 'Blue clip', + address1: "Rio Missisipi #123", + address2: "Paris", + city: "Guerrero", + country: "Mexico", + zip: "5555", + name: "Mario Reyes", + phone: "12345678", + carrier: "Estafeta" + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal nil, response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal "The card was declined", response.message + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + @options[:order_id] = response.params["id"] + assert_success response + assert_equal nil, response.message + + assert response = @gateway.refund(response.authorization, @amount, @options) + assert_success response + assert_equal nil, response.message + end + + def test_unsuccessful_refund + assert response = @gateway.refund("1", @amount, @options) + assert_failure response + assert_equal "The charge does not exist or it is not suitable for this operation", response.message + end + + def test_successful_authorize + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal nil, response.message + end + + def test_unsuccessful_authorize + assert response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert_equal "The card was declined", response.message + end + + def test_successful_capture + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal nil, response.message + + assert response = @gateway.capture(response.authorization, @amount, @options) + assert_success response + assert_equal nil, response.message + end + + def test_successful_store + assert response = @gateway.store(@credit_card, {name: "John Doe", email: "email@example.com"}) + assert_success response + assert_equal "customer", response.params["object"] + assert_equal "John Doe", response.params["name"] + assert_equal "email@example.com", response.params["email"] + end + + def test_successful_unstore + creation = @gateway.store(@credit_card, {name: "John Doe", email: "email@example.com"}) + assert response = @gateway.unstore(creation.params['id']) + assert_success response + assert_equal true, response.params["deleted"] + end + + def test_unsuccessful_capture + @options[:order_id] = "1" + assert response = @gateway.capture(@amount, @options) + assert_failure response + assert_equal "The charge does not exist or it is not suitable for this operation", response.message + end + + def test_invalid_login + gateway = ConektaGateway.new(key: 'invalid_token') + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal "Unrecognized authentication token", response.message + end +end diff --git a/test/unit/gateways/conekta_test.rb b/test/unit/gateways/conekta_test.rb new file mode 100644 index 00000000000..753a466edf0 --- /dev/null +++ b/test/unit/gateways/conekta_test.rb @@ -0,0 +1,280 @@ +require 'test_helper' + +class ConektaTest < Test::Unit::TestCase + def setup + @gateway = ConektaGateway.new(:key => "1tv5yJp3xnVZ7eK67m4h") + + @amount = 300 + + @credit_card = ActiveMerchant::Billing::CreditCard.new( + :number => "4111111111111111", + :verification_value => "183", + :month => "01", + :year => "2018", + :first_name => "Mario F.", + :last_name => "Moreno Reyes" + ) + + @declined_card = ActiveMerchant::Billing::CreditCard.new( + :number => "4000000000000002", + :verification_value => "183", + :month => "01", + :year => "2018", + :first_name => "Mario F.", + :last_name => "Moreno Reyes" + ) + + @options = { + :description => 'Blue clip', + :success_url => "https://www.example.com/success", + :failure_url => "https://www.example.com/failure", + :address1 => "Rio Missisipi #123", + :address2 => "Paris", + :city => "Guerrero", + :country => "Mexico", + :zip => "5555", + :name => "Mario Reyes", + :phone => "12345678", + :carrier => "Estafeta" + } + end + + def test_successful_tokenized_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, 'tok_xxxxxxxxxxxxxxxx', @options) + assert_instance_of Response, response + assert_success response + assert_equal nil, response.message + assert response.test? + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal nil, response.message + assert response.test? + end + + def test_unsuccessful_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_refund + @gateway.expects(:ssl_request).returns(failed_refund_response) + assert response = @gateway.refund("1", @amount, @options) + assert_failure response + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_request).returns(successful_authorize_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_instance_of Response, response + assert_equal nil, response.message + assert response.test? + end + + def test_unsuccessful_authorize + @gateway.expects(:ssl_request).returns(failed_authorize_response) + assert response = @gateway.authorize(@amount, @declined_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_capture + @gateway.expects(:ssl_request).returns(failed_purchase_response) + assert response = @gateway.capture("1", @amount, @options) + assert_failure response + assert response.test? + end + + def test_invalid_login + gateway = ConektaGateway.new(:key => 'invalid_token') + gateway.expects(:ssl_request).returns(failed_login_response) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + private + + def successful_purchase_response + { + 'id' => '521b859fcfc26c0f180002d9', + 'livemode' => false, + 'created_at' => 1377535391, + 'status' => 'pre_authorized', + 'currency' => 'MXN', + 'description' => 'Blue clip', + 'reference_id' => nil, + 'failure_code' => nil, + 'failure_message' => nil, + 'object' => 'charge', + 'amount' => 300, + 'processed_at' => nil, + 'fee' => 348, + 'card' => { + 'name' => 'Mario Reyes', + 'exp_month' => '01', + 'exp_year' => '18', + 'street2' => 'Paris', + 'street3' => 'nil', + 'city' => 'Guerrero', + 'zip' => '5555', + 'country' => 'Mexico', + 'brand' => 'VISA', + 'last4' => '1111', + 'object' => 'card', + 'fraud_response' => '3d_secure_required', + 'redirect_form' => { + 'url' => 'https => //eps.banorte.com/secure3d/Solucion3DSecure.htm', + 'action' => 'POST', + 'attributes' => { + 'MerchantId' => '7376961', + 'MerchantName' => 'GRUPO CONEKTAME', + 'MerchantCity' => 'EstadodeMexico', + 'Cert3D' => '03', + 'ClientId' => '60518', + 'Name' => '7376962', + 'Password' => 'fgt563j', + 'TransType' => 'PreAuth', + 'Mode' => 'Y', + 'E1' => 'qKNKjndV9emCxuKE1G4z', + 'E2' => '521b859fcfc26c0f180002d9', + 'E3' => 'Y', + 'ResponsePath' => 'https => //eps.banorte.com/RespuestaCC.jsp', + 'CardType' => 'VISA', + 'Card' => '4111111111111111', + 'Cvv2Indicator' => '1', + 'Cvv2Val' => '183', + 'Expires' => '01/18', + 'Total' => '3.0', + 'ForwardPath' => 'http => //localhost => 3000/charges/banorte_3d_secure_response', + 'auth_token' => 'qKNKjndV9emCxuKE1G4z' + } + } + } + }.to_json + end + + def failed_purchase_response + { + 'message' => 'The card was declined', + 'type' => 'invalid_parameter_error', + 'param' => '' + }.to_json + end + + def failed_bank_purchase_response + { + 'message' => 'The minimum purchase is 15 MXN pesos for bank transfer payments', + 'type' => 'invalid_parameter_error', + 'param' => '' + }.to_json + end + + def failed_refund_response + { + 'object' => 'error', + 'type' => 200, + 'message' => 'The charge does not exist or it is not suitable for this operation' + }.to_json + end + + def failed_void_response + { + 'object' => 'error', + 'type' => 200, + 'message' => 'The charge does not exist or it is not suitable for this operation' + }.to_json + end + + def successful_authorize_response + { + 'id' => '521b859fcfc26c0f180002d9', + 'livemode' => false, + 'created_at' => 1377535391, + 'status' => 'pre_authorized', + 'currency' => 'MXN', + 'description' => 'Blue clip', + 'reference_id' => nil, + 'failure_code' => nil, + 'failure_message' => nil, + 'object' => 'charge', + 'amount' => 300, + 'processed_at' => nil, + 'fee' => 348, + 'card' => { + 'name' => 'Mario Reyes', + 'exp_month' => '01', + 'exp_year' => '18', + 'street2' => 'Paris', + 'street3' => 'nil', + 'city' => 'Guerrero', + 'zip' => '5555', + 'country' => 'Mexico', + 'brand' => 'VISA', + 'last4' => '1111', + 'object' => 'card', + 'fraud_response' => '3d_secure_required', + 'redirect_form' => { + 'url' => 'https => //eps.banorte.com/secure3d/Solucion3DSecure.htm', + 'action' => 'POST', + 'attributes' => { + 'MerchantId' => '7376961', + 'MerchantName' => 'GRUPO CONEKTAME', + 'MerchantCity' => 'EstadodeMexico', + 'Cert3D' => '03', + 'ClientId' => '60518', + 'Name' => '7376962', + 'Password' => 'fgt563j', + 'TransType' => 'PreAuth', + 'Mode' => 'Y', + 'E1' => 'qKNKjndV9emCxuKE1G4z', + 'E2' => '521b859fcfc26c0f180002d9', + 'E3' => 'Y', + 'ResponsePath' => 'https => //eps.banorte.com/RespuestaCC.jsp', + 'CardType' => 'VISA', + 'Card' => '4111111111111111', + 'Cvv2Indicator' => '1', + 'Cvv2Val' => '183', + 'Expires' => '01/18', + 'Total' => '3.0', + 'ForwardPath' => 'http => //localhost => 3000/charges/banorte_3d_secure_response', + 'auth_token' => 'qKNKjndV9emCxuKE1G4z' + } + } + } + }.to_json + end + + def failed_authorize_response + { + 'message' => 'The card was declined', + 'type' => 'invalid_parameter_error', + 'param' => '' + }.to_json + end + + def failed_capture_response + { + 'object' => 'error', + 'type' => 200, + 'message' => 'The charge does not exist or it is not suitable for this operation' + }.to_json + end + + def failed_login_response + { + 'object' => 'error', + 'type' => 'authentication_error', + 'message' => 'Unrecognized authentication token' + }.to_json + end +end From 953bdf3c118aab3f95b9933bb47d360bf238eadc Mon Sep 17 00:00:00 2001 From: Duff OMelia Date: Tue, 22 Oct 2013 16:42:05 -0400 Subject: [PATCH 010/104] Wirecard: Add support for void and refund Closes #887. --- CHANGELOG | 1 + .../billing/gateways/wirecard.rb | 51 +++--- test/remote/gateways/remote_wirecard_test.rb | 39 ++++- test/unit/gateways/wirecard_test.rb | 157 ++++++++++++++++++ 4 files changed, 215 insertions(+), 33 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9eb001038f3..6e287cfa2f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ = ActiveMerchant CHANGELOG * Add Conekta gateway [leofischer] +* Wirecard: Add support for void and refund [duff] == Version 1.40.0 (October 18th, 2013) diff --git a/lib/active_merchant/billing/gateways/wirecard.rb b/lib/active_merchant/billing/gateways/wirecard.rb index b1f84d536cc..923b8cb90b0 100644 --- a/lib/active_merchant/billing/gateways/wirecard.rb +++ b/lib/active_merchant/billing/gateways/wirecard.rb @@ -3,10 +3,7 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class WirecardGateway < Gateway - # Test server location self.test_url = 'https://c3-test.wirecard.com/secure/ssl-gateway' - - # Live server location self.live_url = 'https://c3.wirecard.com/secure/ssl-gateway' # The Namespaces are not really needed, because it just tells the System, that there's actually no namespace used. @@ -29,57 +26,51 @@ class WirecardGateway < Gateway # number 5551234 within area code 202 (country code 1). VALID_PHONE_FORMAT = /\+\d{1,3}(\(?\d{3}\)?)?\d{3}-\d{4}-\d{3}/ - # The countries the gateway supports merchants from as 2 digit ISO country codes + self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb, :switch ] self.supported_countries = %w(AD CY GI IM MT RO CH AT DK GR IT MC SM TR BE EE HU LV NL SK GB BG FI IS LI NO SI VA FR IL LT PL ES CZ DE IE LU PT SE) - - # Wirecard supports all major credit and debit cards: - # Visa, Mastercard, American Express, Diners Club, - # JCB, Switch, VISA Carte Bancaire, Visa Electron and UATP cards. - # They also support the latest anti-fraud systems such as Verified by Visa or Master Secure Code. - self.supported_cardtypes = [ - :visa, :master, :american_express, :diners_club, :jcb, :switch - ] - - # The homepage URL of the gateway self.homepage_url = 'http://www.wirecard.com' - - # The name of the gateway self.display_name = 'Wirecard' - - # The currency should normally be EUROs self.default_currency = 'EUR' - - # 100 is 1.00 Euro self.money_format = :cents + # Public: Create a new Wirecard gateway. + # + # options - A hash of options: + # :login - The username + # :password - The password + # :signature - The BusinessCaseSignature def initialize(options = {}) - # verify that username and password are supplied - requires!(options, :login, :password) - # unfortunately Wirecard also requires a BusinessCaseSignature in the XML request - requires!(options, :signature) + requires!(options, :login, :password, :signature) super end - # Authorization def authorize(money, creditcard, options = {}) options[:credit_card] = creditcard commit(:preauthorization, money, options) end - # Capture Authorization def capture(money, authorization, options = {}) options[:preauthorization] = authorization commit(:capture, money, options) end - # Purchase def purchase(money, creditcard, options = {}) options[:credit_card] = creditcard commit(:purchase, money, options) end - private + def void(identification, options = {}) + options[:preauthorization] = identification + commit(:reversal, nil, options) + end + + def refund(money, identification, options = {}) + options[:preauthorization] = identification + commit(:bookback, money, options) + end + + private def prepare_options_hash(options) result = @options.merge(options) setup_address_hash!(result) @@ -156,9 +147,11 @@ def add_transaction_data(xml, money, options) add_invoice(xml, money, options) add_creditcard(xml, options[:credit_card]) add_address(xml, options[:billing_address]) - when :capture + when :capture, :bookback xml.tag! 'GuWID', options[:preauthorization] add_amount(xml, money) + when :reversal + xml.tag! 'GuWID', options[:preauthorization] end end end diff --git a/test/remote/gateways/remote_wirecard_test.rb b/test/remote/gateways/remote_wirecard_test.rb index 0499974350e..34f715f053f 100644 --- a/test/remote/gateways/remote_wirecard_test.rb +++ b/test/remote/gateways/remote_wirecard_test.rb @@ -43,7 +43,7 @@ def test_successful_authorize_and_capture amount = @amount assert auth = @gateway.authorize(amount, @credit_card, @options) assert_success auth - assert auth.message[/THIS IS A DEMO/] + assert_match /THIS IS A DEMO/, auth.message assert auth.authorization assert capture = @gateway.capture(amount, auth.authorization, @options) assert_success capture @@ -52,7 +52,7 @@ def test_successful_authorize_and_capture def test_successful_authorize_and_partial_capture assert auth = @gateway.authorize(@amount, @credit_card, @options) assert_success auth - assert auth.message[/THIS IS A DEMO/] + assert_match /THIS IS A DEMO/, auth.message assert auth.authorization #Capture some of the authorized amount @@ -60,11 +60,30 @@ def test_successful_authorize_and_partial_capture assert_success capture end + def test_successful_void + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert response.authorization + + assert void = @gateway.void(response.authorization) + assert_success void + assert_match /THIS IS A DEMO/, void.message + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.authorization + + assert refund = @gateway.refund(@amount - 20, response.authorization) + assert_success refund + assert_match /THIS IS A DEMO/, refund.message + end + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) - # puts response.message assert_success response - assert response.message[/THIS IS A DEMO/] + assert_match /THIS IS A DEMO/, response.message end def test_successful_purchase_with_german_address_german_state_and_german_phone @@ -110,6 +129,18 @@ def test_unauthorized_capture assert_equal "Could not find referenced transaction for GuWID 1234567890123456789012.", response.message end + def test_failed_refund + assert refund = @gateway.refund(@amount - 20, 'C428094138244444404448') + assert_failure refund + assert_match /Could not find referenced transaction/, refund.message + end + + def test_failed_void + assert void = @gateway.void('C428094138244444404448') + assert_failure void + assert_match /Could not find referenced transaction/, void.message + end + def test_invalid_login gateway = WirecardGateway.new(:login => '', :password => '', :signature => '') assert response = gateway.purchase(@amount, @credit_card, @options) diff --git a/test/unit/gateways/wirecard_test.rb b/test/unit/gateways/wirecard_test.rb index 6a1a75218ac..61c2883a9e3 100644 --- a/test/unit/gateways/wirecard_test.rb +++ b/test/unit/gateways/wirecard_test.rb @@ -77,6 +77,32 @@ def test_successful_authorization_and_capture assert response.message[/this is a demo/i] end + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal TEST_PURCHASE_GUWID, response.authorization + + @gateway.expects(:ssl_post).returns(successful_refund_response) + assert response = @gateway.refund(@amount - 30, response.authorization, @options) + assert_success response + assert response.test? + assert_match /All good!/, response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal TEST_PURCHASE_GUWID, response.authorization + + @gateway.expects(:ssl_post).returns(successful_void_response) + assert response = @gateway.void(response.authorization, @options) + assert_success response + assert response.test? + assert_match /Nice one!/, response.message + end + def test_successful_authorization_and_partial_capture @gateway.expects(:ssl_post).returns(successful_authorization_response) assert response = @gateway.authorize(@amount, @credit_card, @options) @@ -99,6 +125,20 @@ def test_unauthorized_capture assert response.message["Could not find referenced transaction for GuWID 1234567890123456789012."] end + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_refund_response) + assert response = @gateway.refund(@amount - 30, "TheIdentifcation", @options) + assert_failure response + assert_match /Not prudent/, response.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + assert response = @gateway.refund(@amount - 30, "TheIdentifcation", @options) + assert_failure response + assert_match /Not gonna do it/, response.message + end + def test_no_error_if_no_state_is_provided_in_address options = @options.merge(:billing_address => @address_without_state) @gateway.expects(:ssl_post).returns(unauthorized_capture_response) @@ -272,6 +312,123 @@ def successful_purchase_response XML end + def successful_refund_response + <<-XML + + + + + + + + + 2a486b3ab747df694d5460c3cb444591 + + C898842138247065382261 + 424492 + All good! + INFO + ACK + 2013-10-22 21:37:33 + + + + + + + XML + end + + def failed_refund_response + <<-XML + + + + + + + + + 98680cbeee81d32e94a2b71397ffdf88 + + C999187138247102291030 + + INFO + NOK + + DATA_ERROR + 20080 + Not prudent + + 2013-10-22 21:43:42 + + + + + + + XML + end + + def successful_void_response + <<-XML + + + + + + + + + 5f1a2ab3fb2ed7a6aaa0eea74dc109e2 + + C907807138247383379288 + 802187 + Nice one! + INFO + ACK + 2013-10-22 22:30:33 + + + + + + + XML + end + + def failed_void_response + <<-XML + + + + + + + + + c11154e9395cf03c49bd68ec5c7087cc + + C941776138247400010330 + + INFO + NOK + + DATA_ERROR + 20080 + Not gonna do it + + 2013-10-22 22:33:20 + + + + + + + XML + end + + # Purchase failure def wrong_creditcard_purchase_response <<-XML From f72aff4c12e54d8ccdd37302986eda87fd16cb28 Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Thu, 24 Oct 2013 10:39:21 -0400 Subject: [PATCH 011/104] Bump for release v1.41.0 --- CHANGELOG | 4 ++++ lib/active_merchant/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 6e287cfa2f4..0196d90e482 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,11 @@ = ActiveMerchant CHANGELOG +== Version 1.41.0 (October 24th, 2013) + +* Stripe: Payments won't fail when specifying a customer with a creditcard number [melari] * Add Conekta gateway [leofischer] * Wirecard: Add support for void and refund [duff] +* Orbital: Mandatory field fix [juicedM3, jduff] == Version 1.40.0 (October 18th, 2013) diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb index 041cc018b1b..08b87931626 100644 --- a/lib/active_merchant/version.rb +++ b/lib/active_merchant/version.rb @@ -1,3 +1,3 @@ module ActiveMerchant - VERSION = "1.40.0" + VERSION = "1.41.0" end From 7eb5b226f0cc0bc6633cd0a8dae2a4222e848856 Mon Sep 17 00:00:00 2001 From: Timothy Klim Date: Thu, 24 Oct 2013 13:19:43 +0400 Subject: [PATCH 012/104] Fix NoMethodError "tr" for params with dash Also fixes a nil being passed to CGI.unescape. Closes #890. --- CHANGELOG | 2 ++ generators/integration/templates/notification.rb | 4 ++-- lib/active_merchant/billing/integrations/notification.rb | 4 ++-- test/unit/integrations/notifications/notification_test.rb | 6 ++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0196d90e482..6359b46fe93 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ = ActiveMerchant CHANGELOG +* Fix NoMethodError "tr" for params with dash [TimothyKlim] + == Version 1.41.0 (October 24th, 2013) * Stripe: Payments won't fail when specifying a customer with a creditcard number [melari] diff --git a/generators/integration/templates/notification.rb b/generators/integration/templates/notification.rb index 72416fbc6cf..4e66fb2f500 100644 --- a/generators/integration/templates/notification.rb +++ b/generators/integration/templates/notification.rb @@ -90,8 +90,8 @@ def acknowledge(authcode = nil) def parse(post) @raw = post.to_s for line in @raw.split('&') - key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten - params[key] = CGI.unescape(value) + key, value = *line.scan( %r{^([A-Za-z0-9_.-]+)\=(.*)$} ).flatten + params[key] = CGI.unescape(value.to_s) if key.present? end end end diff --git a/lib/active_merchant/billing/integrations/notification.rb b/lib/active_merchant/billing/integrations/notification.rb index 56df8ee8aa1..815916b4c42 100644 --- a/lib/active_merchant/billing/integrations/notification.rb +++ b/lib/active_merchant/billing/integrations/notification.rb @@ -61,8 +61,8 @@ def test? def parse(post) @raw = post.to_s for line in @raw.split('&') - key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten - params[key] = CGI.unescape(value) + key, value = *line.scan( %r{^([A-Za-z0-9_.-]+)\=(.*)$} ).flatten + params[key] = CGI.unescape(value.to_s) if key.present? end end end diff --git a/test/unit/integrations/notifications/notification_test.rb b/test/unit/integrations/notifications/notification_test.rb index 0e9d4a759ed..671a5e5da9e 100644 --- a/test/unit/integrations/notifications/notification_test.rb +++ b/test/unit/integrations/notifications/notification_test.rb @@ -16,6 +16,8 @@ def test_parse assert_equal "confirmed", @notification.params['address_status'] assert_equal "EVMXCLDZJV77Q", @notification.params['payer_id'] assert_equal "Completed", @notification.params['payment_status'] + assert_equal "ru", @notification.params['yamoney-lang'] + assert_equal '', @notification.params['empty_params'] assert_equal CGI.unescape("15%3A23%3A54+Apr+15%2C+2005+PDT"), @notification.params['payment_date'] end @@ -45,10 +47,10 @@ def test_valid_sender_always_true_when_no_ips private def http_raw_data - "mc_gross=500.00&address_status=confirmed&payer_id=EVMXCLDZJV77Q&tax=0.00&address_street=164+Waverley+Street&payment_date=15%3A23%3A54+Apr+15%2C+2005+PDT&payment_status=Completed&address_zip=K2P0V6&first_name=Tobias&mc_fee=15.05&address_country_code=CA&address_name=Tobias+Luetke¬ify_version=1.7&custom=&payer_status=unverified&business=tobi%40leetsoft.com&address_country=Canada&address_city=Ottawa&quantity=1&payer_email=tobi%40snowdevil.ca&verify_sign=AEt48rmhLYtkZ9VzOGAtwL7rTGxUAoLNsuf7UewmX7UGvcyC3wfUmzJP&txn_id=6G996328CK404320L&payment_type=instant&last_name=Luetke&address_state=Ontario&receiver_email=tobi%40leetsoft.com&payment_fee=&receiver_id=UQ8PDYXJZQD9Y&txn_type=web_accept&item_name=Store+Purchase&mc_currency=CAD&item_number=&test_ipn=1&payment_gross=&shipping=0.00" + "mc_gross=500.00&address_status=confirmed&payer_id=EVMXCLDZJV77Q&tax=0.00&address_street=164+Waverley+Street&payment_date=15%3A23%3A54+Apr+15%2C+2005+PDT&payment_status=Completed&address_zip=K2P0V6&first_name=Tobias&mc_fee=15.05&address_country_code=CA&address_name=Tobias+Luetke¬ify_version=1.7&custom=&payer_status=unverified&business=tobi%40leetsoft.com&address_country=Canada&address_city=Ottawa&quantity=1&payer_email=tobi%40snowdevil.ca&verify_sign=AEt48rmhLYtkZ9VzOGAtwL7rTGxUAoLNsuf7UewmX7UGvcyC3wfUmzJP&txn_id=6G996328CK404320L&payment_type=instant&last_name=Luetke&address_state=Ontario&receiver_email=tobi%40leetsoft.com&payment_fee=&receiver_id=UQ8PDYXJZQD9Y&txn_type=web_accept&item_name=Store+Purchase&mc_currency=CAD&item_number=&test_ipn=1&payment_gross=&shipping=0.00&yamoney-lang=ru&empty_params=" end def http_raw_data_with_period - "mc_gross=500.00&address_status=confirmed&payer_id=EVMXCLDZJV77Q&tax=0.00&address_street=164+Waverley+Street&payment_date=15%3A23%3A54+Apr+15%2C+2005+PDT&payment_status=Completed&address_zip=K2P0V6&first_name=Tobias&mc_fee=15.05&address_country_code=CA&address_name=Tobias+Luetke¬ify_version=1.7&custom=&payer_status=unverified&business=tobi%40leetsoft.com&address_country=Canada&address_city=Ottawa&quantity=1&payer_email=tobi%40snowdevil.ca&verify_sign=AEt48rmhLYtkZ9VzOGAtwL7rTGxUAoLNsuf7UewmX7UGvcyC3wfUmzJP&txn_id=6G996328CK404320L&payment_type=instant&last_name=Luetke&address_state=Ontario&receiver_email=tobi%40leetsoft.com&payment_fee=&receiver_id=UQ8PDYXJZQD9Y&txn_type=web_accept&item_name=Store+Purchase&mc_currency=CAD&item_number=&test_ipn=1&payment_gross=&shipping=0.00&checkout.x=clicked" + "mc_gross=500.00&address_status=confirmed&payer_id=EVMXCLDZJV77Q&tax=0.00&address_street=164+Waverley+Street&payment_date=15%3A23%3A54+Apr+15%2C+2005+PDT&payment_status=Completed&address_zip=K2P0V6&first_name=Tobias&mc_fee=15.05&address_country_code=CA&address_name=Tobias+Luetke¬ify_version=1.7&custom=&payer_status=unverified&business=tobi%40leetsoft.com&address_country=Canada&address_city=Ottawa&quantity=1&payer_email=tobi%40snowdevil.ca&verify_sign=AEt48rmhLYtkZ9VzOGAtwL7rTGxUAoLNsuf7UewmX7UGvcyC3wfUmzJP&txn_id=6G996328CK404320L&payment_type=instant&last_name=Luetke&address_state=Ontario&receiver_email=tobi%40leetsoft.com&payment_fee=&receiver_id=UQ8PDYXJZQD9Y&txn_type=web_accept&item_name=Store+Purchase&mc_currency=CAD&item_number=&test_ipn=1&payment_gross=&shipping=0.00&checkout.x=clicked&yamoney-lang=ru&empty_params=" end end From e081093ce6ff7578a8a0d906c6bc066f2d8b64e7 Mon Sep 17 00:00:00 2001 From: structure Date: Wed, 14 Aug 2013 11:43:43 -0500 Subject: [PATCH 013/104] Authorize.Net: Add CAVV support Closes #801. --- CHANGELOG | 1 + .../billing/gateways/authorize_net.rb | 14 ++++++++++++-- test/unit/gateways/authorize_net_test.rb | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6359b46fe93..3c1e830aa6e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ = ActiveMerchant CHANGELOG * Fix NoMethodError "tr" for params with dash [TimothyKlim] +* Authorize.Net: Add cardholder authentication options (CAVV) support [structure] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/authorize_net.rb b/lib/active_merchant/billing/gateways/authorize_net.rb index fb2fada9f6f..5abc1a41afa 100644 --- a/lib/active_merchant/billing/gateways/authorize_net.rb +++ b/lib/active_merchant/billing/gateways/authorize_net.rb @@ -39,7 +39,7 @@ class AuthorizeNetGateway < Gateway APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4 RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT, AUTHORIZATION_CODE = 0, 2, 3, 4 - AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE = 5, 6, 38 + AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE, CARDHOLDER_AUTH_CODE = 5, 6, 38, 39 self.default_currency = 'USD' @@ -311,7 +311,8 @@ def parse(body) :avs_result_code => fields[AVS_RESULT_CODE], :transaction_id => fields[TRANSACTION_ID], :card_code => fields[CARD_CODE_RESPONSE_CODE], - :authorization_code => fields[AUTHORIZATION_CODE] + :authorization_code => fields[AUTHORIZATION_CODE], + :cardholder_authentication_code => fields[CARDHOLDER_AUTH_CODE] } results end @@ -383,6 +384,15 @@ def add_customer_data(post, options) if options.has_key? :ip post[:customer_ip] = options[:ip] end + + if options.has_key? :cardholder_authentication_value + post[:cardholder_authentication_value] = options[:cardholder_authentication_value] + end + + if options.has_key? :authentication_indicator + post[:authentication_indicator] = options[:authentication_indicator] + end + end # x_duplicate_window won't be sent by default, because sending it changes the response. diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/gateways/authorize_net_test.rb index db70bb2abfe..c03ba16add4 100644 --- a/test/unit/gateways/authorize_net_test.rb +++ b/test/unit/gateways/authorize_net_test.rb @@ -150,6 +150,14 @@ def test_add_duplicate_window_with_duplicate_window assert_equal 0, result[:duplicate_window] end + def test_add_cardholder_authentication_value + result = {} + params = {:cardholder_authentication_value => 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', :authentication_indicator => '2'} + @gateway.send(:add_customer_data, result, params) + assert_equal 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', result[:cardholder_authentication_value] + assert_equal '2', result[:authentication_indicator] + end + def test_purchase_is_valid_csv params = { :amount => '1.01' } @@ -186,6 +194,13 @@ def test_authorization_code_included_in_params assert_equal('d1GENk', response.params['authorization_code'] ) end + def test_cardholder_authorization_code_included_in_params + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + response = @gateway.capture(50, '123456789') + assert_equal('2', response.params['cardholder_authentication_code'] ) + end + def test_capture_passing_extra_info response = stub_comms do @gateway.capture(50, '123456789', :description => "Yo", :order_id => "Sweetness") From ead63b9fdec15da1a960d02d657b06edbfe297b2 Mon Sep 17 00:00:00 2001 From: Nathaniel Talbott Date: Thu, 24 Oct 2013 16:37:53 -0400 Subject: [PATCH 014/104] Stripe: Add a test for passing a token See #836. --- test/unit/gateways/stripe_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb index 5a590e22ef6..c808d5c6cf6 100644 --- a/test/unit/gateways/stripe_test.rb +++ b/test/unit/gateways/stripe_test.rb @@ -47,6 +47,18 @@ def test_successful_purchase assert response.test? end + def test_successful_purchase_with_token + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, "tok_xxx") + end.check_request do |method, endpoint, data, headers| + assert_match(/card=tok_xxx/, data) + end.respond_with(successful_purchase_response) + + assert response + assert_instance_of Response, response + assert_success response + end + def test_successful_void @gateway.expects(:ssl_request).returns(successful_purchase_response(true)) From 73fa83b3ad6453b2e70031dc02fe89d19bfbd053 Mon Sep 17 00:00:00 2001 From: "Vincens R. Mink" Date: Thu, 19 Sep 2013 16:30:28 +0100 Subject: [PATCH 015/104] CardStreamModern: Add input checks Closes #859. --- CHANGELOG | 1 + .../billing/gateways/card_stream_modern.rb | 3 ++- .../remote_card_stream_modern_test.rb | 27 +------------------ 3 files changed, 4 insertions(+), 27 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3c1e830aa6e..59e2688bed7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ * Fix NoMethodError "tr" for params with dash [TimothyKlim] * Authorize.Net: Add cardholder authentication options (CAVV) support [structure] +* CardStreamModern: Added better checks on inputs from the gateway [ExxKA] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/card_stream_modern.rb b/lib/active_merchant/billing/gateways/card_stream_modern.rb index 1d3393fa22d..984de4b28b9 100644 --- a/lib/active_merchant/billing/gateways/card_stream_modern.rb +++ b/lib/active_merchant/billing/gateways/card_stream_modern.rb @@ -114,7 +114,8 @@ def parse(body) pairs = body.split("&") pairs.each do |pair| a = pair.split("=") - result[a[0].to_sym] = CGI.unescape(a[1]) + #Make sure there is a value, else set it to empty string + result[a[0].to_sym] = a[1] ? CGI.unescape(a[1]) : "" end result end diff --git a/test/remote/gateways/remote_card_stream_modern_test.rb b/test/remote/gateways/remote_card_stream_modern_test.rb index 3b65bd9b279..ea2ea0dbfc7 100644 --- a/test/remote/gateways/remote_card_stream_modern_test.rb +++ b/test/remote/gateways/remote_card_stream_modern_test.rb @@ -13,14 +13,6 @@ def setup :brand => :american_express ) - @uk_maestro = credit_card('6759015050123445002', - :month => '12', - :year => '2014', - :issue_number => '0', - :verification_value => '309', - :brand => :switch - ) - @mastercard = credit_card('5301250070000191', :month => '12', :year => '2014', @@ -93,17 +85,6 @@ def setup :description => 'AM test purchase' } - @uk_maestro_options = { - :billing_address => { - :address1 => 'The Parkway', - :address2 => "5258 Larches Approach", - :city => "Hull", - :state => "North Humberside", - :zip => 'HU10 5OP' - }, - :order_id => generate_unique_id, - :description => 'AM test purchase' - } end def test_successful_visacreditcard_authorization_and_capture @@ -248,17 +229,11 @@ def test_declined_mastercard_purchase def test_expired_mastercard @mastercard.year = 2012 assert response = @gateway.purchase(142, @mastercard, @mastercard_options) - assert_equal 'INVALID CARDEXPIRYDATE', response.message + assert_equal 'CARD EXPIRED', response.message assert_failure response assert response.test? end - def test_successful_maestro_purchase - assert response = @gateway.purchase(142, @uk_maestro, @uk_maestro_options) - assert_equal 'APPROVED', response.message - assert_success response - end - def test_successful_amex_purchase assert response = @gateway.purchase(142, @amex, @amex_options) assert_equal 'APPROVED', response.message From 9c69ba6fa2a05314ab014ae80f1d4316c652527a Mon Sep 17 00:00:00 2001 From: Chris Wise Date: Wed, 23 Oct 2013 14:43:40 -0400 Subject: [PATCH 016/104] changed Balanced response on store to be an ActiveMerchant::Billing::Response and passed the card_uri and account_uri back in authorization --- lib/active_merchant/billing/gateways/balanced.rb | 12 +++++++++--- test/unit/gateways/balanced_test.rb | 6 ++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/active_merchant/billing/gateways/balanced.rb b/lib/active_merchant/billing/gateways/balanced.rb index 63350ebb677..83dfa06677d 100644 --- a/lib/active_merchant/billing/gateways/balanced.rb +++ b/lib/active_merchant/billing/gateways/balanced.rb @@ -261,11 +261,17 @@ def store(credit_card, options = {}) post = {} account_uri = create_or_find_account(post, options) if credit_card.respond_to? :number - add_credit_card(post, credit_card, options) + card_uri = add_credit_card(post, credit_card, options) else - associate_card_to_account(account_uri, credit_card) - credit_card + card_uri = associate_card_to_account(account_uri, credit_card) end + + is_test = false + if @marketplace_uri + is_test = (@marketplace_uri.index("TEST") ? true : false) + end + + Response.new(true, "Card stored", {}, :test => is_test, :authorization => [card_uri, account_uri].compact.join(';')) rescue Error => ex failed_response(ex.response) end diff --git a/test/unit/gateways/balanced_test.rb b/test/unit/gateways/balanced_test.rb index 174d12998ff..3b4fa9e5226 100644 --- a/test/unit/gateways/balanced_test.rb +++ b/test/unit/gateways/balanced_test.rb @@ -283,11 +283,13 @@ def test_store ) card_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/cards/CC6r6kLUcxW3MxG3AmZoiuTf' + account_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC5quPICW5qEHXac1KnjKGYu' assert response = @gateway.store(@credit_card, { :email=>'john.buyer@example.org' }) - assert_instance_of String, response - assert_equal card_uri, response + assert_instance_of Response, response + assert_success response + assert_equal "#{card_uri};#{account_uri}", response.authorization end def test_ensure_does_not_respond_to_credit From 698da06641a124523e0af52485f83b0866c943d6 Mon Sep 17 00:00:00 2001 From: Justin Bull Date: Mon, 7 Oct 2013 15:47:09 -0400 Subject: [PATCH 017/104] Stripe: Send `ip` option instead of `browser_ip` option --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/stripe.rb | 2 +- test/unit/gateways/stripe_test.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 59e2688bed7..a84bc39ecba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ * Fix NoMethodError "tr" for params with dash [TimothyKlim] * Authorize.Net: Add cardholder authentication options (CAVV) support [structure] * CardStreamModern: Added better checks on inputs from the gateway [ExxKA] +* Stripe: Send :ip to the gateway instead of :browser_ip [f3ndot] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index 3073fa72164..b0b9cd5db51 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -145,7 +145,7 @@ def add_application_fee(post, options) end def add_customer_data(post, options) - metadata_options = [:description,:browser_ip,:user_agent,:referrer] + metadata_options = [:description, :ip, :user_agent, :referrer] post.update(options.slice(*metadata_options)) post[:external_id] = options[:order_id] diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb index c808d5c6cf6..d5249911f1d 100644 --- a/test/unit/gateways/stripe_test.rb +++ b/test/unit/gateways/stripe_test.rb @@ -216,7 +216,7 @@ def test_application_fee_is_submitted_for_capture def test_client_data_submitted_with_purchase stub_comms(@gateway, :ssl_request) do - updated_options = @options.merge({:description => "a test customer",:browser_ip => "127.127.127.127", :user_agent => "some browser", :order_id => "42", :email => "foo@wonderfullyfakedomain.com", :referrer =>"http://www.shopify.com"}) + updated_options = @options.merge({:description => "a test customer",:ip => "127.127.127.127", :user_agent => "some browser", :order_id => "42", :email => "foo@wonderfullyfakedomain.com", :referrer =>"http://www.shopify.com"}) @gateway.purchase(@amount,@credit_card,updated_options) end.check_request do |method, endpoint, data, headers| assert_match(/description=a\+test\+customer/, data) From 4359e778be5fa23de67b6b122e0bd1030437e072 Mon Sep 17 00:00:00 2001 From: David Joerg Date: Thu, 31 Oct 2013 10:50:45 -0400 Subject: [PATCH 018/104] fix documented parameter name --- .../billing/gateways/paypal/paypal_recurring_api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb b/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb index 157037cb176..c70308ddd0d 100644 --- a/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +++ b/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb @@ -12,7 +12,7 @@ module PaypalRecurringApi # This transaction creates a recurring payment profile # ==== Parameters # - # * money -- The amount to be charged to the customer at each interval as an Integer value in cents. + # * amount -- The amount to be charged to the customer at each interval as an Integer value in cents. # * credit_card -- The CreditCard details for the transaction. # * options -- A hash of parameters. # From c9d48ec44a949d0fffd6ef011a569037d7889e8e Mon Sep 17 00:00:00 2001 From: mbretter Date: Fri, 20 Sep 2013 09:12:24 +0200 Subject: [PATCH 019/104] Wirecard Checkout Page Integration --- CHANGELOG | 1 + .../integrations/wirecard_checkout_page.rb | 39 +++++ .../wirecard_checkout_page/common.rb | 106 +++++++++++++ .../wirecard_checkout_page/helper.rb | 145 ++++++++++++++++++ .../wirecard_checkout_page/notification.rb | 101 ++++++++++++ .../wirecard_checkout_page/return.rb | 35 +++++ test/fixtures.yml | 5 + .../wirecard_checkout_page_helper_test.rb | 89 +++++++++++ ...irecard_checkout_page_notification_test.rb | 34 ++++ .../wirecard_checkout_page_return_test.rb | 21 +++ .../wirecard_checkout_page_module_test.rb | 9 ++ 11 files changed, 585 insertions(+) create mode 100644 lib/active_merchant/billing/integrations/wirecard_checkout_page.rb create mode 100644 lib/active_merchant/billing/integrations/wirecard_checkout_page/common.rb create mode 100644 lib/active_merchant/billing/integrations/wirecard_checkout_page/helper.rb create mode 100644 lib/active_merchant/billing/integrations/wirecard_checkout_page/notification.rb create mode 100644 lib/active_merchant/billing/integrations/wirecard_checkout_page/return.rb create mode 100644 test/unit/integrations/helpers/wirecard_checkout_page_helper_test.rb create mode 100644 test/unit/integrations/notifications/wirecard_checkout_page_notification_test.rb create mode 100644 test/unit/integrations/returns/wirecard_checkout_page_return_test.rb create mode 100644 test/unit/integrations/wirecard_checkout_page_module_test.rb diff --git a/CHANGELOG b/CHANGELOG index a84bc39ecba..1498d0ab670 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ * Authorize.Net: Add cardholder authentication options (CAVV) support [structure] * CardStreamModern: Added better checks on inputs from the gateway [ExxKA] * Stripe: Send :ip to the gateway instead of :browser_ip [f3ndot] +* Wirecard Page: new offsite gateway [mbretter] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/integrations/wirecard_checkout_page.rb b/lib/active_merchant/billing/integrations/wirecard_checkout_page.rb new file mode 100644 index 00000000000..0af90074116 --- /dev/null +++ b/lib/active_merchant/billing/integrations/wirecard_checkout_page.rb @@ -0,0 +1,39 @@ +=begin + * Shop System Plugins - Terms of use + * + * This terms of use regulates warranty and liability between Wirecard Central Eastern Europe (subsequently referred to as WDCEE) and it's + * contractual partners (subsequently referred to as customer or customers) which are related to the use of plugins provided by WDCEE. + * + * The Plugin is provided by WDCEE free of charge for it's customers and must be used for the purpose of WDCEE's payment platform + * integration only. It explicitly is not part of the general contract between WDCEE and it's customer. The plugin has successfully been tested + * under specific circumstances which are defined as the shopsystem's standard configuration (vendor's delivery state). The Customer is + * responsible for testing the plugin's functionality before putting it into production enviroment. + * The customer uses the plugin at own risk. WDCEE does not guarantee it's full functionality neither does WDCEE assume liability for any + * disadvantage related to the use of this plugin. By installing the plugin into the shopsystem the customer agrees to the terms of use. + * Please do not use this plugin if you do not agree to the terms of use! +=end + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WirecardCheckoutPage + autoload :Common, File.dirname(__FILE__) + '/wirecard_checkout_page/common.rb' + autoload :Helper, File.dirname(__FILE__) + '/wirecard_checkout_page/helper.rb' + autoload :Notification, File.dirname(__FILE__) + '/wirecard_checkout_page/notification.rb' + autoload :Return, File.dirname(__FILE__) + '/wirecard_checkout_page/return.rb' + + mattr_accessor :service_url + self.service_url = 'https://checkout.wirecard.com/page/init.php' + + def self.notification(post, options) + Notification.new(post, options) + end + + def self.return(postdata, options) + Return.new(postdata, options) + end + + end + end + end +end diff --git a/lib/active_merchant/billing/integrations/wirecard_checkout_page/common.rb b/lib/active_merchant/billing/integrations/wirecard_checkout_page/common.rb new file mode 100644 index 00000000000..8ceacf05bbd --- /dev/null +++ b/lib/active_merchant/billing/integrations/wirecard_checkout_page/common.rb @@ -0,0 +1,106 @@ +=begin + * Shop System Plugins - Terms of use + * + * This terms of use regulates warranty and liability between Wirecard Central Eastern Europe (subsequently referred to as WDCEE) and it's + * contractual partners (subsequently referred to as customer or customers) which are related to the use of plugins provided by WDCEE. + * + * The Plugin is provided by WDCEE free of charge for it's customers and must be used for the purpose of WDCEE's payment platform + * integration only. It explicitly is not part of the general contract between WDCEE and it's customer. The plugin has successfully been tested + * under specific circumstances which are defined as the shopsystem's standard configuration (vendor's delivery state). The Customer is + * responsible for testing the plugin's functionality before putting it into production enviroment. + * The customer uses the plugin at own risk. WDCEE does not guarantee it's full functionality neither does WDCEE assume liability for any + * disadvantage related to the use of this plugin. By installing the plugin into the shopsystem the customer agrees to the terms of use. + * Please do not use this plugin if you do not agree to the terms of use! +=end + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WirecardCheckoutPage + module Common + + mattr_accessor :paymenttypes + self.paymenttypes = %w( + SELECT + CCARD + BANCONTACT_MISTERCASH + C2P + CCARD-MOTO + EKONTO + ELV + EPS + GIROPAY + IDL + INSTALLMENT + INSTANTBANK + INVOICE + MAESTRO + MONETA + MPASS + PRZELEWY24 + PAYPAL + PBX + POLI + PSC + QUICK + SKRILLDIRECT + SKRILLWALLET + SOFORTUEBERWEISUNG) + + def message + @message + end + + def verify_response(params, secret) + + logstr = '' + params.each { |key, value| + logstr += "#{key} #{value}\n" + } + Rails.logger.debug "verify_response\n#{logstr}" if Rails.logger + + @paymentstate = 'FAILURE' + + unless params.has_key?('paymentState') + @message = "paymentState is missing" + return false + end + + if params['paymentState'] == 'SUCCESS' || params['paymentState'] == 'PENDING' + unless params.has_key?('responseFingerprint') + @message = "responseFingerprint is missing" + return false + end + + unless params.has_key?('responseFingerprintOrder') + @message = "responseFingerprintOrder is missing" + return false + end + + end + + if params['paymentState'] == 'SUCCESS' || params['paymentState'] == 'PENDING' + fields = params['responseFingerprintOrder'].split(",") + values = '' + fields.each { |f| + values += f == 'secret' ? secret : params[f] + } + + Rails.logger.debug "Fingerprint his: " + params['responseFingerprint'] + " mine: " + Digest::MD5.hexdigest(values) if Rails.logger + + if Digest::MD5.hexdigest(values) != params['responseFingerprint'] + @message = "responseFingerprint verification failed" + return false + end + end + + @paymentstate = params['paymentState'] + true + end + + end + + end + end + end +end diff --git a/lib/active_merchant/billing/integrations/wirecard_checkout_page/helper.rb b/lib/active_merchant/billing/integrations/wirecard_checkout_page/helper.rb new file mode 100644 index 00000000000..62ec0db4d15 --- /dev/null +++ b/lib/active_merchant/billing/integrations/wirecard_checkout_page/helper.rb @@ -0,0 +1,145 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WirecardCheckoutPage + class Helper < ActiveMerchant::Billing::Integrations::Helper + include Common + + PLUGIN_NAME = 'ActiveMerchant_WirecardCheckoutPage' + PLUGIN_VERSION = '1.0.0' + + # Replace with the real mapping + mapping :account, 'customerId' + mapping :amount, 'amount' + + mapping :order, 'xActiveMerchantOrderId' + + mapping :customer, :first_name => 'consumerBillingFirstName', + :last_name => 'consumerBillingLastName', + :email => 'consumerEmail', + :phone => 'consumerBillingPhone', + :ipaddress => 'consumerIpAddress', + :user_agent => 'consumerUserAgent', + :fax => 'consumerBillingFax', + :birthdate => 'consumerBirthDate' # mandatory for INVOICE and INSTALLMENT + + mapping :billing_address, :city => 'consumerBillingCity', + :address1 => 'consumerBillingAddress1', + :address2 => 'consumerBillingAddress2', + :state => 'consumerBillingState', + :zip => 'consumerBillingZipCode', + :country => 'consumerBillingCountry' + + mapping :shipping_address, :first_name => 'consumerShippingFirstName', + :last_name => 'consumerShippingLastName', + :address1 => 'consumerShippingAddress1', + :address2 => 'consumerShippingAddress2', + :city => 'consumerShippingCity', + :state => 'consumerShippingState', + :country => 'consumerShippingCountry', + :zip => 'consumerShippingZipCode', + :phone => 'consumerShippingPhone', + :fax => 'consumerShippingFax' + + mapping :currency, 'currency' + mapping :language, 'language' # language for displayed texts on payment page + + mapping :description, 'orderDescription' # unique description of the consumer's order in a human readable form + + mapping :shop_service_url, 'serviceUrl' # URL of your service page containing contact information + + mapping :notify_url, 'confirmUrl' + mapping :return_url, 'successUrl' + + # defaulting to return_url + mapping :cancel_return_url, 'cancelUrl' + mapping :pending_url, 'pendingUrl' + mapping :failure_url, 'failureUrl' + + # optional parameters + mapping :window_name, 'windowName' # window.name of browser window where payment page is opened + mapping :duplicate_request_check, 'duplicateRequestCheck' # check for duplicate requests done by your consumer + mapping :customer_statement, 'customerStatement' # text displayed on invoice of financial institution of your consumer + mapping :order_reference, 'orderReference' # unique order reference id sent from merchant to financial institution + mapping :display_text, 'displayText' # text displayed to your consumer within the payment page + mapping :image_url, 'imageUrl' # URL of your web shop where your web shop logo is located + mapping :max_retries, 'maxRetries' # maximum number of attempted payments for the same order + mapping :auto_deposit, 'autoDeposit' # enable automated debiting of payments + mapping :financial_institution, 'financialInstitution' # based on pre-selected payment type a sub-selection of financial institutions regarding to pre-selected payment type + + # not used + mapping :tax, '' + mapping :shipping, '' + + def initialize(order, customer_id, options = {}) + @paymenttype = options.delete(:paymenttype) + + raise "Unknown Paymenttype: " + @paymenttype if paymenttypes.find_index(@paymenttype) == nil + + @secret = options.delete(:secret) + @customer_id = customer_id + @shop_id = options.delete(:shop_id) + super + end + + def add_version(shop_name, shop_version) + add_field('pluginVersion', Base64.encode64(shop_name + ';' + shop_version + ';ActiveMerchant;' + PLUGIN_NAME + ';' + PLUGIN_VERSION)) + end + + def add_standard_fields + addfields = {} + addfields['shopId'] = @shop_id if !@shop_id.blank? + addfields['paymentType'] = @paymenttype + + addfields[mappings[:pending_url]] = @fields[mappings[:return_url]] unless @fields.has_key?(mappings[:pending_url]) + addfields[mappings[:cancel_return_url]] = @fields[mappings[:return_url]] unless @fields.has_key?(mappings[:cancel_return_url]) + addfields[mappings[:failure_url]] = @fields[mappings[:return_url]] unless @fields.has_key?(mappings[:failure_url]) + + addfields + end + + def add_request_fingerprint(fpfields) + addfields = {} + fingerprint_order = %w(secret) + fingerprint_values = @secret.to_s + fpfields.each { |key, value| + next if key == 'pluginVersion' + fingerprint_order.append key + fingerprint_values += value.to_s + } + + fingerprint_order.append 'requestFingerprintOrder' + fingerprint_values += fingerprint_order.join(',') + + addfields['requestFingerprintOrder'] = fingerprint_order.join(',') + addfields['requestFingerprint'] = Digest::MD5.hexdigest(fingerprint_values) + + return addfields + end + + def form_fields + result = {} + result.merge!(@fields) + result.merge!(add_standard_fields) + result.merge!(add_request_fingerprint(result)) + result + end + + def secret + @secret + end + + def customer_id + @customer_id + end + + def shop_id + @shop_id + end + + end + + end + end + end +end \ No newline at end of file diff --git a/lib/active_merchant/billing/integrations/wirecard_checkout_page/notification.rb b/lib/active_merchant/billing/integrations/wirecard_checkout_page/notification.rb new file mode 100644 index 00000000000..73590b84c92 --- /dev/null +++ b/lib/active_merchant/billing/integrations/wirecard_checkout_page/notification.rb @@ -0,0 +1,101 @@ +require 'net/http' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WirecardCheckoutPage + class Notification < ActiveMerchant::Billing::Integrations::Notification + include Common + + def complete? + @paymentstate == 'SUCCESS' + end + + def item_id + params['xActiveMerchantOrderId'] + end + + def transaction_id + params['orderNumber'] + end + + # When was this payment received by the client. + def received_at + nil + end + + # the money amount we received in X.2 decimal. + def gross + params['amount'] + end + + # Was this a test transaction? + def test? + false + end + + def status + case @paymentstate + when 'SUCCESS' + 'Completed' + when 'PENDING' + 'Pending' + when 'CANCEL' + 'Cancelled' + when 'FAILURE' + 'Failed' + else + 'Error' + end + end + + def status_code + @paymentstate + end + + + # Acknowledge the transaction to WirecardCheckoutPage. This method has to be called after a new + # apc arrives. WirecardCheckoutPage will verify that all the information we received are correct and will return a + # ok or a fail. + # + # Example: + # + # def ipn + # notify = WirecardCheckoutPageNotification.new(request.raw_post, options) + # + # if notify.acknowledge + # ... process order ... if notify.complete? + # else + # ... log possible hacking attempt ... + # end + def acknowledge + verify_response(params, @options[:secret]) + end + + def response(umessage = nil) + if @message || umessage + '' + else + '' + end + end + + def method_missing(method_id, *args) + return params[method_id.to_s] if params.has_key?(method_id.to_s) + end + + private + + # Take the posted data and move the relevant data into a hash + def parse(post) + @raw = post.to_s + for line in @raw.split('&') + key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten + params[key] = CGI.unescape(value) + end + end + end + end + end + end +end diff --git a/lib/active_merchant/billing/integrations/wirecard_checkout_page/return.rb b/lib/active_merchant/billing/integrations/wirecard_checkout_page/return.rb new file mode 100644 index 00000000000..0e3944a67a5 --- /dev/null +++ b/lib/active_merchant/billing/integrations/wirecard_checkout_page/return.rb @@ -0,0 +1,35 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module WirecardCheckoutPage + class Return < ActiveMerchant::Billing::Integrations::Return + include Common + + def initialize(postdata, options = {}) + @params = parse(postdata) + @options = options + verify_response(@params, options[:secret]) + end + + def success? + @paymentstate == 'SUCCESS' + end + + def cancelled? + @paymentstate == 'CANCEL' + end + + def pending? + @paymentstate == 'PENDING' + end + + def method_missing(method_id, *args) + return params[method_id.to_s] if params.has_key?(method_id.to_s) + end + + end + end + end + end +end + diff --git a/test/fixtures.yml b/test/fixtures.yml index 6e10ecea744..62fb1c0907d 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -654,3 +654,8 @@ iats_payments: bit_pay: api_key: 'rKrahSl7WRrYeKRUhGzbvW3nBzo0jG4FPaL8uPYaoPk' + +wirecard_checkout_page: + secret: B8AKTPWBRMNBV455FG6M2DANE99WU2 + shop_id: '' + paymenttype: IDL diff --git a/test/unit/integrations/helpers/wirecard_checkout_page_helper_test.rb b/test/unit/integrations/helpers/wirecard_checkout_page_helper_test.rb new file mode 100644 index 00000000000..b90e56ef542 --- /dev/null +++ b/test/unit/integrations/helpers/wirecard_checkout_page_helper_test.rb @@ -0,0 +1,89 @@ +require 'test_helper' + +class WirecardCheckoutPageHelperTest < Test::Unit::TestCase + include ActiveMerchant::Billing::Integrations + + def setup + @options = fixtures(:wirecard_checkout_page) + @helper = WirecardCheckoutPage::Helper.new('13', 'D200001', @options) + + @helper.max_retries = 3 + @helper.auto_deposit = true + @helper.add_version('Some Shopsystem', '0.0.1') + + @helper.language 'de' + @helper.description 'Order Number 13' + @helper.shop_service_url 'http://www.example.com/imprint' + @helper.notify_url "https://www.example.com/payment/confirm" + @helper.return_url "http://www.example.com/payment/return" + @helper.cancel_return_url "http://www.example.com/payment/return" + @helper.pending_url "http://www.example.com/payment/return" + @helper.failure_url "http://www.example.com/payment/return" + end + + def test_basic_helper_fields + assert_field 'language', 'de' + assert_field 'orderDescription', 'Order Number 13' + assert_field 'serviceUrl', 'http://www.example.com/imprint' + assert_field 'autoDeposit', "true" + assert_field 'confirmUrl', "https://www.example.com/payment/confirm" + assert_field 'successUrl', "http://www.example.com/payment/return" + assert_field 'cancelUrl', "http://www.example.com/payment/return" + assert_field 'pendingUrl', "http://www.example.com/payment/return" + assert_field 'failureUrl', "http://www.example.com/payment/return" + assert_field 'maxRetries', "3" + assert @helper.secret == 'B8AKTPWBRMNBV455FG6M2DANE99WU2' + assert @helper.customer_id == 'D200001' + assert @helper.shop_id == '' + end + + def test_customer_fields + @helper.customer :first_name => 'Sepp', + :last_name => 'Maier', + :ipaddress => '127.0.0.1', + :user_agent => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0', + :email => 'foo@bar.com' + + assert_field 'consumerBillingFirstName', 'Sepp' + assert_field 'consumerBillingLastName', 'Maier' + assert_field 'consumerIpAddress', '127.0.0.1' + assert_field 'consumerUserAgent', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0' + assert_field 'consumerEmail', 'foo@bar.com' + end + + def test_address_mapping + @helper.billing_address(:address1 => 'Daham 99', + :zip => '8010', + :city => 'Graz', + :state => 'Steiermark', + :country => 'Austria') + + assert_field 'consumerBillingAddress1', 'Daham 99' + assert_field 'consumerBillingZipCode', '8010' + assert_field 'consumerBillingCity', 'Graz' + assert_field 'consumerBillingState', 'Steiermark' + assert_field 'consumerBillingCountry', 'AT' + + @helper.shipping_address(:first_name => 'Arnold', + :last_name => 'Schwarzenegger', + :address1 => 'Broadway 128', + :city => 'Los Angeles', + :state => 'NY', + :country => 'USA', + :zip => '10890', + :phone => '192634520', + :fax => '1926345202') + + assert_field 'consumerShippingFirstName', 'Arnold' + assert_field 'consumerShippingLastName', 'Schwarzenegger' + assert_field 'consumerShippingAddress1', 'Broadway 128' + assert_field 'consumerShippingZipCode', '10890' + assert_field 'consumerShippingCity', 'Los Angeles' + assert_field 'consumerShippingState', 'NY' + assert_field 'consumerShippingCountry', 'US' + assert_field 'consumerShippingPhone', '192634520' + assert_field 'consumerShippingFax', '1926345202' + + end + +end diff --git a/test/unit/integrations/notifications/wirecard_checkout_page_notification_test.rb b/test/unit/integrations/notifications/wirecard_checkout_page_notification_test.rb new file mode 100644 index 00000000000..f3ec7fb3c55 --- /dev/null +++ b/test/unit/integrations/notifications/wirecard_checkout_page_notification_test.rb @@ -0,0 +1,34 @@ +require 'test_helper' +require 'rails' + +class WirecardCheckoutPageNotificationTest < Test::Unit::TestCase + include ActiveMerchant::Billing::Integrations + + def setup + @options = fixtures(:wirecard_checkout_page) + @wirecard_checkout_page = WirecardCheckoutPage::Notification.new(http_raw_data, @options) + end + + def test_accessors + @wirecard_checkout_page.acknowledge + assert_equal nil, @wirecard_checkout_page.message + assert @wirecard_checkout_page.complete? + assert_equal "13", @wirecard_checkout_page.item_id + assert_equal "110.99", @wirecard_checkout_page.gross + assert_equal "EUR", @wirecard_checkout_page.currency + assert_equal "IDL", @wirecard_checkout_page.paymentType + end + + def test_send_acknowledgement + assert_equal '', @wirecard_checkout_page.response + end + + def test_respond_to_acknowledge + assert @wirecard_checkout_page.respond_to?(:acknowledge) + end + + private + def http_raw_data + "amount=110.99¤cy=EUR&paymentType=IDL&financialInstitution=INGBANK&language=de&orderNumber=9882408&paymentState=SUCCESS&utf8=%E2%9C%93&xActiveMerchantOrderId=13&consumerIpAddress=192.168.201.181&consumerUserAgent=Mozilla%2F5.0+%28X11%3B+Ubuntu%3B+Linux+x86_64%3B+rv%3A24.0%29+Gecko%2F20100101+Firefox%2F24.0&commit=Jetzt+bezahlen&idealConsumerName=Test+C%C3%B6ns%C3%BCmer+Utl%C3%B8psdato&idealConsumerBIC=RABONL2U&idealConsumerCity=RABONL2U&idealConsumerIBAN=NL17RABO0213698412&idealConsumerAccountNumber=NL17RABO0213698412&gatewayReferenceNumber=DGW_9882408_RN&gatewayContractNumber=DemoContractNumber123&avsResponseCode=X&avsResponseMessage=Demo+AVS+ResultMessage&responseFingerprintOrder=amount%2Ccurrency%2CpaymentType%2CfinancialInstitution%2Clanguage%2CorderNumber%2CpaymentState%2Cutf8%2CxActiveMerchantOrderId%2CconsumerIpAddress%2CconsumerUserAgent%2Ccommit%2CidealConsumerName%2CidealConsumerBIC%2CidealConsumerCity%2CidealConsumerIBAN%2CidealConsumerAccountNumber%2CgatewayReferenceNumber%2CgatewayContractNumber%2CavsResponseCode%2CavsResponseMessage%2Csecret%2CresponseFingerprintOrder&responseFingerprint=a15a4fceefcab5a41380f97079180d55" + end +end diff --git a/test/unit/integrations/returns/wirecard_checkout_page_return_test.rb b/test/unit/integrations/returns/wirecard_checkout_page_return_test.rb new file mode 100644 index 00000000000..51fe647258a --- /dev/null +++ b/test/unit/integrations/returns/wirecard_checkout_page_return_test.rb @@ -0,0 +1,21 @@ +require 'test_helper' +require 'rails' + +class WirecardCheckoutPageReturnTest < Test::Unit::TestCase + include ActiveMerchant::Billing::Integrations + + def setup + @options = fixtures(:wirecard_checkout_page) + @return = WirecardCheckoutPage::Return.new(http_raw_data, @options) + end + + def test_return + assert @return.success? + end + + private + def http_raw_data + "amount=110.99¤cy=EUR&paymentType=IDL&financialInstitution=INGBANK&language=de&orderNumber=9882408&paymentState=SUCCESS&utf8=%E2%9C%93&xActiveMerchantOrderId=13&consumerIpAddress=192.168.201.181&consumerUserAgent=Mozilla%2F5.0+%28X11%3B+Ubuntu%3B+Linux+x86_64%3B+rv%3A24.0%29+Gecko%2F20100101+Firefox%2F24.0&commit=Jetzt+bezahlen&idealConsumerName=Test+C%C3%B6ns%C3%BCmer+Utl%C3%B8psdato&idealConsumerBIC=RABONL2U&idealConsumerCity=RABONL2U&idealConsumerIBAN=NL17RABO0213698412&idealConsumerAccountNumber=NL17RABO0213698412&gatewayReferenceNumber=DGW_9882408_RN&gatewayContractNumber=DemoContractNumber123&avsResponseCode=X&avsResponseMessage=Demo+AVS+ResultMessage&responseFingerprintOrder=amount%2Ccurrency%2CpaymentType%2CfinancialInstitution%2Clanguage%2CorderNumber%2CpaymentState%2Cutf8%2CxActiveMerchantOrderId%2CconsumerIpAddress%2CconsumerUserAgent%2Ccommit%2CidealConsumerName%2CidealConsumerBIC%2CidealConsumerCity%2CidealConsumerIBAN%2CidealConsumerAccountNumber%2CgatewayReferenceNumber%2CgatewayContractNumber%2CavsResponseCode%2CavsResponseMessage%2Csecret%2CresponseFingerprintOrder&responseFingerprint=a15a4fceefcab5a41380f97079180d55" + end + +end \ No newline at end of file diff --git a/test/unit/integrations/wirecard_checkout_page_module_test.rb b/test/unit/integrations/wirecard_checkout_page_module_test.rb new file mode 100644 index 00000000000..454d9752a9c --- /dev/null +++ b/test/unit/integrations/wirecard_checkout_page_module_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class WirecardCheckoutPageModuleTest < Test::Unit::TestCase + include ActiveMerchant::Billing::Integrations + + def test_notification_method + assert_instance_of WirecardCheckoutPage::Notification, WirecardCheckoutPage.notification('name=cody', {}) + end +end From 1a7054e085284b837300ad3536b7ca937fa02d06 Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Tue, 5 Nov 2013 08:43:40 -0500 Subject: [PATCH 020/104] Remove the requirement for rails in the wirecard checkout page integration. --- .../billing/integrations/wirecard_checkout_page/common.rb | 2 -- .../integrations/returns/wirecard_checkout_page_return_test.rb | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/active_merchant/billing/integrations/wirecard_checkout_page/common.rb b/lib/active_merchant/billing/integrations/wirecard_checkout_page/common.rb index 8ceacf05bbd..88013a39e5b 100644 --- a/lib/active_merchant/billing/integrations/wirecard_checkout_page/common.rb +++ b/lib/active_merchant/billing/integrations/wirecard_checkout_page/common.rb @@ -57,7 +57,6 @@ def verify_response(params, secret) params.each { |key, value| logstr += "#{key} #{value}\n" } - Rails.logger.debug "verify_response\n#{logstr}" if Rails.logger @paymentstate = 'FAILURE' @@ -86,7 +85,6 @@ def verify_response(params, secret) values += f == 'secret' ? secret : params[f] } - Rails.logger.debug "Fingerprint his: " + params['responseFingerprint'] + " mine: " + Digest::MD5.hexdigest(values) if Rails.logger if Digest::MD5.hexdigest(values) != params['responseFingerprint'] @message = "responseFingerprint verification failed" diff --git a/test/unit/integrations/returns/wirecard_checkout_page_return_test.rb b/test/unit/integrations/returns/wirecard_checkout_page_return_test.rb index 51fe647258a..0dc022b0452 100644 --- a/test/unit/integrations/returns/wirecard_checkout_page_return_test.rb +++ b/test/unit/integrations/returns/wirecard_checkout_page_return_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'rails' class WirecardCheckoutPageReturnTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations @@ -18,4 +17,4 @@ def http_raw_data "amount=110.99¤cy=EUR&paymentType=IDL&financialInstitution=INGBANK&language=de&orderNumber=9882408&paymentState=SUCCESS&utf8=%E2%9C%93&xActiveMerchantOrderId=13&consumerIpAddress=192.168.201.181&consumerUserAgent=Mozilla%2F5.0+%28X11%3B+Ubuntu%3B+Linux+x86_64%3B+rv%3A24.0%29+Gecko%2F20100101+Firefox%2F24.0&commit=Jetzt+bezahlen&idealConsumerName=Test+C%C3%B6ns%C3%BCmer+Utl%C3%B8psdato&idealConsumerBIC=RABONL2U&idealConsumerCity=RABONL2U&idealConsumerIBAN=NL17RABO0213698412&idealConsumerAccountNumber=NL17RABO0213698412&gatewayReferenceNumber=DGW_9882408_RN&gatewayContractNumber=DemoContractNumber123&avsResponseCode=X&avsResponseMessage=Demo+AVS+ResultMessage&responseFingerprintOrder=amount%2Ccurrency%2CpaymentType%2CfinancialInstitution%2Clanguage%2CorderNumber%2CpaymentState%2Cutf8%2CxActiveMerchantOrderId%2CconsumerIpAddress%2CconsumerUserAgent%2Ccommit%2CidealConsumerName%2CidealConsumerBIC%2CidealConsumerCity%2CidealConsumerIBAN%2CidealConsumerAccountNumber%2CgatewayReferenceNumber%2CgatewayContractNumber%2CavsResponseCode%2CavsResponseMessage%2Csecret%2CresponseFingerprintOrder&responseFingerprint=a15a4fceefcab5a41380f97079180d55" end -end \ No newline at end of file +end From 477132564069ca95a8457a32270003ebcddeeda8 Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Tue, 5 Nov 2013 08:55:20 -0500 Subject: [PATCH 021/104] Missed one of the rails requires. --- .../notifications/wirecard_checkout_page_notification_test.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/integrations/notifications/wirecard_checkout_page_notification_test.rb b/test/unit/integrations/notifications/wirecard_checkout_page_notification_test.rb index f3ec7fb3c55..8beedfb19ad 100644 --- a/test/unit/integrations/notifications/wirecard_checkout_page_notification_test.rb +++ b/test/unit/integrations/notifications/wirecard_checkout_page_notification_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'rails' class WirecardCheckoutPageNotificationTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations From 3fe13c35ca4d21d81def9f79301b22936fe533f6 Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Thu, 7 Nov 2013 13:06:46 -0500 Subject: [PATCH 022/104] Allow money gem v6. --- activemerchant.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemerchant.gemspec b/activemerchant.gemspec index 152c2bca3d1..6d31a5299fc 100644 --- a/activemerchant.gemspec +++ b/activemerchant.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', '>= 2.3.14', '< 5.0.0') s.add_dependency('i18n', '~> 0.5') - s.add_dependency('money', '< 6.0.0') + s.add_dependency('money', '< 7.0.0') s.add_dependency('builder', '>= 2.1.2', '< 4.0.0') s.add_dependency('json', '~> 1.7') s.add_dependency('active_utils', '~> 2.0', '>= 2.0.1') From ba1890b01707053aa26b775f98cc73227c5a4598 Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Wed, 6 Nov 2013 15:02:18 -0500 Subject: [PATCH 023/104] Ignore money_format when using a non-decimal currency. --- lib/active_merchant/billing/gateway.rb | 9 ++++- .../billing/gateways/eway_rapid.rb | 4 +-- .../billing/gateways/secure_pay_au.rb | 6 ++-- test/unit/gateways/eway_rapid_test.rb | 14 ++++++++ test/unit/gateways/gateway_test.rb | 36 ++++++++++++++++--- test/unit/gateways/secure_pay_au_test.rb | 16 +++++++++ 6 files changed, 76 insertions(+), 9 deletions(-) diff --git a/lib/active_merchant/billing/gateway.rb b/lib/active_merchant/billing/gateway.rb index 97b31208db5..2b2efe2fd80 100644 --- a/lib/active_merchant/billing/gateway.rb +++ b/lib/active_merchant/billing/gateway.rb @@ -161,7 +161,14 @@ def amount(money) def localized_amount(money, currency) amount = amount(money) - non_fractional_currency?(currency) ? amount.split('.').first : amount + + return amount unless non_fractional_currency?(currency) + + if self.money_format == :cents + sprintf("%.0f", amount.to_f / 100) + else + amount.split('.').first + end end def non_fractional_currency?(currency) diff --git a/lib/active_merchant/billing/gateways/eway_rapid.rb b/lib/active_merchant/billing/gateways/eway_rapid.rb index 12616b3f320..f7c3791e554 100644 --- a/lib/active_merchant/billing/gateways/eway_rapid.rb +++ b/lib/active_merchant/billing/gateways/eway_rapid.rb @@ -141,10 +141,10 @@ def add_metadata(doc, options) def add_invoice(doc, money, options) doc.Payment do - doc.TotalAmount amount(money) + currency_code = options[:currency] || currency(money) + doc.TotalAmount localized_amount(money, currency_code) doc.InvoiceReference options[:order_id] doc.InvoiceDescription options[:description] - currency_code = (options[:currency] || currency(money) || default_currency) doc.CurrencyCode currency_code end end diff --git a/lib/active_merchant/billing/gateways/secure_pay_au.rb b/lib/active_merchant/billing/gateways/secure_pay_au.rb index dd552114edb..db05999c1a8 100644 --- a/lib/active_merchant/billing/gateways/secure_pay_au.rb +++ b/lib/active_merchant/billing/gateways/secure_pay_au.rb @@ -108,8 +108,10 @@ def unstore(identification, options = {}) def build_purchase_request(money, credit_card, options) xml = Builder::XmlMarkup.new - xml.tag! 'amount', amount(money) - xml.tag! 'currency', options[:currency] || currency(money) + currency = options[:currency] || currency(money) + + xml.tag! 'amount', localized_amount(money, currency) + xml.tag! 'currency', currency xml.tag! 'purchaseOrderNo', options[:order_id].to_s.gsub(/[ ']/, '') xml.tag! 'CreditCardInfo' do diff --git a/test/unit/gateways/eway_rapid_test.rb b/test/unit/gateways/eway_rapid_test.rb index ab8a7eb246a..b5b9196c1ef 100644 --- a/test/unit/gateways/eway_rapid_test.rb +++ b/test/unit/gateways/eway_rapid_test.rb @@ -38,6 +38,20 @@ def test_successful_setup_purchase assert response.test? end + def test_localized_currency + stub_comms do + @gateway.setup_purchase(100, :currency => 'CAD', :redirect_url => '') + end.check_request do |endpoint, data, headers| + assert_match /100<\/TotalAmount>/, data + end.respond_with(successful_setup_purchase_response) + + stub_comms do + @gateway.setup_purchase(100, :currency => 'JPY', :redirect_url => '') + end.check_request do |endpoint, data, headers| + assert_match /1<\/TotalAmount>/, data + end.respond_with(successful_setup_purchase_response) + end + def test_failed_setup_purchase response = stub_comms do @gateway.setup_purchase(@amount, :redirect_url => "http://bogus") diff --git a/test/unit/gateways/gateway_test.rb b/test/unit/gateways/gateway_test.rb index 2bef0185402..ef1557b0cf2 100644 --- a/test/unit/gateways/gateway_test.rb +++ b/test/unit/gateways/gateway_test.rb @@ -1,6 +1,14 @@ require 'test_helper' class GatewayTest < Test::Unit::TestCase + def setup + @gateway = Gateway.new + end + + def teardown + Gateway.money_format = :dollars + end + def test_should_detect_if_a_card_is_supported Gateway.supported_cardtypes = [:visa, :bogus] assert [:visa, :bogus].all? { |supported_cardtype| Gateway.supports?(supported_cardtype) } @@ -15,17 +23,17 @@ def test_should_gateway_uses_ssl_strict_checking_by_default def test_should_be_able_to_look_for_test_mode Base.gateway_mode = :test - assert Gateway.new.test? + assert @gateway.test? Base.gateway_mode = :production - assert_false Gateway.new.test? + assert_false @gateway.test? end def test_amount_style - assert_equal '10.34', Gateway.new.send(:amount, 1034) + assert_equal '10.34', @gateway.send(:amount, 1034) assert_raise(ArgumentError) do - Gateway.new.send(:amount, '10.34') + @gateway.send(:amount, '10.34') end end @@ -45,4 +53,24 @@ def test_setting_application_id_outside_the_class_definition assert_equal SimpleTestGateway.application_id, SubclassGateway.application_id end + + def test_localized_amount_should_not_modify_for_fractional_currencies + Gateway.money_format = :dollars + assert_equal '1.00', @gateway.send(:localized_amount, 100, 'CAD') + assert_equal '12.34', @gateway.send(:localized_amount, 1234, 'USD') + + Gateway.money_format = :cents + assert_equal '100', @gateway.send(:localized_amount, 100, 'CAD') + assert_equal '1234', @gateway.send(:localized_amount, 1234, 'USD') + end + + def test_localized_amount_should_ignore_money_format_for_non_fractional_currencies + Gateway.money_format = :dollars + assert_equal '1', @gateway.send(:localized_amount, 100, 'JPY') + assert_equal '12', @gateway.send(:localized_amount, 1234, 'HUF') + + Gateway.money_format = :cents + assert_equal '1', @gateway.send(:localized_amount, 100, 'JPY') + assert_equal '12', @gateway.send(:localized_amount, 1234, 'HUF') + end end diff --git a/test/unit/gateways/secure_pay_au_test.rb b/test/unit/gateways/secure_pay_au_test.rb index 54b49c753ba..8684697067d 100644 --- a/test/unit/gateways/secure_pay_au_test.rb +++ b/test/unit/gateways/secure_pay_au_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class SecurePayAuTest < Test::Unit::TestCase + include CommStub + def setup @gateway = SecurePayAuGateway.new( :login => 'login', @@ -48,6 +50,20 @@ def test_successful_purchase assert response.test? end + def test_localized_currency + stub_comms do + @gateway.purchase(100, @credit_card, @options.merge(:currency => 'CAD')) + end.check_request do |endpoint, data, headers| + assert_match /100<\/amount>/, data + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(100, @credit_card, @options.merge(:currency => 'JPY')) + end.check_request do |endpoint, data, headers| + assert_match /1<\/amount>/, data + end.respond_with(successful_purchase_response) + end + def test_failed_purchase @gateway.expects(:ssl_post).returns(failed_purchase_response) From 42337d54bac4e0bcd7ff7a320b3a924de6d03ae8 Mon Sep 17 00:00:00 2001 From: Joseph Shin Date: Wed, 6 Nov 2013 10:12:22 -0500 Subject: [PATCH 024/104] Orbital: Add support for currency with 0 CurrencyExponent (JPY) --- .../billing/gateways/orbital.rb | 27 +++++++++++++++++-- test/unit/gateways/orbital_test.rb | 20 ++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/active_merchant/billing/gateways/orbital.rb b/lib/active_merchant/billing/gateways/orbital.rb index cee7b17c7cb..85eacc75a5e 100644 --- a/lib/active_merchant/billing/gateways/orbital.rb +++ b/lib/active_merchant/billing/gateways/orbital.rb @@ -80,6 +80,25 @@ class OrbitalGateway < Gateway "EUR" => '978' } + CURRENCY_EXPONENTS = { + "AUD" => '2', + "CAD" => '2', + "CZK" => '2', + "DKK" => '2', + "HKD" => '2', + "ICK" => '2', + "JPY" => '0', + "MXN" => '2', + "NZD" => '2', + "NOK" => '2', + "SGD" => '2', + "SEK" => '2', + "CHF" => '2', + "GBP" => '2', + "USD" => '2', + "EUR" => '2' + } + # INDUSTRY TYPES ECOMMERCE_TRANSACTION = 'EC' RECURRING_PAYMENT_TRANSACTION = 'RC' @@ -333,7 +352,7 @@ def add_creditcard(xml, creditcard, currency=nil) end xml.tag! :CurrencyCode, currency_code(currency) - xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen. + xml.tag! :CurrencyExponent, currency_exponents(currency) # If you are trying to collect a Card Verification Number # (CardSecVal) for a Visa or Discover transaction, pass one of these values: @@ -356,7 +375,7 @@ def add_refund(xml, currency=nil) xml.tag! :AccountNum, nil xml.tag! :CurrencyCode, currency_code(currency) - xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen. + xml.tag! :CurrencyExponent, currency_exponents(currency) end def add_managed_billing(xml, options) @@ -544,6 +563,10 @@ def currency_code(currency) CURRENCY_CODES[(currency || self.default_currency)].to_s end + def currency_exponents(currency) + CURRENCY_EXPONENTS[(currency || self.default_currency)].to_s + end + def expiry_date(credit_card) "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}" end diff --git a/test/unit/gateways/orbital_test.rb b/test/unit/gateways/orbital_test.rb index 84e1fce2cc2..99db6eceb75 100644 --- a/test/unit/gateways/orbital_test.rb +++ b/test/unit/gateways/orbital_test.rb @@ -22,6 +22,26 @@ def test_successful_purchase assert_equal '4A5398CF9B87744GG84A1D30F2F2321C66249416;1', response.authorization end + def test_currency_exponents + stub_comms do + @gateway.purchase(50, credit_card, :order_id => '1') + end.check_request do |endpoint, data, headers| + assert_match /2<\/CurrencyExponent>/, data + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(50, credit_card, :order_id => '1', :currency => 'CAD') + end.check_request do |endpoint, data, headers| + assert_match /2<\/CurrencyExponent>/, data + end.respond_with(successful_purchase_response) + + stub_comms do + @gateway.purchase(50, credit_card, :order_id => '1', :currency => 'JPY') + end.check_request do |endpoint, data, headers| + assert_match /0<\/CurrencyExponent>/, data + end.respond_with(successful_purchase_response) + end + def test_unauthenticated_response @gateway.expects(:ssl_post).returns(failed_purchase_response) From 6768d3cadc820fe0058e99a9926da958f08e6baa Mon Sep 17 00:00:00 2001 From: Mike Dalton Date: Sun, 3 Nov 2013 13:49:09 -0500 Subject: [PATCH 025/104] Mercury: Add support for requesting a token There is already tokenization support for generating token after an authorization. This adds support for generating a token without an authorization. This method is useful because Mercury documentation explicitly disallows using a zero-auth for generating a token. Closes #898. --- CHANGELOG | 1 + .../billing/gateways/mercury.rb | 22 +++++++++++++++++++ test/remote/gateways/remote_mercury_test.rb | 8 +++++++ 3 files changed, 31 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1498d0ab670..0abb7427d0d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ * CardStreamModern: Added better checks on inputs from the gateway [ExxKA] * Stripe: Send :ip to the gateway instead of :browser_ip [f3ndot] * Wirecard Page: new offsite gateway [mbretter] +* Mercury: Add support for requesting a token [kcdragon] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/mercury.rb b/lib/active_merchant/billing/gateways/mercury.rb index 4f969015904..12faffb2c02 100644 --- a/lib/active_merchant/billing/gateways/mercury.rb +++ b/lib/active_merchant/billing/gateways/mercury.rb @@ -77,6 +77,11 @@ def void(authorization, options={}) commit('VoidSale', request) end + def store(credit_card, options={}) + request = build_card_lookup_request(credit_card, options) + commit('CardLookup', request) + end + private def build_non_authorized_request(action, money, credit_card, options) @@ -129,6 +134,23 @@ def build_authorized_request(action, money, authorization, credit_card, options) xml = xml.target! end + def build_card_lookup_request(credit_card, options) + xml = Builder::XmlMarkup.new + + xml.tag! "TStream" do + xml.tag! "Transaction" do + xml.tag! 'TranType', 'CardLookup' + xml.tag! 'RecordNo', 'RecordNumberRequested' + xml.tag! 'Frequency', 'OneTime' + + xml.tag! 'Memo', options[:description] + add_customer_data(xml, options) + add_credit_card(xml, credit_card, options) + end + end + xml.target! + end + def add_invoice(xml, invoice_no, ref_no, options) if /^\d+$/ !~ invoice_no.to_s raise ArgumentError.new("order_id '#{invoice_no}' is not numeric as required by Mercury") diff --git a/test/remote/gateways/remote_mercury_test.rb b/test/remote/gateways/remote_mercury_test.rb index 5fda0efb03c..254b4793f7a 100644 --- a/test/remote/gateways/remote_mercury_test.rb +++ b/test/remote/gateways/remote_mercury_test.rb @@ -71,6 +71,14 @@ def test_successful_purchase assert_equal "0.50", response.params["purchase"] end + def test_store + response = @gateway.store(@credit_card, @options) + + assert_success response + assert response.params.has_key?("record_no") + assert response.params['record_no'] != '' + end + def test_credit response = @gateway.credit(50, @credit_card, @options) From 3fe5817168161f6bc4f8180e66c6556ad0d919d2 Mon Sep 17 00:00:00 2001 From: Ian Butler Date: Thu, 4 Jul 2013 09:54:17 +0100 Subject: [PATCH 026/104] Add App55 gateway https://www.app55.com/ Currently supports #purchase and #authorize. Closes #748. --- CONTRIBUTORS | 4 + README.md | 1 + lib/active_merchant/billing/gateways/app55.rb | 185 ++++++++++++++++++ test/fixtures.yml | 4 + test/remote/gateways/remote_app55_test.rb | 65 ++++++ test/unit/gateways/app55_test.rb | 51 +++++ 6 files changed, 310 insertions(+) create mode 100644 lib/active_merchant/billing/gateways/app55.rb create mode 100644 test/remote/gateways/remote_app55_test.rb create mode 100644 test/unit/gateways/app55_test.rb diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 2a362d6f984..d42efb0fda0 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -424,3 +424,7 @@ Be2Bill (September 2013) Conekta (October 2013) * Leo Fischer (leofischer) + +App55 (November 2013) + +* (ianbutler55) diff --git a/README.md b/README.md index 0a184b43613..0475632c152 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ For more in-depth documentation and tutorials, see [GettingStarted.md](GettingSt The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) contains a [table of features supported by each gateway](http://github.com/Shopify/active_merchant/wikis/gatewayfeaturematrix). +* [App55](https://www.app55.com/) - AU, BR, CA, CH, CL, CN, CO, CZ, DK, EU, GB, HK, HU, ID, IS, JP, KE, KR, MX, MY, NO, NZ, PH, PL, TH, TW, US, VN, ZA * [Authorize.Net CIM](http://www.authorize.net/) - US * [Authorize.Net](http://www.authorize.net/) - US, CA, GB * [Balanced](https://www.balancedpayments.com/) - US diff --git a/lib/active_merchant/billing/gateways/app55.rb b/lib/active_merchant/billing/gateways/app55.rb new file mode 100644 index 00000000000..b775dcb8197 --- /dev/null +++ b/lib/active_merchant/billing/gateways/app55.rb @@ -0,0 +1,185 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class App55Gateway < Gateway + self.test_url = 'https://sandbox.app55.com/v1/' + self.live_url = 'https://api.app55.com/v1/' + + self.supported_countries = ['AU', 'BR', 'CA', 'CH', 'CL', 'CN', 'CO', 'CZ', 'DK', 'EU', 'GB', 'HK', 'HU', 'ID', 'IS', 'JP', 'KE', 'KR', 'MX', 'MY', 'NO', 'NZ', 'PH', 'PL', 'TH', 'TW', 'US', 'VN', 'ZA'] + self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :maestro, :solo] + self.default_currency = 'UKP' + self.money_format = :dollars + self.homepage_url = 'https://www.app55.com/' + self.display_name = 'App55' + + # Create gateway + # + # options: + # :api_key - merchants App55 API Key + # :api_secret - merchants App55 Secret Key + def initialize(options = {}) + requires!(options, :api_key, :api_secret) + @api_key = options[:api_key] + @api_secret = options[:api_secret] + super + end + + # Make a purchase (authorize and commit) + # + # money - The monetary amount of the transaction in cents. + # payment_method - The CreditCard or the App55 card token. + # options - A standard ActiveMerchant options hash + def purchase(money, payment_method, options = {}) + authorize(money, payment_method, options.merge(commit: true)) + end + + # Authorize a transaction. + # + # money - The monetary amount of the transaction in cents. + # payment_method - The CreditCard or the App55 card token. + # options - A standard ActiveMerchant options hash + def authorize(money, payment_method, options = {}) + post = {} + add_creditcard(post, payment_method, options) + add_transaction(post, money, options) + + commit(:post, 'transaction', post) + end + + # Commit a pre-authorized transaction. + # + # money - The monetary amount of the transaction in cents. + # authorization - The App55 transaction id string. + # options - A standard ActiveMerchant options hash + def capture(money, authorization, options = {}) + commit(:post, "transaction/#{authorization}") + end + + private + + def add_customer_data(post, options) + metadata_options = [:description, :browser_ip, :user_agent, :referrer] + post.update(options.slice(*metadata_options)) + end + + def add_creditcard(post, creditcard, options) + card = {} + card[:number] = creditcard.number + card[:expiry] = ("%02d". % creditcard.month) + '/' + creditcard.year.to_s + card[:security_code] = creditcard.verification_value if creditcard.verification_value? + card[:holder_name] = creditcard.name if creditcard.name + add_address(card, options) + post[:card] = card + end + + def add_address(card, options) + return unless card && card.kind_of?(Hash) + address_hash = {} + if address = (options[:billing_address] || options[:address]) + address_hash[:street] = address[:address1] if address[:address1] + address_hash[:street2] = address[:address2] if address[:address2] + address_hash[:country] = address[:country] if address[:country] + address_hash[:postal_code] = address[:zip] if address[:zip] + address_hash[:city] = address[:city] if address[:city] + card[:address] = address_hash + end + end + + def add_transaction(post, money, options) + transaction = {} + add_amount(transaction, money, options) + transaction[:description] = (options[:description] || options[:email]) + transaction[:commit] = options[:commit] + post[:transaction] = transaction + end + + def add_amount(obj, money, options) + obj[:amount] = amount(money) + obj[:currency] = (options[:currency] || currency(money)) + end + + def parse(body) + JSON.parse(body) + rescue JSON::ParserError + json_error(raw_response) + end + + def commit(method, resource, parameters=nil, meta={}) + success = false + begin + raw_response = ssl_request( + method, + url(resource), + post_data(parameters), + headers + ) + response = parse(raw_response) + success = response.key?("sig") + rescue ResponseError => e + response = parse(e.response.body) + end + + Response.new( + success, + (success ? "OK" : response["error"]["message"]), + response, + test: test?, + authorization: authorization_from(response) + ) + end + + def authorization_from(response) + if response.key?("transaction") + response["transaction"]["id"] + elsif response.key?("card") + response["card"]["token"] + end + end + + def json_error(raw_response) + msg = "Invalid response from app55 server: Received: #{raw_response.inspect})" + { + "error" => { + "message" => msg + } + } + end + + def url(resource) + (test? ? self.test_url : self.live_url) + resource + end + + def post_data(params) + return nil unless params + + params.map do |key, value| + next if value.blank? + if value.is_a?(Hash) + h = {} + value.each do |k, v| + h["#{key}.#{k}"] = v unless v.blank? + end + post_data(h) + else + "#{key}=#{CGI.escape(value.to_s)}" + end + end.compact.join("&") + end + + def headers + @@ua ||= JSON.dump( + :bindings_version => ActiveMerchant::VERSION, + :lang => 'ruby', + :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", + :platform => RUBY_PLATFORM, + :publisher => 'active_merchant' + ) + + { + "Authorization" => "Basic " + Base64.strict_encode64(@options[:api_key].to_s + ":" + @options[:api_secret].to_s), + "User-Agent" => "ActiveMerchantBindings/#{ActiveMerchant::VERSION}", + } + end + end + end +end + diff --git a/test/fixtures.yml b/test/fixtures.yml index 62fb1c0907d..eaa3ebba971 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -10,6 +10,10 @@ # # Paste any required PEM certificates after the pem key. # +app55: + api_key: "QDtACYMCFqtuKOQ22QtCkGR1TzD7XM8i" + api_secret: "yT1FRpuusBIQoEBIfIQ8bPStkl7yzdTz" + authorize_net: login: X password: Y diff --git a/test/remote/gateways/remote_app55_test.rb b/test/remote/gateways/remote_app55_test.rb new file mode 100644 index 00000000000..b52a889f878 --- /dev/null +++ b/test/remote/gateways/remote_app55_test.rb @@ -0,0 +1,65 @@ +require 'test_helper' + +class RemoteApp55Test < Test::Unit::TestCase + def setup + @gateway = App55Gateway.new(fixtures(:app55)) + + @amount = 100 + @credit_card = credit_card('4111111111111111') + @duff_card = credit_card('400030001111222') + @customer = generate_unique_id + + @options = { + billing_address: address, + description: 'app55 active merchant remote test', + currency: "GBP" + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_not_nil response.params["sig"] + assert_not_nil response.params["transaction"]["auth_code"] + assert_not_nil response.params["transaction"]["id"] + assert_equal response.params["transaction"]["id"], response.authorization + assert_equal @options[:description], response.params["transaction"]["description"] + assert_equal @options[:currency], response.params["transaction"]["currency"] + assert_equal "%.2f" % (@amount / 100), response.params["transaction"]["amount"] + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @duff_card, @options) + assert_failure response + assert_equal "Invalid card number supplied.", response.message + end + + def test_authorize_and_capture + amount = @amount + assert response = @gateway.authorize(amount, @credit_card, @options) + assert_success response + assert response.params["transaction"] + assert_equal @options[:description], response.params["transaction"]["description"] + assert_equal @options[:currency], response.params["transaction"]["currency"] + assert_equal "%.2f" % (@amount / 100), response.params["transaction"]["amount"] + + assert capture = @gateway.capture(amount, response.authorization) + assert_success capture + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '') + assert_failure response + assert !response.params["transaction"] + end + + def test_invalid_login + gateway = App55Gateway.new( + api_key: 'xNSACPYP9ZDUr4860gV9vqvR7TxmVMJP', + api_secret: 'bogus' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'The supplied API Secret does not appear to be valid.', response.message + end +end diff --git a/test/unit/gateways/app55_test.rb b/test/unit/gateways/app55_test.rb new file mode 100644 index 00000000000..8623a0f6d89 --- /dev/null +++ b/test/unit/gateways/app55_test.rb @@ -0,0 +1,51 @@ +require 'test_helper' + +class App55Test < Test::Unit::TestCase + def setup + @gateway = App55Gateway.new( + api_key: 'ABC', + api_secret: 'DEF' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + billing_address: address, + description: 'app55 active merchant unit test' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_request).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal '130703144451_78313', response.authorization + assert response.test? + end + + def test_unsuccessful_purchase + @gateway.expects(:ssl_request).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + private + + def successful_purchase_response + <<-RESPONSE + {"sig":"TxjO6RNAQYstte69KYQu8zmxF_8=","transaction":{"id":"130703144451_78313","description":"app55 active merchant unit test","currency":"GBP","code":"succeeded","amount":"1.00","auth_code":"06603"},"ts":"20130703134451"} + RESPONSE + end + + def failed_purchase_response + <<-RESPONSE + {"error":{"message":"Invalid card number supplied.","type":"validation-error","code":197123}} + RESPONSE + end +end From 89114007c3f140cf3a6b5a123687e285ef7b62a9 Mon Sep 17 00:00:00 2001 From: Oleg Date: Thu, 22 Aug 2013 12:00:25 -0400 Subject: [PATCH 027/104] UsaEpayTransaction: Support for split payments Closes #818. --- CHANGELOG | 2 + .../billing/gateways/usa_epay_transaction.rb | 108 ++++++++++-------- .../gateways/usa_epay_transaction_test.rb | 84 +++++++++++--- 3 files changed, 135 insertions(+), 59 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0abb7427d0d..532a6522e13 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,8 @@ * Stripe: Send :ip to the gateway instead of :browser_ip [f3ndot] * Wirecard Page: new offsite gateway [mbretter] * Mercury: Add support for requesting a token [kcdragon] +* Add App55 gateway [ianbutler55] +* UsaEpayTransaction: Support for split payments [GBH] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb index 8e5ec3e230c..67eb4ab3330 100644 --- a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +++ b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb @@ -2,20 +2,20 @@ module ActiveMerchant #:nodoc: module Billing #:nodoc: class UsaEpayTransactionGateway < Gateway - self.test_url = 'https://sandbox.usaepay.com/gate.php' - self.live_url = 'https://www.usaepay.com/gate.php' + self.live_url = 'https://www.usaepay.com/gate' + self.test_url = 'https://sandbox.usaepay.com/gate' - self.supported_cardtypes = [:visa, :master, :american_express] - self.supported_countries = ['US'] - self.homepage_url = 'http://www.usaepay.com/' - self.display_name = 'USA ePay' + self.supported_cardtypes = [:visa, :master, :american_express] + self.supported_countries = ['US'] + self.homepage_url = 'http://www.usaepay.com/' + self.display_name = 'USA ePay' TRANSACTIONS = { - :authorization => 'authonly', - :purchase => 'sale', - :capture => 'capture', - :refund => 'refund', - :void => 'void' + :authorization => 'cc:authonly', + :purchase => 'cc:sale', + :capture => 'cc:capture', + :refund => 'cc:refund', + :void => 'cc:void' } def initialize(options = {}) @@ -31,6 +31,7 @@ def authorize(money, credit_card, options = {}) add_credit_card(post, credit_card) add_address(post, credit_card, options) add_customer_data(post, options) + add_split_payments(post, options) commit(:authorization, post) end @@ -43,6 +44,7 @@ def purchase(money, credit_card, options = {}) add_credit_card(post, credit_card) add_address(post, credit_card, options) add_customer_data(post, options) + add_split_payments(post, options) commit(:purchase, post) end @@ -66,7 +68,7 @@ def void(authorization, options = {}) commit(:void, post) end - private + private def add_amount(post, money) post[:amount] = amount(money) @@ -108,16 +110,16 @@ def add_address(post, credit_card, options) def add_address_for_type(type, post, credit_card, address) prefix = address_key_prefix(type) - post[address_key(prefix, 'fname')] = credit_card.first_name - post[address_key(prefix, 'lname')] = credit_card.last_name - post[address_key(prefix, 'company')] = address[:company] unless address[:company].blank? - post[address_key(prefix, 'street')] = address[:address1] unless address[:address1].blank? - post[address_key(prefix, 'street2')] = address[:address2] unless address[:address2].blank? - post[address_key(prefix, 'city')] = address[:city] unless address[:city].blank? - post[address_key(prefix, 'state')] = address[:state] unless address[:state].blank? - post[address_key(prefix, 'zip')] = address[:zip] unless address[:zip].blank? - post[address_key(prefix, 'country')] = address[:country] unless address[:country].blank? - post[address_key(prefix, 'phone')] = address[:phone] unless address[:phone].blank? + post[address_key(prefix, 'fname')] = credit_card.first_name + post[address_key(prefix, 'lname')] = credit_card.last_name + post[address_key(prefix, 'company')] = address[:company] unless address[:company].blank? + post[address_key(prefix, 'street')] = address[:address1] unless address[:address1].blank? + post[address_key(prefix, 'street2')] = address[:address2] unless address[:address2].blank? + post[address_key(prefix, 'city')] = address[:city] unless address[:city].blank? + post[address_key(prefix, 'state')] = address[:state] unless address[:state].blank? + post[address_key(prefix, 'zip')] = address[:zip] unless address[:zip].blank? + post[address_key(prefix, 'country')] = address[:country] unless address[:country].blank? + post[address_key(prefix, 'phone')] = address[:phone] unless address[:phone].blank? end def address_key_prefix(type) @@ -132,15 +134,29 @@ def address_key(prefix, key) end def add_invoice(post, options) - post[:invoice] = options[:order_id] - post[:description] = options[:description] + post[:invoice] = options[:order_id] + post[:description] = options[:description] end def add_credit_card(post, credit_card) - post[:card] = credit_card.number - post[:cvv2] = credit_card.verification_value if credit_card.verification_value? + post[:card] = credit_card.number + post[:cvv2] = credit_card.verification_value if credit_card.verification_value? post[:expir] = expdate(credit_card) - post[:name] = credit_card.name + post[:name] = credit_card.name + end + + # see: http://wiki.usaepay.com/developer/transactionapi#split_payments + def add_split_payments(post, options) + return unless options[:split_payments].is_a?(Array) + options[:split_payments].each_with_index do |payment, index| + prefix = '%02d' % (index + 2) + post["#{prefix}key"] = payment[:key] + post["#{prefix}amount"] = amount(payment[:amount]) + post["#{prefix}description"] = payment[:description] + end + + # When blank it's 'Stop'. 'Continue' is another one + post['onError'] = options[:on_error] || 'Void' end def parse(body) @@ -151,32 +167,32 @@ def parse(body) end { - :status => fields['UMstatus'], - :auth_code => fields['UMauthCode'], - :ref_num => fields['UMrefNum'], - :batch => fields['UMbatch'], - :avs_result => fields['UMavsResult'], - :avs_result_code => fields['UMavsResultCode'], - :cvv2_result => fields['UMcvv2Result'], + :status => fields['UMstatus'], + :auth_code => fields['UMauthCode'], + :ref_num => fields['UMrefNum'], + :batch => fields['UMbatch'], + :avs_result => fields['UMavsResult'], + :avs_result_code => fields['UMavsResultCode'], + :cvv2_result => fields['UMcvv2Result'], :cvv2_result_code => fields['UMcvv2ResultCode'], :vpas_result_code => fields['UMvpasResultCode'], - :result => fields['UMresult'], - :error => fields['UMerror'], - :error_code => fields['UMerrorcode'], - :acs_url => fields['UMacsurl'], - :payload => fields['UMpayload'] + :result => fields['UMresult'], + :error => fields['UMerror'], + :error_code => fields['UMerrorcode'], + :acs_url => fields['UMacsurl'], + :payload => fields['UMpayload'] }.delete_if{|k, v| v.nil?} end def commit(action, parameters) - url = (self.test? ? self.test_url : self.live_url) - response = parse( ssl_post(url, post_data(action, parameters)) ) + url = (test? ? self.test_url : self.live_url) + response = parse(ssl_post(url, post_data(action, parameters))) Response.new(response[:status] == 'Approved', message_from(response), response, - :test => test?, - :authorization => response[:ref_num], - :cvv_result => response[:cvv2_result_code], - :avs_result => { :code => response[:avs_result_code] } + :test => test?, + :authorization => response[:ref_num], + :cvv_result => response[:cvv2_result_code], + :avs_result => { :code => response[:avs_result_code] } ) end @@ -191,7 +207,7 @@ def message_from(response) def post_data(action, parameters = {}) parameters[:command] = TRANSACTIONS[action] - parameters[:key] = @options[:login] + parameters[:key] = @options[:login] parameters[:software] = 'Active Merchant' parameters[:testmode] = (@options[:test] ? 1 : 0) diff --git a/test/unit/gateways/usa_epay_transaction_test.rb b/test/unit/gateways/usa_epay_transaction_test.rb index 681682e8ea2..933c76c6602 100644 --- a/test/unit/gateways/usa_epay_transaction_test.rb +++ b/test/unit/gateways/usa_epay_transaction_test.rb @@ -4,17 +4,35 @@ class UsaEpayTransactionTest < Test::Unit::TestCase include CommStub def setup - @gateway = UsaEpayTransactionGateway.new( - :login => 'LOGIN' - ) + @gateway = UsaEpayTransactionGateway.new(:login => 'LOGIN') @credit_card = credit_card('4242424242424242') @options = { - :billing_address => address, + :billing_address => address, :shipping_address => address } @amount = 100 end + + def test_urls + assert_equal 'https://www.usaepay.com/gate', UsaEpayTransactionGateway.live_url + assert_equal 'https://sandbox.usaepay.com/gate', UsaEpayTransactionGateway.test_url + end + + def test_request_url_live + gateway = UsaEpayTransactionGateway.new(:login => 'LOGIN', :test => false) + gateway.expects(:ssl_post). + with('https://www.usaepay.com/gate', purchase_request). + returns(successful_purchase_response) + assert response = gateway.purchase(@amount, @credit_card, @options) + end + + def test_request_url_test + @gateway.expects(:ssl_post). + with('https://sandbox.usaepay.com/gate', purchase_request). + returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + end def test_successful_request @gateway.expects(:ssl_post).returns(successful_purchase_response) @@ -42,6 +60,42 @@ def test_successful_purchase_passing_extra_info end.respond_with(successful_purchase_response) assert_success response end + + def test_successful_purchase_split_payment + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge( + :split_payments => [ + { :key => 'abc123', :amount => 199, :description => 'Second payee' }, + { :key => 'def456', :amount => 911, :description => 'Third payee' }, + ] + )) + end.check_request do |endpoint, data, headers| + assert_match /UM02key=abc123/, data + assert_match /UM02amount=1.99/, data + assert_match /UM02description=Second\+payee/, data + + assert_match /UM03key=def456/, data + assert_match /UM03amount=9.11/, data + assert_match /UM03description=Third\+payee/, data + + assert_match /UMonError=Void/, data + end.respond_with(successful_purchase_response) + assert_success response + end + + def test_successful_purchase_split_payment_with_custom_on_error + response = stub_comms do + @gateway.purchase(@amount, @credit_card, @options.merge( + :split_payments => [ + { :key => 'abc123', :amount => 199, :description => 'Second payee' } + ], + :on_error => 'Continue' + )) + end.check_request do |endpoint, data, headers| + assert_match /UMonError=Continue/, data + end.respond_with(successful_purchase_response) + assert_success response + end def test_address_key_prefix assert_equal 'bill', @gateway.send(:address_key_prefix, :billing) @@ -115,20 +169,20 @@ def test_does_not_raise_error_on_missing_values end end - private +private def assert_address(type, post) prefix = key_prefix(type) - assert_equal @credit_card.first_name, post[key(prefix, 'fname')] - assert_equal @credit_card.last_name, post[key(prefix, 'lname')] - assert_equal @options[:billing_address][:company], post[key(prefix, 'company')] + assert_equal @credit_card.first_name, post[key(prefix, 'fname')] + assert_equal @credit_card.last_name, post[key(prefix, 'lname')] + assert_equal @options[:billing_address][:company], post[key(prefix, 'company')] assert_equal @options[:billing_address][:address1], post[key(prefix, 'street')] assert_equal @options[:billing_address][:address2], post[key(prefix, 'street2')] - assert_equal @options[:billing_address][:city], post[key(prefix, 'city')] - assert_equal @options[:billing_address][:state], post[key(prefix, 'state')] - assert_equal @options[:billing_address][:zip], post[key(prefix, 'zip')] - assert_equal @options[:billing_address][:country], post[key(prefix, 'country')] - assert_equal @options[:billing_address][:phone], post[key(prefix, 'phone')] + assert_equal @options[:billing_address][:city], post[key(prefix, 'city')] + assert_equal @options[:billing_address][:state], post[key(prefix, 'state')] + assert_equal @options[:billing_address][:zip], post[key(prefix, 'zip')] + assert_equal @options[:billing_address][:country], post[key(prefix, 'country')] + assert_equal @options[:billing_address][:phone], post[key(prefix, 'phone')] end def key_prefix(type) @@ -138,6 +192,10 @@ def key_prefix(type) def key(prefix, key) @gateway.send(:address_key, prefix, key) end + + def purchase_request + "UMamount=1.00&UMinvoice=&UMdescription=&UMcard=4242424242424242&UMcvv2=123&UMexpir=0914&UMname=Longbob+Longsen&UMbillfname=Longbob&UMbilllname=Longsen&UMbillcompany=Widgets+Inc&UMbillstreet=1234+My+Street&UMbillstreet2=Apt+1&UMbillcity=Ottawa&UMbillstate=ON&UMbillzip=K1C2N6&UMbillcountry=CA&UMbillphone=%28555%29555-5555&UMshipfname=Longbob&UMshiplname=Longsen&UMshipcompany=Widgets+Inc&UMshipstreet=1234+My+Street&UMshipstreet2=Apt+1&UMshipcity=Ottawa&UMshipstate=ON&UMshipzip=K1C2N6&UMshipcountry=CA&UMshipphone=%28555%29555-5555&UMstreet=1234+My+Street&UMzip=K1C2N6&UMcommand=cc%3Asale&UMkey=LOGIN&UMsoftware=Active+Merchant&UMtestmode=0" + end def successful_purchase_response "UMversion=2.9&UMstatus=Approved&UMauthCode=001716&UMrefNum=55074409&UMavsResult=Address%3A%20Match%20%26%205%20Digit%20Zip%3A%20Match&UMavsResultCode=Y&UMcvv2Result=Match&UMcvv2ResultCode=M&UMresult=A&UMvpasResultCode=&UMerror=Approved&UMerrorcode=00000&UMcustnum=&UMbatch=596&UMisDuplicate=N&UMconvertedAmount=&UMconvertedAmountCurrency=840&UMconversionRate=&UMcustReceiptResult=No%20Receipt%20Sent&UMfiller=filled" From 8faf6899de12c012b8ec991ada6e25192ce098e9 Mon Sep 17 00:00:00 2001 From: Matthew Clarke Date: Wed, 3 Jul 2013 17:11:40 +1200 Subject: [PATCH 028/104] Add Swipe Checkout gateway https://www.swipehq.com Closes #808. --- CHANGELOG | 1 + CONTRIBUTORS | 4 + README.md | 1 + .../billing/gateways/swipe_checkout.rb | 158 ++++++++++++++++++ test/fixtures.yml | 6 + .../gateways/remote_swipe_checkout_test.rb | 69 ++++++++ test/unit/gateways/swipe_checkout_test.rb | 154 +++++++++++++++++ 7 files changed, 393 insertions(+) create mode 100644 lib/active_merchant/billing/gateways/swipe_checkout.rb create mode 100644 test/remote/gateways/remote_swipe_checkout_test.rb create mode 100644 test/unit/gateways/swipe_checkout_test.rb diff --git a/CHANGELOG b/CHANGELOG index 532a6522e13..1801d80c097 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ * Mercury: Add support for requesting a token [kcdragon] * Add App55 gateway [ianbutler55] * UsaEpayTransaction: Support for split payments [GBH] +* Add Swipe Checkout gateway [matt-optimizerhq] == Version 1.41.0 (October 24th, 2013) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index d42efb0fda0..52821b559e4 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -428,3 +428,7 @@ Conekta (October 2013) App55 (November 2013) * (ianbutler55) + +Swipe Checkout (November 2013) + +* (matt-optimizerhq) diff --git a/README.md b/README.md index 0475632c152..b7efe6b475c 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta * [SkipJack](http://www.skipjack.com/) - US, CA * [Spreedly](https://spreedly.com) - AD, AE, AT, AU, BD, BE, BG, BN, CA, CH, CY, CZ, DE, DK, EE, EG, ES, FI, FR, GB, GI, GR, HK, HU, ID, IE, IL, IM, IN, IS, IT, JO, KW, LB, LI, LK, LT, LU, LV, MC, MT, MU, MV, MX, MY, NL, NO, NZ, OM, PH, PL, PT, QA, RO, SA, SE, SG, SI, SK, SM, TR, TT, UM, US, VA, VN, ZA * [Stripe](https://stripe.com/) - US, CA, GB, AU, IE, FR, NL, BE, DE, ES +* [Swipe](https://www.swipehq.com/checkout) - CA, NZ * [TransFirst](http://www.transfirst.com/) - US * [NELiX TransaX](https://www.nelixtransax.com/) - US * [Transnational](http://www.tnbci.com/) - US diff --git a/lib/active_merchant/billing/gateways/swipe_checkout.rb b/lib/active_merchant/billing/gateways/swipe_checkout.rb new file mode 100644 index 00000000000..df235de2ad1 --- /dev/null +++ b/lib/active_merchant/billing/gateways/swipe_checkout.rb @@ -0,0 +1,158 @@ +require 'json' + +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SwipeCheckoutGateway < Gateway + TRANSACTION_APPROVED_MSG = 'Transaction approved' + TRANSACTION_DECLINED_MSG = 'Transaction declined' + + LIVE_URLS = { + 'NZ' => 'https://api.swipehq.com', + 'CA' => 'https://api.swipehq.ca' + } + self.test_url = 'https://api.swipehq.com' + + TRANSACTION_API = '/createShopifyTransaction.php' + + self.supported_countries = %w[ NZ CA ] + self.default_currency = 'NZD' + self.supported_cardtypes = [:visa, :master] + self.homepage_url = 'https://www.swipehq.com/checkout' + self.display_name = 'Swipe Checkout' + self.money_format = :dollars + + # Swipe Checkout requires the merchant's email and API key for authorization. + # This can be found under Settings > API Credentials after logging in to your + # Swipe Checkout merchant console at https://merchant.swipehq.[com|ca] + # + # :region determines which Swipe URL is used, this can be one of "NZ" or "CA". + # Currently Swipe Checkout has New Zealand and Canadian domains (swipehq.com + # and swipehq.ca respectively). Merchants must use the region that they + # signed up in for authentication with their merchant ID and API key to succeed. + def initialize(options = {}) + requires!(options, :login, :api_key, :region) + super + end + + # Transfers funds immediately. + # Note that Swipe Checkout only supports purchase at this stage + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_customer_data(post, creditcard, options) + add_amount(post, money, options) + + commit('sale', money, post) + end + + private + + def add_customer_data(post, creditcard, options) + post[:email] = options[:email] + post[:ip_address] = options[:ip] + + address = options[:billing_address] || options[:address] + return if address.nil? + + post[:company] = address[:company] + + # groups all names after the first into the last name param + post[:first_name], post[:last_name] = address[:name].split(' ', 2) + post[:address] = "#{address[:address1]}, #{address[:address2]}" + post[:city] = address[:city] + post[:country] = address[:country] + post[:mobile] = address[:phone] # API only has a "mobile" field, no "phone" + end + + def add_invoice(post, options) + # store shopping-cart order ID in Swipe for merchant's records + post[:td_user_data] = options[:order_id] if options[:order_id] + post[:td_item] = options[:description] if options[:description] + post[:td_description] = options[:description] if options[:description] + post[:item_quantity] = "1" + end + + def add_creditcard(post, creditcard) + post[:card_number] = creditcard.number + post[:card_type] = creditcard.brand + post[:name_on_card] = "#{creditcard.first_name} #{creditcard.last_name}" + post[:card_expiry] = expdate(creditcard) + post[:secure_number] = creditcard.verification_value + end + + def expdate(creditcard) + year = format(creditcard.year, :two_digits) + month = format(creditcard.month, :two_digits) + + "#{month}#{year}" + end + + def add_amount(post, money, options) + post[:amount] = money.to_s + + post[:currency] = (options[:currency] || currency(money)) + end + + def commit(action, money, parameters) + case action + when "sale" + begin + response = call_api(TRANSACTION_API, parameters) + + # response code and message params should always be present + code = response["response_code"] + message = response["message"] + + if code == 200 + result = response["data"]["result"] + success = (result == 'accepted' || (test? && result == 'test-accepted')) + + Response.new(success, + success ? + TRANSACTION_APPROVED_MSG : + TRANSACTION_DECLINED_MSG, + response, + :test => test? + ) + else + build_error_response(message, response) + end + rescue ResponseError => e + raw_response = e.response.body + build_error_response("ssl_post() with url #{url} raised ResponseError: #{e}") + rescue JSON::ParserError => e + msg = 'Invalid response received from the Swipe Checkout API. ' + + 'Please contact support@optimizerhq.com if you continue to receive this message.' + + " (Full error message: #{e})" + build_error_response(msg) + end + end + end + + def call_api(api, params=nil) + params ||= {} + params[:merchant_id] = @options[:login] + params[:api_key] = @options[:api_key] + + # ssl_post() returns the response body as a string on success, + # or raises a ResponseError exception on failure + JSON.parse(ssl_post(url(@options[:region], api), params.to_query)) + end + + def url(region, api) + ((test? ? self.test_url : LIVE_URLS[region]) + api) + end + + def build_error_response(message, params={}) + Response.new( + false, + message, + params, + :test => test? + ) + end + end + end +end + diff --git a/test/fixtures.yml b/test/fixtures.yml index eaa3ebba971..65a4b9d33d6 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -595,6 +595,12 @@ stripe: login: fee_refund_login: +# Working credentials, no need to replace +swipe_checkout: + login: 2077103073D8B5 + api_key: f2fe4efd5033edfaed9e4aad319ef4d34536a10eea07f90f182616d7216ae2b8 + region: NZ + trans_first: login: LOGIN password: PASSWORD diff --git a/test/remote/gateways/remote_swipe_checkout_test.rb b/test/remote/gateways/remote_swipe_checkout_test.rb new file mode 100644 index 00000000000..6299a506fd2 --- /dev/null +++ b/test/remote/gateways/remote_swipe_checkout_test.rb @@ -0,0 +1,69 @@ +require 'test_helper' + +class RemoteSwipeCheckoutTest < Test::Unit::TestCase + def setup + @gateway = SwipeCheckoutGateway.new(fixtures(:swipe_checkout)) + + @amount = 100 + @accepted_card = credit_card('1234123412341234') + @declined_card = credit_card('1111111111111111') + @invalid_card = credit_card('1000000000000000') + @empty_card = credit_card('') + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @accepted_card, @options) + assert_success response + assert_equal 'Transaction approved', response.message + end + + def test_region_switching + assert response = @gateway.purchase(@amount, @accepted_card, @options.merge(:region => 'CA')) + assert_success response + assert_equal 'Transaction approved', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + assert_equal 'Transaction declined', response.message + end + + def test_invalid_login + gateway = SwipeCheckoutGateway.new( + login: 'invalid', + api_key: 'invalid', + region: 'NZ' + ) + assert response = gateway.purchase(@amount, @accepted_card, @options) + assert_failure response + assert_equal 'Access Denied', response.message + end + + def test_invalid_card + # Note: Swipe Checkout transaction API returns declined if the card number + # is invalid, and "invalid card data" if the card number is empty + assert response = @gateway.purchase(@amount, @invalid_card, @options) + assert_failure response + assert_equal 'Transaction declined', response.message + assert_equal 200, response.params['response_code'] + end + + def test_empty_card + assert response = @gateway.purchase(@amount, @empty_card, @options) + assert_failure response + assert_equal 'Invalid card data', response.message + assert_equal 303, response.params['response_code'] + end + + def test_no_options + assert response = @gateway.purchase(@amount, @accepted_card, {}) + assert_success response + end +end diff --git a/test/unit/gateways/swipe_checkout_test.rb b/test/unit/gateways/swipe_checkout_test.rb new file mode 100644 index 00000000000..e6c4e0da63a --- /dev/null +++ b/test/unit/gateways/swipe_checkout_test.rb @@ -0,0 +1,154 @@ +require 'test_helper' + +class SwipeCheckoutTest < Test::Unit::TestCase + def setup + @gateway = SwipeCheckoutGateway.new( + login: '0000000000000', + api_key: '0000000000000000000000000000000000000000000000000000000000000000', + region: 'NZ' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + order_id: '1', + billing_address: address, + description: 'Store Purchase' + } + end + + def test_supported_countries + assert @gateway.supported_countries == ['NZ', 'CA'] + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal 'Transaction approved', response.message + assert response.test? + end + + def test_successful_test_purchase + @gateway.expects(:ssl_post).returns(successful_test_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + + assert_success response + assert response.test? + end + + def test_unsuccessful_purchase + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_test_purchase + @gateway.expects(:ssl_post).returns(failed_test_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_invalid_card + @gateway.expects(:ssl_post).returns(failed_purchase_response_invalid_card) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_system_error + @gateway.expects(:ssl_post).returns(failed_purchase_response_system_error) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_incorrect_amount + @gateway.expects(:ssl_post).returns(failed_purchase_response_incorrect_amount) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_access_denied + @gateway.expects(:ssl_post).returns(failed_purchase_response_access_denied) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_not_enough_parameters + @gateway.expects(:ssl_post).returns(failed_purchase_response_not_enough_parameters) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_unsuccessful_request_invalid_json_in_response + @gateway.expects(:ssl_post).returns(response_with_invalid_json) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + private + + def successful_purchase_response + '{"response_code": 200, "message": "OK", "data": {"tx_transaction_id": "00000000000000", "result": "accepted"}}' + end + + def successful_test_purchase_response + '{"response_code": 200, "message": "OK", "data": {"tx_transaction_id": "00000000000000", "result": "test-accepted"}}' + end + + def failed_purchase_response + '{"response_code": 200, "message": "OK", "data": {"tx_transaction_id": "00000000000000", "result": "declined"}}' + end + + def failed_test_purchase_response + '{"response_code": 200, "message": "OK", "data": {"tx_transaction_id": "00000000000000", "result": "test-declined"}}' + end + + def failed_purchase_response_invalid_card + build_failed_response 303, 'Invalid card data' + end + + def failed_purchase_response_system_error + build_failed_response 402, 'System error' + end + + def failed_purchase_response_incorrect_amount + build_failed_response 302, 'Incorrect amount' + end + + def failed_purchase_response_access_denied + build_failed_response 400, 'System error' + end + + def failed_purchase_response_not_enough_parameters + build_failed_response 403, 'Not enough parameters' + end + + def response_with_invalid_json + '{"response_code": ' + end + + def build_failed_response(code, message) + "{\"response_code\": #{code}, \"message\": \"#{message}\"}" + end +end From 0a02ed3dbcca8687448d14f6652a4fdf3693cb87 Mon Sep 17 00:00:00 2001 From: Tom Hoen Date: Tue, 13 Aug 2013 16:49:05 -0400 Subject: [PATCH 029/104] Spreedly Core: Allow overriding the gateway token This makes it possible to choose the gateway token when running a transaction, not just when creating the SpreedlyCoreGateway. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/spreedly_core.rb | 2 +- test/unit/gateways/spreedly_core_test.rb | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 1801d80c097..6a549c5248b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ * Add App55 gateway [ianbutler55] * UsaEpayTransaction: Support for split payments [GBH] * Add Swipe Checkout gateway [matt-optimizerhq] +* Spreedly Core: Allow overriding the gateway token when running a transaction [hoenth] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/spreedly_core.rb b/lib/active_merchant/billing/gateways/spreedly_core.rb index bc1e50505ba..888b6ce34dd 100644 --- a/lib/active_merchant/billing/gateways/spreedly_core.rb +++ b/lib/active_merchant/billing/gateways/spreedly_core.rb @@ -118,7 +118,7 @@ def save_card(retain, credit_card, options) def purchase_with_token(money, payment_method_token, options) request = auth_purchase_request(money, payment_method_token, options) - commit("gateways/#{@options[:gateway_token]}/purchase.xml", request) + commit("gateways/#{options[:gateway_token] || @options[:gateway_token]}/purchase.xml", request) end def authorize_with_token(money, payment_method_token, options) diff --git a/test/unit/gateways/spreedly_core_test.rb b/test/unit/gateways/spreedly_core_test.rb index c2c3572e290..3f3bbb091b1 100644 --- a/test/unit/gateways/spreedly_core_test.rb +++ b/test/unit/gateways/spreedly_core_test.rb @@ -75,6 +75,16 @@ def test_failed_purchase_with_credit_card assert_equal '0957', response.params['payment_method_last_four_digits'] end + def test_purchase_without_gateway_token_option + @gateway.expects(:commit).with("gateways/token/purchase.xml", anything) + @gateway.purchase(@amount, @payment_method_token) + end + + def test_purchase_with_gateway_token_option + @gateway.expects(:commit).with("gateways/mynewtoken/purchase.xml", anything) + @gateway.purchase(@amount, @payment_method_token, gateway_token: 'mynewtoken') + end + def test_successful_authorize_with_token_and_capture @gateway.expects(:raw_ssl_request).returns(successful_authorize_response) response = @gateway.authorize(@amount, @payment_method_token) From 24f2da1a92a69bde709cd63e4468e709b84a331f Mon Sep 17 00:00:00 2001 From: Tom Hoen Date: Wed, 18 Sep 2013 10:41:37 -0400 Subject: [PATCH 030/104] Spreedly Core: Add order_id --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/spreedly_core.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6a549c5248b..77ed74de4a1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ * UsaEpayTransaction: Support for split payments [GBH] * Add Swipe Checkout gateway [matt-optimizerhq] * Spreedly Core: Allow overriding the gateway token when running a transaction [hoenth] +* Spreedly Core: Add order_id [hoenth] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/spreedly_core.rb b/lib/active_merchant/billing/gateways/spreedly_core.rb index 888b6ce34dd..9fff84dbf4f 100644 --- a/lib/active_merchant/billing/gateways/spreedly_core.rb +++ b/lib/active_merchant/billing/gateways/spreedly_core.rb @@ -137,6 +137,7 @@ def auth_purchase_request(money, payment_method_token, options) def add_invoice(doc, money, options) doc.amount amount(money) doc.currency_code(options[:currency] || currency(money) || default_currency) + doc.order_id(options[:order_id]) end def add_credit_card(doc, credit_card, options) From 94ecd243a4a733a9e2b76797b8a960b3c1bc7957 Mon Sep 17 00:00:00 2001 From: Tom Hoen Date: Fri, 27 Sep 2013 16:01:37 -0400 Subject: [PATCH 031/104] Spreedly Core: Allow store without retain Closes #800. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/spreedly_core.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 77ed74de4a1..d7cb6ae967f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ * Add Swipe Checkout gateway [matt-optimizerhq] * Spreedly Core: Allow overriding the gateway token when running a transaction [hoenth] * Spreedly Core: Add order_id [hoenth] +* Spreedly Core: Allow store without retain [hoenth] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/spreedly_core.rb b/lib/active_merchant/billing/gateways/spreedly_core.rb index 9fff84dbf4f..5f26aa80808 100644 --- a/lib/active_merchant/billing/gateways/spreedly_core.rb +++ b/lib/active_merchant/billing/gateways/spreedly_core.rb @@ -93,7 +93,8 @@ def void(authorization, options={}) # credit_card - The CreditCard to store # options - A standard ActiveMerchant options hash def store(credit_card, options={}) - save_card(true, credit_card, options) + retain = (options.has_key?(:retain) ? options[:retain] : true) + save_card(retain, credit_card, options) end # Public: Redact the CreditCard in Spreedly. This wipes the sensitive From ad7149860db55f95609aa115aac512d64b2af42c Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Thu, 31 Oct 2013 08:30:25 -0400 Subject: [PATCH 032/104] Stripe: Support multiple cards on account Add support for multiple payment methods per customer. The `store' call now adds a payment method if a customer is specified (it used to clobber the default card instead). You can specify :set_default => true to set the newly created card as default. The `update' call will add the new card and set it as default. A new `update_customer' call has been added to update a customer's metadata. This commit also features a small refactoring in Stripe remote tests to easily swap the default currency used. --- CHANGELOG | 1 + .../billing/gateways/stripe.rb | 22 +++- test/remote/gateways/remote_stripe_test.rb | 22 ++-- test/unit/gateways/stripe_test.rb | 113 ++++++++++++++++++ 4 files changed, 142 insertions(+), 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d7cb6ae967f..3e13006dc7a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ * Spreedly Core: Allow overriding the gateway token when running a transaction [hoenth] * Spreedly Core: Add order_id [hoenth] * Spreedly Core: Allow store without retain [hoenth] +* Stripe: support multiple cards on account [pierre] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index b0b9cd5db51..fef8e1634f9 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -97,26 +97,36 @@ def refund_application_fee(money, identification, options = {}) commit(:post, "application_fees/#{CGI.escape(identification)}/refund", post, options) end + # Note: creating a new credit card will not change the customer's existing default credit card (use :set_default => true) def store(creditcard, options = {}) post = {} add_creditcard(post, creditcard, options) post[:description] = options[:description] post[:email] = options[:email] - path = if options[:customer] - "customers/#{CGI.escape(options[:customer])}" + commit_options = generate_meta(options) + if options[:customer] + MultiResponse.run(:first) do |r| + r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/cards", post, commit_options) } + + return r unless options[:set_default] and r.success? and !r.params["id"].blank? + + r.process { update_customer(options[:customer], :default_card => r.params["id"]) } + end else - 'customers' + commit(:post, 'customers', post, commit_options) end - - commit(:post, path, post, generate_meta(options)) end def update(customer_id, creditcard, options = {}) - options = options.merge(:customer => customer_id) + options = options.merge(:customer => customer_id, :set_default => true) store(creditcard, options) end + def update_customer(customer_id, options = {}) + commit(:post, "customers/#{CGI.escape(customer_id)}", options, generate_meta(options)) + end + def unstore(customer_id, options = {}) commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, generate_meta(options)) end diff --git a/test/remote/gateways/remote_stripe_test.rb b/test/remote/gateways/remote_stripe_test.rb index c2c0ef2374d..a584090cebd 100644 --- a/test/remote/gateways/remote_stripe_test.rb +++ b/test/remote/gateways/remote_stripe_test.rb @@ -6,15 +6,16 @@ def setup @gateway = StripeGateway.new(fixtures(:stripe)) @amount = 100 + # You may have to update the currency, depending on your tenant + @currency = 'CAD' @credit_card = credit_card('4242424242424242') @declined_card = credit_card('4000') @new_credit_card = credit_card('5105105105105100') @options = { - :currency => 'CAD', + :currency => @currency, :description => 'ActiveMerchant Test Purchase', - :email => 'wow@example.com', - :currency => 'CAD' + :email => 'wow@example.com' } end @@ -26,13 +27,13 @@ def test_successful_purchase end def test_purchase_description - assert response = @gateway.purchase(@amount, @credit_card, { :currency => 'CAD', :description => "TheDescription", :email => "email@example.com" }) + assert response = @gateway.purchase(@amount, @credit_card, { :currency => @currency, :description => "TheDescription", :email => "email@example.com" }) assert_equal "TheDescription", response.params["description"], "Use the description if it's specified." - assert response = @gateway.purchase(@amount, @credit_card, { :currency => 'CAD', :email => "email@example.com" }) + assert response = @gateway.purchase(@amount, @credit_card, { :currency => @currency, :email => "email@example.com" }) assert_equal "email@example.com", response.params["description"], "Use the email if no description is specified." - assert response = @gateway.purchase(@amount, @credit_card, { :currency => 'CAD' }) + assert response = @gateway.purchase(@amount, @credit_card, { :currency => @currency }) assert_nil response.params["description"], "No description or email specified." end @@ -89,7 +90,7 @@ def test_unsuccessful_refund end def test_successful_store - assert response = @gateway.store(@credit_card, {:currency => 'CAD', :description => "Active Merchant Test Customer", :email => "email@example.com"}) + assert response = @gateway.store(@credit_card, {:currency => @currency, :description => "Active Merchant Test Customer", :email => "email@example.com"}) assert_success response assert_equal "customer", response.params["object"] assert_equal "Active Merchant Test Customer", response.params["description"] @@ -103,9 +104,10 @@ def test_successful_update creation = @gateway.store(@credit_card, {:description => "Active Merchant Update Customer"}) assert response = @gateway.update(creation.params['id'], @new_credit_card) assert_success response - assert_equal "Active Merchant Update Customer", response.params["description"] - first_card = response.params["cards"]["data"].first - assert_equal response.params["default_card"], first_card["id"] + customer_response = response.responses.last + assert_equal "Active Merchant Update Customer", customer_response.params["description"] + first_card = customer_response.params["cards"]["data"].first + assert_equal customer_response.params["default_card"], first_card["id"] assert_equal @new_credit_card.last_digits, first_card["last4"] end diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb index d5249911f1d..5a75a5f3e88 100644 --- a/test/unit/gateways/stripe_test.rb +++ b/test/unit/gateways/stripe_test.rb @@ -16,6 +16,42 @@ def setup } end + def test_successful_new_customer_with_card + @gateway.expects(:ssl_request).returns(successful_new_customer_response) + + assert response = @gateway.store(@credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'cus_3sgheFxeBgTQ3M', response.authorization + assert response.test? + end + + def test_successful_new_card + @gateway.expects(:ssl_request).returns(successful_new_card_response) + + assert response = @gateway.store(@credit_card, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M')) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert response.test? + end + + def test_successful_new_default_card + @gateway.expects(:ssl_request).twice.returns(successful_new_card_response, successful_new_customer_response) + + assert response = @gateway.store(@credit_card, @options.merge(:customer => 'cus_3sgheFxeBgTQ3M', :set_default => true)) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal 'card_483etw4er9fg4vF3sQdrt3FG', response.authorization + assert_equal 2, response.responses.size + assert_equal 'card_483etw4er9fg4vF3sQdrt3FG', response.responses[0].authorization + assert_equal 'cus_3sgheFxeBgTQ3M', response.responses[1].authorization + assert response.test? + end + def test_successful_authorization @gateway.expects(:ssl_request).returns(successful_authorization_response) @@ -284,6 +320,83 @@ def test_address_is_included_with_card_data private + # Create new customer and set default credit card + def successful_new_customer_response + <<-RESPONSE +{ + "object": "customer", + "created": 1383137317, + "id": "cus_3sgheFxeBgTQ3M", + "livemode": false, + "description": null, + "email": null, + "delinquent": false, + "metadata": {}, + "subscription": null, + "discount": null, + "account_balance": 0, + "cards": + { + "object": "list", + "count": 1, + "url": "/v1/customers/cus_3sgheFxeBgTQ3M/cards", + "data": + [ + { + "id": "card_483etw4er9fg4vF3sQdrt3FG", + "object": "card", + "last4": "4242", + "type": "Visa", + "exp_month": 11, + "exp_year": 2020, + "fingerprint": "5dgRQ3dVRGaQWDFb", + "customer": "cus_3sgheFxeBgTQ3M", + "country": "US", + "name": "John Doe", + "address_line1": null, + "address_line2": null, + "address_city": null, + "address_state": null, + "address_zip": null, + "address_country": null, + "cvc_check": null, + "address_line1_check": null, + "address_zip_check": null + } + ] + }, + "default_card": "card_483etw4er9fg4vF3sQdrt3FG" +} + RESPONSE + end + + def successful_new_card_response + <<-RESPONSE +{ + "id": "card_483etw4er9fg4vF3sQdrt3FG", + "livemode": false, + "object": "card", + "last4": "4242", + "type": "Visa", + "exp_month": 11, + "exp_year": 2020, + "fingerprint": "5dgRQ3dVRGaQWDFb", + "customer": "cus_3sgheFxeBgTQ3M", + "country": "US", + "name": "John Doe", + "address_line1": null, + "address_line2": null, + "address_city": null, + "address_state": null, + "address_zip": null, + "address_country": null, + "cvc_check": null, + "address_line1_check": null, + "address_zip_check": null +} + RESPONSE + end + def successful_authorization_response <<-RESPONSE { From 17e5e81abf58fec5f99dc80258a89c7388da244d Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Meyer Date: Thu, 31 Oct 2013 09:15:18 -0400 Subject: [PATCH 033/104] Stripe: Add card_id parameter to unstore call Specifying a card_id will now delete the card only, without deleting the customer. Closes #895. --- CHANGELOG | 3 ++- lib/active_merchant/billing/gateways/stripe.rb | 8 ++++++-- test/remote/gateways/remote_stripe_test.rb | 17 ++++++++++++++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3e13006dc7a..463a3ca2803 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,7 +12,8 @@ * Spreedly Core: Allow overriding the gateway token when running a transaction [hoenth] * Spreedly Core: Add order_id [hoenth] * Spreedly Core: Allow store without retain [hoenth] -* Stripe: support multiple cards on account [pierre] +* Stripe: Support multiple cards on account [pierre] +* Stripe: Add card_id parameter to unstore call [pierre] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index fef8e1634f9..f5a396e1178 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -127,8 +127,12 @@ def update_customer(customer_id, options = {}) commit(:post, "customers/#{CGI.escape(customer_id)}", options, generate_meta(options)) end - def unstore(customer_id, options = {}) - commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, generate_meta(options)) + def unstore(customer_id, card_id = nil, options = {}) + if card_id.nil? + commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, generate_meta(options)) + else + commit(:delete, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", nil, generate_meta(options)) + end end private diff --git a/test/remote/gateways/remote_stripe_test.rb b/test/remote/gateways/remote_stripe_test.rb index a584090cebd..b67b99d980f 100644 --- a/test/remote/gateways/remote_stripe_test.rb +++ b/test/remote/gateways/remote_stripe_test.rb @@ -4,10 +4,10 @@ class RemoteStripeTest < Test::Unit::TestCase def setup @gateway = StripeGateway.new(fixtures(:stripe)) + @currency = fixtures(:stripe)["currency"] @amount = 100 # You may have to update the currency, depending on your tenant - @currency = 'CAD' @credit_card = credit_card('4242424242424242') @declined_card = credit_card('4000') @new_credit_card = credit_card('5105105105105100') @@ -113,9 +113,20 @@ def test_successful_update def test_successful_unstore creation = @gateway.store(@credit_card, {:description => "Active Merchant Unstore Customer"}) - assert response = @gateway.unstore(creation.params['id']) + customer_id = creation.params['id'] + card_id = creation.params['cards']['data'].first['id'] + + # Unstore the card + assert response = @gateway.unstore(customer_id, card_id) + assert_success response + assert_equal card_id, response.params['id'] + assert_equal true, response.params['deleted'] + + # Unstore the customer + assert response = @gateway.unstore(customer_id) assert_success response - assert_equal true, response.params["deleted"] + assert_equal customer_id, response.params['id'] + assert_equal true, response.params['deleted'] end def test_successful_recurring From 3dcf75a7ad1f86ed0eb66debbc1288f0f0696b30 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Mon, 11 Nov 2013 14:18:53 -0500 Subject: [PATCH 034/104] Fix BitPay Helper to use form_fields for generating invoice id --- .../billing/integrations/bit_pay/helper.rb | 33 ++++++++----------- .../helpers/bit_pay_helper_test.rb | 10 ++++-- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/lib/active_merchant/billing/integrations/bit_pay/helper.rb b/lib/active_merchant/billing/integrations/bit_pay/helper.rb index e5afcdd0f65..91b3b585f07 100644 --- a/lib/active_merchant/billing/integrations/bit_pay/helper.rb +++ b/lib/active_merchant/billing/integrations/bit_pay/helper.rb @@ -6,20 +6,14 @@ class Helper < ActiveMerchant::Billing::Integrations::Helper def initialize(order_id, account, options) super @account = account - add_field('orderID', order_id) - add_field('posData', options[:authcode]) - add_field('currency', options[:currency]) - add_field('fullNotifications', 'true') - add_field('transactionSpeed', options[:transactionSpeed] || "high") - add_field('address1', options[:address1]) - generate_invoice_id + add_field('posData', {'orderId' => order_id}.to_json) + add_field('fullNotifications', true) + add_field('transactionSpeed', 'high') end - # Replace with the real mapping mapping :amount, 'price' - - mapping :order, 'orderID' + mapping :order, 'orderID' mapping :currency, 'currency' mapping :customer, :first_name => 'buyerName', @@ -37,20 +31,20 @@ def initialize(order_id, account, options) mapping :return_url, 'redirectURL' mapping :id, 'id' - def generate_invoice_id - invoice_data = ssl_post(BitPay.invoicing_url) - - add_field('id', JSON.parse(invoice_data.body)['id']) - end - def form_method "GET" end + def form_fields + invoice = create_invoice + + {"id" => invoice['id']} + end + private - def ssl_post(url, options = {}) - uri = URI.parse(url) + def create_invoice + uri = URI.parse(BitPay.invoicing_url) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true @@ -59,7 +53,8 @@ def ssl_post(url, options = {}) request.body = @fields.to_json request.basic_auth @account, '' - http.request(request) + response = http.request(request) + JSON.parse(response.body) end end end diff --git a/test/unit/integrations/helpers/bit_pay_helper_test.rb b/test/unit/integrations/helpers/bit_pay_helper_test.rb index 5f8ee5f5986..8942ca2d56a 100644 --- a/test/unit/integrations/helpers/bit_pay_helper_test.rb +++ b/test/unit/integrations/helpers/bit_pay_helper_test.rb @@ -4,14 +4,12 @@ class BitPayHelperTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def setup - BitPay::Helper.any_instance.expects(:generate_invoice_id).once.returns(nil) - @helper = BitPay::Helper.new(1234, 'cody@example.com', :authcode => "foobar", :amount => 500, :currency => 'USD') + @helper = BitPay::Helper.new(1234, 'cody@example.com', :amount => 500, :currency => 'USD') end def test_basic_helper_fields assert_field 'orderID', "1234" assert_field 'price', "500" - assert_field 'posData', 'foobar' assert_field 'currency', 'USD' end @@ -46,4 +44,10 @@ def test_setting_invalid_address_field @helper.billing_address :street => 'My Street' assert_equal fields, @helper.fields end + + def test_form_fields_uses_invoice_id + Net::HTTP.any_instance.expects(:request).returns(stub(:body => '{"id": "98kui1gJ7FocK41gUaBZxG"}')) + + assert_equal '98kui1gJ7FocK41gUaBZxG', @helper.form_fields['id'] + end end From 9b2fdd1b2e44fee1a07991845fe360c190ced0a4 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Mon, 11 Nov 2013 14:19:56 -0500 Subject: [PATCH 035/104] Fix BitPay Notification status --- .../integrations/bit_pay/notification.rb | 28 +++++++++++-------- test/unit/integrations/bit_pay_module_test.rb | 4 +-- .../bit_pay_notification_test.rb | 22 +++++++++++---- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/lib/active_merchant/billing/integrations/bit_pay/notification.rb b/lib/active_merchant/billing/integrations/bit_pay/notification.rb index 4aa260347a0..a61c99b1bf8 100644 --- a/lib/active_merchant/billing/integrations/bit_pay/notification.rb +++ b/lib/active_merchant/billing/integrations/bit_pay/notification.rb @@ -6,13 +6,28 @@ module Integrations #:nodoc: module BitPay class Notification < ActiveMerchant::Billing::Integrations::Notification def complete? - status == "complete" + status == "Completed" end def transaction_id params['id'] end + def item_id + params['posData']['orderId'] + end + + def status + case params['status'] + when 'complete' + 'Pending' + when 'confirmed' + 'Completed' + when 'invalid' + 'Failed' + end + end + # When was this payment received by the client. def received_at params['invoiceTime'].to_i @@ -22,19 +37,10 @@ def currency params['currency'] end - def amount - params['price'] - end - - # the money amount we received in X.2 decimal. - def btcPrice + def gross params['btcPrice'].to_f end - def status - params['status'].downcase - end - def acknowledge(authcode = nil) authcode == params['posData'] end diff --git a/test/unit/integrations/bit_pay_module_test.rb b/test/unit/integrations/bit_pay_module_test.rb index 8ca4e227578..7422ad8e0b2 100644 --- a/test/unit/integrations/bit_pay_module_test.rb +++ b/test/unit/integrations/bit_pay_module_test.rb @@ -4,10 +4,10 @@ class BitPayModuleTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def test_notification_method - assert_instance_of BitPay::Notification, BitPay.notification('name=cody') + assert_instance_of BitPay::Notification, BitPay.notification('{"name":"cody"}') end def test_return_method - assert_instance_of BitPay::Return, BitPay.return('name=cody', {}) + assert_instance_of BitPay::Return, BitPay.return('{"name":"cody"}', {}) end end diff --git a/test/unit/integrations/notifications/bit_pay_notification_test.rb b/test/unit/integrations/notifications/bit_pay_notification_test.rb index 076c83eb8ca..0bfce0b0986 100644 --- a/test/unit/integrations/notifications/bit_pay_notification_test.rb +++ b/test/unit/integrations/notifications/bit_pay_notification_test.rb @@ -9,15 +9,15 @@ def setup def test_accessors assert @bit_pay.complete? - assert_equal "complete", @bit_pay.status - assert_equal "w1GRw1q86WPSUlT1r2cGsCZffrUM-KqT9fMFnbC9jo=", @bit_pay.transaction_id - assert_equal 0.0083, @bit_pay.btcPrice + assert_equal "Completed", @bit_pay.status + assert_equal "98kui1gJ7FocK41gUaBZxG", @bit_pay.transaction_id + assert_equal 0.0083, @bit_pay.gross assert_equal "USD", @bit_pay.currency assert_equal 1370539476654, @bit_pay.received_at end def test_compositions - assert_equal "1", @bit_pay.amount + assert_equal Money.new(1, 'USD'), @bit_pay.amount end def test_acknowledgement @@ -26,6 +26,18 @@ def test_acknowledgement private def http_raw_data - "id=w1GRw1q86WPSUlT1r2cGsCZffrUM-KqT9fMFnbC9jo=&orderID=123&url=https://bitpay.com/invoice/w1GRw1q86WPSUlT1r2cGsCZffrUM-KqT9fMFnbC9jo=&status=complete&btcPrice=0.0083&price=1¤cy=USD&invoiceTime=1370539476654&expirationTime=1370540376654¤tTime=1370539573956&posData=foobar" + { + "id"=>"98kui1gJ7FocK41gUaBZxG", + "orderID"=>"123", + "url"=>"https://bitpay.com/invoice/98kui1gJ7FocK41gUaBZxG", + "status"=>"confirmed", + "btcPrice"=>"0.0083", + "price"=>"1", + "currency"=>"USD", + "invoiceTime"=>"1370539476654", + "expirationTime"=>"1370540376654", + "currentTime"=>"1370539573956", + "posData" => "asdf" + }.to_json end end From c42151e751f8688101dffec098f1191793affa85 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Mon, 11 Nov 2013 14:20:42 -0500 Subject: [PATCH 036/104] Implement BitPayNotification#acknowledge to request invoice --- .../integrations/bit_pay/notification.rb | 30 ++++++++++++------- .../bit_pay_notification_test.rb | 13 ++++++-- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/lib/active_merchant/billing/integrations/bit_pay/notification.rb b/lib/active_merchant/billing/integrations/bit_pay/notification.rb index a61c99b1bf8..1a9e4ea6e94 100644 --- a/lib/active_merchant/billing/integrations/bit_pay/notification.rb +++ b/lib/active_merchant/billing/integrations/bit_pay/notification.rb @@ -42,18 +42,28 @@ def gross end def acknowledge(authcode = nil) - authcode == params['posData'] - end + uri = URI.parse("#{ActiveMerchant::Billing::Integrations::BitPay.invoicing_url}/#{transaction_id}") - private + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + http.set_debug_output($stdout) - # Take the posted data and move the relevant data into a hash - def parse(post) - @raw = post.to_s - for line in @raw.split('&') - key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten - params[key] = CGI.unescape(value) - end + request = Net::HTTP::Get.new(uri.path) + request.basic_auth @options[:credential1], '' + + response = http.request(request) + + posted_json = JSON.parse(@raw).tap { |j| j.delete('currentTime') } + parse(response.body) + retrieved_json = JSON.parse(@raw).tap { |j| j.delete('currentTime') } + + raise StandardError.new("Faulty BitPay result: #{response.body}") unless posted_json == retrieved_json + true + end + + def parse(body) + @raw = body + @params = JSON.parse(@raw) end end end diff --git a/test/unit/integrations/notifications/bit_pay_notification_test.rb b/test/unit/integrations/notifications/bit_pay_notification_test.rb index 0bfce0b0986..c5b11fb117d 100644 --- a/test/unit/integrations/notifications/bit_pay_notification_test.rb +++ b/test/unit/integrations/notifications/bit_pay_notification_test.rb @@ -20,8 +20,17 @@ def test_compositions assert_equal Money.new(1, 'USD'), @bit_pay.amount end - def test_acknowledgement - assert @bit_pay.acknowledge('foobar') + def test_successful_acknowledgement + Net::HTTP.any_instance.expects(:request).returns(stub(:body => http_raw_data)) + assert @bit_pay.acknowledge + end + + def test_failed_acknowledgement + Net::HTTP.any_instance.expects(:request).returns(stub(:body => '{"error":"Doesnt match"}')) + exception = assert_raise StandardError do + @bit_pay.acknowledge + end + assert_equal 'Faulty BitPay result: {"error":"Doesnt match"}', exception.message end private From 4fc4687074547437bde89326d28b2b28ed4d83dd Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Mon, 11 Nov 2013 15:48:31 -0500 Subject: [PATCH 037/104] BitPay Notification#gross should return price, not btcPrice --- .../billing/integrations/bit_pay/notification.rb | 2 +- .../notifications/bit_pay_notification_test.rb | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/active_merchant/billing/integrations/bit_pay/notification.rb b/lib/active_merchant/billing/integrations/bit_pay/notification.rb index 1a9e4ea6e94..d47d44528ef 100644 --- a/lib/active_merchant/billing/integrations/bit_pay/notification.rb +++ b/lib/active_merchant/billing/integrations/bit_pay/notification.rb @@ -38,7 +38,7 @@ def currency end def gross - params['btcPrice'].to_f + params['price'].to_f end def acknowledge(authcode = nil) diff --git a/test/unit/integrations/notifications/bit_pay_notification_test.rb b/test/unit/integrations/notifications/bit_pay_notification_test.rb index c5b11fb117d..fb59f37d908 100644 --- a/test/unit/integrations/notifications/bit_pay_notification_test.rb +++ b/test/unit/integrations/notifications/bit_pay_notification_test.rb @@ -11,13 +11,13 @@ def test_accessors assert @bit_pay.complete? assert_equal "Completed", @bit_pay.status assert_equal "98kui1gJ7FocK41gUaBZxG", @bit_pay.transaction_id - assert_equal 0.0083, @bit_pay.gross + assert_equal 10.00, @bit_pay.gross assert_equal "USD", @bit_pay.currency assert_equal 1370539476654, @bit_pay.received_at end def test_compositions - assert_equal Money.new(1, 'USD'), @bit_pay.amount + assert_equal Money.new(1000, 'USD'), @bit_pay.amount end def test_successful_acknowledgement @@ -40,13 +40,13 @@ def http_raw_data "orderID"=>"123", "url"=>"https://bitpay.com/invoice/98kui1gJ7FocK41gUaBZxG", "status"=>"confirmed", - "btcPrice"=>"0.0083", - "price"=>"1", + "btcPrice"=>"0.0295", + "price"=>"10.00", "currency"=>"USD", "invoiceTime"=>"1370539476654", "expirationTime"=>"1370540376654", "currentTime"=>"1370539573956", - "posData" => "asdf" + "posData" => "custom" }.to_json end end From 10b7918e68e1919cc356f6ad64429ebc95eb4ad7 Mon Sep 17 00:00:00 2001 From: Nathaniel Talbott Date: Fri, 25 Oct 2013 10:33:14 -0400 Subject: [PATCH 038/104] Remove usage of `uname -a` I discovered that this was leaking internal host names, and could potentially leak other information depending on how a given distro implements uname. It also just seems less than prudent to shell out from within a gateway. I've also done a quick review of the code for any other uses of command execution (`` or %x) and found none. Closes #893. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/stripe.rb | 3 +-- lib/active_merchant/billing/gateways/webpay.rb | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 463a3ca2803..c9cad066fa3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ * Spreedly Core: Allow store without retain [hoenth] * Stripe: Support multiple cards on account [pierre] * Stripe: Add card_id parameter to unstore call [pierre] +* Remove usage of `uname -a` [ntalbott] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index f5a396e1178..877a3ddfbd9 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -248,8 +248,7 @@ def headers(options = {}) :lang => 'ruby', :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", :platform => RUBY_PLATFORM, - :publisher => 'active_merchant', - :uname => (RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil) + :publisher => 'active_merchant' }) key = options[:key] || @api_key diff --git a/lib/active_merchant/billing/gateways/webpay.rb b/lib/active_merchant/billing/gateways/webpay.rb index 1f8ec4f2e68..999a32e5b45 100644 --- a/lib/active_merchant/billing/gateways/webpay.rb +++ b/lib/active_merchant/billing/gateways/webpay.rb @@ -68,8 +68,7 @@ def headers(options = {}) :lang => 'ruby', :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})", :platform => RUBY_PLATFORM, - :publisher => 'active_merchant', - :uname => (RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil) + :publisher => 'active_merchant' }) { From b1230affcad4ce09a3f0615f242cf81b490d80d0 Mon Sep 17 00:00:00 2001 From: Duff OMelia Date: Thu, 7 Nov 2013 13:50:34 -0500 Subject: [PATCH 039/104] Litle: Allow easier access to the response code Prior to this commit, the response code could show up within a number of different response param keys, depending on whether it was an authorize, a purchase, etc. Now, we have the 'response_code' key in the Response params allowing clients to get the code in the same way regardless of the operation. In addition, Active Merchant uses the response_code key for a number of other gateways. Closes #905. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/litle.rb | 10 +++++----- test/unit/gateways/litle_test.rb | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c9cad066fa3..574d354bfae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ * Stripe: Support multiple cards on account [pierre] * Stripe: Add card_id parameter to unstore call [pierre] * Remove usage of `uname -a` [ntalbott] +* Litle: Allow easier access to the response code [duff] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/litle.rb b/lib/active_merchant/billing/gateways/litle.rb index 83ce2bc28dc..afb742a09f2 100755 --- a/lib/active_merchant/billing/gateways/litle.rb +++ b/lib/active_merchant/billing/gateways/litle.rb @@ -171,11 +171,11 @@ def build_response(kind, litle_response, valid_responses=%w(000)) Response.new( valid_responses.include?(detail['response']), detail['message'], - { :litleOnlineResponse => response }, - :authorization => authorization_from(detail, kind), - :avs_result => { :code => fraud['avs'] }, - :cvv_result => fraud['cvv'], - :test => test? + { litleOnlineResponse: response, response_code: detail['response'] }, + authorization: authorization_from(detail, kind), + avs_result: { :code => fraud['avs'] }, + cvv_result: fraud['cvv'], + test: test? ) else Response.new(false, response['message'], :litleOnlineResponse => response, :test => test?) diff --git a/test/unit/gateways/litle_test.rb b/test/unit/gateways/litle_test.rb index e3166870e8f..8ca342b3e0c 100755 --- a/test/unit/gateways/litle_test.rb +++ b/test/unit/gateways/litle_test.rb @@ -377,6 +377,7 @@ def test_auth_pass assert_equal 'successful', responseFrom.message assert_equal '1234;authorization', responseFrom.authorization assert_equal '1111222233334444', responseFrom.params['litleOnlineResponse']['authorizationResponse']['litleToken'] + assert_equal '000', responseFrom.params['response_code'] end def test_avs From 275cb28f95e00be6a45d0e6bd28976c6acb3576d Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Thu, 7 Nov 2013 15:33:50 -0500 Subject: [PATCH 040/104] Stripe: Add the option to pass a version header Closes #906. --- CHANGELOG | 1 + .../billing/gateways/stripe.rb | 23 ++++++++++++------- test/unit/gateways/stripe_test.rb | 8 +++++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 574d354bfae..37486adab3f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,7 @@ * Stripe: Add card_id parameter to unstore call [pierre] * Remove usage of `uname -a` [ntalbott] * Litle: Allow easier access to the response code [duff] +* Stripe: Add the option to pass a version header == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index 877a3ddfbd9..9d49187059d 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -40,7 +40,7 @@ def authorize(money, creditcard, options = {}) post = create_post_for_auth_or_purchase(money, creditcard, options) post[:capture] = "false" - commit(:post, 'charges', post, generate_meta(options)) + commit(:post, 'charges', post, generate_options(options)) end # To create a charge on a card or a token, call @@ -53,7 +53,7 @@ def authorize(money, creditcard, options = {}) def purchase(money, creditcard, options = {}) post = create_post_for_auth_or_purchase(money, creditcard, options) - commit(:post, 'charges', post, generate_meta(options)) + commit(:post, 'charges', post, generate_options(options)) end def capture(money, authorization, options = {}) @@ -69,7 +69,7 @@ def void(identification, options = {}) def refund(money, identification, options = {}) post = {:amount => amount(money)} - commit_options = generate_meta(options) + commit_options = generate_options(options) MultiResponse.run(:first) do |r| r.process { commit(:post, "charges/#{CGI.escape(identification)}/refund", post, commit_options) } @@ -104,7 +104,7 @@ def store(creditcard, options = {}) post[:description] = options[:description] post[:email] = options[:email] - commit_options = generate_meta(options) + commit_options = generate_options(options) if options[:customer] MultiResponse.run(:first) do |r| r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/cards", post, commit_options) } @@ -124,14 +124,14 @@ def update(customer_id, creditcard, options = {}) end def update_customer(customer_id, options = {}) - commit(:post, "customers/#{CGI.escape(customer_id)}", options, generate_meta(options)) + commit(:post, "customers/#{CGI.escape(customer_id)}", options, generate_options(options)) end def unstore(customer_id, card_id = nil, options = {}) if card_id.nil? - commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, generate_meta(options)) + commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, generate_options(options)) else - commit(:delete, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", nil, generate_meta(options)) + commit(:delete, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", nil, generate_options(options)) end end @@ -238,6 +238,11 @@ def post_data(params) end.compact.join("&") end + def generate_options(raw_options) + options = generate_meta(raw_options) + options.merge!(raw_options.slice(:version)) + end + def generate_meta(options) {:meta => {:ip => options[:ip]}} end @@ -253,12 +258,14 @@ def headers(options = {}) key = options[:key] || @api_key - { + headers = { "Authorization" => "Basic " + Base64.encode64(key.to_s + ":").strip, "User-Agent" => "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}", "X-Stripe-Client-User-Agent" => @@ua, "X-Stripe-Client-User-Metadata" => options[:meta].to_json } + headers.merge!("Stripe-Version" => options[:version]) if options[:version] + headers end def commit(method, url, parameters=nil, options = {}) diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb index 5a75a5f3e88..2aeae216027 100644 --- a/test/unit/gateways/stripe_test.rb +++ b/test/unit/gateways/stripe_test.rb @@ -293,6 +293,14 @@ def test_metadata_header @gateway.purchase(@amount, @credit_card, @options.merge(:ip => '1.1.1.1')) end + def test_optional_version_header + @gateway.expects(:ssl_request).once.with {|method, url, post, headers| + headers && headers['Stripe-Version'] == '2013-10-29' + }.returns(successful_purchase_response) + + @gateway.purchase(@amount, @credit_card, @options.merge(:version => '2013-10-29')) + end + def test_track_data_and_traditional_should_be_mutually_exclusive stub_comms(@gateway, :ssl_request) do @gateway.purchase(@amount, @credit_card, @options) From a21d8bb0afbf1b7b42781afeaeda21ab4e3b3db1 Mon Sep 17 00:00:00 2001 From: Duff OMelia Date: Mon, 11 Nov 2013 15:34:45 -0500 Subject: [PATCH 041/104] Elavon: Update supported countries Based on their site: https://www.elavon.com/country-selector/ Closes #909. --- CHANGELOG | 3 ++- lib/active_merchant/billing/gateways/elavon.rb | 2 +- test/unit/gateways/elavon_test.rb | 4 ---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 37486adab3f..3e839f3f50f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,7 +16,8 @@ * Stripe: Add card_id parameter to unstore call [pierre] * Remove usage of `uname -a` [ntalbott] * Litle: Allow easier access to the response code [duff] -* Stripe: Add the option to pass a version header +* Stripe: Add the option to pass a version header [odorcicd] +* Elavon: Update supported countries [duff] == Version 1.41.0 (October 24th, 2013) diff --git a/lib/active_merchant/billing/gateways/elavon.rb b/lib/active_merchant/billing/gateways/elavon.rb index ddc4559b937..d079eabca9c 100644 --- a/lib/active_merchant/billing/gateways/elavon.rb +++ b/lib/active_merchant/billing/gateways/elavon.rb @@ -36,7 +36,7 @@ class ElavonGateway < Gateway self.live_url = 'https://www.myvirtualmerchant.com/VirtualMerchant/process.do' self.display_name = 'Elavon MyVirtualMerchant' - self.supported_countries = ['US', 'CA'] + self.supported_countries = %w(US CA PR DE IE NO PL LU BE NL) self.supported_cardtypes = [:visa, :master, :american_express, :discover] self.homepage_url = 'http://www.elavon.com/' diff --git a/test/unit/gateways/elavon_test.rb b/test/unit/gateways/elavon_test.rb index b4c0054ca23..3ae26fb5c3d 100644 --- a/test/unit/gateways/elavon_test.rb +++ b/test/unit/gateways/elavon_test.rb @@ -97,10 +97,6 @@ def test_invalid_login assert_failure response end - def test_supported_countries - assert_equal ['US', 'CA'], ElavonGateway.supported_countries - end - def test_supported_card_types assert_equal [:visa, :master, :american_express, :discover], ElavonGateway.supported_cardtypes end From cc6cefe9dbb0ef7e8270d66eeee5305ef52bc903 Mon Sep 17 00:00:00 2001 From: Luis Lopez Date: Fri, 16 Aug 2013 16:05:41 -0300 Subject: [PATCH 042/104] Add Raven PacNet gateway http://www.pacnetservices.com/ Closes #810. --- CHANGELOG | 1 + CONTRIBUTORS | 4 + README.md | 1 + .../billing/gateways/pac_net_raven.rb | 187 ++++++ test/fixtures.yml | 6 + .../gateways/remote_pac_net_raven_test.rb | 207 +++++++ test/unit/gateways/pac_net_raven_test.rb | 541 ++++++++++++++++++ 7 files changed, 947 insertions(+) create mode 100644 lib/active_merchant/billing/gateways/pac_net_raven.rb create mode 100644 test/remote/gateways/remote_pac_net_raven_test.rb create mode 100644 test/unit/gateways/pac_net_raven_test.rb diff --git a/CHANGELOG b/CHANGELOG index 3e839f3f50f..8a25f68a2da 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ * Litle: Allow easier access to the response code [duff] * Stripe: Add the option to pass a version header [odorcicd] * Elavon: Update supported countries [duff] +* Add Raven PacNet gateway [llopez] == Version 1.41.0 (October 24th, 2013) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 52821b559e4..75798bd8087 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -432,3 +432,7 @@ App55 (November 2013) Swipe Checkout (November 2013) * (matt-optimizerhq) + +Raven PacNet (November 2013) + +* Luis Lopez (llopez) diff --git a/README.md b/README.md index b7efe6b475c..776649e982e 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta * [QuickBooks Merchant Services](http://payments.intuit.com/) - US * [Quantum Gateway](http://www.quantumgateway.com) - US * [Quickpay](http://quickpay.dk/) - DK, SE +* [Raven PacNet](http://www.pacnetservices.com/) - US * [Realex](http://www.realexpayments.com/) - IE, GB, FR, BE, NL, LU, IT * [Redsys](http://www.redsys.es/) - ES * [SagePay](http://www.sagepay.com) - GB, IE diff --git a/lib/active_merchant/billing/gateways/pac_net_raven.rb b/lib/active_merchant/billing/gateways/pac_net_raven.rb new file mode 100644 index 00000000000..e6b9a8a1416 --- /dev/null +++ b/lib/active_merchant/billing/gateways/pac_net_raven.rb @@ -0,0 +1,187 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PacNetRavenGateway < Gateway + self.test_url = 'https://demo.deepcovelabs.com/realtime/' + self.live_url = 'https://raven.pacnetservices.com/realtime/' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.money_format = :cents + self.default_currency = 'USD' + self.homepage_url = 'http://www.pacnetservices.com/' + self.display_name = 'Raven PacNet' + + def initialize(options = {}) + requires!(options, :user, :secret, :prn) + super + end + + def authorize(money, creditcard, options = {}) + post = {} + add_creditcard(post, creditcard) + add_currency_code(post, money, options) + add_address(post, options) + post['PRN'] = @options[:prn] + + commit('cc_preauth', money, post) + end + + def purchase(money, creditcard, options = {}) + post = {} + add_currency_code(post, money, options) + add_creditcard(post, creditcard) + add_address(post, options) + post['PRN'] = @options[:prn] + + commit('cc_debit', money, post) + end + + def void(authorization, options = {}) + post = {} + post['TrackingNumber'] = authorization + post['PymtType'] = options[:pymt_type] || 'cc_debit' + + commit('void', nil, post) + end + + def capture(money, authorization, options = {}) + post = {} + post['PreauthNumber'] = authorization + post['PRN'] = @options[:prn] + add_currency_code(post, money, options) + + commit('cc_settle', money, post) + end + + def refund(money, template_number, options = {}) + post = {} + post['PRN'] = @options[:prn] + post['TemplateNumber'] = template_number + add_currency_code(post, money, options) + + commit('cc_refund', money, post) + end + + private + + def add_creditcard(post, creditcard) + post['CardNumber'] = creditcard.number + post['Expiry'] = expdate(creditcard) + post['CVV2'] = creditcard.verification_value if creditcard.verification_value + end + + def add_currency_code(post, money, options) + post['Currency'] = options[:currency] || currency(money) + end + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post['BillingStreetAddressLineOne'] = address[:address1].to_s + post['BillingStreetAddressLineFour'] = address[:address2].to_s + post['BillingPostalCode'] = address[:zip].to_s + end + end + + def parse(body) + Hash[body.split('&').map{|x| x.split('=').map{|x| CGI.unescape(x)}}] + end + + def commit(action, money, parameters) + parameters['Amount'] = amount(money) unless action == 'void' + + data = ssl_post url(action), post_data(action, parameters) + + response = parse(data) + response[:action] = action + + message = message_from(response) + + test_mode = test? || message =~ /TESTMODE/ + + Response.new(success?(response), message, response, + :test => test_mode, + :authorization => response['TrackingNumber'], + :fraud_review => fraud_review?(response), + :avs_result => { :postal_match => response['AVSPostalResponseCode'], :street_match => response['AVSAddressResponseCode'] }, + :cvv_result => response['CVV2ResponseCode'] + ) + end + + def url(action) + (test? ? self.test_url : self.live_url) + endpoint(action) + end + + def endpoint(action) + return 'void' if action == 'void' + 'submit' + end + + def fraud_review?(response) + false + end + + def success?(response) + if %w(cc_settle cc_debit cc_preauth cc_refund).include?(response[:action]) + !response['ApprovalCode'].nil? and response['ErrorCode'].nil? and response['Status'] == 'Approved' + elsif response[:action] = 'void' + !response['ApprovalCode'].nil? and response['ErrorCode'].nil? and response['Status'] == 'Voided' + end + end + + def message_from(response) + return response['Message'] if response['Message'] + + if response['Status'] == 'Approved' + "This transaction has been approved" + elsif response['Status'] == 'Declined' + "This transaction has been declined" + elsif response['Status'] == 'Voided' + "This transaction has been voided" + else + response['Status'] + end + end + + def post_data(action, parameters = {}) + post = {} + + post['PymtType'] = action + post['RAPIVersion'] = '2' + post['UserName'] = @options[:user] + post['Timestamp'] = timestamp + post['RequestID'] = request_id + post['Signature'] = signature(action, post, parameters) + + request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&") + request + end + + def timestamp + Time.now.strftime("%Y-%m-%dT%H:%M:%S.Z") + end + + def request_id + (0...21).map{(65+rand(26)).chr}.join.downcase + end + + def signature(action, post, parameters = {}) + string = if %w(cc_settle cc_debit cc_preauth cc_refund).include?(action) + post['UserName'] + post['Timestamp'] + post['RequestID'] + post['PymtType'] + parameters['Amount'].to_s + parameters['Currency'] + elsif action == 'void' + post['UserName'] + post['Timestamp'] + post['RequestID'] + parameters['TrackingNumber'] + else + post['UserName'] + end + Digest::HMAC.hexdigest(string, @options[:secret], Digest::SHA1) + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + + "#{month}#{year[-2..-1]}" + end + end + end +end + diff --git a/test/fixtures.yml b/test/fixtures.yml index 65a4b9d33d6..af3af91dd42 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -10,6 +10,7 @@ # # Paste any required PEM certificates after the pem key. # + app55: api_key: "QDtACYMCFqtuKOQ22QtCkGR1TzD7XM8i" api_secret: "yT1FRpuusBIQoEBIfIQ8bPStkl7yzdTz" @@ -480,6 +481,11 @@ quantum: login: X password: Y +raven_pac_net: + user: ernest + secret: "all good men die young" + prn: 840033 + realex: login: X password: Y diff --git a/test/remote/gateways/remote_pac_net_raven_test.rb b/test/remote/gateways/remote_pac_net_raven_test.rb new file mode 100644 index 00000000000..2f213280180 --- /dev/null +++ b/test/remote/gateways/remote_pac_net_raven_test.rb @@ -0,0 +1,207 @@ +require 'test_helper' + +class RemotePacNetRavenGatewayTest < Test::Unit::TestCase + def setup + @gateway = PacNetRavenGateway.new(fixtures(:raven_pac_net)) + + @amount = 100 + @credit_card = credit_card('4000000000000028') + @declined_card = credit_card('5100000000000040') + + @options = { + billing_address: address + } + end + + def test_successful_purchase + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_success purchase + assert purchase.params['ApprovalCode'] + assert purchase.params['TrackingNumber'] + assert_nil purchase.params['ErrorCode'] + assert_equal 'Approved', purchase.params['Status'] + assert_equal 'ok', purchase.params['RequestResult'] + assert_nil purchase.params['Message'] + assert_equal 'This transaction has been approved', purchase.message + end + + def test_invalid_credit_card_number_purchese + @credit_card = credit_card('0000') + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_failure purchase + assert_nil purchase.params['ApprovalCode'] + assert purchase.params['TrackingNumber'] + assert_equal 'invalid:cardNumber', purchase.params['ErrorCode'] + assert_equal 'Invalid:CardNumber', purchase.params['Status'] + assert_equal 'ok', purchase.params['RequestResult'] + assert_equal "Error processing transaction because CardNumber \"0000\" is not between 12 and 19 in length.", purchase.params['Message'] + end + + def test_expired_credit_card_purchese + @credit_card.month = 9 + @credit_card.year = 2012 + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert_failure purchase + assert_nil purchase.params['ApprovalCode'] + assert purchase.params['TrackingNumber'] + assert_equal 'invalid:CustomerCardExpiryDate', purchase.params['ErrorCode'] + assert_equal 'Invalid:CustomerCardExpiryDate', purchase.params['Status'] + assert_equal 'ok', purchase.params['RequestResult'] + assert_equal "Invalid because the card expiry date (mmyy) \"0912\" is not a date in the future", purchase.params['Message'] + end + + def test_declined_purchese + assert purchase = @gateway.purchase(@amount, @declined_card, @options) + assert_failure purchase + assert_equal 'RepeatDeclined', purchase.message + end + + def test_successful_authorization + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_success auth + assert auth.params['ApprovalCode'] + assert auth.params['TrackingNumber'] + assert_nil auth.params['ErrorCode'] + assert_nil auth.params['Message'] + assert_equal 'Approved', auth.params['Status'] + assert_equal 'ok', auth.params['RequestResult'] + assert_equal 'This transaction has been approved', auth.message + end + + def test_invalid_credit_card_number_authorization + @credit_card = credit_card('0000') + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_failure auth + assert_nil auth.params['ApprovalCode'] + assert auth.params['TrackingNumber'] + assert_equal 'invalid:cardNumber', auth.params['ErrorCode'] + assert_equal 'Invalid:CardNumber', auth.params['Status'] + assert_equal 'ok', auth.params['RequestResult'] + assert_equal "Error processing transaction because CardNumber \"0000\" is not between 12 and 19 in length.", auth.params['Message'] + end + + def test_expired_credit_card_authorization + @credit_card.month = 9 + @credit_card.year = 2012 + assert auth = @gateway.authorize(@amount, @credit_card, @options) + assert_failure auth + assert_nil auth.params['ApprovalCode'] + assert auth.params['TrackingNumber'] + assert_equal 'invalid:CustomerCardExpiryDate', auth.params['ErrorCode'] + assert_equal 'Invalid:CustomerCardExpiryDate', auth.params['Status'] + assert_equal 'ok', auth.params['RequestResult'] + assert_equal "Invalid because the card expiry date (mmyy) \"0912\" is not a date in the future", auth.params['Message'] + end + + def test_declined_authorization + assert auth = @gateway.authorize(@amount, @declined_card, @options) + assert_failure auth + assert_equal 'RepeatDeclined', auth.message + end + + def test_successful_refund + assert purchase = @gateway.purchase(@amount, @credit_card, @options) + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert refund.params['ApprovalCode'] + assert refund.params['TrackingNumber'] + assert_nil refund.params['ErrorCode'] + assert_equal 'Approved', refund.params['Status'] + assert_equal 'ok', refund.params['RequestResult'] + assert_nil refund.params['Message'] + assert_equal 'This transaction has been approved', refund.message + end + + def test_amount_greater_than_original_amount_refund + assert purchase = @gateway.purchase(100, @credit_card, @options) + assert_success purchase + + assert refund = @gateway.refund(200, purchase.authorization) + assert_failure refund + assert_nil refund.params['ApprovalCode'] + assert refund.params['TrackingNumber'] + assert_equal 'invalid:RefundAmountGreaterThanOriginalAmount', refund.params['ErrorCode'] + assert_equal 'Invalid:RefundAmountGreaterThanOriginalAmount', refund.params['Status'] + assert_equal 'ok', refund.params['RequestResult'] + assert_equal "Invalid because the payment amount cannot be greater than the original charge.", refund.params['Message'] + assert_equal 'Invalid because the payment amount cannot be greater than the original charge.', refund.message + end + + def test_purchase_and_void + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert void = @gateway.void(purchase.authorization, {:pymt_type => purchase.params['PymtType']}) + assert_success void + assert void.params['ApprovalCode'] + assert void.params['TrackingNumber'] + assert_nil void.params['ErrorCode'] + assert_equal 'ok', void.params['RequestResult'] + assert_nil void.params['Message'] + assert_equal 'Voided', void.params['Status'] + assert_equal "This transaction has been voided", void.message + end + + def test_authorize_and_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert void = @gateway.void(auth.authorization) + assert_failure void + assert void.params['ApprovalCode'] + assert void.params['TrackingNumber'] + assert_equal 'error:canNotBeVoided', void.params['ErrorCode'] + assert_equal 'ok', void.params['RequestResult'] + assert_equal "Error processing transaction because the payment may not be voided.", void.params['Message'] + assert_equal 'Approved', void.params['Status'] + assert_equal "Error processing transaction because the payment may not be voided.", void.message + end + + def test_authorize_capture_and_void + auth = @gateway.authorize(@amount, @credit_card, @options) + assert capture = @gateway.capture(@amount, auth.authorization) + assert void = @gateway.void(capture.authorization, {:pymt_type => capture.params['PymtType']}) + assert_failure void + assert void.params['ApprovalCode'] + assert void.params['TrackingNumber'] + assert_equal 'error:canNotBeVoided', void.params['ErrorCode'] + assert_equal 'ok', void.params['RequestResult'] + assert_equal "Error processing transaction because the payment may not be voided.", void.params['Message'] + assert_equal 'Approved', void.params['Status'] + assert_equal "Error processing transaction because the payment may not be voided.", void.message + end + + def test_successful_capture + auth = @gateway.authorize(@amount, @credit_card, @options) + assert capture = @gateway.capture(@amount, auth.authorization) + assert_success capture + assert capture.params['ApprovalCode'] + assert capture.params['TrackingNumber'] + assert_nil capture.params['ErrorCode'] + assert_equal 'Approved', capture.params['Status'] + assert_equal 'ok', capture.params['RequestResult'] + assert_nil capture.params['Message'] + assert_equal 'This transaction has been approved', capture.message + end + + def test_invalid_preauth_number_capture + assert capture = @gateway.capture(@amount, '') + assert_failure capture + assert_nil capture.params['ApprovalCode'] + assert capture.params['TrackingNumber'] + assert_equal 'invalid:PreAuthNumber', capture.params['ErrorCode'] + assert_equal 'Invalid:PreauthNumber', capture.params['Status'] + assert_equal 'ok', capture.params['RequestResult'] + assert_equal "Error processing transaction because the pre-auth number \"0\" does not correspond to a pre-existing payment.", capture.params['Message'] + assert capture.message.include?('Error processing transaction because the pre-auth number') + end + + def test_insufficient_preauth_amount_capture + auth = @gateway.authorize(100, @credit_card, @options) + assert capture = @gateway.capture(200, auth.authorization) + assert_failure capture + assert_nil capture.params['ApprovalCode'] + assert capture.params['TrackingNumber'] + assert_equal 'rejected:PreauthAmountInsufficient', capture.params['ErrorCode'] + assert_equal 'Rejected:PreauthAmountInsufficient', capture.params['Status'] + assert_equal 'ok', capture.params['RequestResult'] + assert_equal "Invalid because the preauthorization amount 100 is insufficient", capture.params['Message'] + assert_equal 'Invalid because the preauthorization amount 100 is insufficient', capture.message + end +end diff --git a/test/unit/gateways/pac_net_raven_test.rb b/test/unit/gateways/pac_net_raven_test.rb new file mode 100644 index 00000000000..1727b63fb32 --- /dev/null +++ b/test/unit/gateways/pac_net_raven_test.rb @@ -0,0 +1,541 @@ +require 'test_helper' + +class PacNetRavenGatewayTest < Test::Unit::TestCase + def setup + @gateway = PacNetRavenGateway.new( + user: 'user', + secret: 'secret', + prn: 123456 + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + billing_address: address + } + end + + def test_successful_authorization + @gateway.expects(:ssl_post).returns(successful_authorization_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_equal '123456789', response.authorization + assert response.test? + end + + def test_invalid_credit_card_authorization + @gateway.expects(:ssl_post).returns(invalid_credit_card_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal "Error processing transaction because CardNumber is not between 12 and 19 in length", response.message + end + + def test_expired_credit_card_authorization + @gateway.expects(:ssl_post).returns(expired_credit_card_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal "Invalid because the card expiry date is not a date in the future", response.message + end + + def test_declined_authorization + @gateway.expects(:ssl_post).returns(declined_purchese_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'This transaction has been declined', response.message + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_equal '123456789', response.authorization + assert response.test? + end + + def test_invalid_credit_card_number_purchese + @gateway.expects(:ssl_post).returns(invalid_credit_card_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal "Error processing transaction because CardNumber is not between 12 and 19 in length", response.message + end + + def test_expired_credit_card_purchese + @gateway.expects(:ssl_post).returns(expired_credit_card_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal "Invalid because the card expiry date is not a date in the future", response.message + end + + def test_declined_purchese + @gateway.expects(:ssl_post).returns(declined_purchese_response) + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'This transaction has been declined', response.message + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + assert response = @gateway.capture(@amount, '123456789') + assert_instance_of Response, response + assert_success response + assert_equal 'This transaction has been approved', response.message + assert_equal '123456789', response.authorization + assert response.test? + end + + def test_invalid_preauth_number_capture + @gateway.expects(:ssl_post).returns(invalid_preauth_number_response) + assert response = @gateway.capture(@amount, '') + assert_failure response + assert_equal 'Error processing transaction because the pre-auth number', response.message + end + + def test_insufficient_preauth_amount_capture + @gateway.expects(:ssl_post).returns(insufficient_preauth_amount_response) + assert response = @gateway.capture(200, '123456789') + assert_failure response + assert_equal 'Invalid because the preauthorization amount 100 is insufficient', response.message + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + assert response = @gateway.refund(@amount, '123456789') + assert_success response + assert_equal 'This transaction has been approved', response.message + end + + def test_amount_greater_than_original_amount_refund + @gateway.expects(:ssl_post).returns(amount_greater_than_original_amount_refund_response) + assert response = @gateway.refund(200, '123456789') + assert_failure response + assert_equal 'Invalid because the payment amount cannot be greater than the original charge', response.message + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + assert response = @gateway.void('123456789') + assert_success response + assert_equal "This transaction has been voided", response.message + end + + def test_failed_void + @gateway.expects(:ssl_post).returns(failed_void_response) + assert response = @gateway.void('123456789') + assert_failure response + assert_equal "Error processing transaction because the payment may not be voided", response.message + end + + def test_argument_error_prn + exception = assert_raises(ArgumentError){ + PacNetRavenGateway.new(:user => 'user', :secret => 'secret') + } + assert_equal 'Missing required parameter: prn', exception.message + end + + def test_argument_error_user + exception = assert_raises(ArgumentError){ + PacNetRavenGateway.new(:secret => 'secret', :prn => 123456) + } + assert_equal 'Missing required parameter: user', exception.message + end + + def test_argument_error_secret + exception = assert_raises(ArgumentError){ + PacNetRavenGateway.new(:user => 'user', :prn => 123456) + } + assert_equal 'Missing required parameter: secret', exception.message + end + + def test_add_address + result = {} + @gateway.send(:add_address, result, :billing_address => {:address1 => 'Address 1', :address2 => 'Address 2', :zip => 'ZIP'} ) + assert_equal ["BillingPostalCode", "BillingStreetAddressLineFour", "BillingStreetAddressLineOne"], result.stringify_keys.keys.sort + assert_equal 'ZIP', result['BillingPostalCode'] + assert_equal 'Address 2', result['BillingStreetAddressLineFour'] + assert_equal 'Address 1', result['BillingStreetAddressLineOne'] + end + + def test_add_creditcard + result = {} + @gateway.send(:add_creditcard, result, @credit_card) + assert_equal ["CVV2", "CardNumber", "Expiry"], result.stringify_keys.keys.sort + assert_equal @credit_card.number, result['CardNumber'] + assert_equal @gateway.send(:expdate, @credit_card), result['Expiry'] + assert_equal @credit_card.verification_value, result['CVV2'] + end + + def test_add_currency_code_default + result = {} + @gateway.send(:add_currency_code, result, 100, {}) + assert_equal 'USD', result['Currency'] + end + + def test_add_currency_code_from_options + result = {} + @gateway.send(:add_currency_code, result, 100, {currency: 'CAN'}) + assert_equal 'CAN', result['Currency'] + end + + def test_parse + result = @gateway.send(:parse, "key1=value1&key2=value2") + h = {'key1' => 'value1', 'key2' => 'value2'} + assert_equal h, result + end + + def test_endpoint_for_void + assert_equal 'void', @gateway.send(:endpoint, 'void') + end + + def test_endpoint_for_cc_debit + assert_equal 'submit', @gateway.send(:endpoint, 'cc_debit') + end + + def test_endpoint_for_cc_preauth + assert_equal 'submit', @gateway.send(:endpoint, 'cc_preauth') + end + + def test_endpoint_for_cc_settle + assert_equal 'submit', @gateway.send(:endpoint, 'cc_settle') + end + + def test_endpoint_for_cc_refund + assert_equal 'submit', @gateway.send(:endpoint, 'cc_refund') + end + + def test_success + assert @gateway.send(:success?, { + :action => 'cc_settle', + 'ApprovalCode' => '123456', + 'ErrorCode' => nil, + 'Status' => 'Approved' + }) + + refute @gateway.send(:success?, { + :action => 'cc_settle', + 'ApprovalCode' => nil, + 'ErrorCode' => 'SomeError', + 'Status' => 'SomeError' + }) + + assert @gateway.send(:success?, { + :action => 'cc_debit', + 'ApprovalCode' => '123456', + 'ErrorCode' => nil, + 'Status' => 'Approved' + }) + + refute @gateway.send(:success?, { + :action => 'cc_debit', + 'ApprovalCode' => nil, + 'ErrorCode' => 'SomeError', + 'Status' => 'SomeError' + }) + + assert @gateway.send(:success?, { + :action => 'cc_preauth', + 'ApprovalCode' => '123456', + 'ErrorCode' => nil, + 'Status' => 'Approved' + }) + + refute @gateway.send(:success?, { + :action => 'cc_preauth', + 'ApprovalCode' => nil, + 'ErrorCode' => 'SomeError', + 'Status' => 'SomeError' + }) + + assert @gateway.send(:success?, { + :action => 'cc_refund', + 'ApprovalCode' => '123456', + 'ErrorCode' => nil, + 'Status' => 'Approved' + }) + + refute @gateway.send(:success?, { + :action => 'cc_refund', + 'ApprovalCode' => nil, + 'ErrorCode' => 'SomeError', + 'Status' => 'SomeError' + }) + + assert @gateway.send(:success?, { + :action => 'void', + 'ApprovalCode' => '123456', + 'ErrorCode' => nil, + 'Status' => 'Voided' + }) + + refute @gateway.send(:success?, { + :action => 'void', + 'ApprovalCode' => nil, + 'ErrorCode' => 'SomeError', + 'Status' => 'SomeError' + }) + end + + def test_message_from_approved + assert_equal "This transaction has been approved", @gateway.send(:message_from, { + 'Status' => 'Approved', + 'Message'=> nil + }) + end + + def test_message_from_declined + assert_equal "This transaction has been declined", @gateway.send(:message_from, { + 'Status' => 'Declined', + 'Message'=> nil + }) + end + + def test_message_from_voided + assert_equal "This transaction has been voided", @gateway.send(:message_from, { + 'Status' => 'Voided', + 'Message'=> nil + }) + end + + def test_message_from_status + assert_equal "This is the message", @gateway.send(:message_from, { + 'Status' => 'SomeStatus', + 'Message'=> "This is the message" + }) + end + + def test_post_data + @gateway.stubs(:request_id => "wouykiikdvqbwwxueppby") + @gateway.stubs(:timestamp => "2013-10-08T14:31:54.Z") + + assert_equal "PymtType=cc_preauth&RAPIVersion=2&UserName=user&Timestamp=2013-10-08T14%3A31%3A54.Z&RequestID=wouykiikdvqbwwxueppby&Signature=7794efc8c0d39f0983edc10f778e6143ba13531d&CardNumber=4242424242424242&Expiry=0914&CVV2=123&Currency=USD&BillingStreetAddressLineOne=Address+1&BillingStreetAddressLineFour=Address+2&BillingPostalCode=ZIP123", + @gateway.send(:post_data, 'cc_preauth', { + 'CardNumber' => @credit_card.number, + 'Expiry' => @gateway.send(:expdate, @credit_card), + 'CVV2' => @credit_card.verification_value, + 'Currency' => 'USD', + 'BillingStreetAddressLineOne' => 'Address 1', + 'BillingStreetAddressLineFour' => 'Address 2', + 'BillingPostalCode' => 'ZIP123' + }) + end + + def test_signature_for_cc_preauth_action + assert_equal 'd5ff154d6631333c21d0c78975b3bf5d9ccd0ef8', @gateway.send(:signature, 'cc_preauth', { + 'UserName' => 'user', + 'Timestamp' => '2013-10-08T14:31:54.Z', + 'RequestID' => 'wouykiikdvqbwwxueppby', + 'PymtType' => 'cc_preauth' + }, { + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' + }) + end + + def test_signature_for_cc_settle_action + assert_equal 'c80cccf6c77438785726b5a447d5aed84738c6d1', @gateway.send(:signature, 'cc_settle', { + 'UserName' => 'user', + 'Timestamp' => '2013-10-08T14:31:54.Z', + 'RequestID' => 'wouykiikdvqbwwxueppby', + 'PymtType' => 'cc_settle' + }, { + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' + }) + end + + def test_signature_for_cc_debit_action + assert_equal 'b2a0eb307cfd092152d44b06a49a360feccdb1b9', @gateway.send(:signature, 'cc_debit', { + 'UserName' => 'user', + 'Timestamp' => '2013-10-08T14:31:54.Z', + 'RequestID' => 'wouykiikdvqbwwxueppby', + 'PymtType' => 'cc_debit' + }, { + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' + }) + end + + def test_signature_for_cc_refund_action + assert_equal '9b174f1ebf5763e4793a52027645ff5156fca2e3', @gateway.send(:signature, 'cc_refund', { + 'UserName' => 'user', + 'Timestamp' => '2013-10-08T14:31:54.Z', + 'RequestID' => 'wouykiikdvqbwwxueppby', + 'PymtType' => 'cc_refund' + }, { + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' + }) + end + + def test_signature_for_void_action + assert_equal '236d4a857ee2e8cfec851be250159367d2c7c52e', @gateway.send(:signature, 'void', { + 'UserName' => 'user', + 'Timestamp' => '2013-10-08T14:31:54.Z', + 'RequestID' => 'wouykiikdvqbwwxueppby' + }, { + 'Amount' => 100, + 'Currency' => 'USD', + 'TrackingNumber' => '123456789' + }) + end + + def test_expdate + @credit_card.year = 2015 + @credit_card.month = 9 + assert_equal "0915", @gateway.send(:expdate, @credit_card) + end + + private + + def failed_void_response + %w( + ApprovalCode= + ErrorCode=error:canNotBeVoided + Message=Error+processing+transaction+because+the+payment+may+not+be+voided + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + ).join('&') + end + + def successful_authorization_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + ).join('&') + end + + def successful_purchase_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + ).join('&') + end + + def successful_capture_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + ).join('&') + end + + def invalid_preauth_number_response + %w( + ApprovalCode= + ErrorCode=invalid:PreAuthNumber + Message=Error+processing+transaction+because+the+pre-auth+number + RequestNumber=603758541 + RequestResult=ok + Status=Invalid:PreauthNumber + TrackingNumber=123456789 + ).join('&') + end + + def insufficient_preauth_amount_response + %w( + ApprovalCode= + ErrorCode=rejected:PreauthAmountInsufficient + Message=Invalid+because+the+preauthorization+amount+100+is+insufficient + RequestNumber=603758541 + RequestResult=ok + Status=Rejected:PreauthAmountInsufficient + TrackingNumber=123456789 + ).join('&') + end + + def invalid_credit_card_response + %w( + ApprovalCode= + ErrorCode=invalid:cardNumber + Message=Error+processing+transaction+because+CardNumber+is+not+between+12+and+19+in+length + RequestNumber=603758541 + RequestResult=ok + Status=Invalid:CardNumber + TrackingNumber=123456789 + ).join('&') + end + + def expired_credit_card_response + %w( + ApprovalCode= + ErrorCode=invalid:CustomerCardExpiryDate + Message=Invalid+because+the+card+expiry+date+is+not+a+date+in+the+future + RequestNumber=603758541 + RequestResult=ok + Status=Invalid:CustomerCardExpiryDate + TrackingNumber=123456789 + ).join('&') + end + + def declined_purchese_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Declined + TrackingNumber=123456789 + ).join('&') + end + + def successful_refund_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Approved + TrackingNumber=123456789 + ).join('&') + end + + def amount_greater_than_original_amount_refund_response + %w( + ApprovalCode= + ErrorCode=invalid:RefundAmountGreaterThanOriginalAmount + Message=Invalid+because+the+payment+amount+cannot+be+greater+than+the+original+charge + RequestNumber=603758541 + RequestResult=ok + Status=Invalid:RefundAmountGreaterThanOriginalAmount + TrackingNumber=123456789 + ).join('&') + end + + def successful_void_response + %w( + ApprovalCode=123456 + ErrorCode= + Message= + RequestNumber=603758541 + RequestResult=ok + Status=Voided + TrackingNumber=123456789 + ).join('&') + end +end From e5b194bf1c525cc3e3c35b02038663dca5681323 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Mon, 11 Nov 2013 19:04:58 -0500 Subject: [PATCH 043/104] parse posData as JSON string, and handle JSON ParseErrors --- .../billing/integrations/bit_pay/helper.rb | 1 + .../integrations/bit_pay/notification.rb | 9 ++++--- .../bit_pay_notification_test.rb | 27 +++++++++++++++---- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lib/active_merchant/billing/integrations/bit_pay/helper.rb b/lib/active_merchant/billing/integrations/bit_pay/helper.rb index 91b3b585f07..1861738dbd5 100644 --- a/lib/active_merchant/billing/integrations/bit_pay/helper.rb +++ b/lib/active_merchant/billing/integrations/bit_pay/helper.rb @@ -55,6 +55,7 @@ def create_invoice response = http.request(request) JSON.parse(response.body) + rescue JSON::ParseError end end end diff --git a/lib/active_merchant/billing/integrations/bit_pay/notification.rb b/lib/active_merchant/billing/integrations/bit_pay/notification.rb index d47d44528ef..55ca0c4f009 100644 --- a/lib/active_merchant/billing/integrations/bit_pay/notification.rb +++ b/lib/active_merchant/billing/integrations/bit_pay/notification.rb @@ -14,7 +14,8 @@ def transaction_id end def item_id - params['posData']['orderId'] + JSON.parse(params['posData'])['orderId'] + rescue JSON::ParserError end def status @@ -57,13 +58,15 @@ def acknowledge(authcode = nil) parse(response.body) retrieved_json = JSON.parse(@raw).tap { |j| j.delete('currentTime') } - raise StandardError.new("Faulty BitPay result: #{response.body}") unless posted_json == retrieved_json - true + posted_json == retrieved_json + rescue JSON::ParserError end + private def parse(body) @raw = body @params = JSON.parse(@raw) + rescue JSON::ParserError end end end diff --git a/test/unit/integrations/notifications/bit_pay_notification_test.rb b/test/unit/integrations/notifications/bit_pay_notification_test.rb index fb59f37d908..6e243ee4cb0 100644 --- a/test/unit/integrations/notifications/bit_pay_notification_test.rb +++ b/test/unit/integrations/notifications/bit_pay_notification_test.rb @@ -14,6 +14,21 @@ def test_accessors assert_equal 10.00, @bit_pay.gross assert_equal "USD", @bit_pay.currency assert_equal 1370539476654, @bit_pay.received_at + assert_equal 123, @bit_pay.item_id + end + + def test_invalid_data + hash = JSON.parse(http_raw_data) + @bit_pay = BitPay::Notification.new('{"invalid":json}') + + assert @bit_pay.params.empty? + end + + def test_item_id_invalid_json + hash = JSON.parse(http_raw_data) + @bit_pay = BitPay::Notification.new(hash.merge('posData' => 'Invalid JSON').to_json) + + assert_nil @bit_pay.item_id end def test_compositions @@ -27,10 +42,12 @@ def test_successful_acknowledgement def test_failed_acknowledgement Net::HTTP.any_instance.expects(:request).returns(stub(:body => '{"error":"Doesnt match"}')) - exception = assert_raise StandardError do - @bit_pay.acknowledge - end - assert_equal 'Faulty BitPay result: {"error":"Doesnt match"}', exception.message + assert_nil @bit_pay.acknowledge + end + + def test_failed_acknowledgement + Net::HTTP.any_instance.expects(:request).returns(stub(:body => '{invalid json')) + assert_nil @bit_pay.acknowledge end private @@ -46,7 +63,7 @@ def http_raw_data "invoiceTime"=>"1370539476654", "expirationTime"=>"1370540376654", "currentTime"=>"1370539573956", - "posData" => "custom" + "posData" => '{"orderId":123}' }.to_json end end From 1a43b84c6e52055c3b2cf93249aabb34f9abf2f0 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Mon, 11 Nov 2013 19:50:15 -0500 Subject: [PATCH 044/104] Fix incorrect notification definition which should allow for options to be passed --- lib/active_merchant/billing/integrations/bit_pay.rb | 4 ++-- .../billing/integrations/bit_pay/notification.rb | 1 - test/unit/integrations/bit_pay_module_test.rb | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/active_merchant/billing/integrations/bit_pay.rb b/lib/active_merchant/billing/integrations/bit_pay.rb index 05609b7679a..f6bd1a21117 100644 --- a/lib/active_merchant/billing/integrations/bit_pay.rb +++ b/lib/active_merchant/billing/integrations/bit_pay.rb @@ -13,8 +13,8 @@ module BitPay mattr_accessor :invoicing_url self.invoicing_url = 'https://bitpay.com/api/invoice' - def self.notification(post) - Notification.new(post) + def self.notification(post, options = {}) + Notification.new(post, options) end def self.helper(order, account, options = {}) diff --git a/lib/active_merchant/billing/integrations/bit_pay/notification.rb b/lib/active_merchant/billing/integrations/bit_pay/notification.rb index 55ca0c4f009..1f81bd64b08 100644 --- a/lib/active_merchant/billing/integrations/bit_pay/notification.rb +++ b/lib/active_merchant/billing/integrations/bit_pay/notification.rb @@ -47,7 +47,6 @@ def acknowledge(authcode = nil) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true - http.set_debug_output($stdout) request = Net::HTTP::Get.new(uri.path) request.basic_auth @options[:credential1], '' diff --git a/test/unit/integrations/bit_pay_module_test.rb b/test/unit/integrations/bit_pay_module_test.rb index 7422ad8e0b2..8cd7f204788 100644 --- a/test/unit/integrations/bit_pay_module_test.rb +++ b/test/unit/integrations/bit_pay_module_test.rb @@ -4,7 +4,7 @@ class BitPayModuleTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def test_notification_method - assert_instance_of BitPay::Notification, BitPay.notification('{"name":"cody"}') + assert_instance_of BitPay::Notification, BitPay.notification('{"name":"cody"}', {}) end def test_return_method From 788e4048e136bd4b83c1ccd67fcea526650f098f Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Mon, 11 Nov 2013 21:19:47 -0500 Subject: [PATCH 045/104] Fix status mapping --- .../billing/integrations/bit_pay/notification.rb | 4 ++-- .../integrations/notifications/bit_pay_notification_test.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/active_merchant/billing/integrations/bit_pay/notification.rb b/lib/active_merchant/billing/integrations/bit_pay/notification.rb index 1f81bd64b08..e3e3e7d7999 100644 --- a/lib/active_merchant/billing/integrations/bit_pay/notification.rb +++ b/lib/active_merchant/billing/integrations/bit_pay/notification.rb @@ -21,9 +21,9 @@ def item_id def status case params['status'] when 'complete' - 'Pending' - when 'confirmed' 'Completed' + when 'confirmed' + 'Pending' when 'invalid' 'Failed' end diff --git a/test/unit/integrations/notifications/bit_pay_notification_test.rb b/test/unit/integrations/notifications/bit_pay_notification_test.rb index 6e243ee4cb0..b7b94f964fa 100644 --- a/test/unit/integrations/notifications/bit_pay_notification_test.rb +++ b/test/unit/integrations/notifications/bit_pay_notification_test.rb @@ -56,7 +56,7 @@ def http_raw_data "id"=>"98kui1gJ7FocK41gUaBZxG", "orderID"=>"123", "url"=>"https://bitpay.com/invoice/98kui1gJ7FocK41gUaBZxG", - "status"=>"confirmed", + "status"=>"complete", "btcPrice"=>"0.0295", "price"=>"10.00", "currency"=>"USD", From 0198064dedd5a340a058f114c707acf3104c378d Mon Sep 17 00:00:00 2001 From: Zamith Date: Tue, 12 Nov 2013 14:40:31 +0000 Subject: [PATCH 046/104] Refactor Paymill gateway * Removes duplication * Extracts complex method to class Closes #824. --- .../billing/gateways/paymill.rb | 88 +++++++++++++------ 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/lib/active_merchant/billing/gateways/paymill.rb b/lib/active_merchant/billing/gateways/paymill.rb index 7d1c816a18f..06139864b97 100644 --- a/lib/active_merchant/billing/gateways/paymill.rb +++ b/lib/active_merchant/billing/gateways/paymill.rb @@ -17,27 +17,11 @@ def initialize(options = {}) end def purchase(money, payment_method, options = {}) - case payment_method - when String - purchase_with_token(money, payment_method, options) - else - MultiResponse.run do |r| - r.process { save_card(payment_method) } - r.process { purchase_with_token(money, r.authorization, options) } - end - end + action_with_token(:purchase, money, payment_method, options) end def authorize(money, payment_method, options = {}) - case payment_method - when String - authorize_with_token(money, payment_method, options) - else - MultiResponse.run do |r| - r.process { save_card(payment_method) } - r.process { authorize_with_token(money, r.authorization, options) } - end - end + action_with_token(:authorize, money, payment_method, options) end def capture(money, authorization, options = {}) @@ -110,6 +94,18 @@ def authorization_from(parsed_response) ].join(";") end + def action_with_token(action, money, payment_method, options) + case payment_method + when String + self.send("#{action}_with_token", money, payment_method, options) + else + MultiResponse.run do |r| + r.process { save_card(payment_method) } + r.process { self.send("#{action}_with_token", money, r.authorization, options) } + end + end + end + def purchase_with_token(money, card_token, options) post = {} @@ -147,17 +143,12 @@ def save_card(credit_card) def response_for_save_from(raw_response) options = { :test => test? } - parsed = JSON.parse(raw_response.sub(/jsonPFunction\(/, '').sub(/\)\z/, '')) - if parsed['error'] - succeeded = false - message = parsed['error']['message'] - else - succeeded = parsed['transaction']['processing']['result'] == 'ACK' - message = parsed['transaction']['processing']['return']['message'] - options[:authorization] = parsed['transaction']['identification']['uniqueId'] if succeeded - end + parser = ResponseParser.new(raw_response, options) + parser.generate_response + end - Response.new(succeeded, message, parsed, options) + def parse_reponse(response) + JSON.parse(response.sub(/jsonPFunction\(/, '').sub(/\)\z/, '')) end def save_card_url @@ -183,6 +174,47 @@ def preauth(authorization) def transaction_id(authorization) authorization.split(';').first end + + class ResponseParser + def initialize(raw_response="", options={}) + @raw_response = raw_response + @options = options + end + + def generate_response + parse_response + if parsed['error'] + handle_response_parse_error + else + handle_response_correct_parsing + end + + Response.new(succeeded, message, parsed, options) + end + + private + attr_reader :raw_response, :parsed, :succeeded, :message, :options + + def parse_response + @parsed = JSON.parse(raw_response.sub(/jsonPFunction\(/, '').sub(/\)\z/, '')) + end + + def handle_response_parse_error + @succeeded = false + @message = parsed['error']['message'] + end + + def handle_response_correct_parsing + @message = parsed['transaction']['processing']['return']['message'] + if @succeeded = is_ack? + @options[:authorization] = parsed['transaction']['identification']['uniqueId'] + end + end + + def is_ack? + parsed['transaction']['processing']['result'] == 'ACK' + end + end end end end From d46485d5b860a6cad6c4c5983a20dad76b24188f Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Tue, 12 Nov 2013 15:09:25 -0500 Subject: [PATCH 047/104] Update changelog to include BitPay fixes --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 8a25f68a2da..14b5e24e4cc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ * Stripe: Add the option to pass a version header [odorcicd] * Elavon: Update supported countries [duff] * Add Raven PacNet gateway [llopez] +* BitPay: Fix BitPay issues and implement Notification#acknowledge [odorcicd] == Version 1.41.0 (October 24th, 2013) From 550d64deddfaac88794d93796ff9af598aaa98a3 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Tue, 12 Nov 2013 20:39:39 -0500 Subject: [PATCH 048/104] Bump version to 1.42.0 --- CHANGELOG | 3 ++- lib/active_merchant/version.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 14b5e24e4cc..0b8445023e7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ = ActiveMerchant CHANGELOG +== Version 1.42.0 (November 12th, 2013) + * Fix NoMethodError "tr" for params with dash [TimothyKlim] * Authorize.Net: Add cardholder authentication options (CAVV) support [structure] * CardStreamModern: Added better checks on inputs from the gateway [ExxKA] @@ -1283,4 +1285,3 @@ value [jduff] * Credit card validation methods as static methods of the credit card object == PlanetArgon fork for integrating Merchant eSolutions gateway - diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb index 08b87931626..99512682ab7 100644 --- a/lib/active_merchant/version.rb +++ b/lib/active_merchant/version.rb @@ -1,3 +1,3 @@ module ActiveMerchant - VERSION = "1.41.0" + VERSION = "1.42.0" end From b0537735f9c077ebf266baec87943ce758f4e52f Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Tue, 12 Nov 2013 22:10:07 -0500 Subject: [PATCH 049/104] Better rake task for gem release --- .gitignore | 1 + Rakefile | 41 +++++++++++++++++------------------------ 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 657f2298ada..806ae574dd2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ pkg Gemfile.lock Gemfile*.lock .rbx/ +*.gem \ No newline at end of file diff --git a/Rakefile b/Rakefile index 5124db49d7f..4cfeb8f4461 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,5 @@ $:.unshift File.expand_path('../lib', __FILE__) +require 'active_merchant/version' begin require 'bundler' @@ -10,14 +11,28 @@ end require 'rake' require 'rake/testtask' -require 'rubygems/package_task' require 'support/gateway_support' require 'support/ssl_verify' require 'support/outbound_hosts' +task :gem => :build +task :build do + system "gem build activemerchant.gemspec" +end + +task :install => :build do + system "gem install activemerchant-#{ActiveMerchant::VERSION}.gem" +end + +task :release => :build do + system "git tag -a v#{ActiveMerchant::VERSION} -m 'Tagging #{ActiveMerchant::VERSION}'" + system "git push --tags" + system "gem push activemerchant-#{ActiveMerchant::VERSION}.gem" + system "rm activemerchant-#{ActiveMerchant::VERSION}.gem" +end + desc "Run the unit test suite" task :default => 'test:units' - task :test => 'test:units' namespace :test do @@ -35,28 +50,6 @@ namespace :test do t.libs << 'test' t.verbose = true end - -end - -desc "Delete tar.gz / zip" -task :cleanup => [ :clobber_package ] - -spec = eval(File.read('activemerchant.gemspec')) - -Gem::PackageTask.new(spec) do |p| - p.gem_spec = spec - p.need_tar = true - p.need_zip = true -end - -desc "Release the gems and docs to RubyForge" -task :release => [ 'gemcutter:publish' ] - -namespace :gemcutter do - desc "Publish to gemcutter" - task :publish => :package do - sh "gem push pkg/activemerchant-#{ActiveMerchant::VERSION}.gem" - end end namespace :gateways do From 42d9d18f125e9aff444aea364f02794ecfd34f92 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 13 Nov 2013 12:37:29 -0500 Subject: [PATCH 050/104] Update date on 1.42.0 release --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 0b8445023e7..68a2fe21d5c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ = ActiveMerchant CHANGELOG -== Version 1.42.0 (November 12th, 2013) +== Version 1.42.0 (November 13th, 2013) * Fix NoMethodError "tr" for params with dash [TimothyKlim] * Authorize.Net: Add cardholder authentication options (CAVV) support [structure] From 5c8d8f97d90171d720df63cabdf8ad77394a89ee Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Wed, 13 Nov 2013 13:31:49 -0500 Subject: [PATCH 051/104] Bump for version 1.42.1 --- CHANGELOG | 4 ++++ lib/active_merchant/version.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 68a2fe21d5c..e54d996a6ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ = ActiveMerchant CHANGELOG +== Version 1.42.1 (November 13th, 2013) + +* Signed version of 1.42.0 + == Version 1.42.0 (November 13th, 2013) * Fix NoMethodError "tr" for params with dash [TimothyKlim] diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb index 99512682ab7..d9e258460cb 100644 --- a/lib/active_merchant/version.rb +++ b/lib/active_merchant/version.rb @@ -1,3 +1,3 @@ module ActiveMerchant - VERSION = "1.42.0" + VERSION = "1.42.1" end From ce7bff5786d968da0e2ab27319698a351b587daf Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 13 Nov 2013 15:31:25 -0500 Subject: [PATCH 052/104] New updated public_cert --- gem-public_cert.pem | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/gem-public_cert.pem b/gem-public_cert.pem index c2588d5c296..7d87bf88dbd 100644 --- a/gem-public_cert.pem +++ b/gem-public_cert.pem @@ -1,7 +1,7 @@ -----BEGIN CERTIFICATE----- -MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDApjb2R5 +MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDApjb2R5 ZmF1c2VyMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj -b20wHhcNMDcwMjIyMTcyMTI3WhcNMDgwMjIyMTcyMTI3WjBBMRMwEQYDVQQDDApj +b20wHhcNMTMxMTEzMTk1NjE2WhcNMTQxMTEzMTk1NjE2WjBBMRMwEQYDVQQDDApj b2R5ZmF1c2VyMRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6T4Iqt5iWvAlU iXI6L8UO0URQhIC65X/gJ9hL/x4lwSl/ckVm/R/bPrJGmifT+YooFv824N3y/TIX @@ -9,12 +9,13 @@ iXI6L8UO0URQhIC65X/gJ9hL/x4lwSl/ckVm/R/bPrJGmifT+YooFv824N3y/TIX O3wqEjvW2L6VMozVfK1MfjL9IGgy0rCnl+2g4Gh4jDDpkLfnMG5CWI6cTCf3C1ye ytOpWgi0XpOEy8nQWcFmt/KCQ/kFfzBo4QxqJi54b80842EyvzWT9OB7Oew/CXZG F2yIHtiYxonz6N09vvSzq4CvEuisoUFLKZnktndxMEBKwJU3XeSHAbuS7ix40OKO -WKuI54fHAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW -BBR9QQpefI3oDCAxiqJW/3Gg6jI6qjANBgkqhkiG9w0BAQUFAAOCAQEAs0lX26O+ -HpyMp7WL+SgZuM8k76AjfOHuKajl2GEn3S8pWYGpsa0xu07HtehJhKLiavrfUYeE -qlFtyYMUyOh6/1S2vfkH6VqjX7mWjoi7XKHW/99fkMS40B5SbN+ypAUst+6c5R84 -w390mjtLHpdDE6WQYhS6bFvBN53vK6jG3DLyCJc0K9uMQ7gdHWoxq7RnG92ncQpT -ThpRA+fky5Xt2Q63YJDnJpkYAz79QIama1enSnd4jslKzSl89JS2luq/zioPe/Us -hbyalWR1+HrhgPoSPq7nk+s2FQUBJ9UZFK1lgMzho/4fZgzJwbu+cO8SNuaLS/bj -hPaSTyVU0yCSnw== +WKuI54fHAgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW +BBR9QQpefI3oDCAxiqJW/3Gg6jI6qjAfBgNVHREEGDAWgRRjb2R5ZmF1c2VyQGdt +YWlsLmNvbTAfBgNVHRIEGDAWgRRjb2R5ZmF1c2VyQGdtYWlsLmNvbTANBgkqhkiG +9w0BAQUFAAOCAQEAYJgMj+RlvKSOcks29P76WE+Lexvq3eZBDxxgFHatACdq5Fis +MUEGiiHeLkR1KRTkvkXCos6CtZVUBVUsftueHmKA7adO2yhrjv4YhPTb/WZxWmQC +L59lMhnp9UcFJ0H7TkAiU1TvvXewdQPseX8Ayl0zRwD70RfhGh0LfFsKN0JGR4ZS +yZvtu7hS26h9KwIyo5N3nw7cKSLzT7KsV+s1C+rTjVCb3/JJA9yOe/SCj/Xyc+JW +ZJB9YPQZG+vWBdDSca3sUMtvFxpLUFwdKF5APSPOVnhbFJ3vSXY1ulP/R6XW9vnw +6kkQi2fHhU20ugMzp881Eixr+TjC0RvUerLG7g== -----END CERTIFICATE----- From 0229a5962c9ab019befaf6fe5aaf440a018aee84 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 13 Nov 2013 15:33:29 -0500 Subject: [PATCH 053/104] Version bump --- lib/active_merchant/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb index d9e258460cb..69eda89b8a7 100644 --- a/lib/active_merchant/version.rb +++ b/lib/active_merchant/version.rb @@ -1,3 +1,3 @@ module ActiveMerchant - VERSION = "1.42.1" + VERSION = "1.42.2" end From fb5ccf1ba5e969c3a85018dee21641fe69c63651 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 13 Nov 2013 15:55:54 -0500 Subject: [PATCH 054/104] Require private key to build gem --- Rakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Rakefile b/Rakefile index 4cfeb8f4461..fa64d35f8ea 100644 --- a/Rakefile +++ b/Rakefile @@ -17,6 +17,7 @@ require 'support/outbound_hosts' task :gem => :build task :build do + raise "Please set a private key to sign the gem" unless ENV['GEM_PRIVATE_KEY'] system "gem build activemerchant.gemspec" end From 3c4566f3e1e86f5375f77e6af0f33e496a408dea Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 13 Nov 2013 15:57:06 -0500 Subject: [PATCH 055/104] Update RELEASING file --- RELEASING | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/RELEASING b/RELEASING index a22f2819530..cae01fe9c04 100644 --- a/RELEASING +++ b/RELEASING @@ -4,12 +4,5 @@ releasing Active Merchant 2. Check the Semantic Versioning page for info on how to version the new release: http://semver.org 3. Update the version of Active Merchant in lib/active_merchant/version.rb and in activemerchant.gemspec 4. Add a CHANGELOG entry for the new release with the date -5. Commit the changes with a commit message like "Packaging for release X.Y.Z" -6. Tag the release with the version (Leave REV blank for HEAD or provide a SHA) - $ git tag vX.Y.Z REV -7. Push out the changes - $ git push -8. Push out the tags - $ git push --tags -9. Publish the Gem to gemcutter - $ rake release GEM_PRIVATE_KEY=/path/to/private/key \ No newline at end of file +5. Release the gem to rubygems + $ GEM_PRIVATE_KEY=/path/to/private/key bundle exec rake release \ No newline at end of file From 2d6e460d7453e7e5e0f6eb0e12091bbdb36cedec Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Wed, 13 Nov 2013 16:01:00 -0500 Subject: [PATCH 056/104] Update Changelog --- CHANGELOG | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e54d996a6ce..bc02c9d6e00 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ = ActiveMerchant CHANGELOG +== Version 1.42.2 (November 13th, 2013) + +* Renew public certificate + == Version 1.42.1 (November 13th, 2013) * Signed version of 1.42.0 From a6ba2e945909450361e5e52c56c4e739dbd108d6 Mon Sep 17 00:00:00 2001 From: Duff OMelia Date: Thu, 14 Nov 2013 09:50:38 -0500 Subject: [PATCH 057/104] Balanced: Pass on appears_on_statement_as If it's specified, pass it to Balanced so that the descriptor on the customer's statement can be customized. Closes #916. --- CHANGELOG | 2 ++ lib/active_merchant/billing/gateways/balanced.rb | 9 +++++++-- test/remote/gateways/remote_balanced_test.rb | 8 ++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bc02c9d6e00..063cbaa3ff0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ = ActiveMerchant CHANGELOG +* Balanced: Add support for appears_on_statement_as [duff] + == Version 1.42.2 (November 13th, 2013) * Renew public certificate diff --git a/lib/active_merchant/billing/gateways/balanced.rb b/lib/active_merchant/billing/gateways/balanced.rb index 83dfa06677d..89496890c49 100644 --- a/lib/active_merchant/billing/gateways/balanced.rb +++ b/lib/active_merchant/billing/gateways/balanced.rb @@ -131,6 +131,7 @@ def authorize(money, credit_card, options = {}) post = {} post[:amount] = money post[:description] = options[:description] + post[:appears_on_statement_as] = options[:appears_on_statement_as] if options[:appears_on_statement_as] create_or_find_account(post, options) add_credit_card(post, credit_card, options) @@ -168,6 +169,7 @@ def purchase(money, credit_card, options = {}) post = {} post[:amount] = money post[:description] = options[:description] + post[:appears_on_statement_as] = options[:appears_on_statement_as] if options[:appears_on_statement_as] create_or_find_account(post, options) add_credit_card(post, credit_card, options) @@ -197,6 +199,7 @@ def capture(money, authorization, options = {}) post[:hold_uri] = authorization post[:amount] = money if money post[:description] = options[:description] if options[:description] + post[:appears_on_statement_as] = options[:appears_on_statement_as] if options[:appears_on_statement_as] post[:on_behalf_of_uri] = options[:on_behalf_of_uri] if options[:on_behalf_of_uri] create_transaction(:post, @debits_uri, post) @@ -213,6 +216,7 @@ def capture(money, authorization, options = {}) def void(authorization, options = {}) post = {} post[:is_void] = true + post[:appears_on_statement_as] = options[:appears_on_statement_as] if options[:appears_on_statement_as] create_transaction(:put, authorization, post) rescue Error => ex @@ -246,6 +250,7 @@ def refund(amount, debit_uri = "deprecated", options = {}) post[:debit_uri] = debit_uri post[:amount] = amount post[:description] = options[:description] + post[:appears_on_statement_as] = options[:appears_on_statement_as] if options[:appears_on_statement_as] create_transaction(:post, @refunds_uri, post) rescue Error => ex failed_response(ex.response) @@ -265,12 +270,12 @@ def store(credit_card, options = {}) else card_uri = associate_card_to_account(account_uri, credit_card) end - + is_test = false if @marketplace_uri is_test = (@marketplace_uri.index("TEST") ? true : false) end - + Response.new(true, "Card stored", {}, :test => is_test, :authorization => [card_uri, account_uri].compact.join(';')) rescue Error => ex failed_response(ex.response) diff --git a/test/remote/gateways/remote_balanced_test.rb b/test/remote/gateways/remote_balanced_test.rb index 1ea8135dcf3..61a4cf135a8 100644 --- a/test/remote/gateways/remote_balanced_test.rb +++ b/test/remote/gateways/remote_balanced_test.rb @@ -42,6 +42,14 @@ def test_unsuccessful_purchase assert_match /Account Frozen/, response.message end + def test_passing_appears_on_statement + options = @options.merge(appears_on_statement_as: "Homer Electric") + assert response = @gateway.purchase(@amount, @credit_card, options) + + assert_success response + assert_equal "Homer Electric", response.params['appears_on_statement_as'] + end + def test_authorize_and_capture amount = @amount assert auth = @gateway.authorize(amount, @credit_card, @options) From 8094c68bc95a3d98c479697165c150f3e7db1710 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Thu, 14 Nov 2013 13:11:14 -0500 Subject: [PATCH 058/104] Authorize.Net: Make actioned responses failures This adds 310/311 (already voided, already captured) response code checks as part of #success? Closes #917. --- CHANGELOG | 1 + .../billing/gateways/authorize_net.rb | 3 ++- test/unit/gateways/authorize_net_test.rb | 12 ++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 063cbaa3ff0..6ce46047429 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ = ActiveMerchant CHANGELOG * Balanced: Add support for appears_on_statement_as [duff] +* Authorize.Net: Make already actioned responses failures [odorcicd] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/authorize_net.rb b/lib/active_merchant/billing/gateways/authorize_net.rb index 5abc1a41afa..25942c2b23f 100644 --- a/lib/active_merchant/billing/gateways/authorize_net.rb +++ b/lib/active_merchant/billing/gateways/authorize_net.rb @@ -51,6 +51,7 @@ class AuthorizeNetGateway < Gateway CARD_CODE_ERRORS = %w( N S ) AVS_ERRORS = %w( A E N R W Z ) AVS_REASON_CODES = %w(27 45) + TRANSACTION_ALREADY_ACTIONED = %w(310 311) AUTHORIZE_NET_ARB_NAMESPACE = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd' @@ -294,7 +295,7 @@ def commit(action, money, parameters) end def success?(response) - response[:response_code] == APPROVED + response[:response_code] == APPROVED && TRANSACTION_ALREADY_ACTIONED.exclude?(response[:response_reason_code]) end def fraud_review?(response) diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/gateways/authorize_net_test.rb index c03ba16add4..61b462bb675 100644 --- a/test/unit/gateways/authorize_net_test.rb +++ b/test/unit/gateways/authorize_net_test.rb @@ -101,6 +101,14 @@ def test_failed_authorization assert_equal '508141794', response.authorization end + def test_failed_already_actioned_capture + @gateway.expects(:ssl_post).returns(already_actioned_capture_response) + + assert response = @gateway.capture(50, '123456789') + assert_instance_of Response, response + assert_failure response + end + def test_add_address_outsite_north_america result = {} @@ -431,6 +439,10 @@ def failed_authorization_response '$2$,$1$,$1$,$This transaction was declined.$,$advE7f$,$Y$,$508141794$,$5b3fe66005f3da0ebe51$,$$,$1.00$,$CC$,$auth_only$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$2860A297E0FE804BCB9EF8738599645C$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' end + def already_actioned_capture_response + '$1$,$2$,$311$,$This transaction has already been captured.$,$$,$P$,$0$,$$,$$,$1.00$,$CC$,$credit$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$39265D8BA0CDD4F045B5F4129B2AAA01$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$' + end + def fraud_review_response "$4$,$$,$253$,$Thank you! For security reasons your order is currently being reviewed.$,$$,$X$,$0$,$$,$$,$1.00$,$$,$auth_capture$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$207BCBBF78E85CF174C87AE286B472D2$,$M$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$" end From f12331314f141c3bd28db58a05e010ae36758ff3 Mon Sep 17 00:00:00 2001 From: Tom Davies Date: Fri, 1 Nov 2013 09:47:55 -0400 Subject: [PATCH 059/104] Add Payex gateway http://payex.com/ Closes #901. --- CHANGELOG | 1 + CONTRIBUTORS | 4 + README.md | 1 + lib/active_merchant/billing/gateways/payex.rb | 402 ++++++++++++++++++ test/fixtures.yml | 5 + test/remote/gateways/remote_payex_test.rb | 120 ++++++ test/unit/gateways/payex_test.rb | 284 +++++++++++++ 7 files changed, 817 insertions(+) create mode 100644 lib/active_merchant/billing/gateways/payex.rb create mode 100644 test/remote/gateways/remote_payex_test.rb create mode 100644 test/unit/gateways/payex_test.rb diff --git a/CHANGELOG b/CHANGELOG index 6ce46047429..7eb2a120a81 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ * Balanced: Add support for appears_on_statement_as [duff] * Authorize.Net: Make already actioned responses failures [odorcicd] +* Add Payex gateway [atomgiant] == Version 1.42.2 (November 13th, 2013) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 75798bd8087..fea9c97ffa8 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -436,3 +436,7 @@ Swipe Checkout (November 2013) Raven PacNet (November 2013) * Luis Lopez (llopez) + +Payex (November 2013) + +* Tom Davies (atomgiant) diff --git a/README.md b/README.md index 776649e982e..6cfa9628172 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta * [PayJunction](http://www.payjunction.com/) - US * [PaySecure](http://www.commsecure.com.au/paysecure.shtml) - AU * [Paybox Direct](http://www.paybox.com/) - FR +* [Payex](http://payex.com/) - DK, NO, SE * [PaymentExpress](http://www.paymentexpress.com/) - AU, CA, DE, ES, FR, GB, HK, IE, MY, NL, NZ, SG, US, ZA * [PAYMILL](https://paymill.com) - AD, AT, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HU, IE, IL, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK, TR, VA * [PayPal Express Checkout](https://www.paypal.com/webapps/mpp/express-checkout) - US, CA, SG, AU diff --git a/lib/active_merchant/billing/gateways/payex.rb b/lib/active_merchant/billing/gateways/payex.rb new file mode 100644 index 00000000000..0aaf0f33bec --- /dev/null +++ b/lib/active_merchant/billing/gateways/payex.rb @@ -0,0 +1,402 @@ +require "nokogiri" +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayexGateway < Gateway + self.live_url = 'https://external.payex.com/' + self.test_url = 'https://test-external.payex.com/' + + self.money_format = :cents + self.supported_countries = ['SE', 'NO', 'DK'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.homepage_url = 'http://payex.com/' + self.display_name = 'Payex' + self.default_currency = "EUR" + + # NOTE: the PurchaseCC uses a different url for test transactions + TEST_CONFINED_URL = 'https://test-confined.payex.com/' + + TRANSACTION_STATUS = { + sale: '0', + initialize: '1', + credit: '2', + authorize: '3', + cancel: '4', + failure: '5', + capture: '6', + } + + SOAP_ACTIONS = { + initialize: { name: 'Initialize8', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, + purchasecc: { name: 'PurchaseCC', url: 'pxconfined/pxorder.asmx', xmlns: 'http://confined.payex.com/PxOrder/', confined: true}, + cancel: { name: 'Cancel2', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, + capture: { name: 'Capture5', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, + credit: { name: 'Credit5', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' }, + create_agreement: { name: 'CreateAgreement3', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' }, + delete_agreement: { name: 'DeleteAgreement', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' }, + autopay: { name: 'AutoPay3', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' }, + } + + def initialize(options = {}) + requires!(options, :account, :encryption_key) + super + end + + # Public: Send an authorize Payex request + # + # amount - The monetary amount of the transaction in cents. + # payment_method - The Active Merchant payment method or the +store+ authorization for stored transactions. + # options - A standard ActiveMerchant options hash: + # :currency - Three letter currency code for the transaction (default: "EUR") + # :order_id - The unique order ID for this transaction (required). + # :product_number - The merchant product number (default: '1'). + # :description - The merchant description for this product (default: The :order_id). + # :ip - The client IP address (default: '127.0.0.1'). + # :vat - The vat amount (optional). + # + # Returns an ActiveMerchant::Billing::Response object + def authorize(amount, payment_method, options = {}) + requires!(options, :order_id) + amount = amount(amount) + if payment_method.respond_to?(:number) + # credit card authorization + MultiResponse.new.tap do |r| + r.process {send_initialize(amount, true, options)} + r.process {send_purchasecc(payment_method, r.params['orderref'])} + end + else + # stored authorization + send_autopay(amount, payment_method, true, options) + end + + end + + # Public: Send a purchase Payex request + # + # amount - The monetary amount of the transaction in cents. + # payment_method - The Active Merchant payment method or the +store+ authorization for stored transactions. + # options - A standard ActiveMerchant options hash: + # :currency - Three letter currency code for the transaction (default: "EUR") + # :order_id - The unique order ID for this transaction (required). + # :product_number - The merchant product number (default: '1'). + # :description - The merchant description for this product (default: The :order_id). + # :ip - The client IP address (default: '127.0.0.1'). + # :vat - The vat amount (optional). + # + # Returns an ActiveMerchant::Billing::Response object + def purchase(amount, payment_method, options = {}) + requires!(options, :order_id) + amount = amount(amount) + if payment_method.respond_to?(:number) + # credit card purchase + MultiResponse.new.tap do |r| + r.process {send_initialize(amount, false, options)} + r.process {send_purchasecc(payment_method, r.params['orderref'])} + end + else + # stored purchase + send_autopay(amount, payment_method, false, options) + end + end + + # Public: Capture money from a previously authorized transaction + # + # money - The amount to capture + # authorization - The authorization token from the authorization request + # + # Returns an ActiveMerchant::Billing::Response object + def capture(money, authorization, options = {}) + amount = amount(money) + send_capture(amount, authorization) + end + + # Public: Voids an authorize transaction + # + # authorization - The authorization returned from the successful authorize transaction. + # options - A standard ActiveMerchant options hash + # + # Returns an ActiveMerchant::Billing::Response object + def void(authorization, options={}) + send_cancel(authorization) + end + + # Public: Refunds a purchase transaction + # + # money - The amount to refund + # authorization - The authorization token from the purchase request. + # options - A standard ActiveMerchant options hash: + # :order_id - The unique order ID for this transaction (required). + # :vat_amount - The vat amount (optional). + # + # Returns an ActiveMerchant::Billing::Response object + def refund(money, authorization, options = {}) + requires!(options, :order_id) + amount = amount(money) + send_credit(authorization, amount, options) + end + + # Public: Stores a credit card and creates a Payex agreement with a customer + # + # creditcard - The credit card to store. + # options - A standard ActiveMerchant options hash: + # :order_id - The unique order ID for this transaction (required). + # :merchant_ref - A reference that links this agreement to something the merchant takes money for (default: '1') + # :currency - Three letter currency code for the transaction (default: "EUR") + # :product_number - The merchant product number (default: '1'). + # :description - The merchant description for this product (default: The :order_id). + # :ip - The client IP address (default: '127.0.0.1'). + # :max_amount - The maximum amount to allow to be charged (default: 100000). + # :vat - The vat amount (optional). + # + # Returns an ActiveMerchant::Billing::Response object where the authorization is set to the agreement_ref which is used for stored payments. + def store(creditcard, options = {}) + requires!(options, :order_id) + amount = amount(1) # 1 cent for authorization + MultiResponse.run(:first) do |r| + r.process {send_create_agreement(options)} + r.process {send_initialize(amount, true, options.merge({agreement_ref: r.authorization}))} + order_ref = r.params['orderref'] + r.process {send_purchasecc(creditcard, order_ref)} + end + end + + # Public: Unstores a customer's credit card and deletes their Payex agreement. + # + # authorization - The authorization token from the store request. + # + # Returns an ActiveMerchant::Billing::Response object + def unstore(authorization, options = {}) + send_delete_agreement(authorization) + end + + private + + def send_initialize(amount, is_auth, options = {}) + properties = { + accountNumber: @options[:account], + purchaseOperation: is_auth ? 'AUTHORIZATION' : 'SALE', + price: amount, + priceArgList: nil, + currency: (options[:currency] || default_currency), + vat: options[:vat] || 0, + orderID: options[:order_id], + productNumber: options[:product_number] || '1', + description: options[:description] || options[:order_id], + clientIPAddress: options[:client_ip_address] || '127.0.0.1', + clientIdentifier: nil, + additionalValues: nil, + externalID: nil, + returnUrl: 'http://example.net', # set to dummy value since this is not used but is required + view: 'CREDITCARD', + agreementRef: options[:agreement_ref], # this is used to attach a stored agreement to a transaction as part of the store card + cancelUrl: nil, + clientLanguage: nil + } + hash_fields = [:accountNumber, :purchaseOperation, :price, :priceArgList, :currency, :vat, :orderID, + :productNumber, :description, :clientIPAddress, :clientIdentifier, :additionalValues, + :externalID, :returnUrl, :view, :agreementRef, :cancelUrl, :clientLanguage] + add_request_hash(properties, hash_fields) + soap_action = SOAP_ACTIONS[:initialize] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_purchasecc(payment_method, order_ref) + properties = { + accountNumber: @options[:account], + orderRef: order_ref, + transactionType: 1, # online payment + cardNumber: payment_method.number, + cardNumberExpireMonth: "%02d" % payment_method.month, + cardNumberExpireYear: "%02d" % payment_method.year, + cardHolderName: payment_method.name, + cardNumberCVC: payment_method.verification_value + } + hash_fields = [:accountNumber, :orderRef, :transactionType, :cardNumber, :cardNumberExpireMonth, + :cardNumberExpireYear, :cardNumberCVC, :cardHolderName] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:purchasecc] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_autopay(amount, authorization, is_auth, options = {}) + properties = { + accountNumber: @options[:account], + agreementRef: authorization, + price: amount, + productNumber: options[:product_number] || '1', + description: options[:description] || options[:order_id], + orderId: options[:order_id], + purchaseOperation: is_auth ? 'AUTHORIZATION' : 'SALE', + currency: (options[:currency] || default_currency), + } + hash_fields = [:accountNumber, :agreementRef, :price, :productNumber, :description, :orderId, :purchaseOperation, :currency] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:autopay] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_capture(amount, transaction_number, options = {}) + properties = { + accountNumber: @options[:account], + transactionNumber: transaction_number, + amount: amount, + orderId: options[:order_id] || '', + vatAmount: options[:vat_amount] || 0, + additionalValues: '' + } + hash_fields = [:accountNumber, :transactionNumber, :amount, :orderId, :vatAmount, :additionalValues] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:capture] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_credit(transaction_number, amount, options = {}) + properties = { + accountNumber: @options[:account], + transactionNumber: transaction_number, + amount: amount, + orderId: options[:order_id], + vatAmount: options[:vat_amount] || 0, + additionalValues: '' + } + hash_fields = [:accountNumber, :transactionNumber, :amount, :orderId, :vatAmount, :additionalValues] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:credit] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_cancel(transaction_number) + properties = { + accountNumber: @options[:account], + transactionNumber: transaction_number, + } + hash_fields = [:accountNumber, :transactionNumber] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:cancel] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_create_agreement(options) + properties = { + accountNumber: @options[:account], + merchantRef: options[:merchant_ref] || '1', + description: options[:description] || options[:order_id], + purchaseOperation: 'SALE', + maxAmount: options[:max_amount] || 100000, # default to 1,000 + notifyUrl: '', + startDate: options[:startDate] || '', + stopDate: options[:stopDate] || '' + } + hash_fields = [:accountNumber, :merchantRef, :description, :purchaseOperation, :maxAmount, :notifyUrl, :startDate, :stopDate] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:create_agreement] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def send_delete_agreement(authorization) + properties = { + accountNumber: @options[:account], + agreementRef: authorization, + } + hash_fields = [:accountNumber, :agreementRef] + add_request_hash(properties, hash_fields) + + soap_action = SOAP_ACTIONS[:delete_agreement] + request = build_xml_request(soap_action, properties) + commit(soap_action, request) + end + + def url_for(soap_action) + base_url = test? ? (soap_action[:confined] ? TEST_CONFINED_URL : test_url) : live_url + File.join(base_url, soap_action[:url]) + end + + # this will add a hash to the passed in properties as required by Payex requests + def add_request_hash(properties, fields) + data = fields.map { |e| properties[e] } + data << @options[:encryption_key] + properties['hash_'] = Digest::MD5.hexdigest(data.join('')) + end + + def build_xml_request(soap_action, properties) + builder = Nokogiri::XML::Builder.new + builder.__send__('soap12:Envelope', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope'}) do |root| + root.__send__('soap12:Body') do |body| + body.__send__(soap_action[:name], xmlns: soap_action[:xmlns]) do |doc| + properties.each do |key, val| + doc.send(key, val) + end + end + end + end + builder.to_xml + end + + def parse(xml) + response = {} + + xmldoc = Nokogiri::XML(xml) + body = xmldoc.xpath("//soap:Body/*[1]")[0].inner_text + + doc = Nokogiri::XML(body) + + doc.root.xpath("*").each do |node| + if (node.elements.size == 0) + response[node.name.downcase.to_sym] = node.text + else + node.elements.each do |childnode| + name = "#{node.name.downcase}_#{childnode.name.downcase}" + response[name.to_sym] = childnode.text + end + end + end unless doc.root.nil? + + response + end + + # Commits all requests to the Payex soap endpoint + def commit(soap_action, request) + url = url_for(soap_action) + headers = { + 'Content-Type' => 'application/soap+xml; charset=utf-8', + 'Content-Length' => request.size.to_s + } + response = parse(ssl_post(url, request, headers)) + Response.new(success?(response), + message_from(response), + response, + test: test?, + authorization: build_authorization(response) + ) + end + + def build_authorization(response) + # agreementref is for the store transaction, everything else gets transactionnumber + response[:transactionnumber] || response[:agreementref] + end + + def success?(response) + response[:status_errorcode] == 'OK' && response[:transactionstatus] != TRANSACTION_STATUS[:failure] + end + + def message_from(response) + response[:status_description] + end + end + end +end + diff --git a/test/fixtures.yml b/test/fixtures.yml index af3af91dd42..8bb33826770 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -336,6 +336,11 @@ pay_gate_xml: login: '10011021600' password: 'test' +payex: + account: ACCOUNT + # encryption key is generated via the PayEx Admin console + encryption_key: ENCRYPTION_KEY + payflow: login: LOGIN password: PASSWORD diff --git a/test/remote/gateways/remote_payex_test.rb b/test/remote/gateways/remote_payex_test.rb new file mode 100644 index 00000000000..5483a8f1eda --- /dev/null +++ b/test/remote/gateways/remote_payex_test.rb @@ -0,0 +1,120 @@ +require 'test_helper' + +class RemotePayexTest < Test::Unit::TestCase + + def setup + @gateway = PayexGateway.new(fixtures(:payex)) + + @amount = 1000 + # cvv 210, expire date 02/14 + @credit_card = credit_card('4581090329655682', verification_value: 210, month: 2, year: 14) + @declined_card = credit_card('4000300011112220') + + @options = { + :order_id => '1234', + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'OK', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + # we can't test for a message since the messages vary so much + assert_not_equal 'OK', response.message + end + + def test_authorize_and_capture + amount = @amount + assert response = @gateway.authorize(amount, @credit_card, @options) + assert_success response + assert_equal 'OK', response.message + + assert response.authorization + assert response = @gateway.capture(amount, response.authorization) + assert_success response + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '1') + assert_failure response + assert_not_equal 'OK', response.message + assert_not_equal 'RecordNotFound', response.params[:status_errorcode] + end + + def test_authorization_and_void + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + + assert response = @gateway.void(response.authorization) + assert_success response + end + + def test_unsuccessful_void + assert response = @gateway.void("1") + assert_failure response + assert_not_equal 'OK', response.message + assert_match /1/, response.message + end + + def test_successful_refund + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert response.authorization + assert response = @gateway.refund(@amount - 200, response.authorization, order_id: '123') + assert_success response + end + + def test_unsuccessful_refund + assert response = @gateway.refund(@amount, "1", order_id: '123') + assert_failure response + assert_not_equal 'OK', response.message + assert_match /1/, response.message + end + + def test_successful_store_and_purchase + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'OK', response.message + + assert response = @gateway.purchase(@amount, response.authorization, @options.merge({order_id: '5678'})) + assert_success response + assert_equal 'OK', response.message + end + + def test_successful_store_and_authorize_and_capture + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal 'OK', response.message + + assert response = @gateway.authorize(@amount, response.authorization, @options.merge({order_id: '5678'})) + assert_success response + assert_equal 'OK', response.message + assert response.authorization + + assert response = @gateway.capture(@amount, response.authorization) + assert_success response + end + + def test_successful_unstore + assert response = @gateway.store(@credit_card, @options) + assert_equal 'OK', response.message + assert response = @gateway.unstore(response.authorization) + assert_success response + assert_equal 'OK', response.message + end + + def test_invalid_login + gateway = PayexGateway.new( + :account => '1', + :encryption_key => '1' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_not_equal 'OK', response.message + end +end diff --git a/test/unit/gateways/payex_test.rb b/test/unit/gateways/payex_test.rb new file mode 100644 index 00000000000..b2e5a8a78ef --- /dev/null +++ b/test/unit/gateways/payex_test.rb @@ -0,0 +1,284 @@ +require 'test_helper' + +class PayexTest < Test::Unit::TestCase + def setup + @gateway = PayexGateway.new( + :account => 'account', + :encryption_key => 'encryption_key' + ) + + @credit_card = credit_card + @amount = 1000 + + @options = { + :order_id => '1234', + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).times(2).returns(successful_initialize_response, successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of MultiResponse, response + assert_success response + + assert_equal '2623681', response.authorization + assert response.test? + end + + def test_failed_purchase + @gateway.expects(:ssl_post).times(2).returns(successful_initialize_response, failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_post).times(2).returns(successful_initialize_response, successful_authorize_response) + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert response.authorization + assert_equal 'OK', response.message + assert_equal '2624653', response.authorization + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + assert response = @gateway.capture(@amount, 'fakeauth') + assert_success response + assert_equal '2624655', response.authorization + assert response.test? + end + + def test_failed_capture + @gateway.expects(:ssl_post).returns(failed_capture_response) + assert response = @gateway.capture(@amount, '1') + assert_failure response + assert_not_equal 'OK', response.message + assert_not_equal 'RecordNotFound', response.params[:status_errorcode] + assert response.test? + end + + def test_successful_void + @gateway.expects(:ssl_post).returns(successful_void_response) + + assert response = @gateway.void('fakeauth') + assert_success response + assert_equal '2624825', response.authorization + assert response.test? + end + + def test_unsuccessful_void + @gateway.expects(:ssl_post).returns(unsuccessful_void_response) + assert response = @gateway.void("1") + assert_failure response + assert_not_equal 'OK', response.message + assert_match /1/, response.message + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_refund_response) + assert response = @gateway.refund(@amount - 200, 'fakeauth', order_id: '123') + assert_success response + assert_equal '2624828', response.authorization + assert response.test? + end + + def test_unsuccessful_refund + @gateway.expects(:ssl_post).returns(unsuccessful_refund_response) + assert response = @gateway.refund(@amount, "1", order_id: '123') + assert_failure response + assert_not_equal 'OK', response.message + assert_match /1/, response.message + assert response.test? + end + + def test_successful_store + @gateway.expects(:ssl_post).times(3).returns(successful_store_response, successful_initialize_response, successful_purchase_response) + assert response = @gateway.store(@credit_card, @options.merge({merchant_ref: '9876'})) + assert_success response + assert_equal 'OK', response.message + assert_equal 'bcea4ac8d1f44640bff7a8c93caa249c', response.authorization + assert response.test? + end + + def test_successful_unstore + @gateway.expects(:ssl_post).returns(successful_unstore_response) + assert response = @gateway.unstore('fakeauth') + assert_success response + assert_equal 'OK', response.message + assert response.test? + end + + def test_successful_purchase_with_stored_card + @gateway.expects(:ssl_post).returns(successful_autopay_response) + assert response = @gateway.purchase(@amount, 'fakeauth', @options.merge({order_id: '5678'})) + assert_success response + assert_equal 'OK', response.message + assert_equal '2624657', response.authorization + assert response.test? + end + + private + + def successful_initialize_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>09ef982cf2584d58bf4363dacd2ef127</id><date>2013-11-06 14:19:23</date></header><status><code>OK</code><description>OK</description><errorCode>OK</errorCode><paramName /><thirdPartyError /><thirdPartySubError /></status><orderRef>53681e74064a4621b93a6fcceba20c00</orderRef><sessionRef>7424a69d355c4cafa853ff49553b786f</sessionRef><redirectUrl>https://test-confined.payex.com/PxOrderCC.aspx?orderRef=53681e74064a4621b93a6fcceba20c00</redirectUrl></payex> + + + + } + end + + # Place raw successful response from gateway here + def successful_purchase_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>45eca449c8b54b54ac8811d4c26f638d</id><date>2013-11-06 14:19:35</date></header><status><code>OK</code><description>OK</description><errorCode>OK</errorCode><paramName /><thirdPartyError /></status><transactionStatus>0</transactionStatus><transactionRef>750dc1438350481086abd0438bde0c23</transactionRef><transactionNumber>2623681</transactionNumber><orderId>1234</orderId><productId>4321</productId><paymentMethod>VISA</paymentMethod><productNumber>4321</productNumber><BankHash>00000001-4581-0903-5682-000000000000</BankHash><AuthenticatedStatus>None</AuthenticatedStatus><AuthenticatedWith>N</AuthenticatedWith></payex> + + + + } + end + + def failed_purchase_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>131faa4301f74e91bf29e9749ad8f2a6</id><date>2013-11-06 14:40:18</date></header><status><code>ValidationError_InvalidParameter</code><errorCode>ValidationError_InvalidParameter</errorCode><description>Invalid parameter:expireDate + Parameter name: expireDate</description><paramName>expireDate</paramName><thirdPartyError /></status></payex> + + + + } + end + + def successful_authorize_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>41ebf6488fdb42a79baf49d2ef9e7dc6</id><date>2013-11-06 14:43:01</date></header><status><code>OK</code><description>OK</description><errorCode>OK</errorCode><paramName /><thirdPartyError /></status><transactionStatus>3</transactionStatus><transactionRef>89cbf30161f442228cbca16ff7f886d4</transactionRef><transactionNumber>2624653</transactionNumber><orderId>1234</orderId><productId>4321</productId><paymentMethod>VISA</paymentMethod><productNumber>4321</productNumber><BankHash>00000001-4581-0903-5682-000000000000</BankHash><AuthenticatedStatus>None</AuthenticatedStatus><AuthenticatedWith>N</AuthenticatedWith></payex> + + + + } + end + + def successful_capture_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>efe1a4572c9b4ed5a656d648bf7f9207</id><date>2013-11-06 14:43:03</date></header><status><code>OK</code><description>OK</description><errorCode>OK</errorCode><paramName /><thirdPartyError /><thirdPartySubError /></status><transactionStatus>6</transactionStatus><transactionNumber>2624655</transactionNumber><originalTransactionNumber>2624653</originalTransactionNumber></payex> + + + + } + end + + def failed_capture_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>19706b063aad458aa9783563a4e5bbff</id><date>2013-11-06 14:53:34</date></header><status><code>ValidationError_Generic</code><description>1</description><errorCode>NoRecordFound</errorCode><paramName /><thirdPartyError /><thirdPartySubError /></status></payex> + + + + } + end + + def successful_void_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>219b3a4b4ae0478482c75eba06d5a0dd</id><date>2013-11-06 14:56:48</date></header><status><code>OK</code><description>OK</description><errorCode>OK</errorCode><paramName /><thirdPartyError /><thirdPartySubError /></status><transactionStatus>4</transactionStatus><transactionNumber>2624825</transactionNumber><originalTransactionNumber>2624824</originalTransactionNumber></payex> + + + + } + end + + def unsuccessful_void_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>220b395b966a43eb9d0c70109f6dadd3</id><date>2013-11-06 15:02:24</date></header><status><code>ValidationError_Generic</code><description>1</description><errorCode>Error_Generic</errorCode><paramName /><thirdPartyError /><thirdPartySubError /></status></payex> + + + + } + end + + def successful_refund_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>a1e70daf3ed842ddb72e58a28f5cbc11</id><date>2013-11-06 14:57:54</date></header><status><code>OK</code><description>OK</description><errorCode>OK</errorCode><paramName /><thirdPartyError /><thirdPartySubError /></status><transactionStatus>2</transactionStatus><transactionNumber>2624828</transactionNumber><originalTransactionNumber>2624827</originalTransactionNumber></payex> + + + + } + end + + def unsuccessful_refund_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>e82613f6c59d4e08a7163cd91c2b3ce5</id><date>2013-11-06 15:02:23</date></header><status><code>ValidationError_Generic</code><description>1</description><errorCode>Error_Generic</errorCode><paramName /><thirdPartyError /><thirdPartySubError /></status></payex> + + + + } + end + + def successful_store_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>3c8063b7fcb64a449b715fe711f0a03f</id><date>2013-11-06 14:43:04</date></header><status><code>OK</code><description>OK</description><errorCode>OK</errorCode><paramName /><thirdPartyError /><thirdPartySubError /></status><agreementRef>bcea4ac8d1f44640bff7a8c93caa249c</agreementRef></payex> + + + + } + end + + def successful_unstore_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>7935ca4c24c3467cb36c48a270557194</id><date>2013-11-06 15:06:54</date></header><status><code>OK</code><description>OK</description><errorCode>OK</errorCode><paramName /><thirdPartyError /><thirdPartySubError /></status><agreementRef>7e9c6342dc20459691d1abb027a3c8c0</agreementRef></payex> + + + + } + end + + def successful_autopay_response + %q{ + + + + <?xml version="1.0" encoding="utf-8" ?><payex><header name="Payex Header v1.0"><id>37a7164c16804af199b7d6aade0aa580</id><date>2013-11-06 14:43:09</date></header><status><code>OK</code><description>OK</description><errorCode>OK</errorCode><paramName /><thirdPartyError /><thirdPartySubError /></status><transactionStatus>3</transactionStatus><transactionRef>de2984e302da40b498afe5aced8cea7e</transactionRef><transactionNumber>2624657</transactionNumber><paymentMethod>VISA</paymentMethod><captureTokens><stan>1337</stan><terminalId>666</terminalId><transactionTime>11/6/2013 2:43:09 PM</transactionTime></captureTokens><pending>false</pending></payex> + + + + } + end +end From d614d1902d99e205cb3b59d05c77b9e990141a1a Mon Sep 17 00:00:00 2001 From: Caleb Simpson Date: Thu, 7 Nov 2013 13:29:51 -0500 Subject: [PATCH 060/104] Ipay88: Yet another signature formatting fix. --- .../billing/integrations/ipay88/helper.rb | 2 +- test/unit/integrations/helpers/ipay88_helper_test.rb | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/active_merchant/billing/integrations/ipay88/helper.rb b/lib/active_merchant/billing/integrations/ipay88/helper.rb index 1a889802502..e742e6194fc 100644 --- a/lib/active_merchant/billing/integrations/ipay88/helper.rb +++ b/lib/active_merchant/billing/integrations/ipay88/helper.rb @@ -107,7 +107,7 @@ def sig_components components = [merchant_key] components << fields[mappings[:account]] components << fields[mappings[:order]] - components << amount_in_dollars.to_s.gsub(/0+$/, '').gsub(/[.,]/, '') + components << amount_in_dollars.gsub(/[.,]/, '') components << fields[mappings[:currency]] components.join end diff --git a/test/unit/integrations/helpers/ipay88_helper_test.rb b/test/unit/integrations/helpers/ipay88_helper_test.rb index 8fb50ff2c2f..84b475505a6 100644 --- a/test/unit/integrations/helpers/ipay88_helper_test.rb +++ b/test/unit/integrations/helpers/ipay88_helper_test.rb @@ -74,7 +74,7 @@ def test_unsupported_payment end def test_signature - assert_field "Signature", "sz25/58PfRuHloIGafsscRjk3H4=" + assert_field "Signature", "vDwWN/XHvYnlReq3f1llHFCxDTY=" end def test_valid_amount @@ -96,15 +96,15 @@ def test_invalid_amount_as_negative_integer_in_cents def test_sig_components_amount_doesnt_include_decimal_points @helper.amount = 50 - assert_equal "abcipay88merchcodeorder-50005MYR", @helper.send(:sig_components) + assert_equal "abcipay88merchcodeorder-500050MYR", @helper.send(:sig_components) @helper.amount = 1234 assert_equal "abcipay88merchcodeorder-5001234MYR", @helper.send(:sig_components) @helper.amount = 1000 - assert_equal "abcipay88merchcodeorder-50010MYR", @helper.send(:sig_components) + assert_equal "abcipay88merchcodeorder-5001000MYR", @helper.send(:sig_components) @helper.amount = Money.new(90) - assert_equal "abcipay88merchcodeorder-50009MYR", @helper.send(:sig_components) + assert_equal "abcipay88merchcodeorder-500090MYR", @helper.send(:sig_components) @helper.amount = Money.new(1000) - assert_equal "abcipay88merchcodeorder-50010MYR", @helper.send(:sig_components) + assert_equal "abcipay88merchcodeorder-5001000MYR", @helper.send(:sig_components) end def test_sign_method From 50befdea21f4ccfd6c84df559a518dcaef3e599f Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Fri, 15 Nov 2013 15:30:31 -0500 Subject: [PATCH 061/104] Product info shouldnt be credential2 --- lib/active_merchant/billing/integrations/payu_in/helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_merchant/billing/integrations/payu_in/helper.rb b/lib/active_merchant/billing/integrations/payu_in/helper.rb index 32048d309bc..5681851dfc4 100755 --- a/lib/active_merchant/billing/integrations/payu_in/helper.rb +++ b/lib/active_merchant/billing/integrations/payu_in/helper.rb @@ -7,7 +7,7 @@ class Helper < ActiveMerchant::Billing::Integrations::Helper mapping :amount, 'amount' mapping :account, 'key' mapping :order, 'txnid' - mapping :credential2, 'productinfo' + mapping :description, 'productinfo' mapping :customer, :first_name => 'firstname', :last_name => 'lastname', From b04343e9b2cc65925b0d96c76252b028dbeaa66e Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Fri, 15 Nov 2013 15:32:22 -0500 Subject: [PATCH 062/104] Refactor checkum, PayuPaisa was reversing udf fields --- .../billing/integrations/payu_in.rb | 11 ++-------- .../billing/integrations/payu_in/helper.rb | 21 +++++++------------ .../integrations/payu_in/notification.rb | 6 +++--- .../payu_in_paisa/notification.rb | 10 --------- .../remote_payu_in_integration_test.rb | 4 ++-- .../helpers/payu_in_helper_test.rb | 12 +++++------ .../helpers/payu_in_paisa_helper_test.rb | 13 ++++++------ .../payu_in_notification_test.rb | 12 ++++------- .../payu_in_paisa_notification_test.rb | 16 +++++++------- test/unit/integrations/payu_in_module_test.rb | 9 ++++---- .../returns/payu_in_paisa_return_test.rb | 16 ++++++++------ .../returns/payu_in_return_test.rb | 16 ++++++++------ 12 files changed, 65 insertions(+), 81 deletions(-) diff --git a/lib/active_merchant/billing/integrations/payu_in.rb b/lib/active_merchant/billing/integrations/payu_in.rb index 74933f1c7c6..261bc93c9a5 100755 --- a/lib/active_merchant/billing/integrations/payu_in.rb +++ b/lib/active_merchant/billing/integrations/payu_in.rb @@ -27,15 +27,8 @@ def self.return(post, options = {}) Return.new(post, options) end - def self.checksum(merchant_id, secret_key, *payload_items ) - options = payload_items.pop if Hash === payload_items.last - options ||= {} - payload = if options[:reverse] then - payload_items.dup.push( merchant_id || "" ).unshift( secret_key || "" ).collect{ |x| x.to_s }.join("|") - else - payload_items.dup.unshift( merchant_id || "" ).push( secret_key || "" ).collect{ |x| x.to_s }.join("|") - end - Digest::SHA512.hexdigest( payload ) + def self.checksum(merchant_id, secret_key, payload_items ) + Digest::SHA512.hexdigest([merchant_id, *payload_items, secret_key].join("|")) end end end diff --git a/lib/active_merchant/billing/integrations/payu_in/helper.rb b/lib/active_merchant/billing/integrations/payu_in/helper.rb index 5681851dfc4..b091e3c0b7f 100755 --- a/lib/active_merchant/billing/integrations/payu_in/helper.rb +++ b/lib/active_merchant/billing/integrations/payu_in/helper.rb @@ -44,6 +44,7 @@ class Helper < ActiveMerchant::Billing::Integrations::Helper def initialize(order, account, options = {}) super + @options = options self.pg = 'CC' end @@ -51,19 +52,13 @@ def form_fields @fields.merge(mappings[:checksum] => generate_checksum) end - def generate_checksum( options = {} ) - checksum_fields = [ :order, :amount, :credential2, { :customer => [ :first_name, :email ] }, - { :user_defined => [ :var1, :var2, :var3, :var4, :var5, :var6, :var7, :var8, :var9, :var10 ] } ] - checksum_payload_items = checksum_fields.inject( [] ) do | items, field | - if Hash === field then - key = field.keys.first - field[key].inject( items ){ |s,x| items.push( @fields[ mappings[key][x] ] ) } - else - items.push( @fields[ mappings[field] ] ) - end - end - checksum_payload_items.push( options ) - PayuIn.checksum(@fields["key"], @fields["productinfo"], *checksum_payload_items ) + def generate_checksum + checksum_payload_items = [ + 'txnid', 'amount', 'productinfo', 'firstname', 'email', + 'udf1', 'udf2', 'udf3', 'udf4', 'udf5', 'udf6', 'udf7', 'udf8', 'udf9', 'udf10' + ].map { |field| @fields[field] } + + PayuIn.checksum(@fields["key"], @options[:credential2], checksum_payload_items ) end end diff --git a/lib/active_merchant/billing/integrations/payu_in/notification.rb b/lib/active_merchant/billing/integrations/payu_in/notification.rb index 932c136e7d1..14ace59a42c 100755 --- a/lib/active_merchant/billing/integrations/payu_in/notification.rb +++ b/lib/active_merchant/billing/integrations/payu_in/notification.rb @@ -148,9 +148,9 @@ def acknowledge(authcode = nil) end def checksum_ok? - fields = user_defined.dup.push( customer_email, customer_first_name, product_info, gross, invoice, :reverse => true ) - fields.unshift( transaction_status ) - unless PayuIn.checksum(@merchant_id, @secret_key, *fields ) == checksum + checksum_fields = [transaction_status, *user_defined.reverse, customer_email, customer_first_name, product_info, gross, invoice] + + unless Digest::SHA512.hexdigest([@secret_key, *checksum_fields, @merchant_id].join("|")) == checksum @message = 'Return checksum not matching the data provided' return false end diff --git a/lib/active_merchant/billing/integrations/payu_in_paisa/notification.rb b/lib/active_merchant/billing/integrations/payu_in_paisa/notification.rb index df4bfb7eba8..9d930f4df6a 100644 --- a/lib/active_merchant/billing/integrations/payu_in_paisa/notification.rb +++ b/lib/active_merchant/billing/integrations/payu_in_paisa/notification.rb @@ -6,16 +6,6 @@ class Notification < PayuIn::Notification def item_id params['udf2'] end - - def checksum_ok? - fields = user_defined.reverse.push( customer_email, customer_first_name, product_info, gross, invoice, :reverse => true ) - fields.unshift( transaction_status ) - unless PayuIn.checksum(@merchant_id, @secret_key, *fields ) == checksum - @message = 'Return checksum not matching the data provided' - return false - end - true - end end end end diff --git a/test/remote/integrations/remote_payu_in_integration_test.rb b/test/remote/integrations/remote_payu_in_integration_test.rb index be8aa9c8547..72680842425 100644 --- a/test/remote/integrations/remote_payu_in_integration_test.rb +++ b/test/remote/integrations/remote_payu_in_integration_test.rb @@ -4,7 +4,7 @@ class RemotePayuInIntegrationTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def setup - @payu_in = PayuIn::Notification.new(http_raw_data, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') + @payu_in = PayuIn::Notification.new(http_raw_data, :credential1 => 'merchant_id', :credential2 => 'secret') end def test_raw @@ -21,6 +21,6 @@ def test_raw private def http_raw_data - "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=C0Dr8m&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" + "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=merchant_id&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" end end diff --git a/test/unit/integrations/helpers/payu_in_helper_test.rb b/test/unit/integrations/helpers/payu_in_helper_test.rb index 20d7f368319..81da3ce2fed 100644 --- a/test/unit/integrations/helpers/payu_in_helper_test.rb +++ b/test/unit/integrations/helpers/payu_in_helper_test.rb @@ -4,14 +4,13 @@ class PayuInHelperTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def setup - @helper = PayuIn::Helper.new( 'jh34h53kj4h5hj34kh5', 'C0Dr8m', :amount => '10.00', :credential2 => 'Product Info') + @helper = PayuIn::Helper.new( 'order_id', 'merchant_id', :amount => '10.00', :credential2 => 'secret_key') end def test_basic_helper_fields assert_equal '10.00', @helper.fields['amount'] - assert_equal 'C0Dr8m', @helper.fields['key'] - assert_equal 'jh34h53kj4h5hj34kh5', @helper.fields['txnid'] - assert_equal 'Product Info', @helper.fields['productinfo'] + assert_equal 'merchant_id', @helper.fields['key'] + assert_equal 'order_id', @helper.fields['txnid'] end def test_customer_fields @@ -56,11 +55,12 @@ def test_user_defined_fields end def test_add_checksum_method - options = { :mode => 'CC' } @helper.customer :first_name => 'Payu-Admin', :email => 'test@example.com' + @helper.description "Product Info" @helper.user_defined :var1 => 'var_one', :var2 => 'var_two', :var3 => 'var_three', :var4 => 'var_four', :var5 => 'var_five', :var6 => 'var_six', :var7 => 'var_seven', :var8 => 'var_eight', :var9 => 'var_nine', :var10 => 'var_ten' - assert_equal "032606d7fb5cfe357d9e6b358b4bb8db1d34e9dfa30f039cb7dec75ae6d77f7d1f67a58c123ea0ee358bf040554d5e3048066a369ae63888132e27c14e79ee5a", @helper.form_fields["hash"] + fields = ["txnid", "amount", "productinfo", "firstname", "email", "udf1", "udf2", "udf3", "udf4", "udf5", "udf6", "udf7", "udf8", "udf9", "udf10"].map { |field| @helper.fields[field] } + assert_equal Digest::SHA512.hexdigest(['merchant_id', *fields, 'secret_key'].join("|")), @helper.form_fields["hash"] end end diff --git a/test/unit/integrations/helpers/payu_in_paisa_helper_test.rb b/test/unit/integrations/helpers/payu_in_paisa_helper_test.rb index b4e9a2c2e5f..31e6e4ae4fd 100644 --- a/test/unit/integrations/helpers/payu_in_paisa_helper_test.rb +++ b/test/unit/integrations/helpers/payu_in_paisa_helper_test.rb @@ -4,14 +4,13 @@ class PayuInPaisaHelperTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def setup - @helper = PayuInPaisa::Helper.new( 'jh34h53kj4h5hj34kh5', 'C0Dr8m', :amount => '10.00', :credential2 => 'Product Info') + @helper = PayuInPaisa::Helper.new( 'order_id', 'merchant_id', :amount => '10.00', :credential2 => 'secret') end def test_basic_helper_fields assert_equal '10.00', @helper.fields['amount'] - assert_equal 'C0Dr8m', @helper.fields['key'] - assert_equal 'jh34h53kj4h5hj34kh5', @helper.fields['txnid'] - assert_equal 'Product Info', @helper.fields['productinfo'] + assert_equal 'merchant_id', @helper.fields['key'] + assert_equal 'order_id', @helper.fields['txnid'] end def test_customer_fields @@ -56,10 +55,12 @@ def test_user_defined_fields end def test_add_checksum_method - options = { :mode => 'CC' } @helper.customer :first_name => 'Payu-Admin', :email => 'test@example.com' @helper.user_defined :var1 => 'var_one', :var2 => 'var_two', :var3 => 'var_three', :var4 => 'var_four', :var5 => 'var_five', :var6 => 'var_six', :var7 => 'var_seven', :var8 => 'var_eight', :var9 => 'var_nine', :var10 => 'var_ten' + @helper.description 'Product Info' - assert_equal "032606d7fb5cfe357d9e6b358b4bb8db1d34e9dfa30f039cb7dec75ae6d77f7d1f67a58c123ea0ee358bf040554d5e3048066a369ae63888132e27c14e79ee5a", @helper.form_fields["hash"] + payload = 'merchant_id|order_id|10.00|Product Info|Payu-Admin|test@example.com|var_one|var_two|var_three|var_four|var_five|var_six|var_seven|var_eight|var_nine|var_ten|secret' + checksum = Digest::SHA512.hexdigest(payload) + assert_equal checksum, @helper.form_fields["hash"] end end diff --git a/test/unit/integrations/notifications/payu_in_notification_test.rb b/test/unit/integrations/notifications/payu_in_notification_test.rb index 7ca50e350e1..94f1a6772ce 100644 --- a/test/unit/integrations/notifications/payu_in_notification_test.rb +++ b/test/unit/integrations/notifications/payu_in_notification_test.rb @@ -4,7 +4,7 @@ class PayuInNotificationTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def setup - @payu = PayuIn::Notification.new(http_raw_data, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') + @payu = PayuIn::Notification.new(http_raw_data, :credential1 => 'merchant_id', :credential2 => 'secret') end def test_accessors @@ -19,13 +19,13 @@ def test_accessors assert_equal true, @payu.amount_ok?(BigDecimal.new('10.00'),BigDecimal.new('0.00')) assert_equal "CC", @payu.type assert_equal "4ba4afe87f7e73468f2a", @payu.invoice - assert_equal "C0Dr8m", @payu.account + assert_equal "merchant_id", @payu.account assert_equal "0.00", @payu.discount assert_equal "test@example.com", @payu.customer_email assert_equal "1234567890", @payu.customer_phone assert_equal "Payu-Admin", @payu.customer_first_name assert_equal "", @payu.customer_last_name - assert_equal "ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219", @payu.checksum + assert_equal "d6a5544072d036dc422d1c6393a8da75233d5e30ffc848f11682f121d67cd80c0d4fed1067b99918b5a377b7dcf1c8c9c79975abdf9f444692b35bf34d494105", @payu.checksum assert_equal "E000", @payu.message assert_equal true, @payu.checksum_ok? end @@ -38,12 +38,8 @@ def test_acknowledgement assert @payu.acknowledge end - def test_respond_to_acknowledge - assert @payu.respond_to?(:acknowledge) - end - private def http_raw_data - "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=C0Dr8m&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" + "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=merchant_id&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=d6a5544072d036dc422d1c6393a8da75233d5e30ffc848f11682f121d67cd80c0d4fed1067b99918b5a377b7dcf1c8c9c79975abdf9f444692b35bf34d494105&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" end end diff --git a/test/unit/integrations/notifications/payu_in_paisa_notification_test.rb b/test/unit/integrations/notifications/payu_in_paisa_notification_test.rb index 19601829da5..40b749986ce 100644 --- a/test/unit/integrations/notifications/payu_in_paisa_notification_test.rb +++ b/test/unit/integrations/notifications/payu_in_paisa_notification_test.rb @@ -4,7 +4,7 @@ class PayuInPaisaNotificationTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def setup - @payu = PayuInPaisa::Notification.new(http_raw_data, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') + @payu = PayuInPaisa::Notification.new(http_raw_data, :credential1 => 'merchant_id', :credential2 => 'secret') end def test_accessors @@ -19,13 +19,13 @@ def test_accessors assert_equal true, @payu.amount_ok?(BigDecimal.new('10.00'),BigDecimal.new('0.00')) assert_equal "CC", @payu.type assert_equal "4ba4afe87f7e73468f2a", @payu.invoice - assert_equal "C0Dr8m", @payu.account + assert_equal "merchant_id", @payu.account assert_equal "0.00", @payu.discount assert_equal "test@example.com", @payu.customer_email assert_equal "1234567890", @payu.customer_phone assert_equal "Payu-Admin", @payu.customer_first_name assert_equal "", @payu.customer_last_name - assert_equal "e35f67dc7232d12caa28b16ba31b509f62bdea1e930bb6766a4f71036cc1af34debb8afc0fdd89be50f0604c1e6bca7209dfffe6b3a893c575492edcab3444ee", @payu.checksum + assert_equal checksum, @payu.checksum assert_equal "E000", @payu.message assert_equal true, @payu.checksum_ok? end @@ -38,16 +38,16 @@ def test_acknowledgement assert @payu.acknowledge end - def test_respond_to_acknowledge - assert @payu.respond_to?(:acknowledge) - end - def test_item_id_gives_the_original_item_id assert 'original_item_id', @payu.item_id end private def http_raw_data - "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=C0Dr8m&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=original_item_id&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=e35f67dc7232d12caa28b16ba31b509f62bdea1e930bb6766a4f71036cc1af34debb8afc0fdd89be50f0604c1e6bca7209dfffe6b3a893c575492edcab3444ee&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" + "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=merchant_id&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=original_item_id&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=#{checksum}&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" + end + + def checksum + Digest::SHA512.hexdigest("secret|success|||||||||original_item_id||test@example.com|Payu-Admin|Product Info|10.00|4ba4afe87f7e73468f2a|merchant_id") end end diff --git a/test/unit/integrations/payu_in_module_test.rb b/test/unit/integrations/payu_in_module_test.rb index f563f4a425b..53441994f9c 100644 --- a/test/unit/integrations/payu_in_module_test.rb +++ b/test/unit/integrations/payu_in_module_test.rb @@ -5,8 +5,8 @@ class PayuInModuleTest < Test::Unit::TestCase def setup ActiveMerchant::Billing::Base.integration_mode = :test - @merchant_id = 'C0Dr8m' - @secret_key = '3sf0jURk' + @merchant_id = 'merchant_id' + @secret_key = 'secret' end def test_service_url_method @@ -26,7 +26,8 @@ def test_notification_method end def test_checksum_method - payu_load = "4ba4afe87f7e73468f2a|10.00|Product Info|Payu-Admin|test@example.com||||||||||" - assert_equal "cd324f64891b07d95492a2fd80ae469092e302faa3d3df5ba1b829936fd7497b6e89c3e48fd70e2a131cdd4f17d14bc20f292e9408650c085bc3bedb32f44266", PayuIn.checksum(@merchant_id, @secret_key, payu_load) + payu_load = "order_id|10.00|Product Info|Payu-Admin|test@example.com||||||||||" + checksum = Digest::SHA512.hexdigest([@merchant_id, payu_load, @secret_key].join("|")) + assert_equal checksum, PayuIn.checksum(@merchant_id, @secret_key, payu_load.split("|", -1)) end end diff --git a/test/unit/integrations/returns/payu_in_paisa_return_test.rb b/test/unit/integrations/returns/payu_in_paisa_return_test.rb index 174a682e7c2..97baca37ec0 100644 --- a/test/unit/integrations/returns/payu_in_paisa_return_test.rb +++ b/test/unit/integrations/returns/payu_in_paisa_return_test.rb @@ -4,11 +4,11 @@ class PayuInPaisaReturnTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def setup - @payu = PayuInPaisa::Return.new(http_raw_data_success, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') + @payu = PayuInPaisa::Return.new(http_raw_data_success, :credential1 => 'merchant_id', :credential2 => 'secret') end def setup_failed_return - @payu = PayuInPaisa::Return.new(http_raw_data_failure, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') + @payu = PayuInPaisa::Return.new(http_raw_data_failure, :credential1 => 'merchant_id', :credential2 => 'secret') end def test_success @@ -38,7 +38,7 @@ def test_return_has_notification assert_equal 'CC', @payu.notification.type assert_equal 'INR', notification.currency assert_equal '4ba4afe87f7e73468f2a', notification.invoice - assert_equal 'C0Dr8m', notification.account + assert_equal 'merchant_id', notification.account assert_equal '10.00', notification.gross assert_equal '0.00', notification.discount assert_equal nil, notification.offer_description @@ -48,7 +48,7 @@ def test_return_has_notification assert_equal 'Payu-Admin', notification.customer_first_name assert_equal '', notification.customer_last_name assert_equal ["", "original_item_id", "", "", "", "", "", "", "", ""], notification.user_defined - assert_equal "e35f67dc7232d12caa28b16ba31b509f62bdea1e930bb6766a4f71036cc1af34debb8afc0fdd89be50f0604c1e6bca7209dfffe6b3a893c575492edcab3444ee", notification.checksum + assert_equal checksum, notification.checksum assert_equal 'E000', notification.message assert notification.checksum_ok? end @@ -56,11 +56,15 @@ def test_return_has_notification private def http_raw_data_success - "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=C0Dr8m&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=original_item_id&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=e35f67dc7232d12caa28b16ba31b509f62bdea1e930bb6766a4f71036cc1af34debb8afc0fdd89be50f0604c1e6bca7209dfffe6b3a893c575492edcab3444ee&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" + "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=merchant_id&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=original_item_id&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=#{checksum}&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" end def http_raw_data_failure - "mihpayid=403993715508030204&mode=CC&status=failure&unmappedstatus=failed&key=C0Dr8m&txnid=8ae1034d1abf47fde1cf&amount=10.00&discount=0.00&addedon=2013-05-13 11:09:20&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=65774f82abe64cec54be31107529b2a3eef8f6a3f97a8cb81e9769f4394b890b0e7171f8988c4df3684e7f9f337035d0fe09a844da4b76e68dd643e8ac5e5c63&field1=&field2=&field3=&field4=&field5=!ERROR!-GV00103-Invalid BrandError Code: GV00103&field6=&field7=&field8=failed in enrollment&PG_TYPE=HDFC&bank_ref_num=&bankcode=CC&error=E201&cardnum=411111XXXXXX1111&cardhash=49c73d6c44f27f7ac71b439de842f91e27fcbc3b9ce9dfbcbf1ce9a8fe790c17" + "mihpayid=403993715508030204&mode=CC&status=failure&unmappedstatus=failed&key=merchant_id&txnid=8ae1034d1abf47fde1cf&amount=10.00&discount=0.00&addedon=2013-05-13 11:09:20&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=65774f82abe64cec54be31107529b2a3eef8f6a3f97a8cb81e9769f4394b890b0e7171f8988c4df3684e7f9f337035d0fe09a844da4b76e68dd643e8ac5e5c63&field1=&field2=&field3=&field4=&field5=!ERROR!-GV00103-Invalid BrandError Code: GV00103&field6=&field7=&field8=failed in enrollment&PG_TYPE=HDFC&bank_ref_num=&bankcode=CC&error=E201&cardnum=411111XXXXXX1111&cardhash=49c73d6c44f27f7ac71b439de842f91e27fcbc3b9ce9dfbcbf1ce9a8fe790c17" + end + + def checksum + Digest::SHA512.hexdigest("secret|success|||||||||original_item_id||test@example.com|Payu-Admin|Product Info|10.00|4ba4afe87f7e73468f2a|merchant_id") end end diff --git a/test/unit/integrations/returns/payu_in_return_test.rb b/test/unit/integrations/returns/payu_in_return_test.rb index add0f355cfe..6e49d8f0e5d 100644 --- a/test/unit/integrations/returns/payu_in_return_test.rb +++ b/test/unit/integrations/returns/payu_in_return_test.rb @@ -4,11 +4,11 @@ class PayuInReturnTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def setup - @payu = PayuIn::Return.new(http_raw_data_success, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') + @payu = PayuIn::Return.new(http_raw_data_success, :credential1 => 'merchant_id', :credential2 => 'secret') end def setup_failed_return - @payu = PayuIn::Return.new(http_raw_data_failure, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk') + @payu = PayuIn::Return.new(http_raw_data_failure, :credential1 => 'merchant_id', :credential2 => 'secret') end def test_success @@ -38,7 +38,7 @@ def test_return_has_notification assert_equal 'CC', @payu.notification.type assert_equal 'INR', notification.currency assert_equal '4ba4afe87f7e73468f2a', notification.invoice - assert_equal 'C0Dr8m', notification.account + assert_equal 'merchant_id', notification.account assert_equal '10.00', notification.gross assert_equal '0.00', notification.discount assert_equal nil, notification.offer_description @@ -48,7 +48,7 @@ def test_return_has_notification assert_equal 'Payu-Admin', notification.customer_first_name assert_equal '', notification.customer_last_name assert_equal ["", "", "", "", "", "", "", "", "", ""], notification.user_defined - assert_equal 'ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219', notification.checksum + assert_equal checksum, notification.checksum assert_equal 'E000', notification.message assert notification.checksum_ok? end @@ -56,11 +56,15 @@ def test_return_has_notification private def http_raw_data_success - "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=C0Dr8m&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" + "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=merchant_id&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=#{checksum}&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d" end def http_raw_data_failure - "mihpayid=403993715508030204&mode=CC&status=failure&unmappedstatus=failed&key=C0Dr8m&txnid=8ae1034d1abf47fde1cf&amount=10.00&discount=0.00&addedon=2013-05-13 11:09:20&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=65774f82abe64cec54be31107529b2a3eef8f6a3f97a8cb81e9769f4394b890b0e7171f8988c4df3684e7f9f337035d0fe09a844da4b76e68dd643e8ac5e5c63&field1=&field2=&field3=&field4=&field5=!ERROR!-GV00103-Invalid BrandError Code: GV00103&field6=&field7=&field8=failed in enrollment&PG_TYPE=HDFC&bank_ref_num=&bankcode=CC&error=E201&cardnum=411111XXXXXX1111&cardhash=49c73d6c44f27f7ac71b439de842f91e27fcbc3b9ce9dfbcbf1ce9a8fe790c17" + "mihpayid=403993715508030204&mode=CC&status=failure&unmappedstatus=failed&key=merchant_id&txnid=8ae1034d1abf47fde1cf&amount=10.00&discount=0.00&addedon=2013-05-13 11:09:20&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=65774f82abe64cec54be31107529b2a3eef8f6a3f97a8cb81e9769f4394b890b0e7171f8988c4df3684e7f9f337035d0fe09a844da4b76e68dd643e8ac5e5c63&field1=&field2=&field3=&field4=&field5=!ERROR!-GV00103-Invalid BrandError Code: GV00103&field6=&field7=&field8=failed in enrollment&PG_TYPE=HDFC&bank_ref_num=&bankcode=CC&error=E201&cardnum=411111XXXXXX1111&cardhash=49c73d6c44f27f7ac71b439de842f91e27fcbc3b9ce9dfbcbf1ce9a8fe790c17" + end + + def checksum + Digest::SHA512.hexdigest("secret|success|||||||||||test@example.com|Payu-Admin|Product Info|10.00|4ba4afe87f7e73468f2a|merchant_id") end end From 43cd7905b390237f1508163a16521e40df2ca5ed Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Fri, 15 Nov 2013 15:32:45 -0500 Subject: [PATCH 063/104] Slip down user_defined method --- .../billing/integrations/payu_in/notification.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/active_merchant/billing/integrations/payu_in/notification.rb b/lib/active_merchant/billing/integrations/payu_in/notification.rb index 14ace59a42c..c8f98b762e1 100755 --- a/lib/active_merchant/billing/integrations/payu_in/notification.rb +++ b/lib/active_merchant/billing/integrations/payu_in/notification.rb @@ -129,10 +129,7 @@ def customer_address end def user_defined - return @user_defined if @user_defined - @user_defined = [] - 10.times{ |i| @user_defined.push( params[ "udf#{i+1}" ] ) } - @user_defined + @user_defined ||= 10.times.map { |i| params["udf#{i + 1}"] } end def checksum From 22b2615cecf8ac47fd39e1f489c94e3c3f1dfaea Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Fri, 15 Nov 2013 15:33:37 -0500 Subject: [PATCH 064/104] PayU status should be based off status from param, checksum should be handled by acknowledge --- .../billing/integrations/payu_in/notification.rb | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/active_merchant/billing/integrations/payu_in/notification.rb b/lib/active_merchant/billing/integrations/payu_in/notification.rb index c8f98b762e1..803cfe1d02b 100755 --- a/lib/active_merchant/billing/integrations/payu_in/notification.rb +++ b/lib/active_merchant/billing/integrations/payu_in/notification.rb @@ -15,18 +15,10 @@ def complete? end def status - @status ||= if checksum_ok? - if transaction_id.blank? - 'Invalid' - else - case transaction_status.downcase - when 'success' then 'Completed' - when 'failure' then 'Failed' - when 'pending' then 'Pending' - end - end - else - 'Tampered' + case transaction_status.downcase + when 'success' then 'Completed' + when 'failure' then 'Failed' + when 'pending' then 'Pending' end end From 956174ef15e3ddb477de5fdddcecb6957559f9d4 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Fri, 15 Nov 2013 15:34:09 -0500 Subject: [PATCH 065/104] to_s discount as it can be nil on returns --- .../billing/integrations/payu_in/notification.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_merchant/billing/integrations/payu_in/notification.rb b/lib/active_merchant/billing/integrations/payu_in/notification.rb index 803cfe1d02b..7881a02f9ca 100755 --- a/lib/active_merchant/billing/integrations/payu_in/notification.rb +++ b/lib/active_merchant/billing/integrations/payu_in/notification.rb @@ -28,7 +28,7 @@ def invoice_ok?( order_id ) # Order amount should be equal to gross - discount def amount_ok?( order_amount, order_discount = BigDecimal.new( '0.0' ) ) - BigDecimal.new( gross ) == order_amount && BigDecimal.new( discount ) == order_discount + BigDecimal.new( gross ) == order_amount && BigDecimal.new( discount.to_s ) == order_discount end # Status of transaction return from the PayU. List of possible values: From 706feb1c7d98e489cb5eaf54efccbc2cf69cc0bd Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Sat, 16 Nov 2013 16:56:52 -0500 Subject: [PATCH 066/104] Fix PayuInPaisa module tests --- test/unit/integrations/payu_in_paisa_module_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/integrations/payu_in_paisa_module_test.rb b/test/unit/integrations/payu_in_paisa_module_test.rb index 12e63f431b3..00e3235227b 100644 --- a/test/unit/integrations/payu_in_paisa_module_test.rb +++ b/test/unit/integrations/payu_in_paisa_module_test.rb @@ -9,17 +9,17 @@ def setup def test_service_url_method ActiveMerchant::Billing::Base.integration_mode = :test - assert_equal "https://test.payu.in/_payment.php", PayuIn.service_url + assert_equal "https://test.payu.in/_payment.php", PayuInPaisa.service_url ActiveMerchant::Billing::Base.integration_mode = :production - assert_equal "https://secure.payu.in/_payment.php", PayuIn.service_url + assert_equal "https://secure.payu.in/_payment.php", PayuInPaisa.service_url end def test_return_method - assert_instance_of PayuIn::Return, PayuIn.return('name=foo', {}) + assert_instance_of PayuInPaisa::Return, PayuInPaisa.return('name=foo', {}) end def test_notification_method - assert_instance_of PayuIn::Notification, PayuIn.notification('name=foo', {}) + assert_instance_of PayuInPaisa::Notification, PayuInPaisa.notification('name=foo', {}) end end From 281848e4a4c048bd7dd5cefbd37b8ed2465079d0 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Sat, 16 Nov 2013 17:15:48 -0500 Subject: [PATCH 067/104] Remove super arguments --- .../billing/integrations/payu_in_paisa/helper.rb | 2 +- .../billing/integrations/payu_in_paisa/return.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_merchant/billing/integrations/payu_in_paisa/helper.rb b/lib/active_merchant/billing/integrations/payu_in_paisa/helper.rb index d87c596d1c9..b46561c176e 100644 --- a/lib/active_merchant/billing/integrations/payu_in_paisa/helper.rb +++ b/lib/active_merchant/billing/integrations/payu_in_paisa/helper.rb @@ -7,7 +7,7 @@ class Helper < PayuIn::Helper mapping :service_provider, 'service_provider' def initialize(order, account, options = {}) - super order, account, options + super self.service_provider = 'payu_paisa' self.user_defined = { :var2 => order } end diff --git a/lib/active_merchant/billing/integrations/payu_in_paisa/return.rb b/lib/active_merchant/billing/integrations/payu_in_paisa/return.rb index b5bfce0af52..8b22eb7b08a 100644 --- a/lib/active_merchant/billing/integrations/payu_in_paisa/return.rb +++ b/lib/active_merchant/billing/integrations/payu_in_paisa/return.rb @@ -5,7 +5,7 @@ module PayuInPaisa class Return < PayuIn::Return def initialize(query_string, options = {}) - super query_string, options + super @notification = Notification.new(query_string, options) end end From 2fa18992e82ff949a5ea5b5c122ba0c344dfa40e Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Mon, 18 Nov 2013 14:37:17 -0500 Subject: [PATCH 068/104] Sanitize field values before generating the form fields --- .../billing/integrations/payu_in/helper.rb | 7 +++++++ test/unit/integrations/helpers/payu_in_helper_test.rb | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/active_merchant/billing/integrations/payu_in/helper.rb b/lib/active_merchant/billing/integrations/payu_in/helper.rb index b091e3c0b7f..68a6d839658 100755 --- a/lib/active_merchant/billing/integrations/payu_in/helper.rb +++ b/lib/active_merchant/billing/integrations/payu_in/helper.rb @@ -49,6 +49,7 @@ def initialize(order, account, options = {}) end def form_fields + sanitize_fields @fields.merge(mappings[:checksum] => generate_checksum) end @@ -61,6 +62,12 @@ def generate_checksum PayuIn.checksum(@fields["key"], @options[:credential2], checksum_payload_items ) end + def sanitize_fields + ['address1', 'address2', 'city', 'state', 'country', 'productinfo', 'email', 'phone'].each do |field| + @fields[field].gsub!(/[^a-zA-Z0-9\-_@\/\s.]/, '') if @fields[field] + end + end + end end diff --git a/test/unit/integrations/helpers/payu_in_helper_test.rb b/test/unit/integrations/helpers/payu_in_helper_test.rb index 81da3ce2fed..2d210a59440 100644 --- a/test/unit/integrations/helpers/payu_in_helper_test.rb +++ b/test/unit/integrations/helpers/payu_in_helper_test.rb @@ -63,4 +63,12 @@ def test_add_checksum_method assert_equal Digest::SHA512.hexdigest(['merchant_id', *fields, 'secret_key'].join("|")), @helper.form_fields["hash"] end + def test_sanitize_fields_in_form_fields + @helper.description '{[Valid Description!]}' + @helper.form_fields + + assert_equal 'Valid Description', @helper.fields['productinfo'] + assert_nil @helper.fields['email'] + end + end From 99a074a5f39c776730d26e4929a0f0a4e5c1f9df Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Mon, 18 Nov 2013 15:06:02 -0500 Subject: [PATCH 069/104] Move payu in test credentials to fixtures --- test/fixtures.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/fixtures.yml b/test/fixtures.yml index af3af91dd42..a90f8b1e15a 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -372,6 +372,10 @@ paypal_signature: password: PASSWORD signature: SIGNATURE +payu_in: + login: C0Dr8m + secret: 3sf0jURk + payway: username: password: From 25d5820f6249870efe4f728072d84696949f17e1 Mon Sep 17 00:00:00 2001 From: Jordan Wheeler Date: Tue, 19 Nov 2013 12:56:04 -0500 Subject: [PATCH 070/104] Allow Valitor gateway to accept other currencies --- lib/active_merchant/billing/gateway.rb | 2 +- .../billing/integrations/valitor/helper.rb | 6 +++--- .../helpers/valitor_helper_test.rb | 20 ++++++++++++------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/active_merchant/billing/gateway.rb b/lib/active_merchant/billing/gateway.rb index 2b2efe2fd80..39826a2d7ce 100644 --- a/lib/active_merchant/billing/gateway.rb +++ b/lib/active_merchant/billing/gateway.rb @@ -62,7 +62,7 @@ class Gateway include Utils DEBIT_CARDS = [ :switch, :solo ] - CURRENCIES_WITHOUT_FRACTIONS = [ 'JPY', 'HUF', 'TWD' ] + CURRENCIES_WITHOUT_FRACTIONS = [ 'JPY', 'HUF', 'TWD', 'ISK' ] CREDIT_DEPRECATION_MESSAGE = "Support for using credit to refund existing transactions is deprecated and will be removed from a future release of ActiveMerchant. Please use the refund method instead." cattr_reader :implementations diff --git a/lib/active_merchant/billing/integrations/valitor/helper.rb b/lib/active_merchant/billing/integrations/valitor/helper.rb index 15630f659de..9a6aeafd214 100644 --- a/lib/active_merchant/billing/integrations/valitor/helper.rb +++ b/lib/active_merchant/billing/integrations/valitor/helper.rb @@ -50,7 +50,7 @@ def product(id, options={}) requires!(options, :amount, :description) options.assert_valid_keys([:description, :quantity, :amount, :discount]) - add_field("Vara_#{id}_Verd", format_amount(options[:amount])) + add_field("Vara_#{id}_Verd", format_amount(options[:amount], @fields[mappings[:currency]])) add_field("Vara_#{id}_Fjoldi", options[:quantity] || "1") add_field("Vara_#{id}_Lysing", options[:description]) if options[:description] @@ -76,8 +76,8 @@ def form_fields @fields.merge('RafraenUndirskrift' => signature) end - def format_amount(amount) - amount.to_f.round + def format_amount(amount, currency) + Gateway::CURRENCIES_WITHOUT_FRACTIONS.include?(currency) ? amount.to_f.round : sprintf("%.2f", amount) end end end diff --git a/test/unit/integrations/helpers/valitor_helper_test.rb b/test/unit/integrations/helpers/valitor_helper_test.rb index 64519cb9470..13a4b1e73cc 100644 --- a/test/unit/integrations/helpers/valitor_helper_test.rb +++ b/test/unit/integrations/helpers/valitor_helper_test.rb @@ -20,7 +20,7 @@ def test_basic_helper_fields assert_field 'Tilvisunarnumer', 'order-500' assert_field 'Gjaldmidill', 'USD' - assert_equal Digest::MD5.hexdigest(['123', '0', '1', '1000', '0', 'cody@example.com', 'order-500', 'USD'].join('')), + assert_equal Digest::MD5.hexdigest(['123', '0', '1', '1000.00', '0', 'cody@example.com', 'order-500', 'USD'].join('')), @helper.form_fields['RafraenUndirskrift'] end @@ -30,18 +30,18 @@ def test_products assert_field 'Vara_1_Lysing', 'one' assert_field 'Vara_1_Fjoldi', '2' - assert_field 'Vara_1_Verd', '100' + assert_field 'Vara_1_Verd', '100.00' assert_field 'Vara_1_Afslattur', '50' assert_field 'Vara_2_Lysing', 'two' assert_field 'Vara_2_Fjoldi', '1' - assert_field 'Vara_2_Verd', '200' + assert_field 'Vara_2_Verd', '200.00' assert_field 'Vara_2_Afslattur', '0' assert_equal Digest::MD5.hexdigest( ['123', '0', - '2', '100', '50', - '1', '200', '0', + '2', '100.00', '50', + '1', '200.00', '0', 'cody@example.com', 'order-500', 'USD'].join('')), @helper.form_fields['RafraenUndirskrift'] end @@ -100,7 +100,7 @@ def test_urls assert_equal Digest::MD5.hexdigest( ['123', '0', - '1', '1000', '0', + '1', '1000.00', '0', 'cody@example.com', 'order-500', 'http://example.com/return', 'http://example.com/notify', 'USD'].join('')), @helper.form_fields['RafraenUndirskrift'] end @@ -127,9 +127,15 @@ def test_misc_mappings assert_field 'Lang', 'en' end - def test_amount_gets_sent_without_decimals + def test_amount_gets_sent_without_decimals_for_non_decimal_currencies @helper = Valitor::Helper.new('order-500', 'cody@example.com', :currency => 'ISK', :credential2 => '123', :amount => 115.10) @helper.form_fields assert_field "Vara_1_Verd", '115' end + + def test_amount_gets_sent_with_decimals_for_decimal_currencies + @helper = Valitor::Helper.new('order-500', 'cody@example.com', :currency => 'USD', :credential2 => '123', :amount => 115.10) + @helper.form_fields + assert_field "Vara_1_Verd", '115.10' + end end From 4a525e71ee48cde82e5c63208eef9087e4a10659 Mon Sep 17 00:00:00 2001 From: Jason Normore Date: Fri, 22 Nov 2013 15:58:00 -0500 Subject: [PATCH 071/104] Adds stripe key to allowed options in generate_options --- lib/active_merchant/billing/gateways/stripe.rb | 2 +- test/unit/gateways/stripe_test.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index 9d49187059d..b712d7b210c 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -240,7 +240,7 @@ def post_data(params) def generate_options(raw_options) options = generate_meta(raw_options) - options.merge!(raw_options.slice(:version)) + options.merge!(raw_options.slice(:version, :key)) end def generate_meta(options) diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb index 2aeae216027..ed11b94dbf0 100644 --- a/test/unit/gateways/stripe_test.rb +++ b/test/unit/gateways/stripe_test.rb @@ -326,6 +326,10 @@ def test_address_is_included_with_card_data end.respond_with(successful_purchase_response) end + def generate_options_should_allow_key + assert_equal({:key => '12345'}, generate_options({:key => '12345'})) + end + private # Create new customer and set default credit card From 4296b5e18ab06318504416b6fcd51f4c9f5d3646 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Tue, 26 Nov 2013 13:42:11 -0500 Subject: [PATCH 072/104] Fix JSON ParserError typo in Bitpay --- lib/active_merchant/billing/integrations/bit_pay/helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_merchant/billing/integrations/bit_pay/helper.rb b/lib/active_merchant/billing/integrations/bit_pay/helper.rb index 1861738dbd5..8284022c002 100644 --- a/lib/active_merchant/billing/integrations/bit_pay/helper.rb +++ b/lib/active_merchant/billing/integrations/bit_pay/helper.rb @@ -55,7 +55,7 @@ def create_invoice response = http.request(request) JSON.parse(response.body) - rescue JSON::ParseError + rescue JSON::ParserError end end end From 3e173e8155f2c3beef2a4e84cd1bc9e17ddd2821 Mon Sep 17 00:00:00 2001 From: Duff OMelia Date: Thu, 21 Nov 2013 12:44:22 -0500 Subject: [PATCH 073/104] Paymill: Fix authorizations In May 2013, Paymill added some response codes: https://blog.paymill.com/new-response-codes/ And there's now a way to trigger declines and other errors using their test gateway. This commit now accounts for the fact that a decline may have 4XX HTPP status code or it may have a 200 status code. Success in Paymill means that it's a 200 status code && the response_code within the body is 20000. Except for the Void operation. In that case, just a 200 status code is sufficient since the body doesn't include a response_code. The Purchase operation returns a 4XX for a failure but the Authorize returns a 200 for a failure. Now when an authorize fails on the Paymill side, Active Merchant once again reports that transaction as having failed. This commit also fills the response with the appropriate response message based on the response_code since the body doesn't include the text. This ActiveMerchant adapter now handles the response description mapping to give end users a better idea of what went wrong. Closes #923. --- CHANGELOG | 1 + .../billing/gateways/paymill.rb | 58 +++- test/remote/gateways/remote_paymill_test.rb | 35 +- test/unit/gateways/paymill_test.rb | 321 ++++++++++++++---- 4 files changed, 327 insertions(+), 88 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7eb2a120a81..0bed3b5c897 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ * Balanced: Add support for appears_on_statement_as [duff] * Authorize.Net: Make already actioned responses failures [odorcicd] * Add Payex gateway [atomgiant] +* Paymill: Fix authorizations [duff] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/paymill.rb b/lib/active_merchant/billing/gateways/paymill.rb index 06139864b97..3192d49ec3a 100644 --- a/lib/active_merchant/billing/gateways/paymill.rb +++ b/lib/active_merchant/billing/gateways/paymill.rb @@ -67,7 +67,7 @@ def commit(method, url, parameters=nil) raw_response = ssl_request(method, "https://api.paymill.com/v2/#{url}", post_data(parameters), headers) rescue ResponseError => e parsed = JSON.parse(e.response.body) - return Response.new(false, parsed['error'], parsed, {}) + return Response.new(false, response_message(parsed), parsed, {}) end response_from(raw_response) @@ -75,13 +75,13 @@ def commit(method, url, parameters=nil) def response_from(raw_response) parsed = JSON.parse(raw_response) - options = { :authorization => authorization_from(parsed), :test => (parsed['mode'] == 'test'), } - Response.new(true, 'Transaction approved', parsed, options) + succeeded = (parsed['data'] == []) || (parsed['data']['response_code'] == 20000) + Response.new(succeeded, response_message(parsed), parsed, options) end def authorization_from(parsed_response) @@ -175,6 +175,58 @@ def transaction_id(authorization) authorization.split(';').first end + RESPONSE_CODES = { + 10001 => "General undefined response.", + 10002 => "Still waiting on something.", + + 20000 => "General success response.", + + 40000 => "General problem with data.", + 40001 => "General problem with payment data.", + 40100 => "Problem with credit card data.", + 40101 => "Problem with cvv.", + 40102 => "Card expired or not yet valid.", + 40103 => "Limit exceeded.", + 40104 => "Card invalid.", + 40105 => "Expiry date not valid.", + 40106 => "Credit card brand required.", + 40200 => "Problem with bank account data.", + 40201 => "Bank account data combination mismatch.", + 40202 => "User authentication failed.", + 40300 => "Problem with 3d secure data.", + 40301 => "Currency / amount mismatch", + 40400 => "Problem with input data.", + 40401 => "Amount too low or zero.", + 40402 => "Usage field too long.", + 40403 => "Currency not allowed.", + + 50000 => "General problem with backend.", + 50001 => "Country blacklisted.", + 50100 => "Technical error with credit card.", + 50101 => "Error limit exceeded.", + 50102 => "Card declined by authorization system.", + 50103 => "Manipulation or stolen card.", + 50104 => "Card restricted.", + 50105 => "Invalid card configuration data.", + 50200 => "Technical error with bank account.", + 50201 => "Card blacklisted.", + 50300 => "Technical error with 3D secure.", + 50400 => "Decline because of risk issues.", + 50500 => "General timeout.", + 50501 => "Timeout on side of the acquirer.", + 50502 => "Risk management transaction timeout.", + 50600 => "Duplicate transaction." + } + + def response_message(parsed_response) + return parsed_response["error"] if parsed_response["error"] + return "Transaction approved." if (parsed_response['data'] == []) + + code = parsed_response["data"]["response_code"] + RESPONSE_CODES[code] || code.to_s + end + + class ResponseParser def initialize(raw_response="", options={}) @raw_response = raw_response diff --git a/test/remote/gateways/remote_paymill_test.rb b/test/remote/gateways/remote_paymill_test.rb index c4189267bc2..68c01b2e849 100644 --- a/test/remote/gateways/remote_paymill_test.rb +++ b/test/remote/gateways/remote_paymill_test.rb @@ -5,31 +5,44 @@ def setup @gateway = PaymillGateway.new(fixtures(:paymill)) @amount = 100 - @credit_card = credit_card('5105105105105100') + @credit_card = credit_card('5500000000000004') + @declined_card = credit_card('5105105105105100', month: 5, year: 2020) end def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card) assert_success response - assert_equal 'Transaction approved', response.message + assert_equal 'General success response.', response.message end - def test_failed_purchase_with_invalid_card + def test_failed_store_card_attempting_purchase @credit_card.number = '' assert response = @gateway.purchase(@amount, @credit_card) assert_failure response assert_equal 'Account or Bank Details Incorrect', response.message end + def test_failed_purchase + assert response = @gateway.purchase(@amount, @declined_card) + assert_failure response + assert_equal 'Card declined by authorization system.', response.message + end + def test_successful_authorize_and_capture assert response = @gateway.authorize(@amount, @credit_card) assert_success response - assert_equal 'Transaction approved', response.message + assert_equal 'General success response.', response.message assert response.authorization assert capture_response = @gateway.capture(@amount, response.authorization) assert_success capture_response - assert_equal 'Transaction approved', capture_response.message + assert_equal 'General success response.', capture_response.message + end + + def test_failed_authorize + assert response = @gateway.authorize(@amount, @declined_card) + assert_failure response + assert_equal 'Card declined by authorization system.', response.message end def test_failed_capture @@ -47,12 +60,12 @@ def test_failed_capture def test_successful_authorize_and_void assert response = @gateway.authorize(@amount, @credit_card) assert_success response - assert_equal 'Transaction approved', response.message + assert_equal 'General success response.', response.message assert response.authorization assert void_response = @gateway.void(response.authorization) assert_success void_response - assert_equal 'Transaction approved', void_response.message + assert_equal 'Transaction approved.', void_response.message end def test_successful_refund @@ -62,7 +75,7 @@ def test_successful_refund assert refund = @gateway.refund(@amount, response.authorization) assert_success refund - assert_equal 'Transaction approved', refund.message + assert_equal 'General success response.', refund.message end def test_failed_refund @@ -107,10 +120,4 @@ def test_successful_store_and_authorize assert_success authorize end - # Paymill doesn't yet offer a way to trigger a decline on a test account. - # def test_failed_purchase_with_declined_credit_card - # assert response = @gateway.purchase(@amount, @declined_card) - # assert_failure response - # assert_equal 'Unable to process the purchase transaction.', response.message - # end end diff --git a/test/unit/gateways/paymill_test.rb b/test/unit/gateways/paymill_test.rb index 5417a0016e4..9c311960c7c 100644 --- a/test/unit/gateways/paymill_test.rb +++ b/test/unit/gateways/paymill_test.rb @@ -14,7 +14,7 @@ def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card) assert_success response assert_equal "tran_c94ba7df2dae8fd55028df41173c;", response.authorization - assert_equal "Transaction approved", response.message + assert_equal "General success response.", response.message assert_equal 20000, response.params['data']['response_code'] assert_equal 'pay_b8e6a28fc5e5e1601cdbefbaeb8a', response.params['data']['payment']['id'] assert_equal '5100', response.params['data']['payment']['last4'] @@ -23,7 +23,7 @@ def test_successful_purchase assert response.test? end - def test_failed_purchase_with_invalid_credit_card + def test_failed_store_card_attempting_purchase @gateway.expects(:raw_ssl_request).returns(failed_store_response) response = @gateway.purchase(@amount, @credit_card) assert_failure response @@ -31,6 +31,14 @@ def test_failed_purchase_with_invalid_credit_card assert_equal '000.100.201', response.params['transaction']['processing']['return']['code'] end + def test_failed_purchase + @gateway.stubs(:raw_ssl_request).returns(successful_store_response, failed_purchase_response) + response = @gateway.purchase(@amount, @credit_card) + assert_failure response + assert_equal 'Card declined by authorization system.', response.message + assert_equal 50102, response.params['data']['response_code'] + end + def test_invalid_login @gateway.stubs(:raw_ssl_request).returns(successful_store_response, failed_login_response) response = @gateway.purchase(@amount, @credit_card) @@ -52,10 +60,10 @@ def test_successful_authorize_and_capture assert_success response assert response.test? - assert_equal "tran_50fb13e10636cf1e59e13018d100;preauth_57c0c87ae3d193f66dc8", response.authorization - assert_equal "Transaction approved", response.message - assert_equal '5100', response.params['data']['payment']['last4'] - assert_equal 10001, response.params['data']['response_code'] + assert_equal "tran_4c612d5293e26d56d986eb89648c;preauth_fdf916cab73b97c4a139", response.authorization + assert_equal "General success response.", response.message + assert_equal '0004', response.params['data']['payment']['last4'] + assert_equal 20000, response.params['data']['response_code'] assert_nil response.avs_result["message"] assert_nil response.cvv_result["message"] @@ -64,7 +72,15 @@ def test_successful_authorize_and_capture assert_success response assert response.test? assert_equal 20000, response.params['data']['response_code'] - assert_equal "Transaction approved", response.message + assert_equal "General success response.", response.message + end + + def test_failed_authorize + @gateway.stubs(:raw_ssl_request).returns(successful_store_response, failed_authorize_response) + response = @gateway.authorize(@amount, @credit_card) + assert_failure response + assert_equal 'Card declined by authorization system.', response.message + assert_equal 50102, response.params['data']['response_code'] end def test_successful_authorize_and_void @@ -77,7 +93,7 @@ def test_successful_authorize_and_void response = @gateway.void(response.authorization) assert_success response assert response.test? - assert_equal "Transaction approved", response.message + assert_equal "Transaction approved.", response.message end def test_failed_capture @@ -101,7 +117,7 @@ def test_successful_refund assert_success refund assert response.test? - assert_equal 'Transaction approved', refund.message + assert_equal 'General success response.', refund.message assert_equal 'tran_89c8728e94273510afa99ab64e45', refund.params['data']['transaction']['id'] assert_equal 'refund_d02807f46181c0919016;', refund.authorization assert_equal 20000, refund.params['data']['response_code'] @@ -142,7 +158,7 @@ def test_successful_purchase_with_token assert response = @gateway.purchase(@amount, "token") assert_success response assert_equal "tran_c94ba7df2dae8fd55028df41173c;", response.authorization - assert_equal "Transaction approved", response.message + assert_equal "General success response.", response.message assert_equal 20000, response.params['data']['response_code'] assert_equal 'pay_b8e6a28fc5e5e1601cdbefbaeb8a', response.params['data']['payment']['id'] assert_equal '5100', response.params['data']['payment']['last4'] @@ -158,10 +174,10 @@ def test_successful_authorize_with_token assert_success response assert response.test? - assert_equal "tran_50fb13e10636cf1e59e13018d100;preauth_57c0c87ae3d193f66dc8", response.authorization - assert_equal "Transaction approved", response.message - assert_equal '5100', response.params['data']['payment']['last4'] - assert_equal 10001, response.params['data']['response_code'] + assert_equal "tran_4c612d5293e26d56d986eb89648c;preauth_fdf916cab73b97c4a139", response.authorization + assert_equal "General success response.", response.message + assert_equal '0004', response.params['data']['payment']['last4'] + assert_equal 20000, response.params['data']['response_code'] assert_nil response.avs_result["message"] assert_nil response.cvv_result["message"] end @@ -232,81 +248,244 @@ def successful_purchase_response JSON end + def failed_purchase_response + MockResponse.failed <<-JSON + { + "data":{ + "id":"tran_a432ce3b113cdd65b48e0d05db88", + "amount":"100", + "origin_amount":100, + "status":"failed", + "description":null, + "livemode":false, + "refunds":null, + "currency":"EUR", + "created_at":1385054845, + "updated_at":1385054845, + "response_code":50102, + "short_id":null, + "is_fraud":false, + "invoices":[ + + ], + "app_id":null, + "fees":[ + + ], + "payment":{ + "id":"pay_8b75b960574031979a880f98", + "type":"creditcard", + "client":"client_a69d8452d530ed20b297", + "card_type":"mastercard", + "country":null, + "expire_month":"5", + "expire_year":"2020", + "card_holder":"", + "last4":"5100", + "created_at":1385054844, + "updated_at":1385054845, + "app_id":null + }, + "client":{ + "id":"client_a69d8452d530ed20b297", + "email":null, + "description":null, + "created_at":1385054845, + "updated_at":1385054845, + "app_id":null, + "payment":[ + + ], + "subscription":null + }, + "preauthorization":null + }, + "mode":"test" + } + JSON + end + def successful_authorize_response MockResponse.succeeded <<-JSON - { "data":{ - "id":"tran_50fb13e10636cf1e59e13018d100", - "amount":"100", - "origin_amount":100, - "status":"preauth", - "description":null, - "livemode":false, - "refunds":null, - "currency":"EUR", - "created_at":1360787311, - "updated_at":1360787311, - "response_code":10001, - "invoices":[ - - ], - "payment":{ - "id":"pay_58e0662ef367027b2356f263e5aa", - "type":"creditcard", - "client":"client_9e4b7b0d61adc9a9e64e", - "card_type":"mastercard", - "country":null, - "expire_month":9, - "expire_year":2014, - "card_holder":null, - "last4":"5100", - "created_at":1360787310, - "updated_at":1360787311 - }, - "client":{ - "id":"client_9e4b7b0d61adc9a9e64e", - "email":null, - "description":null, - "created_at":1360787311, - "updated_at":1360787311, - "payment":[ + { + "data":{ + "id":"tran_4c612d5293e26d56d986eb89648c", + "amount":"100", + "origin_amount":100, + "status":"preauth", + "description":null, + "livemode":false, + "refunds":null, + "currency":"EUR", + "created_at":1385054035, + "updated_at":1385054035, + "response_code":20000, + "short_id":"7357.7357.7357", + "is_fraud":false, + "invoices":[ - ], - "subscription":null + ], + "app_id":null, + "fees":[ + + ], + "payment":{ + "id":"pay_f9ff269434185e0789106758", + "type":"creditcard", + "client":"client_d5179e1b6a8f596b19b9", + "card_type":"mastercard", + "country":null, + "expire_month":"9", + "expire_year":"2014", + "card_holder":"", + "last4":"0004", + "created_at":1385054033, + "updated_at":1385054035, + "app_id":null + }, + "client":{ + "id":"client_d5179e1b6a8f596b19b9", + "email":null, + "description":null, + "created_at":1385054035, + "updated_at":1385054035, + "app_id":null, + "payment":[ + + ], + "subscription":null + }, + "preauthorization":{ + "id":"preauth_fdf916cab73b97c4a139", + "amount":"100", + "currency":"EUR", + "status":"closed", + "livemode":false, + "created_at":1385054035, + "updated_at":1385054035, + "app_id":null, + "payment":{ + "id":"pay_f9ff269434185e0789106758", + "type":"creditcard", + "client":"client_d5179e1b6a8f596b19b9", + "card_type":"mastercard", + "country":null, + "expire_month":"9", + "expire_year":"2014", + "card_holder":"", + "last4":"0004", + "created_at":1385054033, + "updated_at":1385054035, + "app_id":null + }, + "client":{ + "id":"client_d5179e1b6a8f596b19b9", + "email":null, + "description":null, + "created_at":1385054035, + "updated_at":1385054035, + "app_id":null, + "payment":[ + + ], + "subscription":null + } + } }, - "preauthorization":{ - "id":"preauth_57c0c87ae3d193f66dc8", + "mode":"test" + } + JSON + end + + # Paymill returns an HTTP Status code of 200 for an auth failure. + def failed_authorize_response + MockResponse.succeeded <<-JSON + { + "data":{ + "id":"tran_e53189278c7250bfa15c9c580ff2", "amount":"100", - "status":"closed", + "origin_amount":100, + "status":"failed", + "description":null, "livemode":false, - "created_at":1360787311, - "updated_at":1360787311, + "refunds":null, + "currency":"EUR", + "created_at":1385054501, + "updated_at":1385054501, + "response_code":50102, + "short_id":null, + "is_fraud":false, + "invoices":[ + + ], + "app_id":null, + "fees":[ + + ], "payment":{ - "id":"pay_58e0662ef367027b2356f263e5aa", + "id":"pay_7bc2d73764f38040df934995", "type":"creditcard", - "client":"client_9e4b7b0d61adc9a9e64e", + "client":"client_531e6247ff900e734884", "card_type":"mastercard", "country":null, - "expire_month":9, - "expire_year":2014, - "card_holder":null, + "expire_month":"5", + "expire_year":"2020", + "card_holder":"", "last4":"5100", - "created_at":1360787310, - "updated_at":1360787311 - }, - "client":{ - "id":"client_9e4b7b0d61adc9a9e64e", + "created_at":1385054500, + "updated_at":1385054501, + "app_id":null + }, + "client":{ + "id":"client_531e6247ff900e734884", "email":null, "description":null, - "created_at":1360787311, - "updated_at":1360787311, + "created_at":1385054501, + "updated_at":1385054501, + "app_id":null, "payment":[ ], "subscription":null + }, + "preauthorization":{ + "id":"preauth_cfa6a29b4c679efee58b", + "amount":"100", + "currency":"EUR", + "status":"failed", + "livemode":false, + "created_at":1385054501, + "updated_at":1385054501, + "app_id":null, + "payment":{ + "id":"pay_7bc2d73764f38040df934995", + "type":"creditcard", + "client":"client_531e6247ff900e734884", + "card_type":"mastercard", + "country":null, + "expire_month":"5", + "expire_year":"2020", + "card_holder":"", + "last4":"5100", + "created_at":1385054500, + "updated_at":1385054501, + "app_id":null + }, + "client":{ + "id":"client_531e6247ff900e734884", + "email":null, + "description":null, + "created_at":1385054501, + "updated_at":1385054501, + "app_id":null, + "payment":[ + + ], + "subscription":null + } } - } - }, - "mode":"test" + }, + "mode":"test" } JSON end From 125e86b71e9b490fce196794a2e2c842383b1d72 Mon Sep 17 00:00:00 2001 From: Nathaniel Talbott Date: Fri, 29 Nov 2013 11:10:28 -0500 Subject: [PATCH 074/104] Braintree Blue: Allow specifying credit card token This makes it possible to use an explicit card token, rather than Braintree automatically generating it. If no token is passed it continues to be generated automatically. Also exposes the credit card token in the response. --- CHANGELOG | 1 + .../billing/gateways/braintree_blue.rb | 6 +++-- .../gateways/remote_braintree_blue_test.rb | 8 +++++++ test/unit/gateways/braintree_blue_test.rb | 22 +++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0bed3b5c897..e71feb37004 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ * Authorize.Net: Make already actioned responses failures [odorcicd] * Add Payex gateway [atomgiant] * Paymill: Fix authorizations [duff] +* Braintree Blue: Allow specifying the credit card token [ntalbott] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb index 656b8ec139c..f8310c8626e 100644 --- a/lib/active_merchant/billing/gateways/braintree_blue.rb +++ b/lib/active_merchant/billing/gateways/braintree_blue.rb @@ -120,7 +120,8 @@ def store(creditcard, options = {}) :number => creditcard.number, :cvv => creditcard.verification_value, :expiration_month => creditcard.month.to_s.rjust(2, "0"), - :expiration_year => creditcard.year.to_s + :expiration_year => creditcard.year.to_s, + :token => options[:credit_card_token] } } result = @braintree_gateway.customer.create(merge_credit_card_options(parameters, options)) @@ -289,7 +290,8 @@ def customer_hash(customer) "token" => cc.token, "last_4" => cc.last_4, "card_type" => cc.card_type, - "masked_number" => cc.masked_number + "masked_number" => cc.masked_number, + "token" => cc.token } end diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb index 5929b5d5d21..3e7f3438e5b 100644 --- a/test/remote/gateways/remote_braintree_blue_test.rb +++ b/test/remote/gateways/remote_braintree_blue_test.rb @@ -148,6 +148,14 @@ def test_successful_store_with_billing_address assert_equal purchase_response.params['braintree_transaction']['billing_details'], response_billing_details end + def test_successful_store_with_credit_card_token + credit_card = credit_card('5105105105105100') + assert response = @gateway.store(credit_card, credit_card_token: "cctoken") + assert_success response + assert_equal 'OK', response.message + assert_equal "cctoken", response.params["braintree_customer"]["credit_cards"][0]["token"] + end + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb index ff3ae73d0fb..24ef957a654 100644 --- a/test/unit/gateways/braintree_blue_test.rb +++ b/test/unit/gateways/braintree_blue_test.rb @@ -155,6 +155,28 @@ def test_store_with_billing_address_options @gateway.store(credit_card("41111111111111111111"), :billing_address => billing_address) end + def test_store_with_credit_card_token + customer = mock( + :email => 'email', + :first_name => 'John', + :last_name => 'Smith' + ) + customer.stubs(:id).returns('123') + + braintree_credit_card = stub_everything(token: "cctoken") + customer.stubs(:credit_cards).returns([braintree_credit_card]) + + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + assert_equal "cctoken", params[:credit_card][:token] + params + end.returns(result) + + response = @gateway.store(credit_card("41111111111111111111"), :credit_card_token => "cctoken") + assert_success response + assert_equal "cctoken", response.params["braintree_customer"]["credit_cards"][0]["token"] + end + def test_update_with_cvv stored_credit_card = mock(:token => "token", :default? => true) customer = mock(:credit_cards => [stored_credit_card], :id => '123') From d4a3b9ee39789e120e78a5eae7b05dae1c18c293 Mon Sep 17 00:00:00 2001 From: Nathaniel Talbott Date: Fri, 29 Nov 2013 11:38:44 -0500 Subject: [PATCH 075/104] Braintree Blue: Allow specifying the customer id This allows specifying a customer id when storing a credit card rather than having one generated automatically. --- CHANGELOG | 1 + .../billing/gateways/braintree_blue.rb | 1 + .../gateways/remote_braintree_blue_test.rb | 16 ++++++++++++--- test/unit/gateways/braintree_blue_test.rb | 20 +++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e71feb37004..11a7fcdc848 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ * Add Payex gateway [atomgiant] * Paymill: Fix authorizations [duff] * Braintree Blue: Allow specifying the credit card token [ntalbott] +* Braintree Blue: Allow specifying the customer id [ntalbott] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb index f8310c8626e..22fd634f0d1 100644 --- a/lib/active_merchant/billing/gateways/braintree_blue.rb +++ b/lib/active_merchant/billing/gateways/braintree_blue.rb @@ -116,6 +116,7 @@ def store(creditcard, options = {}) :first_name => creditcard.first_name, :last_name => creditcard.last_name, :email => options[:email], + :id => options[:customer], :credit_card => { :number => creditcard.number, :cvv => creditcard.verification_value, diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb index 3e7f3438e5b..ba56b125266 100644 --- a/test/remote/gateways/remote_braintree_blue_test.rb +++ b/test/remote/gateways/remote_braintree_blue_test.rb @@ -156,6 +156,16 @@ def test_successful_store_with_credit_card_token assert_equal "cctoken", response.params["braintree_customer"]["credit_cards"][0]["token"] end + def test_successful_store_with_customer_id + credit_card = credit_card('5105105105105100') + customer_id = generate_unique_id + assert response = @gateway.store(credit_card, customer: customer_id) + assert_success response + assert_equal 'OK', response.message + assert_equal customer_id, response.authorization + assert_equal customer_id, response.params["braintree_customer"]["id"] + end + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response @@ -233,7 +243,7 @@ def test_purchase_with_store_using_random_customer_id ) assert_success response assert_equal '1000 Approved', response.message - assert_match(/\A\d{6,7}\z/, response.params["customer_vault_id"]) + assert_match(/\A\d+\z/, response.params["customer_vault_id"]) assert_equal '510510', response.params["braintree_transaction"]["vault_customer"]["credit_cards"][0]["bin"] assert_equal '510510', @braintree_backend.customer.find(response.params["customer_vault_id"]).credit_cards[0].bin end @@ -499,8 +509,8 @@ def test_failed_credit_card_update_on_verify end def test_customer_does_not_have_credit_card_failed_update - customer_without_credit_card = @braintree_backend.create - assert response = @gateway.update(customer_without_credit_card.id, credit_card('5105105105105100')) + customer_without_credit_card = @braintree_backend.customer.create + assert response = @gateway.update(customer_without_credit_card.customer.id, credit_card('5105105105105100')) assert_failure response assert_equal 'Braintree::NotFoundError', response.message end diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb index 24ef957a654..801e0a0fdce 100644 --- a/test/unit/gateways/braintree_blue_test.rb +++ b/test/unit/gateways/braintree_blue_test.rb @@ -177,6 +177,26 @@ def test_store_with_credit_card_token assert_equal "cctoken", response.params["braintree_customer"]["credit_cards"][0]["token"] end + def test_store_with_customer_id + customer = mock( + :email => 'email', + :first_name => 'John', + :last_name => 'Smith', + :credit_cards => [] + ) + customer.stubs(:id).returns("customerid") + + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + assert_equal "customerid", params[:id] + params + end.returns(result) + + response = @gateway.store(credit_card("41111111111111111111"), :customer => "customerid") + assert_success response + assert_equal "customerid", response.params["braintree_customer"]["id"] + end + def test_update_with_cvv stored_credit_card = mock(:token => "token", :default? => true) customer = mock(:credit_cards => [stored_credit_card], :id => '123') From fc6d0acaf50c4bc72cd4298c3b2c3d44b9c3b4d6 Mon Sep 17 00:00:00 2001 From: Nathaniel Talbott Date: Fri, 29 Nov 2013 13:08:34 -0500 Subject: [PATCH 076/104] Braintree Blue: Scrub invalid emails and zips Braintree is strict with emails and zips, which can cause unexpected failures. This simply substitutes nil if a known-bad email or zip is passed in so that the transaction doesn't fail due to the "bad" input. This also improves the remote tests, including giving more guidance on tests that require further configuration before they'll pass. --- CHANGELOG | 1 + .../billing/gateways/braintree_blue.rb | 26 ++++- test/fixtures.yml | 1 + .../gateways/remote_braintree_blue_test.rb | 97 +++++++++---------- test/test_helper.rb | 10 +- test/unit/gateways/braintree_blue_test.rb | 48 +++++++++ 6 files changed, 125 insertions(+), 58 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 11a7fcdc848..7f25824c81f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ * Paymill: Fix authorizations [duff] * Braintree Blue: Allow specifying the credit card token [ntalbott] * Braintree Blue: Allow specifying the customer id [ntalbott] +* Braintree Blue: Scrub invalid emails and zips [ntalbott] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb index 22fd634f0d1..6b1b75c80e9 100644 --- a/lib/active_merchant/billing/gateways/braintree_blue.rb +++ b/lib/active_merchant/billing/gateways/braintree_blue.rb @@ -115,7 +115,7 @@ def store(creditcard, options = {}) parameters = { :first_name => creditcard.first_name, :last_name => creditcard.last_name, - :email => options[:email], + :email => scrub_email(options[:email]), :id => options[:customer], :credit_card => { :number => creditcard.number, @@ -155,7 +155,7 @@ def update(vault_id, creditcard, options = {}) result = @braintree_gateway.customer.update(vault_id, :first_name => creditcard.first_name, :last_name => creditcard.last_name, - :email => options[:email], + :email => scrub_email(options[:email]), :credit_card => credit_card_params ) Response.new(result.success?, message_from_result(result), @@ -175,6 +175,24 @@ def unstore(customer_vault_id, options = {}) private + def scrub_email(email) + return nil unless email.present? + return nil if ( + email !~ /^.+@[^\.]+(\.[^\.]+)+[a-z]$/i || + email =~ /\.(con|met)$/i + ) + email + end + + def scrub_zip(zip) + return nil unless zip.present? + return nil if( + zip.gsub(/[^a-z0-9]/i, '').length > 9 || + zip =~ /[^a-z0-9\- ]/i + ) + zip + end + def merge_credit_card_options(parameters, options) valid_options = {} options.each do |key, value| @@ -195,7 +213,7 @@ def map_address(address) :company => address[:company], :locality => address[:city], :region => address[:state], - :postal_code => address[:zip], + :postal_code => scrub_zip(address[:zip]), } if(address[:country] || address[:country_code_alpha2]) mapped[:country_code_alpha2] = (address[:country] || address[:country_code_alpha2]) @@ -368,7 +386,7 @@ def create_transaction_parameters(money, credit_card_or_vault_id, options) :order_id => options[:order_id], :customer => { :id => options[:store] == true ? "" : options[:store], - :email => options[:email] + :email => scrub_email(options[:email]) }, :options => { :store_in_vault => options[:store] ? true : false, diff --git a/test/fixtures.yml b/test/fixtures.yml index 8bb33826770..0933b7b9ffc 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -64,6 +64,7 @@ braintree_blue: merchant_id: X public_key: Y private_key: Z + merchant_account_id: A braintree_blue_with_processing_rules: merchant_id: X diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb index ba56b125266..3889b2a621e 100644 --- a/test/remote/gateways/remote_braintree_blue_test.rb +++ b/test/remote/gateways/remote_braintree_blue_test.rb @@ -51,19 +51,12 @@ def test_successful_authorize_with_order_id assert_equal '123', response.params["braintree_transaction"]["order_id"] end - def test_successful_authorize_with_merchant_account_id - assert response = @gateway.authorize(@amount, @credit_card, :merchant_account_id => 'sandbox_credit_card_non_default') - assert_success response - assert_equal '1000 Approved', response.message - assert_equal 'sandbox_credit_card_non_default', response.params["braintree_transaction"]["merchant_account_id"] - end - def test_successful_purchase_using_vault_id assert response = @gateway.store(@credit_card) assert_success response assert_equal 'OK', response.message customer_vault_id = response.params["customer_vault_id"] - assert_match(/\A\d{6,7}\z/, customer_vault_id) + assert_match(/\A\d+\z/, customer_vault_id) assert response = @gateway.purchase(@amount, customer_vault_id) assert_success response @@ -77,7 +70,7 @@ def test_successful_purchase_using_vault_id_as_integer assert_success response assert_equal 'OK', response.message customer_vault_id = response.params["customer_vault_id"] - assert_match /\A\d{6,7}\z/, customer_vault_id + assert_match /\A\d+\z/, customer_vault_id assert response = @gateway.purchase(@amount, customer_vault_id.to_i) assert_success response @@ -93,13 +86,6 @@ def test_successful_validate_on_store assert_equal 'OK', response.message end - def test_successful_validate_on_store_with_verification_merchant_account - card = credit_card('4111111111111111', :verification_value => '101') - assert response = @gateway.store(card, :verify_card => true, :verification_merchant_account_id => 'sandbox_credit_card_non_default') - assert_success response - assert_equal 'OK', response.message - end - def test_failed_validate_on_store card = credit_card('4000111111111115', :verification_value => '200') assert response = @gateway.store(card, :verify_card => true) @@ -150,10 +136,11 @@ def test_successful_store_with_billing_address def test_successful_store_with_credit_card_token credit_card = credit_card('5105105105105100') - assert response = @gateway.store(credit_card, credit_card_token: "cctoken") + credit_card_token = generate_unique_id + assert response = @gateway.store(credit_card, credit_card_token: credit_card_token) assert_success response assert_equal 'OK', response.message - assert_equal "cctoken", response.params["braintree_customer"]["credit_cards"][0]["token"] + assert_equal credit_card_token, response.params["braintree_customer"]["credit_cards"][0]["token"] end def test_successful_store_with_customer_id @@ -202,7 +189,7 @@ def test_transaction_fails_with_bad_avs_with_avs_rules ) ) - assert_failure response + assert_failure response, "This test will fail unless you specify a braintree_blue_with_processing_rules that has AVS required." assert_equal("Transaction declined - gateway rejected", response.message) assert_equal({'code' => nil, 'message' => nil, 'street_match' => 'N', 'postal_match' => 'N'}, response.avs_result) end @@ -223,7 +210,8 @@ def test_transaction_fails_with_bad_cvv_with_cvv_rules gateway = BraintreeGateway.new(fixtures(:braintree_blue_with_processing_rules)) assert response = gateway.purchase(@amount, credit_card('5105105105105100', :verification_value => '200')) - assert_failure response + assert_failure response, "This test will fail unless you specify a braintree_blue_with_processing_rules that has CVV required." + assert_equal("Transaction declined - gateway rejected", response.message) assert_equal({'code' => 'N', 'message' => ''}, response.cvv_result) end @@ -308,11 +296,9 @@ def test_unsuccessful_purchase_declined end def test_unsuccessful_purchase_validation_error - assert response = @gateway.purchase(@amount, @credit_card, - @options.merge(:email => "invalid_email") - ) + assert response = @gateway.purchase(@amount, credit_card('51051051051051000')) assert_failure response - assert_equal 'Email is an invalid format. (81604)', response.message + assert_match %r{Credit card number is invalid\. \(81715\)}, response.message assert_equal nil, response.params["braintree_transaction"] end @@ -369,7 +355,7 @@ def test_successful_add_to_vault_with_store_method assert response = @gateway.store(@credit_card) assert_success response assert_equal 'OK', response.message - assert_match(/\A\d{6,7}\z/, response.params["customer_vault_id"]) + assert_match(/\A\d+\z/, response.params["customer_vault_id"]) end def test_failed_add_to_vault @@ -398,26 +384,6 @@ def test_unstore_with_delete_method assert_success delete_response end - def test_successful_credit - assert response = @gateway.credit(@amount, @credit_card, @options) - assert_success response - assert_equal '1002 Processed', response.message - assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"] - end - - def test_successful_credit_with_merchant_account_id - assert response = @gateway.credit(@amount, @credit_card, :merchant_account_id => 'sandbox_credit_card_non_default') - assert_success response - assert_equal '1002 Processed', response.message - assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"] - end - - def test_failed_credit - assert response = @gateway.credit(@amount, credit_card('5105105105105101'), @options) - assert_failure response - assert_equal 'Credit card number is invalid. (81715)', response.message - end - def test_successful_update assert response = @gateway.store( credit_card('4111111111111111', @@ -429,7 +395,7 @@ def test_successful_update assert_success response assert_equal 'OK', response.message customer_vault_id = response.params["customer_vault_id"] - assert_match(/\A\d{6,7}\z/, customer_vault_id) + assert_match(/\A\d+\z/, customer_vault_id) assert_equal "old@example.com", response.params["braintree_customer"]["email"] assert_equal "Old First", response.params["braintree_customer"]["first_name"] assert_equal "Old Last", response.params["braintree_customer"]["last_name"] @@ -464,11 +430,10 @@ def test_failed_customer_update assert response = @gateway.update( customer_vault_id, - credit_card('5105105105105100'), - :email => "invalid-email" + credit_card('51051051051051001') ) assert_failure response - assert_equal 'Email is an invalid format. (81604)', response.message + assert_equal 'Credit card number is invalid. (81715)', response.message assert_equal nil, response.params["braintree_customer"] assert_equal nil, response.params["customer_vault_id"] end @@ -514,4 +479,38 @@ def test_customer_does_not_have_credit_card_failed_update assert_failure response assert_equal 'Braintree::NotFoundError', response.message end + + def test_successful_credit + assert response = @gateway.credit(@amount, @credit_card, @options) + assert_success response, "You must get credits enabled in your Sandbox account for this to pass." + assert_equal '1002 Processed', response.message + assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"] + end + + def test_failed_credit + assert response = @gateway.credit(@amount, credit_card('5105105105105101'), @options) + assert_failure response + assert_equal 'Credit card number is invalid. (81715)', response.message, "You must get credits enabled in your Sandbox account for this to pass." + end + + def test_successful_credit_with_merchant_account_id + assert response = @gateway.credit(@amount, @credit_card, :merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id]) + assert_success response, "You must specify a valid :merchant_account_id key in your fixtures.yml AND get credits enabled in your Sandbox account for this to pass." + assert_equal '1002 Processed', response.message + assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"] + end + + def test_successful_authorize_with_merchant_account_id + assert response = @gateway.authorize(@amount, @credit_card, :merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id]) + assert_success response, "You must specify a valid :merchant_account_id key in your fixtures.yml for this to pass." + assert_equal '1000 Approved', response.message + assert_equal fixtures(:braintree_blue)[:merchant_account_id], response.params["braintree_transaction"]["merchant_account_id"] + end + + def test_successful_validate_on_store_with_verification_merchant_account + card = credit_card('4111111111111111', :verification_value => '101') + assert response = @gateway.store(card, :verify_card => true, :verification_merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id]) + assert_success response, "You must specify a valid :merchant_account_id key in your fixtures.yml for this to pass." + assert_equal 'OK', response.message + end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 6b37b224c42..7f00a54a75a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -94,23 +94,23 @@ def assert_false(boolean, message = nil) # A handy little assertion to check for a successful response: # # # Instead of - # assert_success response + # assert response.success? # # # DRY that up with # assert_success response # # A message will automatically show the inspection of the response # object if things go afoul. - def assert_success(response) + def assert_success(response, message=nil) clean_backtrace do - assert response.success?, "Response failed: #{response.inspect}" + assert response.success?, build_message(nil, "#{message + "\n" if message}Response expected to succeed: ", response) end end # The negative of +assert_success+ - def assert_failure(response) + def assert_failure(response, message=nil) clean_backtrace do - assert_false response.success?, "Response expected to fail: #{response.inspect}" + assert !response.success?, build_message(nil, "#{message + "\n" if message}Response expected to fail: ", response) end end diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb index 801e0a0fdce..f097e2712ae 100644 --- a/test/unit/gateways/braintree_blue_test.rb +++ b/test/unit/gateways/braintree_blue_test.rb @@ -106,6 +106,42 @@ def test_store_with_verify_card_true assert_equal response.params["customer_vault_id"], response.authorization end + def test_passes_email + customer = stub( + :credit_cards => [], + :email => "bob@example.com", + :first_name => 'John', + :last_name => 'Smith', + id: "123" + ) + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + assert_equal "bob@example.com", params[:email] + params + end.returns(result) + + response = @gateway.store(credit_card("41111111111111111111"), :email => "bob@example.com") + assert_success response + end + + def test_scrubs_invalid_email + customer = stub( + :credit_cards => [], + :email => nil, + :first_name => 'John', + :last_name => 'Smith', + :id => "123" + ) + result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:create).with do |params| + assert_equal nil, params[:email] + params + end.returns(result) + + response = @gateway.store(credit_card("41111111111111111111"), :email => "bogus") + assert_success response + end + def test_store_with_verify_card_false customer = mock( :credit_cards => [], @@ -310,6 +346,18 @@ def test_address_country_handling @gateway.purchase(100, credit_card("41111111111111111111"), :billing_address => {:country_code_numeric => 840}) end + def test_address_zip_handling + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:billing][:postal_code] == "12345") + end.returns(braintree_result) + @gateway.purchase(100, credit_card("41111111111111111111"), :billing_address => {:zip => "12345"}) + + Braintree::TransactionGateway.any_instance.expects(:sale).with do |params| + (params[:billing][:postal_code] == nil) + end.returns(braintree_result) + @gateway.purchase(100, credit_card("41111111111111111111"), :billing_address => {:zip => "1234567890"}) + end + def test_passes_recurring_flag @gateway = BraintreeBlueGateway.new( :merchant_id => 'test', From 7dc4aac9a97e78ad10ebe779e41021e698217609 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Fri, 29 Nov 2013 13:54:01 -0500 Subject: [PATCH 077/104] Fix Ipay88 requery --- lib/active_merchant/billing/integrations/ipay88.rb | 4 ++++ lib/active_merchant/billing/integrations/ipay88/return.rb | 2 +- test/unit/integrations/ipay88_module_test.rb | 4 ++++ test/unit/integrations/returns/ipay88_return_test.rb | 6 +++--- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/active_merchant/billing/integrations/ipay88.rb b/lib/active_merchant/billing/integrations/ipay88.rb index cc6b77f2c01..b47516428ad 100644 --- a/lib/active_merchant/billing/integrations/ipay88.rb +++ b/lib/active_merchant/billing/integrations/ipay88.rb @@ -10,6 +10,10 @@ def self.service_url "https://www.mobile88.com/epayment/entry.asp" end + def self.requery_url + "https://www.mobile88.com/epayment/enquiry.asp" + end + def self.return(query_string, options={}) Return.new(query_string, options) end diff --git a/lib/active_merchant/billing/integrations/ipay88/return.rb b/lib/active_merchant/billing/integrations/ipay88/return.rb index 14e1bc8334b..25bafa90f19 100644 --- a/lib/active_merchant/billing/integrations/ipay88/return.rb +++ b/lib/active_merchant/billing/integrations/ipay88/return.rb @@ -76,7 +76,7 @@ def sig_components def requery data = { "MerchantCode" => self.account, "RefNo" => self.order, "Amount" => self.amount } params = parameterize(data) - ssl_post Ipay88.service_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } + ssl_post Ipay88.requery_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } end private diff --git a/test/unit/integrations/ipay88_module_test.rb b/test/unit/integrations/ipay88_module_test.rb index 3125714060c..005ad4792f1 100644 --- a/test/unit/integrations/ipay88_module_test.rb +++ b/test/unit/integrations/ipay88_module_test.rb @@ -10,4 +10,8 @@ def test_return_method def test_service_url assert_equal "https://www.mobile88.com/epayment/entry.asp", Ipay88.service_url end + + def test_requery_url + assert_equal "https://www.mobile88.com/epayment/enquiry.asp", Ipay88.requery_url + end end diff --git a/test/unit/integrations/returns/ipay88_return_test.rb b/test/unit/integrations/returns/ipay88_return_test.rb index efae6dae385..4e62873c9c8 100644 --- a/test/unit/integrations/returns/ipay88_return_test.rb +++ b/test/unit/integrations/returns/ipay88_return_test.rb @@ -31,7 +31,7 @@ def test_insecure_request def test_successful_return params = parameterize(payload) - Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.service_url, params, + Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.requery_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } ).returns("00") @@ -45,7 +45,7 @@ def test_unsuccessful_return_due_to_signature def test_unsuccessful_return_due_to_requery params = parameterize(payload) - Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.service_url, params, + Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.requery_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } ).returns("Invalid parameters") assert !@ipay88.success? @@ -53,7 +53,7 @@ def test_unsuccessful_return_due_to_requery def test_unsuccessful_return_due_to_payment_failed params = parameterize(payload) - Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.service_url, params, + Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.requery_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } ).returns("00") ipay = build_return(http_raw_data(:payment_failed)) From ae0002f87e943328460fd4170187994fccb66214 Mon Sep 17 00:00:00 2001 From: Nathaniel Talbott Date: Fri, 29 Nov 2013 14:21:57 -0500 Subject: [PATCH 078/104] Braintree Blue: Return :credit_card_token Having the token at the top level of the params hash when applicable means much less digging around in the internals of how Braintree structures their results. --- CHANGELOG | 1 + .../billing/gateways/braintree_blue.rb | 3 ++- .../gateways/remote_braintree_blue_test.rb | 2 +- test/unit/gateways/braintree_blue_test.rb | 27 ++++++++++--------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7f25824c81f..f6c7c813ebe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ * Braintree Blue: Allow specifying the credit card token [ntalbott] * Braintree Blue: Allow specifying the customer id [ntalbott] * Braintree Blue: Scrub invalid emails and zips [ntalbott] +* Braintree Blue: Return :credit_card_token as a top level param [ntalbott] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb index 6b1b75c80e9..f8420cfe574 100644 --- a/lib/active_merchant/billing/gateways/braintree_blue.rb +++ b/lib/active_merchant/billing/gateways/braintree_blue.rb @@ -129,7 +129,8 @@ def store(creditcard, options = {}) Response.new(result.success?, message_from_result(result), { :braintree_customer => (customer_hash(result.customer) if result.success?), - :customer_vault_id => (result.customer.id if result.success?) + :customer_vault_id => (result.customer.id if result.success?), + :credit_card_token => (result.customer.credit_cards[0].token if result.success?) }, :authorization => (result.customer.id if result.success?) ) diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb index 3889b2a621e..9fcd9e5b9f0 100644 --- a/test/remote/gateways/remote_braintree_blue_test.rb +++ b/test/remote/gateways/remote_braintree_blue_test.rb @@ -490,7 +490,7 @@ def test_successful_credit def test_failed_credit assert response = @gateway.credit(@amount, credit_card('5105105105105101'), @options) assert_failure response - assert_equal 'Credit card number is invalid. (81715)', response.message, "You must get credits enabled in your Sandbox account for this to pass." + assert_equal 'Credit card number is invalid. (81715)', response.message, "You must get credits enabled in your Sandbox account for this to pass" end def test_successful_credit_with_merchant_account_id diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb index f097e2712ae..af573715105 100644 --- a/test/unit/gateways/braintree_blue_test.rb +++ b/test/unit/gateways/braintree_blue_test.rb @@ -87,8 +87,8 @@ def test_merchant_account_id_absent_if_not_provided end def test_store_with_verify_card_true - customer = mock( - :credit_cards => [], + customer = stub( + :credit_cards => [stub_everything], :email => 'email', :first_name => 'John', :last_name => 'Smith' @@ -108,7 +108,7 @@ def test_store_with_verify_card_true def test_passes_email customer = stub( - :credit_cards => [], + :credit_cards => [stub_everything], :email => "bob@example.com", :first_name => 'John', :last_name => 'Smith', @@ -126,7 +126,7 @@ def test_passes_email def test_scrubs_invalid_email customer = stub( - :credit_cards => [], + :credit_cards => [stub_everything], :email => nil, :first_name => 'John', :last_name => 'Smith', @@ -143,8 +143,8 @@ def test_scrubs_invalid_email end def test_store_with_verify_card_false - customer = mock( - :credit_cards => [], + customer = stub( + :credit_cards => [stub_everything], :email => 'email', :first_name => 'John', :last_name => 'Smith' @@ -164,7 +164,7 @@ def test_store_with_verify_card_false def test_store_with_billing_address_options customer_attributes = { - :credit_cards => [], + :credit_cards => [stub_everything], :email => 'email', :first_name => 'John', :last_name => 'Smith' @@ -177,7 +177,7 @@ def test_store_with_billing_address_options :zip => "60622", :country_name => "US" } - customer = mock(customer_attributes) + customer = stub(customer_attributes) customer.stubs(:id).returns('123') result = Braintree::SuccessfulResult.new(:customer => customer) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| @@ -192,7 +192,7 @@ def test_store_with_billing_address_options end def test_store_with_credit_card_token - customer = mock( + customer = stub( :email => 'email', :first_name => 'John', :last_name => 'Smith' @@ -211,14 +211,15 @@ def test_store_with_credit_card_token response = @gateway.store(credit_card("41111111111111111111"), :credit_card_token => "cctoken") assert_success response assert_equal "cctoken", response.params["braintree_customer"]["credit_cards"][0]["token"] + assert_equal "cctoken", response.params["credit_card_token"] end def test_store_with_customer_id - customer = mock( + customer = stub( :email => 'email', :first_name => 'John', :last_name => 'Smith', - :credit_cards => [] + :credit_cards => [stub_everything] ) customer.stubs(:id).returns("customerid") @@ -249,8 +250,8 @@ def test_update_with_cvv end def test_update_with_verify_card_true - stored_credit_card = mock(:token => "token", :default? => true) - customer = mock(:credit_cards => [stored_credit_card], :id => '123') + stored_credit_card = stub(:token => "token", :default? => true) + customer = stub(:credit_cards => [stored_credit_card], :id => '123') Braintree::CustomerGateway.any_instance.stubs(:find).with('vault_id').returns(customer) BraintreeBlueGateway.any_instance.stubs(:customer_hash) From a781f34841c0e82aec5e2e407cea0cd15f3460b3 Mon Sep 17 00:00:00 2001 From: Nathaniel Talbott Date: Fri, 29 Nov 2013 15:14:00 -0500 Subject: [PATCH 079/104] Braintree Blue: Allow unstoring just a credit card Unfortunately the Braintree implementation of store/unstore/update was originally made very customer-centric where ActiveMerchant is credit card centric. This means that #unstore expects a customer id rather than a credit card token. To at least allow unstoring a card only, this allows passing a credit card token for removal; the customer id must be passed as nil for it to work, though. --- CHANGELOG | 1 + .../billing/gateways/braintree_blue.rb | 6 +++++- test/remote/gateways/remote_braintree_blue_test.rb | 11 ++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f6c7c813ebe..c90799fad96 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ * Braintree Blue: Allow specifying the customer id [ntalbott] * Braintree Blue: Scrub invalid emails and zips [ntalbott] * Braintree Blue: Return :credit_card_token as a top level param [ntalbott] +* Braintree Blue: Allow unstoring just a credit card [ntalbott] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb index f8420cfe574..0decb0452ef 100644 --- a/lib/active_merchant/billing/gateways/braintree_blue.rb +++ b/lib/active_merchant/billing/gateways/braintree_blue.rb @@ -168,7 +168,11 @@ def update(vault_id, creditcard, options = {}) def unstore(customer_vault_id, options = {}) commit do - @braintree_gateway.customer.delete(customer_vault_id) + if(!customer_vault_id && options[:credit_card_token]) + @braintree_gateway.credit_card.delete(options[:credit_card_token]) + else + @braintree_gateway.customer.delete(customer_vault_id) + end Response.new(true, "OK") end end diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb index 9fcd9e5b9f0..ffca3ce8358 100644 --- a/test/remote/gateways/remote_braintree_blue_test.rb +++ b/test/remote/gateways/remote_braintree_blue_test.rb @@ -366,7 +366,7 @@ def test_failed_add_to_vault assert_equal nil, response.params["customer_vault_id"] end - def test_unstore + def test_unstore_customer assert response = @gateway.store(@credit_card) assert_success response assert_equal 'OK', response.message @@ -375,6 +375,15 @@ def test_unstore assert_success delete_response end + def test_unstore_credit_card + assert response = @gateway.store(@credit_card) + assert_success response + assert_equal 'OK', response.message + assert credit_card_token = response.params["credit_card_token"] + assert delete_response = @gateway.unstore(nil, credit_card_token: credit_card_token) + assert_success delete_response + end + def test_unstore_with_delete_method assert response = @gateway.store(@credit_card) assert_success response From 52801ba4312e4efc9d4ac7a5a1018c9a87402088 Mon Sep 17 00:00:00 2001 From: Nathaniel Talbott Date: Fri, 29 Nov 2013 15:57:11 -0500 Subject: [PATCH 080/104] Braintree Blue: Add cards to existing customers When calling #store with an explicit customer id it's useful to add the card to the customer with the id if it exists vs. dying with an error. --- CHANGELOG | 1 + .../billing/gateways/braintree_blue.rb | 135 +++++++++++++----- .../gateways/remote_braintree_blue_test.rb | 17 ++- test/unit/gateways/braintree_blue_test.rb | 24 ++++ 4 files changed, 137 insertions(+), 40 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c90799fad96..1788e8a3d1d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ * Braintree Blue: Scrub invalid emails and zips [ntalbott] * Braintree Blue: Return :credit_card_token as a top level param [ntalbott] * Braintree Blue: Allow unstoring just a credit card [ntalbott] +* Braintree Blue: #store adds cards to existing customers [ntalbott] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb index 0decb0452ef..15073519716 100644 --- a/lib/active_merchant/billing/gateways/braintree_blue.rb +++ b/lib/active_merchant/billing/gateways/braintree_blue.rb @@ -111,29 +111,20 @@ def void(authorization, options = {}) end def store(creditcard, options = {}) - commit do - parameters = { - :first_name => creditcard.first_name, - :last_name => creditcard.last_name, - :email => scrub_email(options[:email]), - :id => options[:customer], - :credit_card => { - :number => creditcard.number, - :cvv => creditcard.verification_value, - :expiration_month => creditcard.month.to_s.rjust(2, "0"), - :expiration_year => creditcard.year.to_s, - :token => options[:credit_card_token] - } - } - result = @braintree_gateway.customer.create(merge_credit_card_options(parameters, options)) - Response.new(result.success?, message_from_result(result), - { - :braintree_customer => (customer_hash(result.customer) if result.success?), - :customer_vault_id => (result.customer.id if result.success?), - :credit_card_token => (result.customer.credit_cards[0].token if result.success?) - }, - :authorization => (result.customer.id if result.success?) - ) + if options[:customer].present? + MultiResponse.new.tap do |r| + customer_exists_response = nil + r.process{customer_exists_response = check_customer_exists(options[:customer])} + r.process do + if customer_exists_response.params["exists"] + add_credit_card_to_customer(creditcard, options) + else + add_customer_with_credit_card(creditcard, options) + end + end + end + else + add_customer_with_credit_card(creditcard, options) end end @@ -160,7 +151,7 @@ def update(vault_id, creditcard, options = {}) :credit_card => credit_card_params ) Response.new(result.success?, message_from_result(result), - :braintree_customer => (customer_hash(@braintree_gateway.customer.find(vault_id)) if result.success?), + :braintree_customer => (customer_hash(@braintree_gateway.customer.find(vault_id), :include_credit_cards) if result.success?), :customer_vault_id => (result.customer.id if result.success?) ) end @@ -180,6 +171,69 @@ def unstore(customer_vault_id, options = {}) private + def check_customer_exists(customer_vault_id) + commit do + begin + @braintree_gateway.customer.find(customer_vault_id) + ActiveMerchant::Billing::Response.new(true, "Customer found", {exists: true}, authorization: customer_vault_id) + rescue Braintree::NotFoundError + ActiveMerchant::Billing::Response.new(true, "Customer not found", {exists: false}) + end + end + end + + def add_customer_with_credit_card(creditcard, options) + commit do + parameters = { + :first_name => creditcard.first_name, + :last_name => creditcard.last_name, + :email => scrub_email(options[:email]), + :id => options[:customer], + :credit_card => { + :number => creditcard.number, + :cvv => creditcard.verification_value, + :expiration_month => creditcard.month.to_s.rjust(2, "0"), + :expiration_year => creditcard.year.to_s, + :token => options[:credit_card_token] + } + } + result = @braintree_gateway.customer.create(merge_credit_card_options(parameters, options)) + Response.new(result.success?, message_from_result(result), + { + :braintree_customer => (customer_hash(result.customer, :include_credit_cards) if result.success?), + :customer_vault_id => (result.customer.id if result.success?), + :credit_card_token => (result.customer.credit_cards[0].token if result.success?) + }, + :authorization => (result.customer.id if result.success?) + ) + end + end + + def add_credit_card_to_customer(credit_card, options) + commit do + parameters = { + customer_id: options[:customer], + token: options[:credit_card_token], + number: credit_card.number, + cvv: credit_card.verification_value, + expiration_month: credit_card.month.to_s.rjust(2, "0"), + expiration_year: credit_card.year.to_s, + } + parameters[:billing_address] = map_address(options[:billing_address]) if options[:billing_address] + + result = @braintree_gateway.credit_card.create(parameters) + ActiveMerchant::Billing::Response.new( + result.success?, + message_from_result(result), + { + customer_vault_id: (result.credit_card.customer_id if result.success?), + credit_card_token: (result.credit_card.token if result.success?) + }, + authorization: (result.credit_card.customer_id if result.success?) + ) + end + end + def scrub_email(email) return nil unless email.present? return nil if ( @@ -306,26 +360,29 @@ def extract_refund_args(args) end end - def customer_hash(customer) - credit_cards = customer.credit_cards.map do |cc| - { - "bin" => cc.bin, - "expiration_date" => cc.expiration_date, - "token" => cc.token, - "last_4" => cc.last_4, - "card_type" => cc.card_type, - "masked_number" => cc.masked_number, - "token" => cc.token - } - end - - { + def customer_hash(customer, include_credit_cards=false) + hash = { "email" => customer.email, "first_name" => customer.first_name, "last_name" => customer.last_name, - "credit_cards" => credit_cards, "id" => customer.id } + + if include_credit_cards + hash["credit_cards"] = customer.credit_cards.map do |cc| + { + "bin" => cc.bin, + "expiration_date" => cc.expiration_date, + "token" => cc.token, + "last_4" => cc.last_4, + "card_type" => cc.card_type, + "masked_number" => cc.masked_number, + "token" => cc.token + } + end + end + + hash end def transaction_hash(transaction) diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb index ffca3ce8358..ed5b90cc0eb 100644 --- a/test/remote/gateways/remote_braintree_blue_test.rb +++ b/test/remote/gateways/remote_braintree_blue_test.rb @@ -143,7 +143,7 @@ def test_successful_store_with_credit_card_token assert_equal credit_card_token, response.params["braintree_customer"]["credit_cards"][0]["token"] end - def test_successful_store_with_customer_id + def test_successful_store_with_new_customer_id credit_card = credit_card('5105105105105100') customer_id = generate_unique_id assert response = @gateway.store(credit_card, customer: customer_id) @@ -153,6 +153,21 @@ def test_successful_store_with_customer_id assert_equal customer_id, response.params["braintree_customer"]["id"] end + def test_successful_store_with_existing_customer_id + credit_card = credit_card('5105105105105100') + customer_id = generate_unique_id + assert response = @gateway.store(credit_card, customer: customer_id) + assert_success response + assert_equal 1, @braintree_backend.customer.find(customer_id).credit_cards.size + + assert response = @gateway.store(credit_card, customer: customer_id) + assert_success response + assert_equal 2, @braintree_backend.customer.find(customer_id).credit_cards.size + assert_equal customer_id, response.params["customer_vault_id"] + assert_equal customer_id, response.authorization + assert_not_nil response.params["credit_card_token"] + end + def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb index af573715105..7c73ba7b2a9 100644 --- a/test/unit/gateways/braintree_blue_test.rb +++ b/test/unit/gateways/braintree_blue_test.rb @@ -224,6 +224,9 @@ def test_store_with_customer_id customer.stubs(:id).returns("customerid") result = Braintree::SuccessfulResult.new(:customer => customer) + Braintree::CustomerGateway.any_instance.expects(:find). + with("customerid"). + raises(Braintree::NotFoundError) Braintree::CustomerGateway.any_instance.expects(:create).with do |params| assert_equal "customerid", params[:id] params @@ -234,6 +237,27 @@ def test_store_with_customer_id assert_equal "customerid", response.params["braintree_customer"]["id"] end + def test_store_with_existing_customer_id + credit_card = stub( + customer_id: "customerid", + token: "cctoken" + ) + + result = Braintree::SuccessfulResult.new(credit_card: credit_card) + Braintree::CustomerGateway.any_instance.expects(:find).with("customerid") + Braintree::CreditCardGateway.any_instance.expects(:create).with do |params| + assert_equal "customerid", params[:customer_id] + assert_equal "41111111111111111111", params[:number] + params + end.returns(result) + + response = @gateway.store(credit_card("41111111111111111111"), customer: "customerid") + assert_success response + assert_nil response.params["braintree_customer"] + assert_equal "customerid", response.params["customer_vault_id"] + assert_equal "cctoken", response.params["credit_card_token"] + end + def test_update_with_cvv stored_credit_card = mock(:token => "token", :default? => true) customer = mock(:credit_cards => [stored_credit_card], :id => '123') From 2fba18c9d0f8b355604e0a71035ab229674f7f9f Mon Sep 17 00:00:00 2001 From: Thomas Cort Date: Mon, 2 Dec 2013 00:00:03 -0500 Subject: [PATCH 081/104] Fix spelling mistakes. --- lib/active_merchant/billing/credit_card_methods.rb | 2 +- .../billing/gateways/authorize_net.rb | 2 +- .../billing/gateways/authorize_net_cim.rb | 12 ++++++------ lib/active_merchant/billing/gateways/beanstream.rb | 2 +- .../billing/gateways/cyber_source.rb | 4 ++-- lib/active_merchant/billing/gateways/moneris.rb | 2 +- lib/active_merchant/billing/gateways/moneris_us.rb | 2 +- .../billing/gateways/nab_transact.rb | 2 +- lib/active_merchant/billing/gateways/netpay.rb | 2 +- .../billing/gateways/pay_junction.rb | 14 +++++++------- .../billing/gateways/payment_express.rb | 2 +- .../billing/gateways/paypal/paypal_common_api.rb | 2 +- lib/active_merchant/billing/gateways/realex.rb | 2 +- lib/active_merchant/billing/gateways/redsys.rb | 2 +- .../billing/gateways/usa_epay_advanced.rb | 8 ++++---- lib/active_merchant/billing/gateways/wirecard.rb | 2 +- .../billing/integrations/pay_fast/notification.rb | 2 +- .../billing/integrations/paypal/helper.rb | 2 +- .../billing/integrations/two_checkout/helper.rb | 2 +- .../integrations/verkkomaksut/notification.rb | 2 +- .../billing/integrations/wirecard_checkout_page.rb | 2 +- .../billing/integrations/world_pay/helper.rb | 4 ++-- test/remote/gateways/remote_pay_junction_test.rb | 2 +- test/remote/gateways/remote_paystation_test.rb | 2 +- .../gateways/remote_usa_epay_advanced_test.rb | 2 +- test/unit/gateways/braintree_blue_test.rb | 2 +- 26 files changed, 42 insertions(+), 42 deletions(-) diff --git a/lib/active_merchant/billing/credit_card_methods.rb b/lib/active_merchant/billing/credit_card_methods.rb index fef4bf16c52..a7801819fcd 100644 --- a/lib/active_merchant/billing/credit_card_methods.rb +++ b/lib/active_merchant/billing/credit_card_methods.rb @@ -126,7 +126,7 @@ def valid_test_mode_card_number?(number) #:nodoc: %w[1 2 3 success failure error].include?(number.to_s) end - # Checks the validity of a card number by use of the the Luhn Algorithm. + # Checks the validity of a card number by use of the Luhn Algorithm. # Please see http://en.wikipedia.org/wiki/Luhn_algorithm for details. def valid_checksum?(number) #:nodoc: sum = 0 diff --git a/lib/active_merchant/billing/gateways/authorize_net.rb b/lib/active_merchant/billing/gateways/authorize_net.rb index 25942c2b23f..dc98492d254 100644 --- a/lib/active_merchant/billing/gateways/authorize_net.rb +++ b/lib/active_merchant/billing/gateways/authorize_net.rb @@ -200,7 +200,7 @@ def credit(money, identification, options = {}) # For example, to charge the customer once every three months the hash would be # +:interval => { :unit => :months, :length => 3 }+ (REQUIRED) # * :duration -- A hash containing keys for the :start_date the subscription begins (also the date the - # initial billing occurs) and the total number of billing :occurences or payments for the subscription. (REQUIRED) + # initial billing occurs) and the total number of billing :occurrences or payments for the subscription. (REQUIRED) def recurring(money, creditcard, options={}) requires!(options, :interval, :duration, :billing_address) requires!(options[:interval], :length, [:unit, :days, :months]) diff --git a/lib/active_merchant/billing/gateways/authorize_net_cim.rb b/lib/active_merchant/billing/gateways/authorize_net_cim.rb index 39aaca66522..3ef54d507b3 100644 --- a/lib/active_merchant/billing/gateways/authorize_net_cim.rb +++ b/lib/active_merchant/billing/gateways/authorize_net_cim.rb @@ -95,7 +95,7 @@ class AuthorizeNetCimGateway < Gateway # * :test -- +true+ or +false+. If true, perform transactions against the test server. # Otherwise, perform transactions against the production server. # * :test_requests -- +true+ or +false+. If true, perform transactions without the - # test flag. This is useful when you need to generate card declines, AVS or CVV erros. + # test flag. This is useful when you need to generate card declines, AVS or CVV errors. # Will hold the same value as :test by default. # * :delimiter -- The delimiter used in the direct response. Default is ',' (comma). def initialize(options = {}) @@ -340,7 +340,7 @@ def update_customer_shipping_address(options) # ==== Transaction # # * :type -- The type of transaction. Can be either :auth_only, :capture_only, :auth_capture, :prior_auth_capture, :refund or :void. (REQUIRED) - # * :amount -- The amount for the tranaction. Formatted with a decimal. For example "4.95" (CONDITIONAL) + # * :amount -- The amount for the transaction. Formatted with a decimal. For example "4.95" (CONDITIONAL) # - :type == :void (NOT USED) # - :type == :refund (OPTIONAL) # - :type == (:auth_only, :capture_only, :auth_capture, :prior_auth_capture) (REQUIRED) @@ -369,8 +369,8 @@ def update_customer_shipping_address(options) # - :type = (:prior_auth_capture) (OPTIONAL) # # ==== For :type == :refund only - # * :credit_card_number_masked -- (CONDITIONAL - requied for credit card refunds is :customer_profile_id AND :customer_payment_profile_id are missing) - # * :bank_routing_number_masked && :bank_account_number_masked -- (CONDITIONAL - requied for electronic check refunds is :customer_profile_id AND :customer_payment_profile_id are missing) (NOT ABLE TO TEST - I keep getting "ACH transactions are not accepted by this merchant." when trying to make a payment and, until that's possible I can't refund (wiseleyb@gmail.com)) + # * :credit_card_number_masked -- (CONDITIONAL - required for credit card refunds is :customer_profile_id AND :customer_payment_profile_id are missing) + # * :bank_routing_number_masked && :bank_account_number_masked -- (CONDITIONAL - required for electronic check refunds is :customer_profile_id AND :customer_payment_profile_id are missing) (NOT ABLE TO TEST - I keep getting "ACH transactions are not accepted by this merchant." when trying to make a payment and, until that's possible I can't refund (wiseleyb@gmail.com)) def create_customer_profile_transaction(options) requires!(options, :transaction) requires!(options[:transaction], :type) @@ -410,13 +410,13 @@ def create_customer_profile_transaction(options) # * :customer_profile_id -- The Customer Profile ID of the customer to use in this transaction. (CONDITIONAL :customer_payment_profile_id must be included if used) # * :customer_payment_profile_id -- The Customer Payment Profile ID of the Customer Payment Profile to use in this transaction. (CONDITIONAL :customer_profile_id must be included if used) # - # * :credit_card_number_masked -- Four Xs follwed by the last four digits of the credit card (CONDITIONAL - used if customer_profile_id and customer_payment_profile_id aren't given) + # * :credit_card_number_masked -- Four Xs followed by the last four digits of the credit card (CONDITIONAL - used if customer_profile_id and customer_payment_profile_id aren't given) # # * :bank_routing_number_masked -- The last four gidits of the routing number to be refunded (CONDITIONAL - must be used with :bank_account_number_masked) # * :bank_account_number_masked -- The last four digis of the bank account number to be refunded, Ex. XXXX1234 (CONDITIONAL - must be used with :bank_routing_number_masked) # # * :tax - A hash containing tax information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) - # * :duty - A hash containting duty information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) + # * :duty - A hash containing duty information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) # * :shipping - A hash containing shipping information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) def create_customer_profile_transaction_for_refund(options) requires!(options, :transaction) diff --git a/lib/active_merchant/billing/gateways/beanstream.rb b/lib/active_merchant/billing/gateways/beanstream.rb index edc3bb11f02..9b8680ac2ea 100644 --- a/lib/active_merchant/billing/gateways/beanstream.rb +++ b/lib/active_merchant/billing/gateways/beanstream.rb @@ -139,7 +139,7 @@ def store(credit_card, options = {}) commit(post, true) end - #can't actually delete a secure profile with the supplicaed API. This function sets the status of the profile to closed (C). + #can't actually delete a secure profile with the supplicated API. This function sets the status of the profile to closed (C). #Closed profiles will have to removed manually. def delete(vault_id) update(vault_id, false, {:status => "C"}) diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb index f72480808d6..c959b66e309 100644 --- a/lib/active_merchant/billing/gateways/cyber_source.rb +++ b/lib/active_merchant/billing/gateways/cyber_source.rb @@ -24,7 +24,7 @@ module Billing #:nodoc: # CyberSource what kind of item you are selling. It is used when # calculating tax/VAT. # * All transactions use dollar values. - # * To process pinless debit cards throught the pinless debit card + # * To process pinless debit cards through the pinless debit card # network, your Cybersource merchant account must accept pinless # debit card payments. class CyberSourceGateway < Gateway @@ -102,7 +102,7 @@ class CyberSourceGateway < Gateway # :vat_reg_number => your VAT registration number # # :nexus => "WI CA QC" sets the states/provinces where you have a physical - # presense for tax purposes + # presence for tax purposes # # :ignore_avs => true don't want to use AVS so continue processing even # if AVS would have failed diff --git a/lib/active_merchant/billing/gateways/moneris.rb b/lib/active_merchant/billing/gateways/moneris.rb index 5ff1121c316..68588262f80 100644 --- a/lib/active_merchant/billing/gateways/moneris.rb +++ b/lib/active_merchant/billing/gateways/moneris.rb @@ -167,7 +167,7 @@ def crediting_params(authorization, options = {}) }.merge(options) end - # Splits an +authorization+ param and retrives the order id and + # Splits an +authorization+ param and retrieves the order id and # transaction number in that order. def split_authorization(authorization) if authorization.nil? || authorization.empty? || authorization !~ /;/ diff --git a/lib/active_merchant/billing/gateways/moneris_us.rb b/lib/active_merchant/billing/gateways/moneris_us.rb index 92feec27397..2113e166ce4 100644 --- a/lib/active_merchant/billing/gateways/moneris_us.rb +++ b/lib/active_merchant/billing/gateways/moneris_us.rb @@ -111,7 +111,7 @@ def crediting_params(authorization, options = {}) }.merge(options) end - # Splits an +authorization+ param and retrives the order id and + # Splits an +authorization+ param and retrieves the order id and # transaction number in that order. def split_authorization(authorization) if authorization.nil? || authorization.empty? || authorization !~ /;/ diff --git a/lib/active_merchant/billing/gateways/nab_transact.rb b/lib/active_merchant/billing/gateways/nab_transact.rb index 456d582b343..754a45dee54 100644 --- a/lib/active_merchant/billing/gateways/nab_transact.rb +++ b/lib/active_merchant/billing/gateways/nab_transact.rb @@ -131,7 +131,7 @@ def build_reference_request(money, reference, options) end #Generate payment request XML - # - API is set to allow multiple Txn's but currentlu only allows one + # - API is set to allow multiple Txn's but currently only allows one # - txnSource = 23 - (XML) def build_request(action, body) xml = Builder::XmlMarkup.new diff --git a/lib/active_merchant/billing/gateways/netpay.rb b/lib/active_merchant/billing/gateways/netpay.rb index babf9542502..b950d33073b 100644 --- a/lib/active_merchant/billing/gateways/netpay.rb +++ b/lib/active_merchant/billing/gateways/netpay.rb @@ -18,7 +18,7 @@ module Billing #:nodoc: # transaction. After this, a refund should be performed instead. # # In addition to the regular ActiveMerchant transaction options, NETPAY - # also supports a `:mode` parameter. This allows testing to be peformed + # also supports a `:mode` parameter. This allows testing to be performed # in production and force specific results. # # * 'P' - Production diff --git a/lib/active_merchant/billing/gateways/pay_junction.rb b/lib/active_merchant/billing/gateways/pay_junction.rb index ceb2e184d93..ada083f48ca 100644 --- a/lib/active_merchant/billing/gateways/pay_junction.rb +++ b/lib/active_merchant/billing/gateways/pay_junction.rb @@ -73,16 +73,16 @@ module Billing #:nodoc: # # PayJunction Field ActiveMerchant Use # - # dc_logon provide as :login value to gateway instantation + # dc_logon provide as :login value to gateway instantiation # dc_password provide as :password value to gateway instantiation # # dc_name will be retrieved from credit_card.name - # dc_first_name :first_name on CreditCard object instantation - # dc_last_name :last_name on CreditCard object instantation - # dc_number :number on CreditCard object instantation - # dc_expiration_month :month on CreditCard object instantation - # dc_expiration_year :year on CreditCard object instantation - # dc_verification_number :verification_value on CC object instantation + # dc_first_name :first_name on CreditCard object instantiation + # dc_last_name :last_name on CreditCard object instantiation + # dc_number :number on CreditCard object instantiation + # dc_expiration_month :month on CreditCard object instantiation + # dc_expiration_year :year on CreditCard object instantiation + # dc_verification_number :verification_value on CC object instantiation # # dc_transaction_amount include as argument to method for your transaction type # dc_transaction_type do nothing, set by your transaction type diff --git a/lib/active_merchant/billing/gateways/payment_express.rb b/lib/active_merchant/billing/gateways/payment_express.rb index 450b26e2661..290c593ca34 100644 --- a/lib/active_merchant/billing/gateways/payment_express.rb +++ b/lib/active_merchant/billing/gateways/payment_express.rb @@ -227,7 +227,7 @@ def add_address_verification_data(xml, options) end # The options hash may contain optional data which will be passed - # through the the specialized optional fields at PaymentExpress + # through the specialized optional fields at PaymentExpress # as follows: # # { diff --git a/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb b/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb index 74fb4d4c91b..2fad6c2128c 100644 --- a/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +++ b/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb @@ -242,7 +242,7 @@ def authorize_transaction(transaction_id, money, options = {}) commit 'DoAuthorization', build_do_authorize(transaction_id, money, options) end - # The ManagePendingTransactionStatus API operation accepts or denys a + # The ManagePendingTransactionStatus API operation accepts or denies a # pending transaction held by Fraud Management Filters. # # ==== Parameters: diff --git a/lib/active_merchant/billing/gateways/realex.rb b/lib/active_merchant/billing/gateways/realex.rb index 06ebdabb0c5..f4404710e7f 100644 --- a/lib/active_merchant/billing/gateways/realex.rb +++ b/lib/active_merchant/billing/gateways/realex.rb @@ -12,7 +12,7 @@ module Billing # login - The unique id of the merchant # password - The secret is used to digitally sign the request # account - This is an optional third part of the authentication process - # and is used if the merchant wishes do distuinguish cc traffic from the different sources + # and is used if the merchant wishes do distinguish cc traffic from the different sources # by using a different account. This must be created in advance # # the Realex team decided to make the orderid unique per request, diff --git a/lib/active_merchant/billing/gateways/redsys.rb b/lib/active_merchant/billing/gateways/redsys.rb index 583e025393c..114a3ca7d4e 100644 --- a/lib/active_merchant/billing/gateways/redsys.rb +++ b/lib/active_merchant/billing/gateways/redsys.rb @@ -49,7 +49,7 @@ class RedsysGateway < Gateway self.default_currency = 'EUR' self.money_format = :cents - # Not all card types may be actived by the bank! + # Not all card types may be activated by the bank! self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :diners_club] # Homepage URL of the gateway for reference diff --git a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb index 6b3faeb312b..ef4a076f070 100644 --- a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +++ b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb @@ -355,7 +355,7 @@ def update_customer(options={}) # Enable a customer for recurring billing. # - # Note: Customer does not need to have all recurring paramerters to succeed. + # Note: Customer does not need to have all recurring parameters to succeed. # # ==== Required # * :customer_number @@ -454,7 +454,7 @@ def update_customer_payment_method(options={}) commit(__method__, request) end - # Delete one the payment methods beloning to a customer + # Delete one the payment methods belonging to a customer # # ==== Required # * :customer_number @@ -547,7 +547,7 @@ def run_customer_transaction(options={}) # * :command -- sale, credit, void, creditvoid, authonly, capture, postauth, check, checkcredit; defaults to sale; only required for run_transaction when other than sale # * :reference_number -- for the original transaction; obtained by sale or authonly # * :authorization_code -- required for postauth; obtained offline - # * :ignore_duplicate -- set +true+ if you want to override the duplicate tranaction handling + # * :ignore_duplicate -- set +true+ if you want to override the duplicate transaction handling # * :account_holder -- name of account holder # * :customer_id -- merchant assigned id # * :customer_receipt -- set +true+ to email receipt to billing email address @@ -680,7 +680,7 @@ def refund_transaction(options={}) commit(__method__, request) end - # Override transaction flagged for mananager approval. + # Override transaction flagged for manager approval. # # Note: Checks only! # diff --git a/lib/active_merchant/billing/gateways/wirecard.rb b/lib/active_merchant/billing/gateways/wirecard.rb index 923b8cb90b0..b448bac75ee 100644 --- a/lib/active_merchant/billing/gateways/wirecard.rb +++ b/lib/active_merchant/billing/gateways/wirecard.rb @@ -232,7 +232,7 @@ def parse(xml) response end - # Parse the Element which containts all important information + # Parse the Element which contains all important information def parse_response(response, root) status = nil # get the root element for this Transaction diff --git a/lib/active_merchant/billing/integrations/pay_fast/notification.rb b/lib/active_merchant/billing/integrations/pay_fast/notification.rb index 21c402570c1..01bb8bc841b 100644 --- a/lib/active_merchant/billing/integrations/pay_fast/notification.rb +++ b/lib/active_merchant/billing/integrations/pay_fast/notification.rb @@ -73,7 +73,7 @@ def gross params['amount_gross'] end - # The total in fees which was deducated from the amount. + # The total in fees which was deducted from the amount. def fee params['amount_fee'] end diff --git a/lib/active_merchant/billing/integrations/paypal/helper.rb b/lib/active_merchant/billing/integrations/paypal/helper.rb index c905ae75901..1f894672320 100644 --- a/lib/active_merchant/billing/integrations/paypal/helper.rb +++ b/lib/active_merchant/billing/integrations/paypal/helper.rb @@ -69,7 +69,7 @@ def shipping_address(params = {}) if params.has_key?(:phone) phone = params.delete(:phone).to_s - # Whipe all non digits + # Wipe all non digits phone.gsub!(/\D+/, '') if ['US', 'CA'].include?(country_code) && phone =~ /(\d{3})(\d{3})(\d{4})$/ diff --git a/lib/active_merchant/billing/integrations/two_checkout/helper.rb b/lib/active_merchant/billing/integrations/two_checkout/helper.rb index 077b9ae424d..4f4eac351fa 100644 --- a/lib/active_merchant/billing/integrations/two_checkout/helper.rb +++ b/lib/active_merchant/billing/integrations/two_checkout/helper.rb @@ -56,7 +56,7 @@ def customer(params = {}) end # Uses Pass Through Product Parameters to pass in lineitems. - # (must mark tanigble sales as shipped to settle the transaction) + # (must mark tangible sales as shipped to settle the transaction) def line_item(params = {}) add_field('mode', '2CO') (max_existing_line_item_id = form_fields.keys.map do |key| diff --git a/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb b/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb index aaaf5eee163..53ce8eaac3c 100644 --- a/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb +++ b/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb @@ -40,7 +40,7 @@ def status end end - # Acknowldges the payment. If the authcodes match, returns true. + # Acknowledges the payment. If the authcodes match, returns true. def acknowledge(authcode = nil) return_authcode = [params["ORDER_NUMBER"], params["TIMESTAMP"], params["PAID"], params["METHOD"], authcode].join("|") Digest::MD5.hexdigest(return_authcode).upcase == params["RETURN_AUTHCODE"] diff --git a/lib/active_merchant/billing/integrations/wirecard_checkout_page.rb b/lib/active_merchant/billing/integrations/wirecard_checkout_page.rb index 0af90074116..c305a20f78f 100644 --- a/lib/active_merchant/billing/integrations/wirecard_checkout_page.rb +++ b/lib/active_merchant/billing/integrations/wirecard_checkout_page.rb @@ -7,7 +7,7 @@ * The Plugin is provided by WDCEE free of charge for it's customers and must be used for the purpose of WDCEE's payment platform * integration only. It explicitly is not part of the general contract between WDCEE and it's customer. The plugin has successfully been tested * under specific circumstances which are defined as the shopsystem's standard configuration (vendor's delivery state). The Customer is - * responsible for testing the plugin's functionality before putting it into production enviroment. + * responsible for testing the plugin's functionality before putting it into production environment. * The customer uses the plugin at own risk. WDCEE does not guarantee it's full functionality neither does WDCEE assume liability for any * disadvantage related to the use of this plugin. By installing the plugin into the shopsystem the customer agrees to the terms of use. * Please do not use this plugin if you do not agree to the terms of use! diff --git a/lib/active_merchant/billing/integrations/world_pay/helper.rb b/lib/active_merchant/billing/integrations/world_pay/helper.rb index 68ab9d911dd..cd80897cd80 100644 --- a/lib/active_merchant/billing/integrations/world_pay/helper.rb +++ b/lib/active_merchant/billing/integrations/world_pay/helper.rb @@ -58,7 +58,7 @@ def customer(params={}) end # Support for a MD5 hash of selected fields to prevent tampering - # For futher information read the tech note at the address below: + # For further information read the tech note at the address below: # http://support.worldpay.com/kb/integration_guides/junior/integration/help/tech_notes/sjig_tn_009.html def encrypt(secret, fields = [:amount, :currency, :account, :order]) signature_fields = fields.collect{ |field| mappings[field] } @@ -98,4 +98,4 @@ def combined_params(params={}) end end end -end \ No newline at end of file +end diff --git a/test/remote/gateways/remote_pay_junction_test.rb b/test/remote/gateways/remote_pay_junction_test.rb index db8724e1811..4744ea59995 100644 --- a/test/remote/gateways/remote_pay_junction_test.rb +++ b/test/remote/gateways/remote_pay_junction_test.rb @@ -98,7 +98,7 @@ def test_successful_void end def test_successful_instant_purchase - # this takes advatange of the PayJunction feature where another + # this takes advantage of the PayJunction feature where another # transaction can be executed if you have the transaction ID of a # previous successful transaction. diff --git a/test/remote/gateways/remote_paystation_test.rb b/test/remote/gateways/remote_paystation_test.rb index 40b3a732080..b3277889c34 100644 --- a/test/remote/gateways/remote_paystation_test.rb +++ b/test/remote/gateways/remote_paystation_test.rb @@ -78,7 +78,7 @@ def test_authorize_and_capture end def test_capture_without_cvv - # for some merchant accounts, paystation requires you send through the card vertification value + # for some merchant accounts, paystation requires you send through the card verification value # on a capture request assert auth = @gateway.authorize(@successful_amount, @credit_card, @options.merge(:order_id => get_uid)) diff --git a/test/remote/gateways/remote_usa_epay_advanced_test.rb b/test/remote/gateways/remote_usa_epay_advanced_test.rb index 440c9a350c3..1692e30dd4c 100644 --- a/test/remote/gateways/remote_usa_epay_advanced_test.rb +++ b/test/remote/gateways/remote_usa_epay_advanced_test.rb @@ -345,7 +345,7 @@ def test_refund_transaction assert response.params['refund_transaction_return'] end - # TODO how to test override_transction + # TODO how to test override_transaction def test_override_transaction options = @options.merge(@run_check_sale_options) response = @gateway.run_check_sale(options) diff --git a/test/unit/gateways/braintree_blue_test.rb b/test/unit/gateways/braintree_blue_test.rb index 7c73ba7b2a9..9dc6725d5e3 100644 --- a/test/unit/gateways/braintree_blue_test.rb +++ b/test/unit/gateways/braintree_blue_test.rb @@ -420,7 +420,7 @@ def test_default_logger_sets_warn_level_without_overwriting_global assert Braintree::Configuration.logger.level != Logger::DEBUG Braintree::Configuration.logger.level = Logger::DEBUG - # Re-instatiate a gateway to show it doesn't touch the global + # Re-instantiate a gateway to show it doesn't touch the global gateway = BraintreeBlueGateway.new( :merchant_id => 'test', :public_key => 'test', From 6f54aecdc4ce654d88a47fc26dcb5e291226f86b Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Tue, 3 Dec 2013 18:55:10 -0500 Subject: [PATCH 082/104] Move ordered checksum fields into a constant in the PayU helper --- .../billing/integrations/payu_in/helper.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/active_merchant/billing/integrations/payu_in/helper.rb b/lib/active_merchant/billing/integrations/payu_in/helper.rb index 68a6d839658..6ac746e9015 100755 --- a/lib/active_merchant/billing/integrations/payu_in/helper.rb +++ b/lib/active_merchant/billing/integrations/payu_in/helper.rb @@ -4,6 +4,9 @@ module Integrations #:nodoc: module PayuIn class Helper < ActiveMerchant::Billing::Integrations::Helper + CHECKSUM_FIELDS = [ 'txnid', 'amount', 'productinfo', 'firstname', 'email', 'udf1', 'udf2', 'udf3', 'udf4', + 'udf5', 'udf6', 'udf7', 'udf8', 'udf9', 'udf10'] + mapping :amount, 'amount' mapping :account, 'key' mapping :order, 'txnid' @@ -54,10 +57,7 @@ def form_fields end def generate_checksum - checksum_payload_items = [ - 'txnid', 'amount', 'productinfo', 'firstname', 'email', - 'udf1', 'udf2', 'udf3', 'udf4', 'udf5', 'udf6', 'udf7', 'udf8', 'udf9', 'udf10' - ].map { |field| @fields[field] } + checksum_payload_items = CHECKSUM_FIELDS.map { |field| @fields[field] } PayuIn.checksum(@fields["key"], @options[:credential2], checksum_payload_items ) end From bbbc1fe3265b2a00c17c062b000cea4570ff1e32 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Thu, 5 Dec 2013 13:56:58 -0500 Subject: [PATCH 083/104] Add message and cancelled to ipay88 return --- .../billing/integrations/ipay88/return.rb | 8 ++++++++ test/unit/integrations/returns/ipay88_return_test.rb | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/active_merchant/billing/integrations/ipay88/return.rb b/lib/active_merchant/billing/integrations/ipay88/return.rb index 25bafa90f19..057f70f6043 100644 --- a/lib/active_merchant/billing/integrations/ipay88/return.rb +++ b/lib/active_merchant/billing/integrations/ipay88/return.rb @@ -59,6 +59,14 @@ def success? self.secure? && self.requery == "00" && self.status == "1" end + def cancelled? + error == 'Customer Cancel Transaction' + end + + def message + params["ErrDesc"] + end + protected def generated_signature diff --git a/test/unit/integrations/returns/ipay88_return_test.rb b/test/unit/integrations/returns/ipay88_return_test.rb index 4e62873c9c8..3ae6c963a6c 100644 --- a/test/unit/integrations/returns/ipay88_return_test.rb +++ b/test/unit/integrations/returns/ipay88_return_test.rb @@ -65,6 +65,16 @@ def test_unsuccessful_return_due_to_missing_amount assert !ipay.success? end + def test_message_returns_error_description + ipay = build_return(parameterize({"ErrDesc" => "Invalid merchant"})) + assert_equal 'Invalid merchant', ipay.message + end + + def test_cancelled + ipay = build_return(parameterize({"ErrDesc" => "Customer Cancel Transaction"})) + assert ipay.cancelled? + end + private def http_raw_data(mode=:success) base = { "MerchantCode" => "ipay88merchcode", From 1893f3895049b8968a1501c58fae5b50a54b3e2d Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Fri, 6 Dec 2013 11:46:25 +1100 Subject: [PATCH 084/104] [pin] Use CreditCard#name instead of manually concatenating first_name & last_name --- lib/active_merchant/billing/gateways/pin.rb | 2 +- test/unit/gateways/pin_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_merchant/billing/gateways/pin.rb b/lib/active_merchant/billing/gateways/pin.rb index 8e1c2ffd0be..26e7cce06b2 100644 --- a/lib/active_merchant/billing/gateways/pin.rb +++ b/lib/active_merchant/billing/gateways/pin.rb @@ -90,7 +90,7 @@ def add_creditcard(post, creditcard) :expiry_month => creditcard.month, :expiry_year => creditcard.year, :cvc => creditcard.verification_value, - :name => "#{creditcard.first_name} #{creditcard.last_name}" + :name => creditcard.name ) elsif creditcard.kind_of?(String) if creditcard =~ /^card_/ diff --git a/test/unit/gateways/pin_test.rb b/test/unit/gateways/pin_test.rb index c8edf25f776..975edc2db8e 100644 --- a/test/unit/gateways/pin_test.rb +++ b/test/unit/gateways/pin_test.rb @@ -202,7 +202,7 @@ def test_add_creditcard assert_equal @credit_card.month, post[:card][:expiry_month] assert_equal @credit_card.year, post[:card][:expiry_year] assert_equal @credit_card.verification_value, post[:card][:cvc] - assert_equal "#{@credit_card.first_name} #{@credit_card.last_name}", post[:card][:name] + assert_equal @credit_card.name, post[:card][:name] end def test_add_creditcard_with_card_token From 99d7fbe7df51d0ad1dde31cf3895363306dfd5b7 Mon Sep 17 00:00:00 2001 From: Denis Odorcic Date: Thu, 5 Dec 2013 19:13:57 -0500 Subject: [PATCH 085/104] Refactor IPay88 to follow correct integration using Notification --- .../integrations/ipay88/notification.rb | 103 +++++++++++++++++ .../billing/integrations/ipay88/return.rb | 84 +------------- .../notifications/ipay88_notification_test.rb | 107 ++++++++++++++++++ .../returns/ipay88_return_test.rb | 104 ++--------------- 4 files changed, 222 insertions(+), 176 deletions(-) create mode 100644 lib/active_merchant/billing/integrations/ipay88/notification.rb create mode 100644 test/unit/integrations/notifications/ipay88_notification_test.rb diff --git a/lib/active_merchant/billing/integrations/ipay88/notification.rb b/lib/active_merchant/billing/integrations/ipay88/notification.rb new file mode 100644 index 00000000000..0badfc4b434 --- /dev/null +++ b/lib/active_merchant/billing/integrations/ipay88/notification.rb @@ -0,0 +1,103 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + module Integrations #:nodoc: + module Ipay88 + class Notification < ActiveMerchant::Billing::Integrations::Notification + include ActiveMerchant::PostsData + + def status + params["Status"] == '1' ? 'Completed' : 'Failed' + end + + def complete? + status == 'Completed' + end + + def item_id + params["RefNo"] + end + + def gross + params["Amount"] + end + + def currency + params["Currency"] + end + + def account + params["MerchantCode"] + end + + def payment + params["PaymentId"].to_i + end + + def remark + params["Remark"] + end + + def transaction_id + params["TransId"] + end + + def auth_code + params["AuthCode"] + end + + def error + params["ErrDesc"] + end + + def signature + params["Signature"] + end + + def secure? + generated_signature == signature + end + + def success? + status == 'Completed' + end + + def acknowledge + secure? && success? && requery == "00" + end + + protected + + def generated_signature + Helper.sign(sig_components) + end + + def sig_components + components = [@options[:credential2]] + [:account, :payment, :item_id, :amount_in_cents, :currency].each do |i| + components << send(i) + end + components << params["Status"] + components.join + end + + def requery + data = { "MerchantCode" => account, "RefNo" => item_id, "Amount" => gross } + params = parameterize(data) + ssl_post Ipay88.requery_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } + end + + private + + def parameterize(params) + params.reject { |k, v| v.blank? }.keys.sort.collect { |key| "#{key}=#{CGI.escape(params[key].to_s)}" }.join("&") + end + + def amount_in_cents + @amount_in_cents ||= (gross || "").gsub(/[.,]/, "") + end + end + end + end + end +end + diff --git a/lib/active_merchant/billing/integrations/ipay88/return.rb b/lib/active_merchant/billing/integrations/ipay88/return.rb index 057f70f6043..acb9de1f0dc 100644 --- a/lib/active_merchant/billing/integrations/ipay88/return.rb +++ b/lib/active_merchant/billing/integrations/ipay88/return.rb @@ -5,97 +5,23 @@ module Billing #:nodoc: module Integrations #:nodoc: module Ipay88 class Return < ActiveMerchant::Billing::Integrations::Return - include ActiveMerchant::PostsData - def account - params["MerchantCode"] - end - - def payment - params["PaymentId"].to_i - end - - def order - params["RefNo"] - end - - def amount - params["Amount"] - end - - def currency - params["Currency"] - end - - def remark - params["Remark"] - end - - def transaction - params["TransId"] - end - - def auth_code - params["AuthCode"] - end - - def status - params["Status"] - end - - def error - params["ErrDesc"] - end - - def signature - params["Signature"] - end - - def secure? - self.generated_signature == self.signature + def initialize(query_string, options = {}) + super + @notification = Notification.new(query_string, options) end def success? - self.secure? && self.requery == "00" && self.status == "1" + params["Status"] == "1" end def cancelled? - error == 'Customer Cancel Transaction' + params["ErrDesc"] == 'Customer Cancel Transaction' end def message params["ErrDesc"] end - - protected - - def generated_signature - Helper.sign(self.sig_components) - end - - def sig_components - components = [@options[:credential2]] - [:account, :payment, :order, :amount_in_cents, :currency, :status].each do |i| - components << self.send(i) - end - components.join - end - - def requery - data = { "MerchantCode" => self.account, "RefNo" => self.order, "Amount" => self.amount } - params = parameterize(data) - ssl_post Ipay88.requery_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } - end - - private - - def parameterize(params) - params.reject { |k, v| v.blank? }.keys.sort.collect { |key| "#{key}=#{CGI.escape(params[key].to_s)}" }.join("&") - end - - def amount_in_cents - @amount_in_cents ||= (self.amount || "").gsub(/[.,]/, "") - end end end end diff --git a/test/unit/integrations/notifications/ipay88_notification_test.rb b/test/unit/integrations/notifications/ipay88_notification_test.rb new file mode 100644 index 00000000000..80b89bfc78f --- /dev/null +++ b/test/unit/integrations/notifications/ipay88_notification_test.rb @@ -0,0 +1,107 @@ +require 'test_helper' + +class Ipay88NotificationTest < Test::Unit::TestCase + include ActiveMerchant::Billing::Integrations + + def setup + @ipay88 = build_notification(http_raw_data) + end + + def test_accessors + assert_equal "ipay88merchcode", @ipay88.account + assert_equal 6, @ipay88.payment + assert_equal "order-500", @ipay88.item_id + assert_equal "5.00", @ipay88.gross + assert_equal "MYR", @ipay88.currency + assert_equal "Remarkable", @ipay88.remark + assert_equal "12345", @ipay88.transaction_id + assert_equal "auth123", @ipay88.auth_code + assert_equal "Completed", @ipay88.status + assert_equal "Invalid merchant", @ipay88.error + assert_equal "bPlMszCBwxlfGX9ZkgmSfT+OeLQ=", @ipay88.signature + end + + def test_secure_request + assert @ipay88.secure? + end + + def test_success + assert @ipay88.success? + end + + def test_insecure_request + assert !build_notification(http_raw_data(:invalid_sig)).secure? + end + + def test_acknowledge + params = parameterize(payload) + @ipay88.expects(:ssl_post).with(Ipay88.requery_url, params, + { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } + ).returns("00") + + assert @ipay88.acknowledge + end + + def test_unsuccessful_acknowledge_due_to_signature + ipay = build_notification(http_raw_data(:invalid_sig)) + assert !ipay.acknowledge + end + + def test_unsuccessful_acknowledge_due_to_requery + params = parameterize(payload) + @ipay88.expects(:ssl_post).with(Ipay88.requery_url, params, + { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } + ).returns("Invalid parameters") + assert !@ipay88.acknowledge + end + + def test_unsuccessful_acknowledge_due_to_payment_failed + params = parameterize(payload) + ipay = build_notification(http_raw_data(:payment_failed)) + assert !ipay.acknowledge + end + + def test_unsuccessful_acknowledge_due_to_missing_amount + ipay = build_notification(http_raw_data(:missing_amount)) + assert !ipay.acknowledge + end + + private + def http_raw_data(mode=:success) + base = { "MerchantCode" => "ipay88merchcode", + "PaymentId" => 6, + "RefNo" => "order-500", + "Amount" => "5.00", + "Currency" => "MYR", + "Remark" => "Remarkable", + "TransId" => "12345", + "AuthCode" => "auth123", + "Status" => 1, + "ErrDesc" => "Invalid merchant" } + + case mode + when :success + parameterize(base.merge("Signature" => "bPlMszCBwxlfGX9ZkgmSfT+OeLQ=")) + when :invalid_sig + parameterize(base.merge("Signature" => "hacked")) + when :payment_failed + parameterize(base.merge("Status" => 0, "Signature" => "p8nXYcl/wytpNMzsf31O/iu/2EU=")) + when :missing_amount + parameterize(base.except("Amount")) + else + "" + end + end + + def payload + { "MerchantCode" => "ipay88merchcode", "RefNo" => "order-500", "Amount" => "5.00" } + end + + def parameterize(params) + params.reject{|k, v| v.blank?}.keys.sort.collect { |key| "#{key}=#{CGI.escape(params[key].to_s)}" }.join("&") + end + + def build_notification(data) + Ipay88::Notification.new(data, :credential2 => "apple") + end +end diff --git a/test/unit/integrations/returns/ipay88_return_test.rb b/test/unit/integrations/returns/ipay88_return_test.rb index 3ae6c963a6c..e797905ae39 100644 --- a/test/unit/integrations/returns/ipay88_return_test.rb +++ b/test/unit/integrations/returns/ipay88_return_test.rb @@ -4,113 +4,23 @@ class Ipay88ReturnTest < Test::Unit::TestCase include ActiveMerchant::Billing::Integrations def setup - @ipay88 = build_return(http_raw_data) + @ipay = Ipay88::Return.new(http_raw_data, :credential2 => 'secret') end - def test_accessors - assert_equal "ipay88merchcode", @ipay88.account - assert_equal 6, @ipay88.payment - assert_equal "order-500", @ipay88.order - assert_equal "5.00", @ipay88.amount - assert_equal "MYR", @ipay88.currency - assert_equal "Remarkable", @ipay88.remark - assert_equal "12345", @ipay88.transaction - assert_equal "auth123", @ipay88.auth_code - assert_equal "1", @ipay88.status - assert_equal "Invalid merchant", @ipay88.error - assert_equal "bPlMszCBwxlfGX9ZkgmSfT+OeLQ=", @ipay88.signature - end - - def test_secure_request - assert @ipay88.secure? - end - - def test_insecure_request - assert !build_return(http_raw_data(:invalid_sig)).secure? - end - - def test_successful_return - params = parameterize(payload) - Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.requery_url, params, - { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } - ).returns("00") - - assert @ipay88.success? - end - - def test_unsuccessful_return_due_to_signature - ipay = build_return(http_raw_data(:invalid_sig)) - assert !ipay.success? - end - - def test_unsuccessful_return_due_to_requery - params = parameterize(payload) - Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.requery_url, params, - { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } - ).returns("Invalid parameters") - assert !@ipay88.success? - end - - def test_unsuccessful_return_due_to_payment_failed - params = parameterize(payload) - Ipay88::Return.any_instance.expects(:ssl_post).with(Ipay88.requery_url, params, - { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" } - ).returns("00") - ipay = build_return(http_raw_data(:payment_failed)) - assert !ipay.success? - end - - def test_unsuccessful_return_due_to_missing_amount - ipay = build_return(http_raw_data(:missing_amount)) - assert !ipay.success? + def test_success? + assert @ipay.success? end def test_message_returns_error_description - ipay = build_return(parameterize({"ErrDesc" => "Invalid merchant"})) - assert_equal 'Invalid merchant', ipay.message + assert_equal 'Customer Cancel Transaction', @ipay.message end def test_cancelled - ipay = build_return(parameterize({"ErrDesc" => "Customer Cancel Transaction"})) - assert ipay.cancelled? + assert @ipay.cancelled? end private - def http_raw_data(mode=:success) - base = { "MerchantCode" => "ipay88merchcode", - "PaymentId" => 6, - "RefNo" => "order-500", - "Amount" => "5.00", - "Currency" => "MYR", - "Remark" => "Remarkable", - "TransId" => "12345", - "AuthCode" => "auth123", - "Status" => 1, - "ErrDesc" => "Invalid merchant" } - - case mode - when :success - parameterize(base.merge("Signature" => "bPlMszCBwxlfGX9ZkgmSfT+OeLQ=")) - when :invalid_sig - parameterize(base.merge("Signature" => "hacked")) - when :payment_failed - parameterize(base.merge("Status" => 0, "Signature" => "p8nXYcl/wytpNMzsf31O/iu/2EU=")) - when :missing_amount - parameterize(base.except("Amount")) - else - "" - end - end - - def payload - { "MerchantCode" => "ipay88merchcode", "RefNo" => "order-500", "Amount" => "5.00" } - end - - def parameterize(params) - params.reject{|k, v| v.blank?}.keys.sort.collect { |key| "#{key}=#{CGI.escape(params[key].to_s)}" }.join("&") - end - - def build_return(data) - Ipay88::Return.new(data, :credential2 => "apple") + def http_raw_data + "Amount=0.10&AuthCode=12345678&Currency=USD&ErrDesc=Customer Cancel Transaction&MerchantCode=M00001&PaymentId=1&RefNo=10000001&Remark=&Signature=RWAehzFtiNCKQWpXheazrCF33J4%3D&Status=1&TransId=T123456789" end end From cfc15a685008a5caeda51d3fcae598ed8b1e741b Mon Sep 17 00:00:00 2001 From: Nathan Verni Date: Fri, 6 Dec 2013 08:30:48 -0500 Subject: [PATCH 086/104] Fixing typos in AuthorizeNetCimGateway --- lib/active_merchant/billing/gateways/authorize_net_cim.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/active_merchant/billing/gateways/authorize_net_cim.rb b/lib/active_merchant/billing/gateways/authorize_net_cim.rb index 3ef54d507b3..bc3c356f5d6 100644 --- a/lib/active_merchant/billing/gateways/authorize_net_cim.rb +++ b/lib/active_merchant/billing/gateways/authorize_net_cim.rb @@ -369,8 +369,8 @@ def update_customer_shipping_address(options) # - :type = (:prior_auth_capture) (OPTIONAL) # # ==== For :type == :refund only - # * :credit_card_number_masked -- (CONDITIONAL - required for credit card refunds is :customer_profile_id AND :customer_payment_profile_id are missing) - # * :bank_routing_number_masked && :bank_account_number_masked -- (CONDITIONAL - required for electronic check refunds is :customer_profile_id AND :customer_payment_profile_id are missing) (NOT ABLE TO TEST - I keep getting "ACH transactions are not accepted by this merchant." when trying to make a payment and, until that's possible I can't refund (wiseleyb@gmail.com)) + # * :credit_card_number_masked -- (CONDITIONAL - required for credit card refunds if :customer_profile_id AND :customer_payment_profile_id are missing) + # * :bank_routing_number_masked && :bank_account_number_masked -- (CONDITIONAL - required for electronic check refunds if :customer_profile_id AND :customer_payment_profile_id are missing) (NOT ABLE TO TEST - I keep getting "ACH transactions are not accepted by this merchant." when trying to make a payment and, until that's possible I can't refund (wiseleyb@gmail.com)) def create_customer_profile_transaction(options) requires!(options, :transaction) requires!(options[:transaction], :type) @@ -412,8 +412,8 @@ def create_customer_profile_transaction(options) # # * :credit_card_number_masked -- Four Xs followed by the last four digits of the credit card (CONDITIONAL - used if customer_profile_id and customer_payment_profile_id aren't given) # - # * :bank_routing_number_masked -- The last four gidits of the routing number to be refunded (CONDITIONAL - must be used with :bank_account_number_masked) - # * :bank_account_number_masked -- The last four digis of the bank account number to be refunded, Ex. XXXX1234 (CONDITIONAL - must be used with :bank_routing_number_masked) + # * :bank_routing_number_masked -- The last four digits of the routing number to be refunded (CONDITIONAL - must be used with :bank_account_number_masked) + # * :bank_account_number_masked -- The last four digits of the bank account number to be refunded, Ex. XXXX1234 (CONDITIONAL - must be used with :bank_routing_number_masked) # # * :tax - A hash containing tax information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) # * :duty - A hash containing duty information for the refund (OPTIONAL - :amount, :name (31 characters), :description (255 characters)) From 48fb0807347e877f005e19f211bb8af44b21c8cb Mon Sep 17 00:00:00 2001 From: Thierry Joyal Date: Tue, 10 Dec 2013 07:33:25 -0500 Subject: [PATCH 087/104] Refactor gateway selector error handling --- lib/active_merchant/billing/base.rb | 11 +++++++++-- test/unit/base_test.rb | 24 +++++++++++++++++++----- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/lib/active_merchant/billing/base.rb b/lib/active_merchant/billing/base.rb index 719480c5f6c..f059de6b705 100644 --- a/lib/active_merchant/billing/base.rb +++ b/lib/active_merchant/billing/base.rb @@ -31,8 +31,15 @@ def self.mode=(mode) # # ActiveMerchant::Billing::Base.gateway('moneris').new def self.gateway(name) - raise NameError if (name_str = name.to_s.downcase).blank? - Billing.const_get("#{name_str}_gateway".camelize) + name_str = name.to_s.strip.downcase + + raise(ArgumentError, 'A gateway provider must be specified') if name_str.blank? + + begin + Billing.const_get("#{name_str}_gateway".camelize) + rescue + raise ArgumentError, "The specified gateway is not valid (#{name_str})" + end end # Return the matching integration module diff --git a/test/unit/base_test.rb b/test/unit/base_test.rb index 2ec4f4721ac..5679e4e9816 100644 --- a/test/unit/base_test.rb +++ b/test/unit/base_test.rb @@ -18,18 +18,32 @@ def test_should_return_a_new_gateway_specified_by_symbol_name assert_equal LinkpointGateway, Base.gateway(:linkpoint) end - def test_should_raise_when_invalid_gateway_is_passed - assert_raise NameError do - Base.gateway(:nil) + def test_should_raise_when_nil_gateway_is_passed + e = assert_raise ArgumentError do + Base.gateway(nil) end + assert_equal 'A gateway provider must be specified', e.message + end - assert_raise NameError do + def test_should_raise_when_empty_gateway_is_passed + e = assert_raise ArgumentError do Base.gateway('') end + assert_equal 'A gateway provider must be specified', e.message + end - assert_raise NameError do + def test_should_raise_when_invalid_gateway_symbol_is_passed + e = assert_raise ArgumentError do Base.gateway(:hotdog) end + assert_equal 'The specified gateway is not valid (hotdog)', e.message + end + + def test_should_raise_when_invalid_gateway_string_is_passed + e = assert_raise ArgumentError do + Base.gateway('hotdog') + end + assert_equal 'The specified gateway is not valid (hotdog)', e.message end def test_should_return_an_integration_by_name From 90488d5710a3e17458fa95c5ba00dea9f3943249 Mon Sep 17 00:00:00 2001 From: Matthew Smith Date: Thu, 23 Feb 2012 08:35:46 -0800 Subject: [PATCH 088/104] USA ePay Advanced: Fix check handling --- CHANGELOG | 1 + .../billing/gateways/usa_epay_advanced.rb | 8 ++- .../gateways/remote_usa_epay_advanced_test.rb | 60 ++++++++++--------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1788e8a3d1d..057683fd2f6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ * Braintree Blue: Return :credit_card_token as a top level param [ntalbott] * Braintree Blue: Allow unstoring just a credit card [ntalbott] * Braintree Blue: #store adds cards to existing customers [ntalbott] +* USA ePay Advanced: Fix check handling [nearapogee] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb index ef4a076f070..6da7602f575 100644 --- a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +++ b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb @@ -179,7 +179,6 @@ class UsaEpayAdvancedGateway < Gateway } #:nodoc: CHECK_DATA_OPTIONS = { - :check_number => [:integer, 'CheckNumber'], :drivers_license => [:string, 'DriversLicense'], :drivers_license_state => [:string, 'DriversLicenseState'], :record_type => [:string, 'RecordType'], @@ -1251,9 +1250,11 @@ def build_credit_card_or_check(soap, payment_method) end build_tag soap, :string, 'CardCode', payment_method[:method].verification_value when payment_method[:method].kind_of?(ActiveMerchant::Billing::Check) - build_tag soap, :string, 'Account', payment_method[:method].number + build_tag soap, :string, 'Account', payment_method[:method].account_number build_tag soap, :string, 'Routing', payment_method[:method].routing_number - build_tag soap, :string, 'AccountType', payment_method[:method].account_type.capitalize + unless payment_method[:method].account_type.nil? + build_tag soap, :string, 'AccountType', payment_method[:method].account_type.capitalize + end build_tag soap, :string, 'DriversLicense', options[:drivers_license] build_tag soap, :string, 'DriversLicenseState', options[:drivers_license_state] build_tag soap, :string, 'RecordType', options[:record_type] @@ -1339,6 +1340,7 @@ def build_credit_card_data(soap, options) def build_check_data(soap, options) soap.CheckData 'xsi:type' => "ns1:CheckData" do |soap| + build_tag soap, :integer, 'CheckNumber', options[:payment_method].number build_tag soap, :string, 'Account', options[:payment_method].account_number build_tag soap, :string, 'Routing', options[:payment_method].routing_number build_tag soap, :string, 'AccountType', options[:payment_method].account_type.capitalize diff --git a/test/remote/gateways/remote_usa_epay_advanced_test.rb b/test/remote/gateways/remote_usa_epay_advanced_test.rb index 1692e30dd4c..17af64106d4 100644 --- a/test/remote/gateways/remote_usa_epay_advanced_test.rb +++ b/test/remote/gateways/remote_usa_epay_advanced_test.rb @@ -2,20 +2,11 @@ require 'logger' class RemoteUsaEpayAdvancedTest < Test::Unit::TestCase - def setup - # Optional Logger Setup - # UsaEpayAdvancedGateway.logger = Logger.new('/tmp/usa_epay.log') - # UsaEpayAdvancedGateway.logger.level = Logger::DEBUG - - # Optional Wiredump Setup - # UsaEpayAdvancedGateway.wiredump_device = File.open('/tmp/usa_epay_dump.log', 'a+') - # UsaEpayAdvancedGateway.wiredump_device.sync = true - @gateway = UsaEpayAdvancedGateway.new(fixtures(:usa_epay_advanced)) @amount = 2111 - + @credit_card = ActiveMerchant::Billing::CreditCard.new( :number => '4000100011112224', :month => 12, @@ -37,19 +28,19 @@ def setup ) @check = ActiveMerchant::Billing::Check.new( - :number => '123456789', + :account_number => '123456789', :routing_number => '120450780', :account_type => 'checking', :first_name => "Fred", :last_name => "Flintstone" ) - + cc_method = [ - {:name => "My CC", :sort => 5, :method => @credit_card}, + {:name => "My CC", :sort => 5, :method => @credit_card}, {:name => "Other CC", :sort => 12, :method => @credit_card} ] - @options = { + @options = { :client_ip => '127.0.0.1', :billing_address => address, } @@ -86,6 +77,12 @@ def setup :amount => 10000 } + @run_transaction_check_options = { + :payment_method => @check, + :command => 'check', + :amount => 10000 + } + @run_sale_options = { :payment_method => @credit_card, :amount => 5000 @@ -97,12 +94,12 @@ def setup } payment_methods = [ - { + { :name => "My Visa", # optional :sort => 2, # optional :method => @credit_card }, - { + { :name => "My Checking", :method => @check } @@ -110,7 +107,7 @@ def setup end # Standard Gateway ================================================== - + def test_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_equal 'A', response.params['run_sale_return']['result_code'] @@ -130,7 +127,7 @@ def test_capture def test_void assert purchase = @gateway.purchase(@amount, @credit_card, @options.dup) - + assert credit = @gateway.void(purchase.authorization, @options) assert_equal 'true', credit.params['void_transaction_return'] end @@ -161,7 +158,7 @@ def test_invalid_login assert_failure response assert_equal 'Invalid software ID', response.message end - + # Customer ========================================================== def test_add_customer @@ -172,7 +169,7 @@ def test_add_customer def test_update_customer response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + @options.merge!(@update_customer_options.merge!(:customer_number => customer_number)) response = @gateway.update_customer(@options) assert response.params['update_customer_return'] @@ -192,7 +189,7 @@ def test_enable_disable_customer def test_add_customer_payment_method response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + @options.merge!(:customer_number => customer_number).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) assert response.params['add_customer_payment_method_return'] @@ -201,7 +198,7 @@ def test_add_customer_payment_method def test_add_customer_payment_method_verify response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + @add_payment_options[:payment_method][:method] = @bad_credit_card @options.merge!(:customer_number => customer_number, :verify => true).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) @@ -211,7 +208,7 @@ def test_add_customer_payment_method_verify def test_get_customer_payment_methods response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + response = @gateway.get_customer_payment_methods(:customer_number => customer_number) assert response.params['get_customer_payment_methods_return']['item'] end @@ -230,12 +227,12 @@ def test_get_customer_payment_method def test_update_customer_payment_method response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + @options.merge!(:customer_number => customer_number).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) payment_method_id = response.params['add_customer_payment_method_return'] - update_payment_options = @add_payment_options[:payment_method].merge(:method_id => payment_method_id, + update_payment_options = @add_payment_options[:payment_method].merge(:method_id => payment_method_id, :name => "Updated Card.") response = @gateway.update_customer_payment_method(update_payment_options) assert response.params['update_customer_payment_method_return'] @@ -244,7 +241,7 @@ def test_update_customer_payment_method def test_delete_customer_payment_method response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + @options.merge!(:customer_number => customer_number).merge!(@add_payment_options) response = @gateway.add_customer_payment_method(@options) id = response.params['add_customer_payment_method_return'] @@ -256,7 +253,7 @@ def test_delete_customer_payment_method def test_delete_customer response = @gateway.add_customer(@options.merge(@customer_options)) customer_number = response.params['add_customer_return'] - + response = @gateway.delete_customer(:customer_number => customer_number) assert response.params['delete_customer_return'] end @@ -278,6 +275,13 @@ def test_run_transaction assert response.params['run_transaction_return'] end + def test_run_transaction_check + @options.merge!(@run_transaction_check_options) + response = @gateway.run_transaction(@options) + assert response.params['run_transaction_return'] + assert response.success? + end + def test_run_sale @options.merge!(@run_sale_options) response = @gateway.run_sale(@options) @@ -413,7 +417,7 @@ def test_get_transaction_custom response = @gateway.run_sale(@options.merge(@run_sale_options)) reference_number = response.params['run_sale_return']['ref_num'] - response = @gateway.get_transaction_custom(:reference_number => reference_number, + response = @gateway.get_transaction_custom(:reference_number => reference_number, :fields => ['Response.StatusCode', 'Response.Status']) assert response.params['get_transaction_custom_return'] end From 0b40fba9fa3a15169f98004205166955b73b7441 Mon Sep 17 00:00:00 2001 From: Matthew Smith Date: Thu, 8 Mar 2012 17:20:57 -0800 Subject: [PATCH 089/104] USA ePay Advanced: Fix card expiration handling --- CHANGELOG | 1 + .../billing/gateways/usa_epay_advanced.rb | 24 ++++++++++++------- .../gateways/remote_usa_epay_advanced_test.rb | 10 ++++---- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 057683fd2f6..e869ddf50d8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ * Braintree Blue: Allow unstoring just a credit card [ntalbott] * Braintree Blue: #store adds cards to existing customers [ntalbott] * USA ePay Advanced: Fix check handling [nearapogee] +* USA ePay Advanced: Fix credit card expiration handling [nearapogee] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb index 6da7602f575..00ac58b0d28 100644 --- a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +++ b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb @@ -338,10 +338,11 @@ def add_customer(options={}) commit(__method__, request) end - # Update a customer by replacing all of the customer details.. - # - # Use quickUpdateCustomer to just update a few attributes. + # Update a customer by replacing all of the customer details. # + # ==== Required + # * :customer_number -- customer to update + # # ==== Options # * Same as add_customer # @@ -542,7 +543,7 @@ def run_customer_transaction(options={}) # will be returned in the response. # # ==== Options - # * :method -- credit_card or check + # * :payment_method -- credit_card or check # * :command -- sale, credit, void, creditvoid, authonly, capture, postauth, check, checkcredit; defaults to sale; only required for run_transaction when other than sale # * :reference_number -- for the original transaction; obtained by sale or authonly # * :authorization_code -- required for postauth; obtained offline @@ -1242,8 +1243,8 @@ def build_credit_card_or_check(soap, payment_method) case when payment_method[:method].kind_of?(ActiveMerchant::Billing::CreditCard) build_tag soap, :string, 'CardNumber', payment_method[:method].number - build_tag soap, :string, 'CardExpiration', - "#{"%02d" % payment_method[:method].month}#{payment_method[:method].year}" + build_tag soap, :string, 'CardExpiration', + "#{"%02d" % payment_method[:method].month}#{payment_method[:method].year.to_s[-2..-1]}" if options[:billing_address] build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1] build_tag soap, :string, 'AvsZip', options[:billing_address][:zip] @@ -1324,8 +1325,7 @@ def build_transaction_detail(soap, options) def build_credit_card_data(soap, options) soap.CreditCardData 'xsi:type' => "ns1:CreditCardData" do |soap| build_tag soap, :string, 'CardNumber', options[:payment_method].number - build_tag soap, :string, 'CardExpiration', - "#{"%02d" % options[:payment_method].month}#{options[:payment_method].year}" + build_tag soap, :string, 'CardExpiration', build_card_expiration(options) if options[:billing_address] build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1] build_tag soap, :string, 'AvsZip', options[:billing_address][:zip] @@ -1338,6 +1338,14 @@ def build_credit_card_data(soap, options) end end + def build_card_expiration(options) + month = options[:payment_method].month + year = options[:payment_method].year + unless month.nil? || year.nil? + "#{"%02d" % month}#{year.to_s[-2..-1]}" + end + end + def build_check_data(soap, options) soap.CheckData 'xsi:type' => "ns1:CheckData" do |soap| build_tag soap, :integer, 'CheckNumber', options[:payment_method].number diff --git a/test/remote/gateways/remote_usa_epay_advanced_test.rb b/test/remote/gateways/remote_usa_epay_advanced_test.rb index 17af64106d4..4198325cc72 100644 --- a/test/remote/gateways/remote_usa_epay_advanced_test.rb +++ b/test/remote/gateways/remote_usa_epay_advanced_test.rb @@ -9,8 +9,8 @@ def setup @credit_card = ActiveMerchant::Billing::CreditCard.new( :number => '4000100011112224', - :month => 12, - :year => 12, + :month => 9, + :year => 14, :brand => 'visa', :verification_value => '123', :first_name => "Fred", @@ -19,8 +19,8 @@ def setup @bad_credit_card = ActiveMerchant::Billing::CreditCard.new( :number => '4000300011112220', - :month => 12, - :year => 12, + :month => 9, + :year => 14, :brand => 'visa', :verification_value => '999', :first_name => "Fred", @@ -234,6 +234,7 @@ def test_update_customer_payment_method update_payment_options = @add_payment_options[:payment_method].merge(:method_id => payment_method_id, :name => "Updated Card.") + response = @gateway.update_customer_payment_method(update_payment_options) assert response.params['update_customer_payment_method_return'] end @@ -273,6 +274,7 @@ def test_run_transaction @options.merge!(@run_transaction_options) response = @gateway.run_transaction(@options) assert response.params['run_transaction_return'] + assert response.success? end def test_run_transaction_check From d2191f7c2af0d11df1d4a67d551f3f8082ff835f Mon Sep 17 00:00:00 2001 From: Matthew Smith Date: Thu, 8 Mar 2012 18:23:48 -0800 Subject: [PATCH 090/104] USA ePay Advanced: Fix custom trans responses It was not working correctly for single items. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/usa_epay_advanced.rb | 7 ++++--- test/remote/gateways/remote_usa_epay_advanced_test.rb | 3 +++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e869ddf50d8..376df2cf5ae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ * Braintree Blue: #store adds cards to existing customers [ntalbott] * USA ePay Advanced: Fix check handling [nearapogee] * USA ePay Advanced: Fix credit card expiration handling [nearapogee] +* USA ePay Advanced: Fix handling of custom transaction responses for single items [nearapogee] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb index 00ac58b0d28..ff1d738a7b7 100644 --- a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +++ b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb @@ -342,7 +342,7 @@ def add_customer(options={}) # # ==== Required # * :customer_number -- customer to update - # + # # ==== Options # * Same as add_customer # @@ -1243,7 +1243,7 @@ def build_credit_card_or_check(soap, payment_method) case when payment_method[:method].kind_of?(ActiveMerchant::Billing::CreditCard) build_tag soap, :string, 'CardNumber', payment_method[:method].number - build_tag soap, :string, 'CardExpiration', + build_tag soap, :string, 'CardExpiration', "#{"%02d" % payment_method[:method].month}#{payment_method[:method].year.to_s[-2..-1]}" if options[:billing_address] build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1] @@ -1471,7 +1471,8 @@ def parse(action, soap) when :get_customer_payment_methods p['item'] when :get_transaction_custom - p['item'].inject({}) { |map, field| map[field['field']] = field['value']; map } + items = p['item'].kind_of?(Array) ? p['item'] : [p['item']] + items.inject({}) { |hash, item| hash[item['field']] = item['value']; hash } else p end diff --git a/test/remote/gateways/remote_usa_epay_advanced_test.rb b/test/remote/gateways/remote_usa_epay_advanced_test.rb index 4198325cc72..2488438a814 100644 --- a/test/remote/gateways/remote_usa_epay_advanced_test.rb +++ b/test/remote/gateways/remote_usa_epay_advanced_test.rb @@ -422,6 +422,9 @@ def test_get_transaction_custom response = @gateway.get_transaction_custom(:reference_number => reference_number, :fields => ['Response.StatusCode', 'Response.Status']) assert response.params['get_transaction_custom_return'] + response = @gateway.get_transaction_custom(:reference_number => reference_number, + :fields => ['Response.StatusCode']) + assert response.params['get_transaction_custom_return'] end def test_get_check_trace From dc6d4a4f065a483ae818edba4789ee5f7438b770 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Tue, 15 May 2012 15:33:08 -0700 Subject: [PATCH 091/104] USA ePay Advanced: Fix capture amount Closes #926. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/usa_epay_advanced.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 376df2cf5ae..973007d7adb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ * USA ePay Advanced: Fix check handling [nearapogee] * USA ePay Advanced: Fix credit card expiration handling [nearapogee] * USA ePay Advanced: Fix handling of custom transaction responses for single items [nearapogee] +* USA ePay Advanced: Fix capture amount [nearapogee] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb index ff1d738a7b7..acd48e2b367 100644 --- a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +++ b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb @@ -1166,7 +1166,7 @@ def build_capture_transaction(soap, options) soap.tag! "ns1:captureTransaction" do |soap| build_token soap, options build_tag soap, :integer, 'RefNum', options[:reference_number] - build_tag soap, :double, 'RefNum', amount(options[:amount]) + build_tag soap, :double, 'Amount', amount(options[:amount]) end end From 953a54c9cff1effa686a4d5ac46ba326e275c55f Mon Sep 17 00:00:00 2001 From: Jesse Storimer Date: Wed, 11 Dec 2013 12:24:04 -0500 Subject: [PATCH 092/104] Handle comma-delimited amounts with Valitor --- .../billing/integrations/valitor/response_fields.rb | 4 ++-- .../notifications/valitor_notification_test.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/active_merchant/billing/integrations/valitor/response_fields.rb b/lib/active_merchant/billing/integrations/valitor/response_fields.rb index 636a0cc7081..29c8176a0a3 100644 --- a/lib/active_merchant/billing/integrations/valitor/response_fields.rb +++ b/lib/active_merchant/billing/integrations/valitor/response_fields.rb @@ -36,7 +36,7 @@ def received_at end def gross - "%0.2f" % params['Upphaed'].to_s + "%0.2f" % params['Upphaed'].to_s.sub(',', '.') end def card_type @@ -94,4 +94,4 @@ def acknowledge(authcode = nil) end end end -end \ No newline at end of file +end diff --git a/test/unit/integrations/notifications/valitor_notification_test.rb b/test/unit/integrations/notifications/valitor_notification_test.rb index a49502e4108..4775abc9acb 100644 --- a/test/unit/integrations/notifications/valitor_notification_test.rb +++ b/test/unit/integrations/notifications/valitor_notification_test.rb @@ -33,6 +33,12 @@ def test_accessors assert !@notification.test? end + + def test_comma_delimited_notification + @notification = Valitor::Notification.new(http_raw_query_with_comma_delimited_currency) + + assert_equal "25.99", @notification.gross + end def test_acknowledge valid = Valitor.notification(http_raw_query, :credential2 => 'password') @@ -54,4 +60,8 @@ def test_test_mode def http_raw_query "Kortategund=VISA&KortnumerSidustu=9999&Dagsetning=21.01.2011&Heimildarnumer=123450&Faerslunumer=FÆRSLUNR: 0026237&VefverslunSalaID=2b969de3-6928-4fa7-a0d6-6dec63fec5c3&Tilvisunarnumer=order684afbb93730db2492a8fa2f3fedbcb9&RafraenUndirskriftSvar=03d859813eff711d6c8667b0caf5f5a5&Upphaed=100&Nafn=NAME&Heimilisfang=123 ADDRESS&Postnumer=98765&Stadur=CITY&Land=COUNTRY&Tolvupostfang=EMAIL@EXAMPLE.COM&Athugasemdir=COMMENTS&LeyfirEndurtoku=" end + + def http_raw_query_with_comma_delimited_currency + "Kortategund=VISA&KortnumerSidustu=9999&Dagsetning=21.01.2011&Heimildarnumer=123450&Faerslunumer=FÆRSLUNR: 0026237&VefverslunSalaID=2b969de3-6928-4fa7-a0d6-6dec63fec5c3&Tilvisunarnumer=order684afbb93730db2492a8fa2f3fedbcb9&RafraenUndirskriftSvar=03d859813eff711d6c8667b0caf5f5a5&Upphaed=25,99&Nafn=NAME&Heimilisfang=123 ADDRESS&Postnumer=98765&Stadur=CITY&Land=COUNTRY&Tolvupostfang=EMAIL@EXAMPLE.COM&Athugasemdir=COMMENTS&LeyfirEndurtoku=" + end end From 33ce76b11efa6993f71d87503c74df4443b3dfc4 Mon Sep 17 00:00:00 2001 From: Justin Jones Date: Wed, 4 Dec 2013 14:12:36 +0800 Subject: [PATCH 093/104] NAB Transact: Fix capture/refund descriptor Fixes a problem where merchant descriptor wasn't being handled for capture and authorization requests. Closes #936. --- CHANGELOG | 1 + .../billing/gateways/nab_transact.rb | 2 + test/unit/gateways/nab_transact_test.rb | 65 ++++++++++++++++++- 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 973007d7adb..6493c7a56db 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ * USA ePay Advanced: Fix credit card expiration handling [nearapogee] * USA ePay Advanced: Fix handling of custom transaction responses for single items [nearapogee] * USA ePay Advanced: Fix capture amount [nearapogee] +* NAB Transact: Fix merchant descriptor with capture/refund requests [nagash] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/nab_transact.rb b/lib/active_merchant/billing/gateways/nab_transact.rb index 754a45dee54..8e75cbf5943 100644 --- a/lib/active_merchant/billing/gateways/nab_transact.rb +++ b/lib/active_merchant/billing/gateways/nab_transact.rb @@ -127,6 +127,8 @@ def build_reference_request(money, reference, options) xml.tag! 'purchaseOrderNo', order_id xml.tag! 'preauthID', preauth_id + add_metadata(xml, options) + xml.target! end diff --git a/test/unit/gateways/nab_transact_test.rb b/test/unit/gateways/nab_transact_test.rb index 126d9cde318..09c4b5c7105 100644 --- a/test/unit/gateways/nab_transact_test.rb +++ b/test/unit/gateways/nab_transact_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class NabTransactTest < Test::Unit::TestCase + include CommStub + def setup @gateway = NabTransactGateway.new( :login => 'login', @@ -16,7 +18,6 @@ def setup } end - def test_successful_purchase @gateway.expects(:ssl_post).with(&check_transaction_type(:purchase)).returns(successful_purchase_response) @@ -28,6 +29,18 @@ def test_successful_purchase assert response.test? end + def test_successful_purchase_with_merchant_descriptor + name, location = 'Active Merchant', 'USA' + + response = assert_metadata(name, location) do + response = @gateway.purchase(@amount, @credit_card, @options.merge(:merchant_name => name, :merchant_location => location)) + end + + assert response + assert_instance_of Response, response + assert_success response + end + def test_successful_authorize @gateway.expects(:ssl_post).with(&check_transaction_type(:authorization)).returns(successful_authorize_response) assert response = @gateway.authorize(@amount, @credit_card, @options) @@ -35,6 +48,18 @@ def test_successful_authorize assert response.test? end + def test_successful_authorize_with_merchant_descriptor + name, location = 'Active Merchant', 'USA' + + response = assert_metadata(name, location) do + response = @gateway.authorize(@amount, @credit_card, @options.merge(:merchant_name => name, :merchant_location => location)) + end + + assert response + assert_instance_of Response, response + assert_success response + end + def test_successful_capture @gateway.expects(:ssl_post).with(&check_transaction_type(:capture)).returns(successful_purchase_response) assert response = @gateway.capture(@amount, '009887*test*009887*200') @@ -42,6 +67,18 @@ def test_successful_capture assert response.test? end + def test_successful_capture_with_merchant_descriptor + name, location = 'Active Merchant', 'USA' + + response = assert_metadata(name, location) do + response = @gateway.capture(@amount, '009887*test*009887*200', @options.merge(:merchant_name => name, :merchant_location => location)) + end + + assert response + assert_instance_of Response, response + assert_success response + end + def test_unsuccessful_purchase @gateway.expects(:ssl_post).with(&check_transaction_type(:purchase)).returns(failed_purchase_response) @@ -74,6 +111,18 @@ def test_successful_refund assert_success @gateway.refund(@amount, "009887", {:order_id => '1'}) end + def test_successful_refund_with_merchant_descriptor + name, location = 'Active Merchant', 'USA' + + response = assert_metadata(name, location) do + response = @gateway.refund(@amount, '009887', {:order_id => '1', :merchant_name => name, :merchant_location => location}) + end + + assert response + assert_instance_of Response, response + assert_success response + end + def test_failed_refund @gateway.expects(:ssl_post).with(&check_transaction_type(:refund)).returns(failed_refund_response) @@ -91,6 +140,20 @@ def check_transaction_type(type) end end + def valid_metadata(name, location) + valid_metadata = <<-XML.gsub(/^\s{4}/,'').gsub(/\n/, '') + + XML + end + + def assert_metadata(name, location, &block) + stub_comms(@gateway, :ssl_request) do + block.call + end.check_request do |method, endpoint, data, headers| + metadata_matcher = Regexp.escape(valid_metadata(name, location)) + assert_match /#{metadata_matcher}/, data + end.respond_with(successful_purchase_response) + end def failed_login_response '504Invalid merchant ID' From 8dba9d3c2358458a5901ef81d085dc047478028b Mon Sep 17 00:00:00 2001 From: Maxim Pechnikov Date: Wed, 4 Dec 2013 17:51:35 +0300 Subject: [PATCH 094/104] Braintree Blue: Add custom_fields & device_data Closes #937. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/braintree_blue.rb | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 6493c7a56db..8298b8c213c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ * USA ePay Advanced: Fix handling of custom transaction responses for single items [nearapogee] * USA ePay Advanced: Fix capture amount [nearapogee] * NAB Transact: Fix merchant descriptor with capture/refund requests [nagash] +* Braintree Blue: Add custom_fields & device_data parameters [parallel588] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb index 15073519716..05b68e2529c 100644 --- a/lib/active_merchant/billing/gateways/braintree_blue.rb +++ b/lib/active_merchant/billing/gateways/braintree_blue.rb @@ -456,6 +456,8 @@ def create_transaction_parameters(money, credit_card_or_vault_id, options) } } + parameters[:custom_fields] = options[:custom_fields] + parameters[:device_data] = options[:device_data] if options[:device_data] if merchant_account_id = (options[:merchant_account_id] || @merchant_account_id) parameters[:merchant_account_id] = merchant_account_id end From 78d93c9b2a0fcd665b3909fded0da79b47f22af3 Mon Sep 17 00:00:00 2001 From: Kei Kubo Date: Fri, 6 Dec 2013 20:49:37 +0900 Subject: [PATCH 095/104] Webpay: Add authorize & capture Closes #942. --- CHANGELOG | 1 + .../billing/gateways/webpay.rb | 30 ++++-- test/remote/gateways/remote_webpay_test.rb | 18 ++++ test/unit/gateways/webpay_test.rb | 94 ++++++++++++++++++- 4 files changed, 136 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8298b8c213c..b8d071a6f55 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,7 @@ * USA ePay Advanced: Fix capture amount [nearapogee] * NAB Transact: Fix merchant descriptor with capture/refund requests [nagash] * Braintree Blue: Add custom_fields & device_data parameters [parallel588] +* Webpay: Add authorize & capture [keikubo] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/webpay.rb b/lib/active_merchant/billing/gateways/webpay.rb index 999a32e5b45..de8d6e484dc 100644 --- a/lib/active_merchant/billing/gateways/webpay.rb +++ b/lib/active_merchant/billing/gateways/webpay.rb @@ -13,12 +13,10 @@ class WebpayGateway < StripeGateway self.homepage_url = 'https://webpay.jp/' self.display_name = 'WebPay' - def authorize(money, credit_card, options = {}) - raise NotImplementedError.new - end - - def capture(money, credit_card, options = {}) - raise NotImplementedError.new + def capture(money, authorization, options = {}) + post = {:amount => localized_amount(money)} + add_application_fee(post, options) + commit(:post, "charges/#{CGI.escape(authorization)}/capture", post) end def refund(money, identification, options = {}) @@ -52,6 +50,26 @@ def add_customer(post, creditcard, options) post[:customer] = options[:customer] if options[:customer] && !creditcard.respond_to?(:number) end + def store(creditcard, options = {}) + post = {} + add_creditcard(post, creditcard, options) + post[:description] = options[:description] + post[:email] = options[:email] + + commit_options = generate_options(options) + if options[:customer] + MultiResponse.run(:first) do |r| + r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/", post, commit_options) } + + return r unless options[:set_default] and r.success? and !r.params["id"].blank? + + r.process { update_customer(options[:customer], :default_card => r.params["id"]) } + end + else + commit(:post, 'customers', post, commit_options) + end + end + def json_error(raw_response) msg = 'Invalid response received from the WebPay API. Please contact support@webpay.jp if you continue to receive this message.' msg += " (The raw response returned by the API was #{raw_response.inspect})" diff --git a/test/remote/gateways/remote_webpay_test.rb b/test/remote/gateways/remote_webpay_test.rb index 3aa0edf27ea..27645565d6a 100644 --- a/test/remote/gateways/remote_webpay_test.rb +++ b/test/remote/gateways/remote_webpay_test.rb @@ -48,6 +48,24 @@ def test_unsuccessful_purchase assert_equal 'Your card number is incorrect', response.message end + def test_authorization_and_capture + assert authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert !authorization.params["captured"] + + assert capture = @gateway.capture(@amount, authorization.authorization) + assert_success capture + end + + def test_authorization_and_void + assert authorization = @gateway.authorize(@amount, @credit_card, @options) + assert_success authorization + assert !authorization.params["captured"] + + assert void = @gateway.void(authorization.authorization) + assert_success void + end + def test_successful_void assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response diff --git a/test/unit/gateways/webpay_test.rb b/test/unit/gateways/webpay_test.rb index 98fc8749852..87a08584fe7 100644 --- a/test/unit/gateways/webpay_test.rb +++ b/test/unit/gateways/webpay_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class WebpayTest < Test::Unit::TestCase + include CommStub + def setup @gateway = WebpayGateway.new(:login => 'login') @@ -14,6 +16,25 @@ def setup } end + def test_successful_authorization + @gateway.expects(:ssl_request).returns(successful_authorization_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal 'ch_test_charge', response.authorization + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_request).returns(successful_capture_response) + + assert response = @gateway.capture(@amount, "ch_test_charge") + assert_success response + assert response.test? + end + def test_successful_purchase @gateway.expects(:ssl_request).returns(successful_purchase_response) @@ -26,7 +47,7 @@ def test_successful_purchase assert response.test? end - def test_appropiate_purchase_amount + def test_appropriate_purchase_amount @gateway.expects(:ssl_request).returns(successful_purchase_response) response = @gateway.purchase(@amount, @credit_card, @options) @@ -36,6 +57,17 @@ def test_appropiate_purchase_amount assert_equal @amount / 100, response.params["amount"] end + def test_successful_purchase_with_token + response = stub_comms(@gateway, :ssl_request) do + @gateway.purchase(@amount, "tok_xxx") + end.check_request do |method, endpoint, data, headers| + assert_match(/card=tok_xxx/, data) + end.respond_with(successful_purchase_response) + + assert response + assert_instance_of Response, response + assert_success response + end def test_successful_void @gateway.expects(:ssl_request).returns(successful_purchase_response(true)) @@ -134,6 +166,66 @@ def test_metadata_header private + def successful_authorization_response + <<-RESPONSE +{ + "id": "ch_test_charge", + "object": "charge", + "created": 1309131571, + "livemode": false, + "paid": true, + "amount": 40000, + "currency": "jpy", + "refunded": false, + "fee": 0, + "fee_details": [], + "card": { + "country": "JP", + "exp_month": 9, + "exp_year": #{Time.now.year + 1}, + "last4": "4242", + "object": "card", + "type": "Visa" + }, + "captured": false, + "description": "ActiveMerchant Test Purchase", + "dispute": null, + "uncaptured": true, + "disputed": false +} + RESPONSE + end + + def successful_capture_response + <<-RESPONSE +{ + "id": "ch_test_charge", + "object": "charge", + "created": 1309131571, + "livemode": false, + "paid": true, + "amount": 40000, + "currency": "jpy", + "refunded": false, + "fee": 0, + "fee_details": [], + "card": { + "country": "JP", + "exp_month": 9, + "exp_year": #{Time.now.year + 1}, + "last4": "4242", + "object": "card", + "type": "Visa" + }, + "captured": true, + "description": "ActiveMerchant Test Purchase", + "dispute": null, + "uncaptured": false, + "disputed": false +} + RESPONSE + end + # Place raw successful response from gateway here def successful_purchase_response(refunded=false) <<-RESPONSE From a014a3cf1ec086752739d0f5e854adcd68a2631a Mon Sep 17 00:00:00 2001 From: Duff OMelia Date: Fri, 6 Dec 2013 17:12:57 -0500 Subject: [PATCH 096/104] MerchantWarrior: Pass description Merchant Warrior docs say the transactionProduct attribute is the description: http://cl.ly/image/0D0E3u3k1417/content.png http://dox.merchantwarrior.com/files/MWE%20API.pdf Use options[:description] like many other gateways to abstract this away so we're not passing in custom options for each gateway. Closes #945. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/merchant_warrior.rb | 2 +- test/remote/gateways/remote_merchant_warrior_test.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b8d071a6f55..57e5c1fd059 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ * NAB Transact: Fix merchant descriptor with capture/refund requests [nagash] * Braintree Blue: Add custom_fields & device_data parameters [parallel588] * Webpay: Add authorize & capture [keikubo] +* MerchantWarrior: Pass description [duff] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/merchant_warrior.rb b/lib/active_merchant/billing/gateways/merchant_warrior.rb index 9ee22a3c3b1..74aa6ada287 100644 --- a/lib/active_merchant/billing/gateways/merchant_warrior.rb +++ b/lib/active_merchant/billing/gateways/merchant_warrior.rb @@ -86,7 +86,7 @@ def add_address(post, options) end def add_product(post, options) - post['transactionProduct'] = options[:transaction_product] + post['transactionProduct'] = options[:description] end def add_payment_method(post, payment_method) diff --git a/test/remote/gateways/remote_merchant_warrior_test.rb b/test/remote/gateways/remote_merchant_warrior_test.rb index dadef6c86a4..005ab78ee1b 100644 --- a/test/remote/gateways/remote_merchant_warrior_test.rb +++ b/test/remote/gateways/remote_merchant_warrior_test.rb @@ -24,7 +24,7 @@ def setup :address1 => '123 test st', :zip => '4000' }, - :transaction_product => 'TestProduct' + :description => 'TestProduct' } end From c92b6c06c91339fbd14b9f0bf6a8a67abc114630 Mon Sep 17 00:00:00 2001 From: Duff OMelia Date: Mon, 9 Dec 2013 12:20:52 -0500 Subject: [PATCH 097/104] Stripe: Separate email from description Before: Specify the description for the purchase or authorization using the options[:description] || options[:email]. After: Use options[:description] for description. Use metadata for the email. Keep the 2 options separate as the Stripe docs suggest: https://stripe.com/docs/api#metadata Closes #946. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/stripe.rb | 3 ++- test/remote/gateways/remote_stripe_test.rb | 15 ++++----------- test/unit/gateways/stripe_test.rb | 1 + 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 57e5c1fd059..3b22cfacd10 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ * Braintree Blue: Add custom_fields & device_data parameters [parallel588] * Webpay: Add authorize & capture [keikubo] * MerchantWarrior: Pass description [duff] +* Stripe: Separate email from description [duff] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb index b712d7b210c..cf2af37b42c 100644 --- a/lib/active_merchant/billing/gateways/stripe.rb +++ b/lib/active_merchant/billing/gateways/stripe.rb @@ -143,7 +143,8 @@ def create_post_for_auth_or_purchase(money, creditcard, options) add_creditcard(post, creditcard, options) add_customer(post, creditcard, options) add_customer_data(post,options) - post[:description] = options[:description] || options[:email] + post[:description] = options[:description] + post[:metadata] = { email: options[:email] } if options[:email] add_flags(post, options) add_application_fee(post, options) post diff --git a/test/remote/gateways/remote_stripe_test.rb b/test/remote/gateways/remote_stripe_test.rb index b67b99d980f..f82fb5dbf5a 100644 --- a/test/remote/gateways/remote_stripe_test.rb +++ b/test/remote/gateways/remote_stripe_test.rb @@ -24,17 +24,8 @@ def test_successful_purchase assert_success response assert_equal "charge", response.params["object"] assert response.params["paid"] - end - - def test_purchase_description - assert response = @gateway.purchase(@amount, @credit_card, { :currency => @currency, :description => "TheDescription", :email => "email@example.com" }) - assert_equal "TheDescription", response.params["description"], "Use the description if it's specified." - - assert response = @gateway.purchase(@amount, @credit_card, { :currency => @currency, :email => "email@example.com" }) - assert_equal "email@example.com", response.params["description"], "Use the email if no description is specified." - - assert response = @gateway.purchase(@amount, @credit_card, { :currency => @currency }) - assert_nil response.params["description"], "No description or email specified." + assert_equal "ActiveMerchant Test Purchase", response.params["description"] + assert_equal "wow@example.com", response.params["metadata"]["email"] end def test_unsuccessful_purchase @@ -47,6 +38,8 @@ def test_authorization_and_capture assert authorization = @gateway.authorize(@amount, @credit_card, @options) assert_success authorization assert !authorization.params["captured"] + assert_equal "ActiveMerchant Test Purchase", authorization.params["description"] + assert_equal "wow@example.com", authorization.params["metadata"]["email"] assert capture = @gateway.capture(@amount, authorization.authorization) assert_success capture diff --git a/test/unit/gateways/stripe_test.rb b/test/unit/gateways/stripe_test.rb index ed11b94dbf0..9ef3407819c 100644 --- a/test/unit/gateways/stripe_test.rb +++ b/test/unit/gateways/stripe_test.rb @@ -261,6 +261,7 @@ def test_client_data_submitted_with_purchase assert_match(/external_id=42/, data) assert_match(/referrer=http\%3A\%2F\%2Fwww\.shopify\.com/, data) assert_match(/payment_user_agent=Stripe\%2Fv1\+ActiveMerchantBindings\%2F\d+\.\d+\.\d+/, data) + assert_match(/metadata\[email\]=foo\%40wonderfullyfakedomain\.com/, data) end.respond_with(successful_purchase_response) end From 83e57c6188e617c82b365082987fb957b02a030f Mon Sep 17 00:00:00 2001 From: Luis Lopez Date: Thu, 31 Oct 2013 12:31:25 -0300 Subject: [PATCH 098/104] Add Payscout gateway http://www.payscout.com/ Closes #899. --- CHANGELOG | 1 + CONTRIBUTORS | 4 + README.md | 1 + .../billing/gateways/payscout.rb | 171 ++++++ test/fixtures.yml | 4 + test/remote/gateways/remote_payscout_test.rb | 160 ++++++ test/unit/gateways/payscout_test.rb | 538 ++++++++++++++++++ 7 files changed, 879 insertions(+) create mode 100644 lib/active_merchant/billing/gateways/payscout.rb create mode 100644 test/remote/gateways/remote_payscout_test.rb create mode 100644 test/unit/gateways/payscout_test.rb diff --git a/CHANGELOG b/CHANGELOG index 3b22cfacd10..8957a608b6d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ * Webpay: Add authorize & capture [keikubo] * MerchantWarrior: Pass description [duff] * Stripe: Separate email from description [duff] +* Add Payscout gateway [llopez] == Version 1.42.2 (November 13th, 2013) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index fea9c97ffa8..31fead6f38a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -440,3 +440,7 @@ Raven PacNet (November 2013) Payex (November 2013) * Tom Davies (atomgiant) + +Payscout (December 2013) + +* Luis Lopez (llopez) diff --git a/README.md b/README.md index 6cfa9628172..145068c1dfd 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta * [PayPal Payments Pro (UK)](https://www.paypal.com/uk/webapps/mpp/pro) - GB * [PayPal Website Payments Pro (CA)](https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside) - CA * [PayPal Express Checkout for Digital Goods](https://www.x.com/community/ppx/xspaces/digital_goods) - AU, CA, CN, FI, GB, ID, IN, IT, MY, NO, NZ, PH, PL, SE, SG, TH, VN +* [Payscout](http://www.payscout.com/) - US * [Paystation](http://paystation.co.nz) - NZ * [Pay Way](http://www.payway.com.au) - AU * [Pin](http://www.pin.net.au/) - AU diff --git a/lib/active_merchant/billing/gateways/payscout.rb b/lib/active_merchant/billing/gateways/payscout.rb new file mode 100644 index 00000000000..b56c391fcce --- /dev/null +++ b/lib/active_merchant/billing/gateways/payscout.rb @@ -0,0 +1,171 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class PayscoutGateway < Gateway + self.live_url = self.test_url = 'https://secure.payscout.com/api/transact.php' + + self.supported_countries = ['US'] + self.supported_cardtypes = [:visa, :master, :american_express, :discover] + self.default_currency = 'USD' + self.homepage_url = 'http://www.payscout.com/' + self.display_name = 'Payscout' + + self.ssl_version = 'SSLv3' + + def initialize(options = {}) + requires!(options, :username, :password) + super + end + + def authorize(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_currency(post, money, options) + add_address(post, options) + + commit('auth', money, post) + end + + def purchase(money, creditcard, options = {}) + post = {} + add_invoice(post, options) + add_creditcard(post, creditcard) + add_currency(post, money, options) + add_address(post, options) + + commit('sale', money, post) + end + + def capture(money, authorization, options = {}) + post = {} + post[:transactionid] = authorization + + commit('capture', money, post) + end + + + def refund(money, authorization, options = {}) + post = {} + post[:transactionid] = authorization + + commit('refund', money, post) + end + + def void(authorization, options = {}) + post = {} + post[:transactionid] = authorization + + commit('void', nil, post) + end + + private + + def add_address(post, options) + if address = options[:billing_address] || options[:address] + post[:address1] = address[:address1].to_s + post[:address2] = address[:address2].to_s + post[:city] = address[:city].to_s + post[:state] = (address[:state].blank? ? 'n/a' : address[:state]) + post[:zip] = address[:zip].to_s + post[:country] = address[:country].to_s + post[:phone] = address[:phone].to_s + post[:fax] = address[:fax].to_s + post[:email] = address[:email].to_s + end + + if address = options[:shipping_address] + post[:shipping_firstname] = address[:first_name].to_s + post[:shipping_lastname] = address[:last_name].to_s + post[:shipping_company] = address[:company].to_s + post[:shipping_address1] = address[:address1].to_s + post[:shipping_address2] = address[:address2].to_s + post[:shipping_city] = address[:city].to_s + post[:shipping_country] = address[:country].to_s + post[:shipping_state] = (address[:state].blank? ? 'n/a' : address[:state]) + post[:shipping_zip] = address[:zip].to_s + post[:shipping_email] = address[:email].to_s + end + end + + def add_currency(post, money, options) + post[:currency] = options[:currency] || currency(money) + end + + def add_invoice(post, options) + post[:orderdescription] = options[:description] + post[:orderid] = options[:order_id] + end + + def add_creditcard(post, creditcard) + post[:ccnumber] = creditcard.number + post[:cvv] = creditcard.verification_value if creditcard.verification_value? + post[:ccexp] = expdate(creditcard) + post[:firstname] = creditcard.first_name + post[:lastname] = creditcard.last_name + end + + def parse(body) + Hash[body.split('&').map{|x|x.split('=')}] + end + + def commit(action, money, parameters) + parameters[:amount] = amount(money) unless action == 'void' + url = (test? ? self.test_url : self.live_url) + data = ssl_post(url, post_data(action, parameters)) + + response = parse(data) + response[:action] = action + + message = message_from(response) + test_mode = (test? || message =~ /TESTMODE/) + Response.new(success?(response), message, response, + :test => test_mode, + :authorization => response['transactionid'], + :fraud_review => fraud_review?(response), + :avs_result => { :code => response['avsresponse'] }, + :cvv_result => response['cvvresponse'] + ) + end + + def message_from(response) + case response['response'] + when '1' + 'The transaction has been approved' + when '2' + 'The transaction has been declined' + when '3' + response['responsetext'] + else + 'There was an error processing the transaction' + end + end + + def fraud_review?(response) + false + end + + def success?(response) + (response['response'] == '1') + end + + def post_data(action, parameters = {}) + post = {} + + post[:username] = @options[:username] + post[:password] = @options[:password] + post[:type] = action + + request = post.merge(parameters).collect { |key, value| "#{key}=#{URI.escape(value.to_s)}" }.join("&") + request + end + + def expdate(creditcard) + year = sprintf("%.4i", creditcard.year) + month = sprintf("%.2i", creditcard.month) + + "#{month}#{year[-2..-1]}" + end + end + end +end + diff --git a/test/fixtures.yml b/test/fixtures.yml index e8286a9abd2..56dab3cb3f4 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -474,6 +474,10 @@ psl_visa_debit_address: state: zip: +payscout: + username: demo + password: password + # Quickpay offers a test account. # To log in to the manager, use demo@quickpay.net and test234 quickpay: diff --git a/test/remote/gateways/remote_payscout_test.rb b/test/remote/gateways/remote_payscout_test.rb new file mode 100644 index 00000000000..e3ad29ce708 --- /dev/null +++ b/test/remote/gateways/remote_payscout_test.rb @@ -0,0 +1,160 @@ +require 'test_helper' + +class RemotePayscoutTest < Test::Unit::TestCase + def setup + @gateway = PayscoutGateway.new(fixtures(:payscout)) + + @amount = 100 + @credit_card = credit_card('4111111111111111', verification_value: 999) + @declined_card = credit_card('34343') + + @options = { + :order_id => '1', + :description => 'Store Purchase', + :billing_address => address + } + end + + ########## Purchase ########## + + def test_cvv_fail_purchase + @credit_card = credit_card('4111111111111111') + assert response = @gateway.purchase(@amount, @credit_card, @options) + + + assert_success response + assert_equal 'The transaction has been approved', response.message + assert_equal 'N', response.cvv_result['code'] + end + + + def test_approved_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'The transaction has been approved', response.message + end + + def test_declined_purchase + @amount = 60 + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'The transaction has been declined', response.message + end + + ########## Authorize ########## + + def test_approved_authorization + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_success response + assert_equal 'The transaction has been approved', response.message + end + + def test_declined_authorization + @amount = 60 + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_failure response + assert_equal 'The transaction has been declined', response.message + end + + ########## Capture ########## + + def test_approved_capture + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert_equal 'The transaction has been approved', auth.message + assert auth.authorization + assert capture = @gateway.capture(amount, auth.authorization) + assert_success capture + end + + def test_invalid_amount_capture + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert_equal 'The transaction has been approved', auth.message + assert auth.authorization + amount = 200 + assert capture = @gateway.capture(amount, auth.authorization) + assert_failure capture + assert_match 'The specified amount of 2.00 exceeds the authorization amount of 1.00', capture.message + end + + def test_not_found_transaction_id_capture + assert capture = @gateway.capture(@amount, '1234567890') + assert_failure capture + assert_match 'Transaction not found', capture.message + end + + def test_invalid_transaction_id_capture + assert capture = @gateway.capture(@amount, '') + assert_failure capture + assert_match 'Invalid Transaction ID', capture.message + end + + ########## Refund ########## + + def test_approved_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert refund = @gateway.refund(@amount, purchase.authorization) + assert_success refund + assert_equal "The transaction has been approved", refund.message + end + + def test_not_found_transaction_id_refund + assert refund = @gateway.refund(@amount, '1234567890') + assert_failure refund + assert_match "Transaction not found", refund.message + end + + def test_invalid_transaction_id_refund + assert refund = @gateway.refund(@amount, '') + assert_failure refund + assert_match "Invalid Transaction ID", refund.message + end + + def test_invalid_amount_refund + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert refund = @gateway.refund(200, purchase.authorization) + assert_failure refund + assert_match "Refund amount may not exceed the transaction balance", refund.message + end + + ########## Void ########## + + def test_approved_void_purchase + purchase = @gateway.purchase(@amount, @credit_card, @options) + assert void = @gateway.void(purchase.authorization) + assert_success void + assert_equal "The transaction has been approved", void.message + end + + def test_approved_void_authorization + auth = @gateway.authorize(@amount, @credit_card, @options) + assert void = @gateway.void(auth.authorization) + assert_success void + assert_equal "The transaction has been approved", void.message + end + + def test_invalid_transaction_id_void + assert void = @gateway.void('') + assert_failure void + assert_match "Invalid Transaction ID", void.message + end + + def test_not_found_transaction_id_void + assert void = @gateway.void('1234567890') + assert_failure void + assert_match "Transaction not found", void.message + end + + def test_invalid_credentials + gateway = PayscoutGateway.new( + :username => 'xxx', + :password => 'xxx' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Authentication Failed', response.message + end +end diff --git a/test/unit/gateways/payscout_test.rb b/test/unit/gateways/payscout_test.rb new file mode 100644 index 00000000000..95dd04ef4be --- /dev/null +++ b/test/unit/gateways/payscout_test.rb @@ -0,0 +1,538 @@ +require 'test_helper' + +class PayscoutTest < Test::Unit::TestCase + def setup + @gateway = PayscoutGateway.new( + :username => 'xxx', + :password => 'xxx' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + :order_id => '1', + :billing_address => address, + :description => 'Store Purchase' + } + end + + # Purchase + + def test_approved_puschase + @gateway.expects(:ssl_post).returns(approved_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal '1234567891', response.authorization + assert_equal 'The transaction has been approved', response.message + assert response.test? + end + + def test_declined_puschase + @gateway.expects(:ssl_post).returns(declined_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + + assert_equal '1234567892', response.authorization + assert_equal 'The transaction has been declined', response.message + assert response.test? + end + + # Authorization + + def test_approved_authorization + @gateway.expects(:ssl_post).returns(approved_authorization_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal '1234567890', response.authorization + assert_equal 'The transaction has been approved', response.message + assert response.test? + end + + def test_declined_authorization + @gateway.expects(:ssl_post).returns(declined_authorization_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_failure response + + assert_equal '1234567893', response.authorization + assert_equal 'The transaction has been declined', response.message + assert response.test? + end + + # Capture + + def test_approved_capture + @gateway.expects(:ssl_post).returns(approved_capture_response) + + assert response = @gateway.capture(@amount, '1234567894', @options) + assert_instance_of Response, response + assert_success response + + assert_equal '1234567894', response.authorization + assert_equal 'The transaction has been approved', response.message + assert response.test? + end + + def test_invalid_amount_capture + @gateway.expects(:ssl_post).returns(invalid_amount_capture_response) + + assert response = @gateway.capture(@amount, '1234567895', @options) + assert_instance_of Response, response + assert_failure response + + assert_equal '1234567895', response.authorization + assert_equal 'The+specified+amount+of+2.00+exceeds+the+authorization+amount+of+1.00', response.message + assert response.test? + end + + def test_not_found_transaction_id_capture + @gateway.expects(:ssl_post).returns(not_found_transaction_id_capture_response) + + assert capture = @gateway.capture(@amount, '1234567890') + assert_failure capture + assert_match 'Transaction+not+found', capture.message + end + + def test_invalid_transaction_id_capture + @gateway.expects(:ssl_post).returns(invalid_transaction_id_capture_response) + + assert capture = @gateway.capture(@amount, '') + assert_failure capture + assert_match 'Invalid+Transaction+ID', capture.message + end + + # Refund + + def test_approved_refund + @gateway.expects(:ssl_post).returns(approved_refund_response) + + assert refund = @gateway.refund(@amount, '1234567890') + assert_success refund + assert_equal "The transaction has been approved", refund.message + end + + def test_not_found_transaction_id_refund + @gateway.expects(:ssl_post).returns(not_found_transaction_id_refund_response) + + assert refund = @gateway.refund(@amount, '1234567890') + assert_failure refund + assert_match "Transaction+not+found", refund.message + end + + def test_invalid_transaction_id_refund + @gateway.expects(:ssl_post).returns(invalid_transaction_id_refund_response) + + assert refund = @gateway.refund(@amount, '') + assert_failure refund + assert_match "Invalid+Transaction+ID", refund.message + end + + def test_invalid_amount_refund + @gateway.expects(:ssl_post).returns(invalid_amount_refund_response) + + assert refund = @gateway.refund(200, '1234567890') + assert_failure refund + assert_match "Refund+amount+may+not+exceed+the+transaction+balance", refund.message + end + + # Void + + def test_approved_void_purchase + @gateway.expects(:ssl_post).returns(approved_void_purchase_response) + + assert void = @gateway.void('1234567890') + assert_success void + assert_equal "The transaction has been approved", void.message + end + + def test_approved_void_authorization + @gateway.expects(:ssl_post).returns(approved_void_authorization_response) + + assert void = @gateway.void('1234567890') + assert_success void + assert_equal "The transaction has been approved", void.message + end + + def test_invalid_transaction_id_void + @gateway.expects(:ssl_post).returns(invalid_transaction_id_void_response) + + assert void = @gateway.void('') + assert_failure void + assert_match "Invalid+Transaction+ID", void.message + end + + def test_not_found_transaction_id_void + @gateway.expects(:ssl_post).returns(not_found_transaction_id_void_response) + + assert void = @gateway.void('1234567890') + assert_failure void + assert_match "Transaction+not+found", void.message + end + + # Methods + + def test_billing_address + post = {} + address = address(email: 'example@example.com') + @gateway.send(:add_address, post, { billing_address: address }) + + assert_equal address[:address1], post[:address1] + assert_equal address[:address2], post[:address2] + assert_equal address[:city], post[:city] + assert_equal address[:state], post[:state] + assert_equal address[:zip], post[:zip] + assert_equal address[:country], post[:country] + assert_equal address[:phone], post[:phone] + assert_equal address[:fax], post[:fax] + assert_equal address[:email], post[:email] + end + + def test_shipping_address + post = {} + address = address(email: 'example@example.com', first_name: 'John', last_name: 'Doe') + @gateway.send(:add_address, post, { shipping_address: address }) + + assert_equal address[:first_name], post[:shipping_firstname] + assert_equal address[:last_name], post[:shipping_lastname] + assert_equal address[:company], post[:shipping_company] + assert_equal address[:address1], post[:shipping_address1] + assert_equal address[:address2], post[:shipping_address2] + assert_equal address[:city], post[:shipping_city] + assert_equal address[:country], post[:shipping_country] + assert_equal address[:state], post[:shipping_state] + assert_equal address[:zip], post[:shipping_zip] + assert_equal address[:email], post[:shipping_email] + end + + + def test_add_currency_from_options + post = {} + @gateway.send(:add_currency, post, 100, { currency: 'CAD' }) + + assert_equal 'CAD', post[:currency] + end + + def test_add_currency_from_money + post = {} + @gateway.send(:add_currency, post, 100, {}) + + assert_equal 'USD', post[:currency] + end + + def test_add_invoice + post = {} + options = {description: 'Order Description', order_id: '123'} + @gateway.send(:add_invoice, post, options) + + assert_equal 'Order Description', post[:orderdescription] + assert_equal '123', post[:orderid] + end + + def test_expdate + @credit_card = credit_card + @credit_card.year = 2015 + @credit_card.month = 8 + + assert_equal "0815", @gateway.send(:expdate, @credit_card) + end + + def test_add_creditcard + post = {} + @gateway.send(:add_creditcard, post, @credit_card) + + assert_equal @credit_card.number, post[:ccnumber] + assert_equal @credit_card.verification_value, post[:cvv] + assert_equal @gateway.send(:expdate, @credit_card), post[:ccexp] + assert_equal @credit_card.first_name, post[:firstname] + assert_equal @credit_card.last_name, post[:lastname] + end + + def test_parse + data = @gateway.send(:parse, approved_authorization_response) + + assert data.keys.include?('response') + assert data.keys.include?('responsetext') + assert data.keys.include?('authcode') + assert data.keys.include?('transactionid') + assert data.keys.include?('avsresponse') + assert data.keys.include?('cvvresponse') + assert data.keys.include?('orderid') + assert data.keys.include?('type') + assert data.keys.include?('response_code') + + assert_equal '1', data['response'] + assert_equal 'SUCCESS', data['responsetext'] + assert_equal '123456', data['authcode'] + assert_equal '1234567890', data['transactionid'] + assert_equal 'N', data['avsresponse'] + assert_equal 'M', data['cvvresponse'] + assert_equal '1', data['orderid'] + assert_equal 'auth', data['type'] + assert_equal '100', data['response_code'] + end + + def test_message_from_for_approved_response + assert_equal 'The transaction has been approved', @gateway.send(:message_from, {'response' => '1'}) + end + + def test_message_from_for_declined_response + assert_equal 'The transaction has been declined', @gateway.send(:message_from, {'response' => '2'}) + end + + def test_message_from_for_failed_response + assert_equal 'Error message', @gateway.send(:message_from, {'response' => '3', 'responsetext' => 'Error message'}) + end + + def test_success + assert @gateway.send(:success?, {'response' => '1'}) + refute @gateway.send(:success?, {'response' => '2'}) + refute @gateway.send(:success?, {'response' => '3'}) + end + + def test_post_data + parameters = {param1: 'value1', param2: 'value2'} + result = @gateway.send(:post_data, 'auth', parameters) + + assert_match "username=xxx", result + assert_match "password=xxx", result + assert_match "type=auth", result + assert_match "param1=value1", result + assert_match "param2=value2", result + end + + private + + def approved_authorization_response + %w( + response=1 + responsetext=SUCCESS + authcode=123456 + transactionid=1234567890 + avsresponse=N + cvvresponse=M + orderid=1 + type=auth + response_code=100 + ).join('&') + end + + def declined_authorization_response + %w( + response=2 + responsetext=DECLINE + authcode= + transactionid=1234567893 + avsresponse=N + cvvresponse=M + orderid=1 + type=auth + response_code=200 + ).join('&') + end + + def approved_purchase_response + %w( + response=1 + responsetext=SUCCESS + authcode=123456 + transactionid=1234567891 + avsresponse=N + cvvresponse=M + orderid=1 + type=sale + response_code=100 + ).join('&') + end + + def declined_purchase_response + %w( + response=2 + responsetext=DECLINE + authcode= + transactionid=1234567892 + avsresponse=N + cvvresponse=M + orderid=1 + type=sale + response_code=200 + ).join('&') + end + + def approved_capture_response + %w( + response=1 + responsetext=SUCCESS + authcode=123456 + transactionid=1234567894 + avsresponse=N + cvvresponse=M + orderid=1 + type=capture + response_code=100 + ).join('&') + end + + def invalid_amount_capture_response + %w( + response=3 + responsetext=The+specified+amount+of+2.00+exceeds+the+authorization+amount+of+1.00 + authcode= + transactionid=1234567895 + avsresponse=N + cvvresponse=M + orderid=1 + type=capture + response_code=300 + ).join('&') + end + + def not_found_transaction_id_capture_response + %w( + response=3 + responsetext=Transaction+not+found+REFID:4054576 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=capture + response_code=300 + ).join('&') + end + + def invalid_transaction_id_capture_response + %w( + response=3 + responsetext=Invalid+Transaction+ID+/+Object+ID+specified:++REFID:4054567 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=capture + response_code=300 + ).join('&') + end + + def approved_refund_response + %w( + response=1 + responsetext=SUCCESS + authcode= + transactionid=1234567896 + avsresponse= + cvvresponse= + orderid=1 + type=refund + response_code=100 + ).join('&') + end + + def not_found_transaction_id_refund_response + %w( + response=3 + responsetext=Transaction+not+found+REFID:4054576 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=refund + response_code=300 + ).join('&') + end + + def invalid_transaction_id_refund_response + %w( + response=3 + responsetext=Invalid+Transaction+ID+/+Object+ID+specified:++REFID:4054567 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=refund + response_code=300 + ).join('&') + end + + def invalid_amount_refund_response + %w( + response=3 + responsetext=Refund+amount+may+not+exceed+the+transaction+balance+REFID:4054562 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=refund + response_code=300 + ).join('&') + end + + def approved_void_purchase_response + %w( + response=1 + responsetext=Transaction+Void+Successful + authcode=123456 + transactionid=1234567896 + avsresponse= + cvvresponse= + orderid=1 + type=void + response_code=100 + ).join('&') + end + + def approved_void_authorization_response + %w( + response=1 + responsetext=Transaction+Void+Successful + authcode=123456 + transactionid=1234567896 + avsresponse= + cvvresponse= + orderid=1 + type=void + response_code=100 + ).join('&') + end + + def test_invalid_transaction_id_void + %w( + response=3 + responsetext=Invalid+Transaction+ID+/+Object+ID+specified:++REFID:4054572 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=void + response_code=300 + ).join('&') + end + + def not_found_transaction_id_void_response + %w( + response=3 + responsetext=Transaction+not+found+REFID:4054582 + authcode= + transactionid= + avsresponse= + cvvresponse= + orderid=1 + type=void + response_code=300 + ).join('&') + end +end From 73e86d51453375813121fc9ed3f76bc9f90c3f57 Mon Sep 17 00:00:00 2001 From: Duff OMelia Date: Tue, 10 Dec 2013 12:44:44 -0500 Subject: [PATCH 099/104] Merchant Warrior: Use billing_address This allows folks to use either billing_address or address just like many other gateways do. Previously our Merchant Warrior support ignored billing_address. Closes #948. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/merchant_warrior.rb | 2 +- test/remote/gateways/remote_merchant_warrior_test.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8957a608b6d..9e0ce7bb0b0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ * MerchantWarrior: Pass description [duff] * Stripe: Separate email from description [duff] * Add Payscout gateway [llopez] +* Merchant Warrior: Use billing_address [duff] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/merchant_warrior.rb b/lib/active_merchant/billing/gateways/merchant_warrior.rb index 74aa6ada287..8b22c837b68 100644 --- a/lib/active_merchant/billing/gateways/merchant_warrior.rb +++ b/lib/active_merchant/billing/gateways/merchant_warrior.rb @@ -75,7 +75,7 @@ def add_transaction(post, identification) end def add_address(post, options) - return unless(address = options[:address]) + return unless(address = (options[:billing_address] || options[:address])) post['customerName'] = address[:name] post['customerCountry'] = address[:country] diff --git a/test/remote/gateways/remote_merchant_warrior_test.rb b/test/remote/gateways/remote_merchant_warrior_test.rb index 005ab78ee1b..da81661c4ef 100644 --- a/test/remote/gateways/remote_merchant_warrior_test.rb +++ b/test/remote/gateways/remote_merchant_warrior_test.rb @@ -16,7 +16,7 @@ def setup ) @options = { - :address => { + :billing_address => { :name => 'Longbob Longsen', :country => 'AU', :state => 'Queensland', From b7045e3901ed02253f8f7f02b0548a22134121d2 Mon Sep 17 00:00:00 2001 From: Ivan Radovanovic Date: Thu, 29 Aug 2013 12:15:12 +0200 Subject: [PATCH 100/104] Add SoEasyPay gateway http://www.soeasypay.com/ Closes #830. --- CHANGELOG | 1 + CONTRIBUTORS | 4 + README.md | 1 + .../billing/gateways/so_easy_pay.rb | 194 +++++++++++++++ test/fixtures.yml | 4 + .../gateways/remote_so_easy_pay_test.rb | 66 +++++ test/unit/gateways/so_easy_pay_test.rb | 225 ++++++++++++++++++ 7 files changed, 495 insertions(+) create mode 100644 lib/active_merchant/billing/gateways/so_easy_pay.rb create mode 100644 test/remote/gateways/remote_so_easy_pay_test.rb create mode 100644 test/unit/gateways/so_easy_pay_test.rb diff --git a/CHANGELOG b/CHANGELOG index 9e0ce7bb0b0..3af4e83a3c2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ * Stripe: Separate email from description [duff] * Add Payscout gateway [llopez] * Merchant Warrior: Use billing_address [duff] +* Add SoEasyPay gateway [ir-soeasycorp] == Version 1.42.2 (November 13th, 2013) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 31fead6f38a..d5adc5543c2 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -444,3 +444,7 @@ Payex (November 2013) Payscout (December 2013) * Luis Lopez (llopez) + +SoEasyPay (December 2013) + +* Ivan Radovanovic (ir-soeasycorp) diff --git a/README.md b/README.md index 145068c1dfd..d5af60cc5f3 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta * [SecurePay](http://www.securepay.com/) - US, CA, GB, AU * [SecurePayTech](http://www.securepaytech.com/) - NZ * [SkipJack](http://www.skipjack.com/) - US, CA +* [SoEasyPay](http://www.soeasypay.com/) - US, CA, AT, BE, BG, HR, CY, CZ, DK, EE, FI, FR, DE, GR, HU, IE, IT, LV, LT, LU, MT, NL, PL, PT, RO, SK, SI, ES, SE, GB, IS, NO, CH * [Spreedly](https://spreedly.com) - AD, AE, AT, AU, BD, BE, BG, BN, CA, CH, CY, CZ, DE, DK, EE, EG, ES, FI, FR, GB, GI, GR, HK, HU, ID, IE, IL, IM, IN, IS, IT, JO, KW, LB, LI, LK, LT, LU, LV, MC, MT, MU, MV, MX, MY, NL, NO, NZ, OM, PH, PL, PT, QA, RO, SA, SE, SG, SI, SK, SM, TR, TT, UM, US, VA, VN, ZA * [Stripe](https://stripe.com/) - US, CA, GB, AU, IE, FR, NL, BE, DE, ES * [Swipe](https://www.swipehq.com/checkout) - CA, NZ diff --git a/lib/active_merchant/billing/gateways/so_easy_pay.rb b/lib/active_merchant/billing/gateways/so_easy_pay.rb new file mode 100644 index 00000000000..2227a74ebe4 --- /dev/null +++ b/lib/active_merchant/billing/gateways/so_easy_pay.rb @@ -0,0 +1,194 @@ +module ActiveMerchant #:nodoc: + module Billing #:nodoc: + class SoEasyPayGateway < Gateway + self.live_url = self.test_url = 'https://secure.soeasypay.com/gateway.asmx' + self.money_format = :cents + + self.supported_countries = [ + 'US', 'CA', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', + 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', + 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB', + 'IS', 'NO', 'CH' + ] + self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :solo, :diners_club] + self.homepage_url = 'http://www.soeasypay.com/' + self.display_name = 'SoEasyPay' + + def initialize(options = {}) + requires!(options, :login, :password) + super + end + + def authorize(money, payment_source, options = {}) + if payment_source.respond_to?(:number) + commit('AuthorizeTransaction', do_authorization(money, payment_source, options), options) + else + commit('ReauthorizeTransaction', do_reauthorization(money, payment_source, options), options) + end + end + + def purchase(money, payment_source, options = {}) + if payment_source.respond_to?(:number) + commit('SaleTransaction', do_sale(money, payment_source, options), options) + else + commit('RebillTransaction', do_rebill(money, payment_source, options), options) + end + end + + def capture(money, authorization, options = {}) + commit('CaptureTransaction', do_capture(money, authorization, options), options) + end + + def refund(money, authorization, options={}) + commit('RefundTransaction', do_refund(money, authorization, options), options) + end + + def void(authorization, options={}) + commit('CancelTransaction', do_void(authorization, options), options) + end + + private + + def do_authorization(money, card, options) + build_soap('AuthorizeTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options) + fill_cardholder(soap, card, options) + fill_card(soap, card) + end + end + + def do_sale(money, card, options) + build_soap('SaleTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options) + fill_cardholder(soap, card, options) + fill_card(soap, card) + end + end + + def do_reauthorization(money, authorization, options) + build_soap('ReauthorizeTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options) + fill_transaction_id(soap, authorization) + end + end + + def do_rebill(money, authorization, options) + build_soap('RebillTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options) + fill_transaction_id(soap, authorization) + end + end + + def do_capture(money, authorization, options) + build_soap('CaptureTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options, :no_currency) + fill_transaction_id(soap, authorization) + end + end + + def do_refund(money, authorization, options) + build_soap('RefundTransaction') do |soap| + fill_credentials(soap, options) + fill_order_info(soap, money, options, :no_currency) + fill_transaction_id(soap, authorization) + end + end + + def do_void(authorization, options) + build_soap('CancelTransaction') do |soap| + fill_credentials(soap, options) + fill_transaction_id(soap, authorization) + end + end + + def fill_credentials(soap, options) + soap.tag!('websiteID', @options[:login].to_s) + soap.tag!('password', @options[:password].to_s) + end + + def fill_cardholder(soap, card, options) + ch_info = options[:billing_address] || options[:address] + + soap.tag!('customerIP',options[:ip].to_s) + name = card.name || ch_info[:name] + soap.tag!('cardHolderName', name.to_s) + address = ch_info[:address1] || '' + address << ch_info[:address2] if ch_info[:address2] + soap.tag!('cardHolderAddress', address.to_s) + soap.tag!('cardHolderZipcode', ch_info[:zip].to_s) + soap.tag!('cardHolderCity', ch_info[:city].to_s) + soap.tag!('cardHolderState', ch_info[:state].to_s) + soap.tag!('cardHolderCountryCode', ch_info[:country].to_s) + soap.tag!('cardHolderPhone', ch_info[:phone].to_s) + soap.tag!('cardHolderEmail', options[:email].to_s) + end + + def fill_transaction_id(soap, transaction_id) + soap.tag!('transactionID', transaction_id.to_s) + end + + def fill_card(soap, card) + soap.tag!('cardNumber', card.number.to_s) + soap.tag!('cardSecurityCode', card.verification_value.to_s) + soap.tag!('cardExpireMonth', card.month.to_s.rjust(2, "0")) + soap.tag!('cardExpireYear', card.year.to_s) + end + + def fill_order_info(soap, money, options, skip_currency=false) + soap.tag!('orderID', options[:order_id].to_s) + soap.tag!('orderDescription', "Order #{options[:order_id]}") + soap.tag!('amount', amount(money).to_s) + soap.tag!('currency', (options[:currency] || currency(money)).to_s) unless skip_currency + end + + def parse(response, action) + result = {} + document = REXML::Document.new(response) + response_element = document.root.get_elements("//[@xsi:type='tns:#{action}Response']").first + response_element.elements.each do |element| + result[element.name.underscore] = element.text + end + result + end + + def commit(soap_action, soap, options) + headers = {"SOAPAction" => "\"urn:Interface##{soap_action}\"", + "Content-Type" => "text/xml; charset=utf-8"} + response_string = ssl_post(test? ? self.test_url : self.live_url, soap, headers) + response = parse(response_string, soap_action) + return Response.new(response['errorcode'] == '000', + response['errormessage'], + response, + :test => test?, + :authorization => response['transaction_id']) + end + + def build_soap(request) + retval = Builder::XmlMarkup.new(:indent => 2) + retval.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') + retval.tag!('soap:Envelope', { + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', + 'xmlns:soapenc' => 'http://schemas.xmlsoap.org/soap/encoding/', + 'xmlns:tns' => 'urn:Interface', + 'xmlns:types' => 'urn:Interface/encodedTypes', + 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/'}) do + retval.tag!('soap:Body', {'soap:encodingStyle'=>'http://schemas.xmlsoap.org/soap/encoding/'}) do + retval.tag!("tns:#{request}") do + retval.tag!("#{request}Request", {'xsi:type'=>"tns:#{request}Request"}) do + yield retval + end + end + end + end + retval.target! + end + + end + end +end diff --git a/test/fixtures.yml b/test/fixtures.yml index 56dab3cb3f4..16a42a0e82f 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -605,6 +605,10 @@ skipjack: login: X password: Y +so_easy_pay: + login: 110159 + password: b1fe9de37c234ef8fd53668377076687d6314dc8f6146023338d61b5969719e0 + # Working credentials, no need to replace spreedly_core: login: "4Y9bVkOCpYesPQOfDi7TXQyUw50" diff --git a/test/remote/gateways/remote_so_easy_pay_test.rb b/test/remote/gateways/remote_so_easy_pay_test.rb new file mode 100644 index 00000000000..8803620f610 --- /dev/null +++ b/test/remote/gateways/remote_so_easy_pay_test.rb @@ -0,0 +1,66 @@ +require 'test_helper' + +class RemoteSoEasyPayTest < Test::Unit::TestCase + + + def setup + @gateway = SoEasyPayGateway.new(fixtures(:so_easy_pay)) + + @amount = 100 + @credit_card = credit_card('4111111111111111', {:verification_value => '000', :month => '12', :year => '2015'}) + @declined_card = credit_card('4000300011112220') + + @options = { + :currency => 'EUR', + :ip => '192.168.19.123', + :email => 'test@blaha.com', + :order_id => generate_unique_id, + :billing_address => address, + :description => 'Store Purchase' + } + end + + def test_successful_purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal 'Transaction successful', response.message + end + + def test_unsuccessful_purchase + assert response = @gateway.purchase(@amount, @declined_card, @options) + assert_failure response + end + + def test_authorize_and_capture + amount = @amount + assert auth = @gateway.authorize(amount, @credit_card, @options) + assert_success auth + assert auth.authorization + assert capture = @gateway.capture(amount, auth.authorization) + assert_success capture + end + + def test_failed_capture + assert response = @gateway.capture(@amount, '') + assert_failure response + end + + def test_successful_void + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + + assert response = @gateway.void(response.authorization) + assert_success response + end + + def test_invalid_login + gateway = SoEasyPayGateway.new( + :login => 'one', + :password => 'wrong' + ) + assert response = gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal 'Website verification failed, wrong websiteID or password', response.message + end +end + diff --git a/test/unit/gateways/so_easy_pay_test.rb b/test/unit/gateways/so_easy_pay_test.rb new file mode 100644 index 00000000000..daa8a9f9d35 --- /dev/null +++ b/test/unit/gateways/so_easy_pay_test.rb @@ -0,0 +1,225 @@ +require 'test_helper' + +class SoEasyPayTest < Test::Unit::TestCase + def setup + @gateway = SoEasyPayGateway.new( + :login => 'login', + :password => 'password' + ) + + @credit_card = credit_card + @amount = 100 + + @options = { + :order_id => '1', + :billing_address => address, + :description => 'Store Purchase' + } + end + + def test_successful_purchase + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + # Replace with authorization number from the successful response + assert_equal '1708978', response.authorization + assert response.test? + end + + def test_unsuccessful_request + @gateway.expects(:ssl_post).returns(failed_purchase_response) + + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_failure response + assert_equal '1708979', response.authorization + assert response.test? + end + + def test_successful_authorize + @gateway.expects(:ssl_post).returns(successful_authorize_response) + + assert response = @gateway.authorize(@amount, @credit_card, @options) + assert_instance_of Response, response + assert_success response + + assert_equal('1708980', response.authorization) + assert response.test? + end + + def test_successful_capture + @gateway.expects(:ssl_post).returns(successful_capture_response) + + assert response = @gateway.capture(1111, "1708980") + assert_instance_of Response, response + assert_success response + + assert_equal('1708981', response.authorization) + assert response.test? + end + + def test_successful_refund + @gateway.expects(:ssl_post).returns(successful_credit_response) + assert response = @gateway.refund(@amount, '1708978') + assert_instance_of Response, response + assert_success response + assert_equal 'Transaction successful', response.message + end + + def test_failed_refund + @gateway.expects(:ssl_post).returns(failed_credit_response) + + assert response = @gateway.refund(@amount, '1708978') + assert_instance_of Response, response + assert_failure response + assert_equal 'Card declined', response.message + end + + def test_do_not_depend_on_expiry_date_class + @gateway.stubs(:ssl_post).returns(successful_purchase_response) + @credit_card.expects(:expiry_date).never + + @gateway.purchase(@amount, @credit_card, @options) + end + + def test_use_ducktyping_for_credit_card + @gateway.expects(:ssl_post).returns(successful_purchase_response) + + credit_card = stub(:number => '4242424242424242', :verification_value => '123', :name => "Hans Tester", :year => 2012, :month => 1) + + assert_nothing_raised do + assert_success @gateway.purchase(@amount, credit_card, @options) + end + end + + private + + # Place raw successful response from gateway here + def successful_purchase_response + %( + + + + + + + 1708978 + 12 + Authorized + 000 + Transaction successful + K + NOSCORE + 0000 + **21 + 02/16 + VISA + + + ) + end + + # Place raw failed response from gateway here + def failed_purchase_response + %( + + + + + + + 1708979 + 12 + Not Authorized + 002 + Card declined + K + NOSCORE + 0000 + **21 + 02/16 + VISA + + + ) + end + + def successful_authorize_response + %( + + + + + + + 1708980 + 12 + Authorized + 000 + Transaction successful + K + NOSCORE + 0000 + **21 + 02/16 + VISA + + + ) + end + + def successful_capture_response + %( + + + + + + + 1708981 + Authorized + 000 + Transaction successful + + + ) + end + + def successful_credit_response + %( + + + + + + + 1708982 + Authorized + 000 + Transaction successful + + + ) + end + + def failed_credit_response + %( + + + + + + + 1708983 + Not Authorized + 002 + Card declined + + + ) + end + +end + From 7db748cc9248b4a9ab1a2fb4049c02b75cd01acd Mon Sep 17 00:00:00 2001 From: Nathaniel Talbott Date: Wed, 11 Dec 2013 14:54:00 -0500 Subject: [PATCH 101/104] Fix accidentally overwritten test methods --- test/unit/gateways/payscout_test.rb | 2 +- .../notifications/bit_pay_notification_test.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/gateways/payscout_test.rb b/test/unit/gateways/payscout_test.rb index 95dd04ef4be..0781da14568 100644 --- a/test/unit/gateways/payscout_test.rb +++ b/test/unit/gateways/payscout_test.rb @@ -508,7 +508,7 @@ def approved_void_authorization_response ).join('&') end - def test_invalid_transaction_id_void + def invalid_transaction_id_void_response %w( response=3 responsetext=Invalid+Transaction+ID+/+Object+ID+specified:++REFID:4054572 diff --git a/test/unit/integrations/notifications/bit_pay_notification_test.rb b/test/unit/integrations/notifications/bit_pay_notification_test.rb index b7b94f964fa..5f55c145570 100644 --- a/test/unit/integrations/notifications/bit_pay_notification_test.rb +++ b/test/unit/integrations/notifications/bit_pay_notification_test.rb @@ -40,14 +40,14 @@ def test_successful_acknowledgement assert @bit_pay.acknowledge end - def test_failed_acknowledgement + def test_acknowledgement_error Net::HTTP.any_instance.expects(:request).returns(stub(:body => '{"error":"Doesnt match"}')) - assert_nil @bit_pay.acknowledge + assert !@bit_pay.acknowledge end - def test_failed_acknowledgement + def test_acknowledgement_invalid_json Net::HTTP.any_instance.expects(:request).returns(stub(:body => '{invalid json')) - assert_nil @bit_pay.acknowledge + assert !@bit_pay.acknowledge end private From 08a19e72e438aabb42183858276067cd1b228199 Mon Sep 17 00:00:00 2001 From: Nathan Verni Date: Fri, 6 Dec 2013 11:53:04 -0500 Subject: [PATCH 102/104] Bogus: Add Check support Provides support for passing valid and invalid account numbers to the bogus gateway and returning relevant error messages. A valid account number is any account number that ends with 1. An invalid account number is any account number that ends with 2. Any object that responds to `account_number` will be treated as a check. Note, because ActiveMerchant::Billing::Check has a `number` attribute in addition to `account_number`, priority is given to `number` for backwards compatibility. CREDIT_ERROR_MESSAGE was removed because it was the same as ERROR_MESSAGE. Closes #944. --- CHANGELOG | 1 + lib/active_merchant/billing/gateways/bogus.rb | 54 +++++---- test/unit/gateways/bogus_test.rb | 111 ++++++++++++++---- 3 files changed, 121 insertions(+), 45 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3af4e83a3c2..bf572b5da6c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ * Add Payscout gateway [llopez] * Merchant Warrior: Use billing_address [duff] * Add SoEasyPay gateway [ir-soeasycorp] +* Bogus: Add check support [npverni] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/bogus.rb b/lib/active_merchant/billing/gateways/bogus.rb index 3a23de6e8b0..3d1bdd9cf61 100644 --- a/lib/active_merchant/billing/gateways/bogus.rb +++ b/lib/active_merchant/billing/gateways/bogus.rb @@ -7,67 +7,67 @@ class BogusGateway < Gateway SUCCESS_MESSAGE = "Bogus Gateway: Forced success" FAILURE_MESSAGE = "Bogus Gateway: Forced failure" ERROR_MESSAGE = "Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error" - CREDIT_ERROR_MESSAGE = "Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error" UNSTORE_ERROR_MESSAGE = "Bogus Gateway: Use trans_id ending in 1 for success, 2 for exception and anything else for error" CAPTURE_ERROR_MESSAGE = "Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success" VOID_ERROR_MESSAGE = "Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success" REFUND_ERROR_MESSAGE = "Bogus Gateway: Use trans_id number ending in 1 for exception, 2 for error and anything else for success" + CHECK_ERROR_MESSAGE = "Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error" self.supported_countries = ['US'] self.supported_cardtypes = [:bogus] self.homepage_url = 'http://example.com' self.display_name = 'Bogus' - def authorize(money, credit_card_or_reference, options = {}) + def authorize(money, paysource, options = {}) money = amount(money) - case normalize(credit_card_or_reference) + case normalize(paysource) when /1$/ Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION ) when /2$/ Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true) else - raise Error, ERROR_MESSAGE + raise Error, error_message(paysource) end end - def purchase(money, credit_card_or_reference, options = {}) + def purchase(money, paysource, options = {}) money = amount(money) - case normalize(credit_card_or_reference) + case normalize(paysource) when /1$/, AUTHORIZATION Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true, :authorization => AUTHORIZATION) when /2$/ Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE },:test => true) else - raise Error, ERROR_MESSAGE + raise Error, error_message(paysource) end end - def recurring(money, credit_card_or_reference, options = {}) + def recurring(money, paysource, options = {}) money = amount(money) - case normalize(credit_card_or_reference) + case normalize(paysource) when /1$/ Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true) when /2$/ Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE },:test => true) else - raise Error, ERROR_MESSAGE + raise Error, error_message(paysource) end end - def credit(money, credit_card_or_reference, options = {}) - if credit_card_or_reference.is_a?(String) + def credit(money, paysource, options = {}) + if paysource.is_a?(String) deprecated CREDIT_DEPRECATION_MESSAGE - return refund(money, credit_card_or_reference, options) + return refund(money, paysource, options) end money = amount(money) - case normalize(credit_card_or_reference) + case normalize(paysource) when /1$/ Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true ) when /2$/ Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true) else - raise Error, CREDIT_ERROR_MESSAGE + raise Error, error_message(paysource) end end @@ -106,14 +106,14 @@ def void(reference, options = {}) end end - def store(credit_card_or_reference, options = {}) - case normalize(credit_card_or_reference) + def store(paysource, options = {}) + case normalize(paysource) when /1$/ Response.new(true, SUCCESS_MESSAGE, {:billingid => '1'}, :test => true, :authorization => AUTHORIZATION) when /2$/ Response.new(false, FAILURE_MESSAGE, {:billingid => nil, :error => FAILURE_MESSAGE }, :test => true) else - raise Error, ERROR_MESSAGE + raise Error, error_message(paysource) end end @@ -130,11 +130,21 @@ def unstore(reference, options = {}) private - def normalize(credit_card_or_reference) - if credit_card_or_reference.respond_to?(:number) - credit_card_or_reference.number + def normalize(paysource) + if paysource.respond_to?(:account_number) && (paysource.try(:number).blank? || paysource.number.blank?) + paysource.account_number + elsif paysource.respond_to?(:number) + paysource.number else - credit_card_or_reference.to_s + paysource.to_s + end + end + + def error_message(paysource) + if paysource.respond_to?(:account_number) + CHECK_ERROR_MESSAGE + elsif paysource.respond_to?(:number) + ERROR_MESSAGE end end end diff --git a/test/unit/gateways/bogus_test.rb b/test/unit/gateways/bogus_test.rb index 87b6148d724..2c2b7798a06 100644 --- a/test/unit/gateways/bogus_test.rb +++ b/test/unit/gateways/bogus_test.rb @@ -1,8 +1,10 @@ require 'test_helper' class BogusTest < Test::Unit::TestCase - SUCCESS_PLACEHOLDER = '4444333322221111' - FAILURE_PLACEHOLDER = '4444333311112222' + CC_SUCCESS_PLACEHOLDER = '4444333322221111' + CC_FAILURE_PLACEHOLDER = '4444333311112222' + CHECK_SUCCESS_PLACEHOLDER = '111111111111' + CHECK_FAILURE_PLACEHOLDER = '222222222222' def setup @gateway = BogusGateway.new( @@ -10,58 +12,62 @@ def setup :password => 'bogus' ) - @creditcard = credit_card(SUCCESS_PLACEHOLDER) + @creditcard = credit_card(CC_SUCCESS_PLACEHOLDER) @response = ActiveMerchant::Billing::Response.new(true, "Transaction successful", :transid => BogusGateway::AUTHORIZATION) end def test_authorize - assert @gateway.authorize(1000, credit_card(SUCCESS_PLACEHOLDER)).success? - assert !@gateway.authorize(1000, credit_card(FAILURE_PLACEHOLDER)).success? - assert_raises(ActiveMerchant::Billing::Error) do + assert @gateway.authorize(1000, credit_card(CC_SUCCESS_PLACEHOLDER)).success? + assert !@gateway.authorize(1000, credit_card(CC_FAILURE_PLACEHOLDER)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do @gateway.authorize(1000, credit_card('123')) end + assert_equal("Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error", e.message) end def test_purchase - assert @gateway.purchase(1000, credit_card(SUCCESS_PLACEHOLDER)).success? - assert !@gateway.purchase(1000, credit_card(FAILURE_PLACEHOLDER)).success? - assert_raises(ActiveMerchant::Billing::Error) do + assert @gateway.purchase(1000, credit_card(CC_SUCCESS_PLACEHOLDER)).success? + assert !@gateway.purchase(1000, credit_card(CC_FAILURE_PLACEHOLDER)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do @gateway.purchase(1000, credit_card('123')) end + assert_equal("Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error", e.message) end def test_recurring - assert @gateway.recurring(1000, credit_card(SUCCESS_PLACEHOLDER)).success? - assert !@gateway.recurring(1000, credit_card(FAILURE_PLACEHOLDER)).success? - assert_raises(ActiveMerchant::Billing::Error) do + assert @gateway.recurring(1000, credit_card(CC_SUCCESS_PLACEHOLDER)).success? + assert !@gateway.recurring(1000, credit_card(CC_FAILURE_PLACEHOLDER)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do @gateway.recurring(1000, credit_card('123')) end + assert_equal("Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error", e.message) end def test_capture assert @gateway.capture(1000, '1337').success? assert @gateway.capture(1000, @response.params["transid"]).success? - assert !@gateway.capture(1000, FAILURE_PLACEHOLDER).success? + assert !@gateway.capture(1000, CC_FAILURE_PLACEHOLDER).success? assert_raises(ActiveMerchant::Billing::Error) do - @gateway.capture(1000, SUCCESS_PLACEHOLDER) + @gateway.capture(1000, CC_SUCCESS_PLACEHOLDER) end end def test_credit - assert @gateway.credit(1000, credit_card(SUCCESS_PLACEHOLDER)).success? - assert !@gateway.credit(1000, credit_card(FAILURE_PLACEHOLDER)).success? - assert_raises(ActiveMerchant::Billing::Error) do + assert @gateway.credit(1000, credit_card(CC_SUCCESS_PLACEHOLDER)).success? + assert !@gateway.credit(1000, credit_card(CC_FAILURE_PLACEHOLDER)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do @gateway.credit(1000, credit_card('123')) end + assert_equal("Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error", e.message) end def test_refund assert @gateway.refund(1000, '1337').success? assert @gateway.refund(1000, @response.params["transid"]).success? - assert !@gateway.refund(1000, FAILURE_PLACEHOLDER).success? + assert !@gateway.refund(1000, CC_FAILURE_PLACEHOLDER).success? assert_raises(ActiveMerchant::Billing::Error) do - @gateway.refund(1000, SUCCESS_PLACEHOLDER) + @gateway.refund(1000, CC_SUCCESS_PLACEHOLDER) end end @@ -76,18 +82,23 @@ def test_credit_uses_refund def test_void assert @gateway.void('1337').success? assert @gateway.void(@response.params["transid"]).success? - assert !@gateway.void(FAILURE_PLACEHOLDER).success? + assert !@gateway.void(CC_FAILURE_PLACEHOLDER).success? assert_raises(ActiveMerchant::Billing::Error) do - @gateway.void(SUCCESS_PLACEHOLDER) + @gateway.void(CC_SUCCESS_PLACEHOLDER) end end def test_store - @gateway.store(@creditcard) + assert @gateway.store(credit_card(CC_SUCCESS_PLACEHOLDER)).success? + assert !@gateway.store(credit_card(CC_FAILURE_PLACEHOLDER)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.store(credit_card('123')) + end + assert_equal("Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error", e.message) end def test_unstore - @gateway.unstore(SUCCESS_PLACEHOLDER) + @gateway.unstore(CC_SUCCESS_PLACEHOLDER) end def test_store_then_purchase @@ -102,4 +113,58 @@ def test_supported_countries def test_supported_card_types assert_equal [:bogus], BogusGateway.supported_cardtypes end + + def test_authorize_with_check + assert @gateway.authorize(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? + assert !@gateway.authorize(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.authorize(1000, check(:account_number => '123', :number => nil)) + end + assert_equal("Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error", e.message) + end + + def test_purchase_with_check + # use account number if number isn't given + assert @gateway.purchase(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? + assert !@gateway.purchase(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + # give priority to number over account_number if given + assert !@gateway.purchase(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => CHECK_FAILURE_PLACEHOLDER)).success? + assert @gateway.purchase(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => CHECK_SUCCESS_PLACEHOLDER)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.purchase(1000, check(:account_number => '123', :number => nil)) + end + assert_equal("Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error", e.message) + end + + def test_recurring_with_check + assert @gateway.recurring(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? + assert !@gateway.recurring(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.recurring(1000, check(:account_number => '123', :number => nil)) + end + assert_equal("Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error", e.message) + end + + def test_store_with_check + assert @gateway.store(check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? + assert !@gateway.store(check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.store(check(:account_number => '123', :number => nil)) + end + assert_equal("Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error", e.message) + end + + def test_credit_with_check + assert @gateway.credit(1000, check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)).success? + assert !@gateway.credit(1000, check(:account_number => CHECK_FAILURE_PLACEHOLDER, :number => nil)).success? + e = assert_raises(ActiveMerchant::Billing::Error) do + @gateway.credit(1000, check(:account_number => '123', :number => nil)) + end + assert_equal("Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error", e.message) + end + + def test_store_then_purchase_with_check + reference = @gateway.store(check(:account_number => CHECK_SUCCESS_PLACEHOLDER, :number => nil)) + assert @gateway.purchase(1000, reference.authorization).success? + end end From e38d083b4562844bfe8b1e8843426ab0b516325e Mon Sep 17 00:00:00 2001 From: Ivan Barrios Date: Mon, 2 Dec 2013 19:58:14 -0800 Subject: [PATCH 103/104] Payflow: Add Check support Closes #935. --- CHANGELOG | 1 + .../billing/gateways/payflow.rb | 58 +++++++-- test/remote/gateways/remote_payflow_test.rb | 121 +++++++++++------- test/unit/gateways/payflow_test.rb | 13 ++ 4 files changed, 135 insertions(+), 58 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bf572b5da6c..a4cdff21dea 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ * Merchant Warrior: Use billing_address [duff] * Add SoEasyPay gateway [ir-soeasycorp] * Bogus: Add check support [npverni] +* Payflow: Add Check support [crazyivan] == Version 1.42.2 (November 13th, 2013) diff --git a/lib/active_merchant/billing/gateways/payflow.rb b/lib/active_merchant/billing/gateways/payflow.rb index 770c426d8fe..4f8c63271c9 100644 --- a/lib/active_merchant/billing/gateways/payflow.rb +++ b/lib/active_merchant/billing/gateways/payflow.rb @@ -19,21 +19,26 @@ def authorize(money, credit_card_or_reference, options = {}) commit(request, options) end - def purchase(money, credit_card_or_reference, options = {}) - request = build_sale_or_authorization_request(:purchase, money, credit_card_or_reference, options) + def purchase(money, funding_source, options = {}) + request = build_sale_or_authorization_request(:purchase, money, funding_source, options) commit(request, options) end - def credit(money, identification_or_credit_card, options = {}) - if identification_or_credit_card.is_a?(String) + def credit(money, funding_source, options = {}) + case + when funding_source.is_a?(String) deprecated CREDIT_DEPRECATION_MESSAGE # Perform referenced credit - refund(money, identification_or_credit_card, options) - else + refund(money, funding_source, options) + when funding_source.is_a?(CreditCard) # Perform non-referenced credit - request = build_credit_card_request(:credit, money, identification_or_credit_card, options) + request = build_credit_card_request(:credit, money, funding_source, options) + commit(request, options) + when funding_source.is_a?(Check) + request = build_check_request(:credit, money, funding_source, options) commit(request, options) + else raise ArgumentError, "Unsupported funding source provided" end end @@ -76,11 +81,15 @@ def express end private - def build_sale_or_authorization_request(action, money, credit_card_or_reference, options) - if credit_card_or_reference.is_a?(String) - build_reference_sale_or_authorization_request(action, money, credit_card_or_reference, options) - else - build_credit_card_request(action, money, credit_card_or_reference, options) + def build_sale_or_authorization_request(action, money, funding_source, options) + case + when funding_source.is_a?(String) + build_reference_sale_or_authorization_request(action, money, funding_source, options) + when funding_source.is_a?(CreditCard) + build_credit_card_request(action, money, funding_source, options) + when funding_source.is_a?(Check) + build_check_request(action, money, funding_source, options) + else raise ArgumentError, "Unsupported funding source provided" end end @@ -147,6 +156,31 @@ def build_credit_card_request(action, money, credit_card, options) xml.target! end + def build_check_request(action, money, check, options) + xml = Builder::XmlMarkup.new + xml.tag! TRANSACTIONS[action] do + xml.tag! 'PayData' do + xml.tag! 'Invoice' do + xml.tag! 'CustIP', options[:ip] unless options[:ip].blank? + xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank? + xml.tag! 'Description', options[:description] unless options[:description].blank? + xml.tag! 'BillTo' do + xml.tag! 'Name', check.name + end + xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money) + end + xml.tag! 'Tender' do + xml.tag! 'ACH' do + xml.tag! 'AcctType', check.account_type == 'checking' ? 'C' : 'S' + xml.tag! 'AcctNum', check.account_number + xml.tag! 'ABA', check.routing_number + end + end + end + end + xml.target! + end + def add_credit_card(xml, credit_card) xml.tag! 'Card' do xml.tag! 'CardType', credit_card_type(credit_card) diff --git a/test/remote/gateways/remote_payflow_test.rb b/test/remote/gateways/remote_payflow_test.rb index b3d49a939d3..0622c7957fd 100644 --- a/test/remote/gateways/remote_payflow_test.rb +++ b/test/remote/gateways/remote_payflow_test.rb @@ -5,17 +5,24 @@ def setup ActiveMerchant::Billing::Base.gateway_mode = :test @gateway = PayflowGateway.new(fixtures(:payflow)) - - @credit_card = credit_card('5105105105105100', + + @credit_card = credit_card( + '5105105105105100', :brand => 'master' ) - @options = { :billing_address => address, - :email => 'cody@example.com', - :customer => 'codyexample' - } + @options = { + :billing_address => address, + :email => 'cody@example.com', + :customer => 'codyexample' + } + + @check = check( + :routing_number => '111111118', + :account_number => '1234567801' + ) end - + def test_successful_purchase assert response = @gateway.purchase(100000, @credit_card, @options) assert_equal "Approved", response.message @@ -23,14 +30,28 @@ def test_successful_purchase assert response.test? assert_not_nil response.authorization end - + def test_declined_purchase assert response = @gateway.purchase(210000, @credit_card, @options) assert_equal 'Declined', response.message assert_failure response assert response.test? end - + + # Additional steps are required to enable ACH in a Payflow Pro account. + # See the "Payflow ACH Payment Service Guide" for more details: + # http://www.paypalobjects.com/webstatic/en_US/developer/docs/pdf/pp_achpayment_guide.pdf + # + # Also, when testing against the pilot-payflowpro.paypal.com endpoint, ACH must be enabled by Payflow support. + # This can be accomplished by sending an email to payflow-support@paypal.com with your Merchant Login. + def test_successful_ach_purchase + assert response = @gateway.purchase(50, @check) + assert_equal "Approved", response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + end + def test_successful_authorization assert response = @gateway.authorize(100, @credit_card, @options) assert_equal "Approved", response.message @@ -47,23 +68,23 @@ def test_authorize_and_capture assert capture = @gateway.capture(100, auth.authorization) assert_success capture end - + def test_authorize_and_partial_capture assert auth = @gateway.authorize(100 * 2, @credit_card, @options) assert_success auth assert_equal 'Approved', auth.message assert auth.authorization - + assert capture = @gateway.capture(100, auth.authorization) assert_success capture end - + def test_failed_capture assert response = @gateway.capture(100, '999') assert_failure response assert_equal 'Invalid tender', response.message end - + def test_authorize_and_void assert auth = @gateway.authorize(100, @credit_card, @options) assert_success auth @@ -72,7 +93,7 @@ def test_authorize_and_void assert void = @gateway.void(auth.authorization) assert_success void end - + def test_invalid_login gateway = PayflowGateway.new( :login => '', @@ -82,27 +103,27 @@ def test_invalid_login assert_equal 'Invalid vendor account', response.message assert_failure response end - + def test_duplicate_request_id request_id = Digest::MD5.hexdigest(rand.to_s) @gateway.expects(:generate_unique_id).times(2).returns(request_id) - + response1 = @gateway.purchase(100, @credit_card, @options) assert response1.success? assert_nil response1.params['duplicate'] - + response2 = @gateway.purchase(100, @credit_card, @options) assert response2.success? assert response2.params['duplicate'] end - + def test_create_recurring_profile response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly) assert_success response assert !response.params['profile_id'].blank? assert response.test? end - + def test_create_recurring_profile_with_invalid_date response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly, :starting_at => Time.now) assert_failure response @@ -110,18 +131,18 @@ def test_create_recurring_profile_with_invalid_date assert response.params['profile_id'].blank? assert response.test? end - + def test_create_and_cancel_recurring_profile response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly) assert_success response assert !response.params['profile_id'].blank? assert response.test? - + response = @gateway.cancel_recurring(response.params['profile_id']) assert_success response assert response.test? end - + def test_full_feature_set_for_recurring_profiles # Test add @options.update( @@ -137,7 +158,7 @@ def test_full_feature_set_for_recurring_profiles assert response.test? assert !response.params['profile_id'].blank? @recurring_profile_id = response.params['profile_id'] - + # Test modify @options.update( :periodicity => :monthly, @@ -150,19 +171,19 @@ def test_full_feature_set_for_recurring_profiles assert_equal "0", response.params['result'] assert_success response assert response.test? - + # Test inquiry - response = @gateway.recurring_inquiry(@recurring_profile_id) + response = @gateway.recurring_inquiry(@recurring_profile_id) assert_equal "0", response.params['result'] assert_success response assert response.test? - + # Test payment history inquiry response = @gateway.recurring_inquiry(@recurring_profile_id, :history => true) assert_equal '0', response.params['result'] assert_success response assert response.test? - + # Test cancel response = @gateway.cancel_recurring(@recurring_profile_id) assert_equal "Approved", response.params['message'] @@ -170,7 +191,7 @@ def test_full_feature_set_for_recurring_profiles assert_success response assert response.test? end - + # Note that this test will only work if you enable reference transactions!! def test_reference_purchase assert response = @gateway.purchase(10000, @credit_card, @options) @@ -178,60 +199,68 @@ def test_reference_purchase assert_success response assert response.test? assert_not_nil pn_ref = response.authorization - + # now another purchase, by reference assert response = @gateway.purchase(10000, pn_ref) assert_equal "Approved", response.message assert_success response assert response.test? end - + def test_recurring_with_initial_authorization - response = @gateway.recurring(1000, @credit_card, + response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly, :initial_transaction => { - :brand => :authorization + :type => :authorization } ) - + assert_success response assert !response.params['profile_id'].blank? assert response.test? end - + def test_recurring_with_initial_authorization - response = @gateway.recurring(1000, @credit_card, + response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly, :initial_transaction => { - :brand => :purchase, + :type => :purchase, :amount => 500 } ) - + assert_success response assert !response.params['profile_id'].blank? assert response.test? end - - def test_purchase_and_referenced_credit + + def test_purchase_and_refund amount = 100 - + assert purchase = @gateway.purchase(amount, @credit_card, @options) assert_success purchase assert_equal 'Approved', purchase.message assert !purchase.authorization.blank? - - assert credit = @gateway.credit(amount, purchase.authorization) + + assert credit = @gateway.refund(amount, purchase.authorization) assert_success credit end - - # The default security setting for Payflow Pro accounts is Allow + + # The default security setting for Payflow Pro accounts is Allow # non-referenced credits = No. # - # Non-referenced credits will fail with Result code 117 (failed the security + # Non-referenced credits will fail with Result code 117 (failed the security # check) unless Allow non-referenced credits = Yes in PayPal manager - def test_purchase_and_non_referenced_credit + def test_purchase_and_credit assert credit = @gateway.credit(100, @credit_card, @options) assert_success credit end + + def test_successful_ach_credit + assert response = @gateway.credit(50, @check) + assert_equal "Approved", response.message + assert_success response + assert response.test? + assert_not_nil response.authorization + end end diff --git a/test/unit/gateways/payflow_test.rb b/test/unit/gateways/payflow_test.rb index 9ee933154d0..685da48bd49 100644 --- a/test/unit/gateways/payflow_test.rb +++ b/test/unit/gateways/payflow_test.rb @@ -12,6 +12,7 @@ def setup @amount = 100 @credit_card = credit_card('4242424242424242') @options = { :billing_address => address.merge(:first_name => "Longbob", :last_name => "Longsen") } + @check = check( :name => 'Jim Smith' ) end def test_successful_authorization @@ -78,6 +79,18 @@ def test_cvv_result assert_equal 'M', response.cvv_result['code'] end + def test_ach_purchase + @gateway.expects(:ssl_post).with(anything, regexp_matches(/#{@check.account_number}<\//), anything).returns("") + @gateway.expects(:parse).returns({}) + @gateway.purchase(@amount, @check) + end + + def test_ach_credit + @gateway.expects(:ssl_post).with(anything, regexp_matches(/#{@check.account_number}<\//), anything).returns("") + @gateway.expects(:parse).returns({}) + @gateway.credit(@amount, @check) + end + def test_using_test_mode assert @gateway.test? end From e5e3fe0eb7ef13e3eb77fd568b48fdd457392551 Mon Sep 17 00:00:00 2001 From: Tom Davies Date: Fri, 1 Nov 2013 11:10:31 -0400 Subject: [PATCH 104/104] eWay Rapid: Upgrade to 3.1 This replaces Rapid 3.0 with Rapid 3.1, which is a much more appropriate API for direct calls. In particular, all calls are now a single remote vs. the 2+ that most calls required in 3.0. 3.1 support must be enabled on a per-account basis on the eWay side. Close #958. --- CHANGELOG | 1 + README.md | 2 +- .../billing/gateways/eway_rapid.rb | 413 ++++----- test/fixtures.yml | 5 +- .../remote/gateways/remote_eway_rapid_test.rb | 67 +- test/unit/gateways/eway_rapid_test.rb | 809 ++++++++++++------ 6 files changed, 804 insertions(+), 493 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a4cdff21dea..ab25c8fb8dd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ * Add SoEasyPay gateway [ir-soeasycorp] * Bogus: Add check support [npverni] * Payflow: Add Check support [crazyivan] +* eWay Rapid: Upgrade to 3.1 [atomgiant] == Version 1.42.2 (November 13th, 2013) diff --git a/README.md b/README.md index d5af60cc5f3..b4d8372fe89 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta * [ePay](http://epay.dk/) - DK, SE, NO * [EVO Canada](http://www.evocanada.com/) - CA * [eWAY](http://www.eway.com.au/) - AU, NZ, GB -* [eWAY Rapid 3.0](http://www.eway.com.au/) - AU +* [eWAY Rapid 3.1](http://www.eway.com.au/) - AU * [E-xact](http://www.e-xact.com) - CA, US * [Fat Zebra](https://www.fatzebra.com.au/) - AU * [Federated Canada](http://www.federatedcanada.com/) - CA diff --git a/lib/active_merchant/billing/gateways/eway_rapid.rb b/lib/active_merchant/billing/gateways/eway_rapid.rb index f7c3791e554..380e4be0a4f 100644 --- a/lib/active_merchant/billing/gateways/eway_rapid.rb +++ b/lib/active_merchant/billing/gateways/eway_rapid.rb @@ -1,5 +1,4 @@ -require "nokogiri" -require "cgi" +require 'json' module ActiveMerchant #:nodoc: module Billing #:nodoc: @@ -11,7 +10,7 @@ class EwayRapidGateway < Gateway self.supported_countries = ["AU"] self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] self.homepage_url = "http://www.eway.com.au/" - self.display_name = "eWAY Rapid 3.0" + self.display_name = "eWAY Rapid 3.1" self.default_currency = "AUD" def initialize(options = {}) @@ -19,203 +18,219 @@ def initialize(options = {}) super end - # Public: Run a purchase transaction. Treats the Rapid 3.0 transparent - # redirect as an API endpoint in order to conform to the standard - # ActiveMerchant #purchase API. + # Public: Run a purchase transaction. # - # amount - The monetary amount of the transaction in cents. - # options - A standard ActiveMerchant options hash: - # :order_id - A merchant-supplied identifier for the - # transaction (optional). - # :description - A merchant-supplied description of the - # transaction (optional). - # :currency - Three letter currency code for the - # transaction (default: "AUD") - # :billing_address - Standard ActiveMerchant address hash - # (optional). - # :shipping_address - Standard ActiveMerchant address hash - # (optional). - # :ip - The ip of the consumer initiating the - # transaction (optional). - # :application_id - A string identifying the application - # submitting the transaction - # (default: "https://github.com/Shopify/active_merchant") + # amount - The monetary amount of the transaction in cents. + # payment_method - The payment method or authorization token returned from store. + # options - A standard ActiveMerchant options hash: + # :transaction_type - One of: Purchase (default), MOTO + # or Recurring. For stored card payments (aka - TokenPayments), + # this must be either MOTO or Recurring. + # :order_id - A merchant-supplied identifier for the + # transaction (optional). + # :description - A merchant-supplied description of the + # transaction (optional). + # :currency - Three letter currency code for the + # transaction (default: "AUD") + # :billing_address - Standard ActiveMerchant address hash + # (optional). + # :shipping_address - Standard ActiveMerchant address hash + # (optional). + # :ip - The ip of the consumer initiating the + # transaction (optional). + # :application_id - A string identifying the application + # submitting the transaction + # (default: "https://github.com/Shopify/active_merchant") # - # Returns an ActiveMerchant::Billing::Response object + # Returns an ActiveMerchant::Billing::Response object where authorization is the Transaction ID on success def purchase(amount, payment_method, options={}) - MultiResponse.new.tap do |r| - # Rather than follow the redirect, we detect the 302 and capture the - # token out of the Location header in the run_purchase step. But we - # still need a placeholder url to pass to eWay, and that is what - # example.com is used for here. - r.process{setup_purchase(amount, options.merge(:redirect_url => "http://example.com/"))} - r.process{run_purchase(r.authorization, payment_method, r.params["formactionurl"])} - r.process{status(r.authorization)} - end + params = {} + add_metadata(params, options) + add_invoice(params, amount, options) + add_customer_data(params, options) + add_credit_card(params, payment_method, options) + commit(url_for('Transaction'), params) end - # Public: Acquire the token necessary to run a transparent redirect. + # Public: Refund a transaction. # - # amount - The monetary amount of the transaction in cents. - # options - A supplemented ActiveMerchant options hash: - # :redirect_url - The url to return the customer to after - # the transparent redirect is completed - # (required). - # :order_id - A merchant-supplied identifier for the - # transaction (optional). - # :description - A merchant-supplied description of the - # transaction (optional). - # :currency - Three letter currency code for the - # transaction (default: "AUD") - # :billing_address - Standard ActiveMerchant address hash - # (optional). - # :shipping_address - Standard ActiveMerchant address hash - # (optional). - # :ip - The ip of the consumer initiating the - # transaction (optional). - # :application_id - A string identifying the application - # submitting the transaction - # (default: "https://github.com/Shopify/active_merchant") + # money - The monetary amount of the transaction in cents + # identification - The transaction id which is returned in the + # authorization of the successful purchase transaction + # options - A standard ActiveMerchant options hash: + # :order_id - A merchant-supplied identifier for the + # transaction (optional). + # :description - A merchant-supplied description of the + # transaction (optional). + # :currency - Three letter currency code for the + # transaction (default: "AUD") + # :billing_address - Standard ActiveMerchant address hash + # (optional). + # :shipping_address - Standard ActiveMerchant address hash + # (optional). + # :ip - The ip of the consumer initiating the + # transaction (optional). + # :application_id - A string identifying the application + # submitting the transaction + # (default: "https://github.com/Shopify/active_merchant") # - # Returns an EwayRapidResponse object, which conforms to the - # ActiveMerchant::Billing::Response API, but also exposes #form_url. - def setup_purchase(amount, options={}) - requires!(options, :redirect_url) - request = build_xml_request("CreateAccessCodeRequest") do |doc| - add_metadata(doc, options) - add_invoice(doc, amount, options) - add_customer_data(doc, options) - end - - commit(url_for("CreateAccessCode"), request) + # Returns an ActiveMerchant::Billing::Response object + def refund(money, identification, options = {}) + params = {} + add_metadata(params, options) + add_invoice(params, money, options.merge(refund_transaction_id: identification)) + add_customer_data(params, options) + commit(url_for("Transaction/#{identification}/Refund"), params) end - # Public: Retrieve the status of a transaction. + # Public: Store card details and return a valid token # - # identification - The Eway Rapid 3.0 access code for the transaction - # (returned as the response.authorization by - # #setup_purchase). + # payment_method - The payment method or nil if :customer_token is provided + # options - A supplemented ActiveMerchant options hash: + # :order_id - A merchant-supplied identifier for the + # transaction (optional). + # :description - A merchant-supplied description of the + # transaction (optional). + # :billing_address - Standard ActiveMerchant address hash + # (required). + # :ip - The ip of the consumer initiating the + # transaction (optional). + # :application_id - A string identifying the application + # submitting the transaction + # (default: "https://github.com/Shopify/active_merchant") # - # Returns an EwayRapidResponse object. - def status(identification) - request = build_xml_request("GetAccessCodeResultRequest") do |doc| - doc.AccessCode identification - end - commit(url_for("GetAccessCodeResult"), request) + # Returns an ActiveMerchant::Billing::Response object where the authorization is the customer_token on success + def store(payment_method, options = {}) + requires!(options, :billing_address) + params = {} + add_metadata(params, options) + add_invoice(params, 0, options) + add_customer_data(params, options) + add_credit_card(params, payment_method, options) + params['Method'] = 'CreateTokenCustomer' + commit(url_for("Transaction"), params) end - # Public: Store card details and return a valid token + # Public: Update a customer's data # - # options - A supplemented ActiveMerchant options hash: - # :order_id - A merchant-supplied identifier for the - # transaction (optional). - # :billing_address - Standard ActiveMerchant address hash - # (required). - # :ip - The ip of the consumer initiating the - # transaction (optional). - # :application_id - A string identifying the application - # submitting the transaction - # (default: "https://github.com/Shopify/active_merchant") - def store(payment_method, options = {}) - requires!(options, :billing_address) - purchase(0, payment_method, options.merge(:request_method => "CreateTokenCustomer")) + # customer_token - The customer token returned in the authorization of + # a successful store transaction. + # payment_method - The payment method or nil if :customer_token is provided + # options - A supplemented ActiveMerchant options hash: + # :order_id - A merchant-supplied identifier for the + # transaction (optional). + # :description - A merchant-supplied description of the + # transaction (optional). + # :billing_address - Standard ActiveMerchant address hash + # (optional). + # :ip - The ip of the consumer initiating the + # transaction (optional). + # :application_id - A string identifying the application + # submitting the transaction + # (default: "https://github.com/Shopify/active_merchant") + # + # Returns an ActiveMerchant::Billing::Response object where the authorization is the customer_token on success + def update(customer_token, payment_method, options = {}) + params = {} + add_metadata(params, options) + add_invoice(params, 0, options) + add_customer_data(params, options) + add_credit_card(params, payment_method, options) + add_customer_token(params, customer_token) + params['Method'] = 'UpdateTokenCustomer' + commit(url_for("Transaction"), params) end private - def run_purchase(identification, payment_method, endpoint) - post = { - "accesscode" => identification - } - add_credit_card(post, payment_method) - - commit_form(endpoint, build_form_request(post), :identification => identification) - end - - def add_metadata(doc, options) - doc.RedirectUrl(options[:redirect_url]) - doc.CustomerIP options[:ip] if options[:ip] - doc.Method options[:request_method] || "ProcessPayment" - doc.DeviceID(options[:application_id] || application_id) + def add_metadata(params, options) + params['RedirectUrl'] = options[:redirect_url] || 'http://example.com' + params['CustomerIP'] = options[:ip] if options[:ip] + params['TransactionType'] = options[:transaction_type] || 'Purchase' + params['DeviceID'] = options[:application_id] || application_id end - def add_invoice(doc, money, options) - doc.Payment do - currency_code = options[:currency] || currency(money) - doc.TotalAmount localized_amount(money, currency_code) - doc.InvoiceReference options[:order_id] - doc.InvoiceDescription options[:description] - doc.CurrencyCode currency_code + def add_invoice(params, money, options) + currency_code = options[:currency] || currency(money) + invoice = { + 'TotalAmount' => localized_amount(money, currency_code), + 'InvoiceReference' => options[:order_id], + 'InvoiceDescription' => options[:description], + 'CurrencyCode' => currency_code, + } + if options[:refund_transaction_id] + # must include the original transaction id for refunds + invoice['TransactionID'] = options[:refund_transaction_id] if options[:refund_transaction_id] + params['Refund'] = invoice + else + params['Payment'] = invoice end end - def add_customer_data(doc, options) - doc.Customer do - add_address(doc, (options[:billing_address] || options[:address]), {:email => options[:email]}) - end - doc.ShippingAddress do - add_address(doc, options[:shipping_address], {:skip_company => true}) - end + def add_customer_data(params, options) + params['Customer'] ||= {} + add_address(params['Customer'], (options[:billing_address] || options[:address]), {:email => options[:email]}) + params['ShippingAddress'] = {} + add_address(params['ShippingAddress'], options[:shipping_address], {:skip_company => true}) end - def add_address(doc, address, options={}) + def add_address(params, address, options={}) return unless address - if name = address[:name] - parts = name.split(/\s+/) - doc.FirstName parts.shift if parts.size > 1 - doc.LastName parts.join(" ") - end - doc.Title address[:title] - doc.CompanyName address[:company] unless options[:skip_company] - doc.Street1 address[:address1] - doc.Street2 address[:address2] - doc.City address[:city] - doc.State address[:state] - doc.PostalCode address[:zip] - doc.Country address[:country].to_s.downcase - doc.Phone address[:phone] - doc.Fax address[:fax] - doc.Email options[:email] - end - def add_credit_card(post, credit_card) - post["cardname"] = credit_card.name - post["cardnumber"] = credit_card.number - post["cardexpirymonth"] = credit_card.month - post["cardexpiryyear"] = credit_card.year - post["cardcvn"] = credit_card.verification_value + if address[:name] + parts = address[:name].split(/\s+/) + params['FirstName'] = parts.shift if parts.size > 1 + params['LastName'] = parts.join(" ") + end + params['Title'] = address[:title] + params['CompanyName'] = address[:company] unless options[:skip_company] + params['Street1'] = address[:address1] + params['Street2'] = address[:address2] + params['City'] = address[:city] + params['State'] = address[:state] + params['PostalCode'] = address[:zip] + params['Country'] = address[:country].to_s.downcase + params['Phone'] = address[:phone] + params['Fax'] = address[:fax] + params['Email'] = options[:email] end - def build_xml_request(root) - builder = Nokogiri::XML::Builder.new - builder.__send__(root) do |doc| - yield(doc) + def add_credit_card(params, credit_card, options) + return unless credit_card + params['Customer'] ||= {} + if credit_card.respond_to? :number + params['Method'] = 'ProcessPayment' + card_details = params['Customer']['CardDetails'] = {} + card_details['Name'] = credit_card.name + card_details['Number'] = credit_card.number + card_details['ExpiryMonth'] = "%02d" % (credit_card.month || 0) + card_details['ExpiryYear'] = "%02d" % (credit_card.year || 0) + card_details['CVN'] = credit_card.verification_value + else + params['Method'] = 'TokenPayment' + add_customer_token(params, credit_card) end - builder.to_xml end - def build_form_request(post) - request = [] - post.each do |key, value| - request << "EWAY_#{key.upcase}=#{CGI.escape(value.to_s)}" - end - request.join("&") + def add_customer_token(params, token) + params['Customer'] ||= {} + params['Customer']['TokenCustomerID'] = token end def url_for(action) - (test? ? test_url : live_url) + action + ".xml" + (test? ? test_url : live_url) + action end - def commit(url, request, form_post=false) + def commit(url, params) headers = { "Authorization" => ("Basic " + Base64.strict_encode64(@options[:login].to_s + ":" + @options[:password].to_s).chomp), - "Content-Type" => "text/xml" + "Content-Type" => "application/json" } - + request = params.to_json raw = parse(ssl_post(url, request, headers)) succeeded = success?(raw) - EwayRapidResponse.new( + ActiveMerchant::Billing::Response.new( succeeded, message_from(succeeded, raw), raw, @@ -225,56 +240,32 @@ def commit(url, request, form_post=false) :cvv_result => cvv_result_from(raw) ) rescue ActiveMerchant::ResponseError => e - return EwayRapidResponse.new(false, e.response.message, {:status_code => e.response.code}, :test => test?) + return ActiveMerchant::Billing::Response.new(false, e.response.message, {:status_code => e.response.code}, :test => test?) end - def commit_form(url, request, parameters) - http_response = raw_ssl_request(:post, url, request) - - success = (http_response.code.to_s == "302") - message = (success ? "Succeeded" : http_response.body) - authorization = parameters[:identification] if success - - Response.new(success, message, {:location => http_response["Location"]}, :authorization => authorization, :test => test?) - end - - def parse(xml) - response = {} - - doc = Nokogiri::XML(xml) - doc.root.xpath("*").each do |node| - if (node.elements.size == 0) - response[node.name.downcase.to_sym] = node.text - else - node.elements.each do |childnode| - name = "#{node.name.downcase}_#{childnode.name.downcase}" - response[name.to_sym] = childnode.text - end - end - end unless doc.root.nil? - - response + def parse(data) + JSON.parse(data) end def success?(response) - if response[:errors] + if response['Errors'] false - elsif response[:responsecode] == "00" + elsif response['ResponseCode'] == "00" true - elsif response[:transactionstatus] - (response[:transactionstatus] == "true") + elsif response['TransactionStatus'] + (response['TransactionStatus'] == true) else true end end def message_from(succeeded, response) - if response[:errors] - (MESSAGES[response[:errors]] || response[:errors]) - elsif response[:responsecode] - ActiveMerchant::Billing::EwayGateway::MESSAGES[response[:responsecode]] - elsif response[:responsemessage] - (MESSAGES[response[:responsemessage]] || response[:responsemessage]) + if response['Errors'] + (MESSAGES[response['Errors']] || response['Errors']) + elsif response['Responsecode'] + ActiveMerchant::Billing::EwayGateway::MESSAGES[response['ResponseCode']] + elsif response['ResponseMessage'] + (MESSAGES[response['ResponseMessage']] || response['ResponseMessage']) elsif succeeded "Succeeded" else @@ -283,11 +274,14 @@ def message_from(succeeded, response) end def authorization_from(response) - response[:accesscode] + # Note: TransactionID is always null for store requests, but TokenCustomerID is also sent back for purchase from + # stored card transactions so we give precendence to TransactionID + response['TransactionID'] || response['Customer']['TokenCustomerID'] end def avs_result_from(response) - code = case response[:verification_address] + verification = response['Verification'] || {} + code = case verification['Address'] when "Valid" "M" when "Invalid" @@ -299,7 +293,8 @@ def avs_result_from(response) end def cvv_result_from(response) - case response[:verification_cvn] + verification = response['Verification'] || {} + case verification['CVN'] when "Valid" "M" when "Invalid" @@ -309,16 +304,25 @@ def cvv_result_from(response) end end - class EwayRapidResponse < ActiveMerchant::Billing::Response - def form_url - params["formactionurl"] - end - end - MESSAGES = { + 'A2000' => 'Transaction Approved Successful', + 'A2008' => 'Honour With Identification Successful', + 'A2010' => 'Approved For Partial Amount Successful', + 'A2011' => 'Approved, VIP Successful', + 'A2016' => 'Approved, Update Track 3 Successful', + 'S5000' => 'System Error', + 'S5085' => 'Started 3dSecure', + 'S5086' => 'Routed 3dSecure', + 'S5087' => 'Completed 3dSecure', + 'S5088' => 'PayPal Transaction Created', + 'S5099' => 'Incomplete (Access Code in progress/incomplete)', + 'S5010' => 'Unknown error returned by gateway', 'V6000' => 'Validation error', 'V6001' => 'Invalid CustomerIP', 'V6002' => 'Invalid DeviceID', + 'V6003' => 'Invalid Request PartnerID', + 'V6004' => 'Invalid Request Method', + 'V6010' => 'Invalid TransactionType, account not certified for eCome only MOTO or Recurring available', 'V6011' => 'Invalid Payment TotalAmount', 'V6012' => 'Invalid Payment InvoiceDescription', 'V6013' => 'Invalid Payment InvoiceNumber', @@ -385,7 +389,8 @@ def form_url 'V6107' => 'Invalid EWAY_ACCESSCODE', 'V6108' => 'Invalid CustomerHostAddress', 'V6109' => 'Invalid UserAgent', - 'V6110' => 'Invalid EWAY_CARDNUMBER' + 'V6110' => 'Invalid EWAY_CARDNUMBER', + 'V6111' => 'Unauthorised API Access, Account Not PCI Certified' } end end diff --git a/test/fixtures.yml b/test/fixtures.yml index 16a42a0e82f..598e9269d79 100644 --- a/test/fixtures.yml +++ b/test/fixtures.yml @@ -128,10 +128,9 @@ eway_managed: username: 'test@eway.com.au' password: 'test123' -# Working credentials, no need to replace eway_rapid: - login: "F9802CIVgUgNaD8y/H52MBMnL5OvMoy4cYimpi1L/dCXeNNR6or3vLPoC9GjeLVdA7ymi+" - password: "sandbox1" + login: LOGIN + password: PASSWORD # Working credentials, no need to replace exact: diff --git a/test/remote/gateways/remote_eway_rapid_test.rb b/test/remote/gateways/remote_eway_rapid_test.rb index 1cce2cf232a..1c26a3271d1 100644 --- a/test/remote/gateways/remote_eway_rapid_test.rb +++ b/test/remote/gateways/remote_eway_rapid_test.rb @@ -5,7 +5,7 @@ def setup @gateway = EwayRapidGateway.new(fixtures(:eway_rapid)) @amount = 100 - @failed_amount = 105 + @failed_amount = -100 @credit_card = credit_card("4444333322221111") @options = { @@ -19,7 +19,7 @@ def setup def test_successful_purchase assert response = @gateway.purchase(@amount, @credit_card, @options) assert_success response - assert_equal "Transaction Approved", response.message + assert_equal "Transaction Approved Successful", response.message end def test_fully_loaded_purchase @@ -27,6 +27,7 @@ def test_fully_loaded_purchase :redirect_url => "http://awesomesauce.com", :ip => "0.0.0.0", :application_id => "Woohoo", + :transaction_type => "Purchase", :description => "Description", :order_id => "orderid1", :currency => "AUD", @@ -64,49 +65,61 @@ def test_fully_loaded_purchase def test_failed_purchase assert response = @gateway.purchase(@failed_amount, @credit_card, @options) assert_failure response - assert_equal "Do Not Honour", response.message + assert_equal "Invalid Payment TotalAmount", response.message end - def test_failed_setup_purchase - assert response = @gateway.setup_purchase(@amount, :redirect_url => "") - assert_failure response - assert_equal "V6047", response.message - end - - def test_failed_run_purchase - setup_response = @gateway.setup_purchase(@amount, @options) - assert_success setup_response + def test_successful_refund + # purchase + assert response = @gateway.purchase(@amount, @credit_card, @options) + assert_success response + assert_equal "Transaction Approved Successful", response.message - assert response = @gateway.send(:run_purchase, "bogus", @credit_card, setup_response.params["formactionurl"]) - assert_failure response - assert_match(%r{Access Code Invalid}, response.message) + # refund + assert response = @gateway.refund(@amount, response.authorization, @options) + assert_success response + assert_equal "Transaction Approved Successful", response.message end - def test_failed_status - setup_response = @gateway.setup_purchase(@failed_amount, @options) - assert_success setup_response - - assert run_response = @gateway.send(:run_purchase, setup_response.authorization, @credit_card, setup_response.params["formactionurl"]) - assert_success run_response - - response = @gateway.status(run_response.authorization) + def test_failed_refund + assert response = @gateway.refund(@amount, 'fakeid', @options) assert_failure response - assert_equal "Do Not Honour", response.message - assert_equal run_response.authorization, response.authorization + assert_equal "System Error", response.message end def test_successful_store @options[:billing_address].merge!(:title => "Dr.") assert response = @gateway.store(@credit_card, @options) assert_success response - assert_equal "Transaction Approved", response.message + assert_equal "Transaction Approved Successful", response.message end def test_failed_store @options[:billing_address].merge!(:country => nil) assert response = @gateway.store(@credit_card, @options) assert_failure response - assert_equal "V6044", response.message + assert_equal "V6044", response.params["Errors"] + assert_equal "Customer CountryCode Required", response.message + end + + def test_successful_update + @options[:billing_address].merge!(:title => "Dr.") + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal "Transaction Approved Successful", response.message + assert response = @gateway.update(response.authorization, @credit_card, @options) + assert_success response + assert_equal "Transaction Approved Successful", response.message + end + + def test_successful_store_purchase + @options[:billing_address].merge!(:title => "Dr.") + assert response = @gateway.store(@credit_card, @options) + assert_success response + assert_equal "Transaction Approved Successful", response.message + + assert response = @gateway.purchase(@amount, response.authorization, {transaction_type: 'MOTO'}) + assert_success response + assert_equal "Transaction Approved Successful", response.message end def test_invalid_login diff --git a/test/unit/gateways/eway_rapid_test.rb b/test/unit/gateways/eway_rapid_test.rb index b5b9196c1ef..78063eef570 100644 --- a/test/unit/gateways/eway_rapid_test.rb +++ b/test/unit/gateways/eway_rapid_test.rb @@ -13,62 +13,48 @@ def setup @amount = 100 end - def test_purchase_calls_sub_methods - request = sequence("request") - @gateway.expects(:setup_purchase).with(@amount, {:order_id => 1, :redirect_url => "http://example.com/"}).returns(Response.new(true, "Success", {"formactionurl" => "url"}, :authorization => "auth1")).in_sequence(request) - @gateway.expects(:run_purchase).with("auth1", @credit_card, "url").returns(Response.new(true, "Success", {}, :authorization => "auth2")).in_sequence(request) - @gateway.expects(:status).with("auth2").returns(Response.new(true, "Success", {})).in_sequence(request) - - response = @gateway.purchase(@amount, @credit_card, :order_id => 1) - assert_success response - end - - def test_successful_setup_purchase + def test_successful_purchase response = stub_comms do - @gateway.setup_purchase(@amount, :redirect_url => "http://bogus") - end.respond_with(successful_setup_purchase_response) + @gateway.purchase(@amount, @credit_card) + end.respond_with(successful_purchase_response) assert_success response - assert_equal "Succeeded", response.message - assert_equal( - "60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT", - response.authorization - ) - assert_equal "https://secure-au.sandbox.ewaypayments.com/Process", response.form_url + assert_equal "Transaction Approved Successful", response.message + assert_equal 10440187, response.authorization assert response.test? end def test_localized_currency stub_comms do - @gateway.setup_purchase(100, :currency => 'CAD', :redirect_url => '') + @gateway.purchase(100, @credit_card, :currency => 'CAD') end.check_request do |endpoint, data, headers| - assert_match /100<\/TotalAmount>/, data - end.respond_with(successful_setup_purchase_response) + assert_match '"TotalAmount":"100"', data + end.respond_with(successful_purchase_response) stub_comms do - @gateway.setup_purchase(100, :currency => 'JPY', :redirect_url => '') + @gateway.purchase(100, @credit_card, :currency => 'JPY') end.check_request do |endpoint, data, headers| - assert_match /1<\/TotalAmount>/, data - end.respond_with(successful_setup_purchase_response) + assert_match '"TotalAmount":"1"', data + end.respond_with(successful_purchase_response) end - def test_failed_setup_purchase + def test_failed_purchase response = stub_comms do - @gateway.setup_purchase(@amount, :redirect_url => "http://bogus") - end.respond_with(failed_setup_purchase_response) + @gateway.purchase(-100, @credit_card) + end.respond_with(failed_purchase_response) assert_failure response - assert_equal "RedirectURL Required", response.message + assert_equal "Invalid Payment TotalAmount", response.message assert_nil response.authorization assert response.test? end - def test_setup_purchase_with_all_options + def test_purchase_with_all_options response = stub_comms do - @gateway.setup_purchase(200, + @gateway.purchase(200, @credit_card, + :transaction_type => 'CustomTransactionType', :redirect_url => "http://awesomesauce.com", :ip => "0.0.0.0", - :request_method => "CustomRequest", :application_id => "Woohoo", :description => "Description", :order_id => "orderid1", @@ -102,158 +88,161 @@ def test_setup_purchase_with_all_options } ) end.check_request do |endpoint, data, headers| - assert_no_match(%r{#{@credit_card.number}}, data) - - assert_match(%r{RedirectUrl>http://awesomesauce.com<}, data) - assert_match(%r{CustomerIP>0.0.0.0<}, data) - assert_match(%r{Method>CustomRequest<}, data) - assert_match(%r{DeviceID>Woohoo<}, data) - - assert_match(%r{TotalAmount>200<}, data) - assert_match(%r{InvoiceDescription>Description<}, data) - assert_match(%r{InvoiceReference>orderid1<}, data) - assert_match(%r{CurrencyCode>INR<}, data) - - assert_match(%r{Title>Mr.<}, data) - assert_match(%r{FirstName>Jim<}, data) - assert_match(%r{LastName>Awesome Smith<}, data) - assert_match(%r{CompanyName>Awesome Co<}, data) - assert_match(%r{Street1>1234 My Street<}, data) - assert_match(%r{Street2>Apt 1<}, data) - assert_match(%r{City>Ottawa<}, data) - assert_match(%r{State>ON<}, data) - assert_match(%r{PostalCode>K1C2N6<}, data) - assert_match(%r{Country>ca<}, data) - assert_match(%r{Phone>\(555\)555-5555<}, data) - assert_match(%r{Fax>\(555\)555-6666<}, data) - assert_match(%r{Email>jim@example\.com<}, data) - - assert_match(%r{Title>Ms.<}, data) - assert_match(%r{LastName>Baker<}, data) + # assert_no_match(%r{#{@credit_card.number}}, data) + + assert_match(%r{"TransactionType":"CustomTransactionType"}, data) + assert_match(%r{"RedirectUrl":"http://awesomesauce.com"}, data) + assert_match(%r{"CustomerIP":"0.0.0.0"}, data) + assert_match(%r{"DeviceID":"Woohoo"}, data) + + assert_match(%r{"TotalAmount":"200"}, data) + assert_match(%r{"InvoiceDescription":"Description"}, data) + assert_match(%r{"InvoiceReference":"orderid1"}, data) + assert_match(%r{"CurrencyCode":"INR"}, data) + + assert_match(%r{"Title":"Mr."}, data) + assert_match(%r{"FirstName":"Jim"}, data) + assert_match(%r{"LastName":"Awesome Smith"}, data) + assert_match(%r{"CompanyName":"Awesome Co"}, data) + assert_match(%r{"Street1":"1234 My Street"}, data) + assert_match(%r{"Street2":"Apt 1"}, data) + assert_match(%r{"City":"Ottawa"}, data) + assert_match(%r{"State":"ON"}, data) + assert_match(%r{"PostalCode":"K1C2N6"}, data) + assert_match(%r{"Country":"ca"}, data) + assert_match(%r{"Phone":"\(555\)555-5555"}, data) + assert_match(%r{"Fax":"\(555\)555-6666"}, data) + assert_match(%r{"Email":"jim@example\.com"}, data) + + assert_match(%r{"Title":"Ms."}, data) + assert_match(%r{"LastName":"Baker"}, data) assert_no_match(%r{Elsewhere Inc.}, data) - assert_match(%r{Street1>4321 Their St.<}, data) - assert_match(%r{Street2>Apt 2<}, data) - assert_match(%r{City>Chicago<}, data) - assert_match(%r{State>IL<}, data) - assert_match(%r{PostalCode>60625<}, data) - assert_match(%r{Country>us<}, data) - assert_match(%r{Phone>1115555555<}, data) - assert_match(%r{Fax>1115556666<}, data) - assert_match(%r{Email>(\s+)?<}, data) - end.respond_with(successful_setup_purchase_response) + assert_match(%r{"Street1":"4321 Their St."}, data) + assert_match(%r{"Street2":"Apt 2"}, data) + assert_match(%r{"City":"Chicago"}, data) + assert_match(%r{"State":"IL"}, data) + assert_match(%r{"PostalCode":"60625"}, data) + assert_match(%r{"Country":"us"}, data) + assert_match(%r{"Phone":"1115555555"}, data) + assert_match(%r{"Fax":"1115556666"}, data) + assert_match(%r{"Email":null}, data) + end.respond_with(successful_purchase_response) assert_success response - assert_equal( - "60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT", - response.authorization - ) + assert_equal 10440187, response.authorization assert response.test? end - def test_successful_run_purchase - request_sequence = sequence("request") - @gateway.expects(:ssl_request).returns(successful_setup_purchase_response).in_sequence(request_sequence) - @gateway.expects(:raw_ssl_request).with( - :post, - "https://secure-au.sandbox.ewaypayments.com/Process", - all_of( - regexp_matches(%r{EWAY_ACCESSCODE=60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT}), - regexp_matches(%r{EWAY_CARDNAME=Longbob\+Longsen}), - regexp_matches(%r{EWAY_CARDNUMBER=#{@credit_card.number}}), - regexp_matches(%r{EWAY_CARDEXPIRYMONTH=#{@credit_card.month}}), - regexp_matches(%r{EWAY_CARDEXPIRYYEAR=#{@credit_card.year}}), - regexp_matches(%r{EWAY_CARDCVN=#{@credit_card.verification_value}}) - ), - anything - ).returns(successful_run_purchase_response).in_sequence(request_sequence) - @gateway.expects(:ssl_request).returns(successful_status_response).in_sequence(request_sequence) - - response = @gateway.purchase(@amount, @credit_card, :order_id => 1) + def test_successful_store + response = stub_comms do + @gateway.store(@credit_card, :billing_address => { + :title => "Mr.", + :name => "Jim Awesome Smith", + :company => "Awesome Co", + :address1 => "1234 My Street", + :address2 => "Apt 1", + :city => "Ottawa", + :state => "ON", + :zip => "K1C2N6", + :country => "CA", + :phone => "(555)555-5555", + :fax => "(555)555-6666" + }) + end.check_request do |endpoint, data, headers| + assert_match '"Method":"CreateTokenCustomer"', data + end.respond_with(successful_store_response) + assert_success response - assert_equal "Transaction Approved", response.message - assert_equal( - "60CF3sfH7-yvAsUAHrdIiGppPrQW7v7DMAXxKkaKwyrIUoqvUvK44XbK9G9HNbngIz_iwQpfmPT_duMgh2G0pXCX8i4z1RAmMHpUQwa6VrghV3Bx9rh_tojjym7LC_fE-eR97", - response.authorization - ) + assert_equal "Transaction Approved Successful", response.message + assert_equal 917224224772, response.authorization assert response.test? end - def test_failed_run_purchase - request_sequence = sequence("request") - @gateway.expects(:ssl_request).returns(successful_setup_purchase_response).in_sequence(request_sequence) - @gateway.expects(:raw_ssl_request).returns(failed_run_purchase_response).in_sequence(request_sequence) + def test_failed_store + response = stub_comms do + @gateway.store(@credit_card, :billing_address => {}) + end.respond_with(failed_store_response) - response = @gateway.purchase(@amount, @credit_card, :order_id => 1) assert_failure response - assert_match %r{Not Found}, response.message + assert_equal "Customer CountryCode Required", response.message assert_nil response.authorization assert response.test? end - def test_successful_status + def test_successful_update response = stub_comms do - @gateway.status("thetransauth") + @gateway.update('faketoken', nil) end.check_request do |endpoint, data, headers| - assert_match(%r{thetransauth}, data) - end.respond_with(successful_status_response) + assert_match '"Method":"UpdateTokenCustomer"', data + end.respond_with(successful_update_response) assert_success response - assert_equal( - "60CF3sfH7-yvAsUAHrdIiGppPrQW7v7DMAXxKkaKwyrIUoqvUvK44XbK9G9HNbngIz_iwQpfmPT_duMgh2G0pXCX8i4z1RAmMHpUQwa6VrghV3Bx9rh_tojjym7LC_fE-eR97", - response.authorization - ) + assert_equal "Transaction Approved Successful", response.message + assert_equal 916161208398, response.authorization + assert response.test? + end + + def test_successful_refund + response = stub_comms do + @gateway.refund(@amount, '1234567') + end.check_request do |endpoint, data, headers| + assert_match /Transaction\/1234567\/Refund$/, endpoint + json = JSON.parse(data) + assert_equal '100', json['Refund']['TotalAmount'] + assert_equal '1234567', json['Refund']['TransactionID'] + end.respond_with(successful_refund_response) + + assert_success response + assert_equal "Transaction Approved Successful", response.message + assert_equal 10488258, response.authorization assert response.test? - assert_equal "Transaction Approved", response.message - assert_equal "orderid1", response.params["invoicereference"] end - def test_failed_status + def test_failed_refund response = stub_comms do - @gateway.status("thetransauth") - end.respond_with(failed_status_response) + @gateway.refund(@amount, '1234567') + end.respond_with(failed_refund_response) assert_failure response - assert_equal( - "A1001WfAHR_QP8daLnG6fQLcadzuCBJbpIp-zsUL6FkQgUyY2MXwVA0etYvflPe_rDBiuOMV-BfTSGDKt7uU3E2bLUhsD1rrXwGT9BTPcOOH_Vh9jHDSn2inqk8udwQIRcxuc", - response.authorization - ) + assert_equal "System Error", response.message + assert_nil response.authorization assert response.test? - assert_equal "Do Not Honour", response.message - assert_equal "1", response.params["invoicereference"] end - def test_store_calls_sub_methods - options = { - :order_id => 1, - :billing_address => { - :name => "Jim Awesome Smith", - } - } - @gateway.expects(:purchase).with(0, @credit_card, options.merge(:request_method => "CreateTokenCustomer")) + def test_successful_stored_card_purchase + response = stub_comms do + @gateway.purchase(100, 'the_customer_token', transaction_type: 'MOTO') + end.check_request do |endpoint, data, headers| + assert_match '"Method":"TokenPayment"', data + assert_match '"TransactionType":"MOTO"', data + end.respond_with(successful_store_purchase_response) - @gateway.store(@credit_card, options) + assert_success response + assert_equal "Transaction Approved Successful", response.message + assert_equal 10440234, response.authorization + assert response.test? end def test_verification_results response = stub_comms do - @gateway.status("thetransauth") - end.respond_with(successful_status_response(:verification_status => "Valid")) + @gateway.purchase(100, @credit_card) + end.respond_with(successful_purchase_response(:verification_status => "Valid")) assert_success response assert_equal "M", response.cvv_result["code"] assert_equal "M", response.avs_result["code"] response = stub_comms do - @gateway.status("thetransauth") - end.respond_with(successful_status_response(:verification_status => "Invalid")) + @gateway.purchase(100, @credit_card) + end.respond_with(successful_purchase_response(:verification_status => "Invalid")) assert_success response assert_equal "N", response.cvv_result["code"] assert_equal "N", response.avs_result["code"] response = stub_comms do - @gateway.status("thetransauth") - end.respond_with(successful_status_response(:verification_status => "Unchecked")) + @gateway.purchase(100, @credit_card) + end.respond_with(successful_purchase_response(:verification_status => "Unchecked")) assert_success response assert_equal "P", response.cvv_result["code"] @@ -262,148 +251,452 @@ def test_verification_results private - def successful_setup_purchase_response + def successful_purchase_response(options = {}) + verification_status = options[:verification_status] || 0 + verification_status = %Q{"#{verification_status}"} if verification_status.is_a? String %( - - 60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT - - - - - <FirstName /> - <LastName /> - <CompanyName /> - <JobDescription /> - <Street1 /> - <Street2 /> - <City /> - <State /> - <PostalCode /> - <Country /> - <Email /> - <Phone /> - <Mobile /> - <Comments /> - <Fax /> - <Url /> - <CardNumber /> - <CardStartMonth /> - <CardStartYear /> - <CardIssueNumber /> - <CardName /> - <CardExpiryMonth /> - <CardExpiryYear /> - <IsActive>false</IsActive> - </Customer> - <Payment> - <TotalAmount>100</TotalAmount> - <InvoiceDescription>Store Purchase</InvoiceDescription> - <InvoiceReference>1</InvoiceReference> - <CurrencyCode>AUD</CurrencyCode> - </Payment> - <FormActionURL>https://secure-au.sandbox.ewaypayments.com/Process</FormActionURL> - </CreateAccessCodeResponse> + { + "AuthorisationCode": "763051", + "ResponseCode": "00", + "ResponseMessage": "A2000", + "TransactionID": 10440187, + "TransactionStatus": true, + "TransactionType": "Purchase", + "BeagleScore": 0, + "Verification": { + "CVN": #{verification_status}, + "Address": #{verification_status}, + "Email": #{verification_status}, + "Mobile": #{verification_status}, + "Phone": #{verification_status} + }, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "14", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": "", + "Title": "Mr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": "", + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": "", + "Phone": "(555)555-5555", + "Mobile": "", + "Comments": "", + "Fax": "(555)555-6666", + "Url": "" + }, + "Payment": { + "TotalAmount": 100, + "InvoiceNumber": "", + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": null + } ) end - def failed_setup_purchase_response + def failed_purchase_response %( - <CreateAccessCodeResponse> - <Errors>V6047</Errors> - <Customer> - <TokenCustomerID p3:nil="true" xmlns:p3="http://www.w3.org/2001/XMLSchema-instance" /> - <IsActive>false</IsActive> - </Customer> - <Payment> - <TotalAmount>100</TotalAmount> - <CurrencyCode>AUD</CurrencyCode> - </Payment> - </CreateAccessCodeResponse> + { + "AuthorisationCode": null, + "ResponseCode": null, + "ResponseMessage": null, + "TransactionID": null, + "TransactionStatus": null, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": null, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "2014", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": null, + "Title": "Mr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": null, + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": null, + "Phone": "(555)555-5555", + "Mobile": null, + "Comments": null, + "Fax": "(555)555-6666", + "Url": null + }, + "Payment": { + "TotalAmount": -100, + "InvoiceNumber": null, + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": "V6011" + } ) end - def successful_status_response(options={}) - verification_status = (options[:verification_status] || "Unchecked") + def successful_store_response %( - <GetAccessCodeResultResponse> - <AccessCode>60CF3sfH7-yvAsUAHrdIiGppPrQW7v7DMAXxKkaKwyrIUoqvUvK44XbK9G9HNbngIz_iwQpfmPT_duMgh2G0pXCX8i4z1RAmMHpUQwa6VrghV3Bx9rh_tojjym7LC_fE-eR97</AccessCode> - <AuthorisationCode>957199</AuthorisationCode> - <ResponseCode>00</ResponseCode> - <ResponseMessage>A2000</ResponseMessage> - <InvoiceNumber /> - <InvoiceReference>orderid1</InvoiceReference> - <TotalAmount>100</TotalAmount> - <TransactionID>9942726</TransactionID> - <TransactionStatus>true</TransactionStatus> - <TokenCustomerID p2:nil=\"true\" xmlns:p2=\"http://www.w3.org/2001/XMLSchema-instance\" /> - <BeagleScore>0</BeagleScore> - <Options /> - <Verification> - <CVN>#{verification_status}</CVN> - <Address>#{verification_status}</Address> - <Email>#{verification_status}</Email> - <Mobile>#{verification_status}</Mobile> - <Phone>#{verification_status}</Phone> - </Verification> - </GetAccessCodeResultResponse> + { + "AuthorisationCode": null, + "ResponseCode": "00", + "ResponseMessage": "A2000", + "TransactionID": null, + "TransactionStatus": false, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": { + "CVN": 0, + "Address": 0, + "Email": 0, + "Mobile": 0, + "Phone": 0 + }, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "14", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": 917224224772, + "Reference": "", + "Title": "Dr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": "", + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": "", + "Phone": "(555)555-5555", + "Mobile": "", + "Comments": "", + "Fax": "(555)555-6666", + "Url": "" + }, + "Payment": { + "TotalAmount": 0, + "InvoiceNumber": "", + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": null + } ) end - def failed_status_response + def failed_store_response %( - <GetAccessCodeResultResponse> - <AccessCode>A1001WfAHR_QP8daLnG6fQLcadzuCBJbpIp-zsUL6FkQgUyY2MXwVA0etYvflPe_rDBiuOMV-BfTSGDKt7uU3E2bLUhsD1rrXwGT9BTPcOOH_Vh9jHDSn2inqk8udwQIRcxuc</AccessCode> - <AuthorisationCode /> - <ResponseCode>05</ResponseCode> - <ResponseMessage>D4405</ResponseMessage> - <InvoiceNumber /> - <InvoiceReference>1</InvoiceReference> - <TotalAmount>105</TotalAmount> - <TransactionID>9942743</TransactionID> - <TransactionStatus>false</TransactionStatus> - <TokenCustomerID p2:nil=\"true\" xmlns:p2=\"http://www.w3.org/2001/XMLSchema-instance\" /> - <BeagleScore>0</BeagleScore> - <Options /> - <Verification> - <CVN>Unchecked</CVN> - <Address>Unchecked</Address> - <Email>Unchecked</Email> - <Mobile>Unchecked</Mobile> - <Phone>Unchecked</Phone> - </Verification> - </GetAccessCodeResultResponse> + { + "AuthorisationCode": null, + "ResponseCode": null, + "ResponseMessage": null, + "TransactionID": null, + "TransactionStatus": null, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": null, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "2014", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": null, + "Title": "Mr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": null, + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": null, + "Email": null, + "Phone": "(555)555-5555", + "Mobile": null, + "Comments": null, + "Fax": "(555)555-6666", + "Url": null + }, + "Payment": { + "TotalAmount": 0, + "InvoiceNumber": null, + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": "V6044" + } ) end - class MockResponse - attr_reader :code, :body - def initialize(code, body, headers={}) - @code, @body, @headers = code, body, headers - end + def successful_update_response + %( + { + "AuthorisationCode": null, + "ResponseCode": "00", + "ResponseMessage": "A2000", + "TransactionID": null, + "TransactionStatus": false, + "TransactionType": "Purchase", + "BeagleScore": null, + "Verification": { + "CVN": 0, + "Address": 0, + "Email": 0, + "Mobile": 0, + "Phone": 0 + }, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "14", + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": 916161208398, + "Reference": "", + "Title": "Dr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": "", + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": "", + "Phone": "(555)555-5555", + "Mobile": "", + "Comments": "", + "Fax": "(555)555-6666", + "Url": "" + }, + "Payment": { + "TotalAmount": 0, + "InvoiceNumber": "", + "InvoiceDescription": "Store Purchase", + "InvoiceReference": "1", + "CurrencyCode": "AUD" + }, + "Errors": null + } + ) + end - def [](header) - @headers[header] - end + def successful_refund_response + %( + { + "AuthorisationCode": "457313", + "ResponseCode": null, + "ResponseMessage": "A2000", + "TransactionID": 10488258, + "TransactionStatus": true, + "Verification": null, + "Customer": { + "CardDetails": { + "Number": null, + "Name": null, + "ExpiryMonth": null, + "ExpiryYear": null, + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": null, + "Title": null, + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": null, + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": null, + "Phone": "(555)555-5555", + "Mobile": null, + "Comments": null, + "Fax": "(555)555-6666", + "Url": null + }, + "Refund": { + "TransactionID": null, + "TotalAmount": 0, + "InvoiceNumber": null, + "InvoiceDescription": null, + "InvoiceReference": null, + "CurrencyCode": null + }, + "Errors": null + } + ) end - def successful_run_purchase_response - MockResponse.new( - 302, - %( - <html><head><title>Object moved -

Object moved to here.

- - ), - "Location" => "http://example.com/?AccessCode=60CF3xWrFUQeDCEsJcA8zNHaspAT3CKpe-0DiqWjTYA3RZw1xhw2LU-BFCNYbr7eJt8KFaxCxmzYh9WDAYX8yIuYexTq0tC8i2kOt0dm0EV-mjxYEQ2YeHP2dazkSc7j58OiT" + def failed_refund_response + %( + { + "AuthorisationCode": null, + "ResponseCode": null, + "ResponseMessage": null, + "TransactionID": null, + "TransactionStatus": false, + "Verification": null, + "Customer": { + "CardDetails": { + "Number": null, + "Name": null, + "ExpiryMonth": null, + "ExpiryYear": null, + "StartMonth": null, + "StartYear": null, + "IssueNumber": null + }, + "TokenCustomerID": null, + "Reference": null, + "Title": null, + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": null, + "Street1": "1234 My Street", + "Street2": "Apt 1", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": null, + "Phone": "(555)555-5555", + "Mobile": null, + "Comments": null, + "Fax": "(555)555-6666", + "Url": null + }, + "Refund": { + "TransactionID": null, + "TotalAmount": 0, + "InvoiceNumber": null, + "InvoiceDescription": null, + "InvoiceReference": null, + "CurrencyCode": null + }, + "Errors": "S5000" + } ) end - def failed_run_purchase_response - MockResponse.new( - 200, - %( - {"Message":"Not Found","Errors":null} - ) + def successful_store_purchase_response + %( + { + "AuthorisationCode": "232671", + "ResponseCode": "00", + "ResponseMessage": "A2000", + "TransactionID": 10440234, + "TransactionStatus": true, + "TransactionType": "MOTO", + "BeagleScore": 0, + "Verification": { + "CVN": 0, + "Address": 0, + "Email": 0, + "Mobile": 0, + "Phone": 0 + }, + "Customer": { + "CardDetails": { + "Number": "444433XXXXXX1111", + "Name": "Longbob Longsen", + "ExpiryMonth": "09", + "ExpiryYear": "14", + "StartMonth": "", + "StartYear": "", + "IssueNumber": "" + }, + "TokenCustomerID": 912056757740, + "Reference": "", + "Title": "Mr.", + "FirstName": "Jim", + "LastName": "Smith", + "CompanyName": "Widgets Inc", + "JobDescription": "", + "Street1": "1234 My Street, Apt 1", + "Street2": "", + "City": "Ottawa", + "State": "ON", + "PostalCode": "K1C2N6", + "Country": "ca", + "Email": "", + "Phone": "(555)555-5555", + "Mobile": "", + "Comments": "", + "Fax": "(555)555-6666", + "Url": "" + }, + "Payment": { + "TotalAmount": 100, + "InvoiceNumber": "", + "InvoiceDescription": "", + "InvoiceReference": "", + "CurrencyCode": "AUD" + }, + "Errors": null + } ) end + end