activemerchant 1.90.0 → 1.91.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +38 -0
  3. data/README.md +3 -2
  4. data/lib/active_merchant/billing/credit_card_methods.rb +1 -1
  5. data/lib/active_merchant/billing/gateways/adyen.rb +26 -21
  6. data/lib/active_merchant/billing/gateways/bambora_apac.rb +186 -0
  7. data/lib/active_merchant/billing/gateways/blue_snap.rb +189 -34
  8. data/lib/active_merchant/billing/gateways/braintree_blue.rb +11 -9
  9. data/lib/active_merchant/billing/gateways/card_connect.rb +3 -0
  10. data/lib/active_merchant/billing/gateways/cecabank.rb +13 -2
  11. data/lib/active_merchant/billing/gateways/fat_zebra.rb +20 -7
  12. data/lib/active_merchant/billing/gateways/ipp.rb +1 -0
  13. data/lib/active_merchant/billing/gateways/moneris.rb +3 -4
  14. data/lib/active_merchant/billing/gateways/netbanx.rb +4 -0
  15. data/lib/active_merchant/billing/gateways/nmi.rb +6 -4
  16. data/lib/active_merchant/billing/gateways/openpay.rb +1 -1
  17. data/lib/active_merchant/billing/gateways/orbital.rb +8 -1
  18. data/lib/active_merchant/billing/gateways/payment_express.rb +4 -1
  19. data/lib/active_merchant/billing/gateways/paymentez.rb +4 -9
  20. data/lib/active_merchant/billing/gateways/pin.rb +19 -6
  21. data/lib/active_merchant/billing/gateways/pro_pay.rb +1 -1
  22. data/lib/active_merchant/billing/gateways/qvalent.rb +11 -0
  23. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +3 -3
  24. data/lib/active_merchant/billing/gateways/trust_commerce.rb +1 -0
  25. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +11 -3
  26. data/lib/active_merchant/billing/gateways/worldpay.rb +51 -2
  27. data/lib/active_merchant/country.rb +1 -1
  28. data/lib/active_merchant/version.rb +1 -1
  29. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0966806ab488c5e7ce61def32caf6d697a360f41e8044398309b58e3318396b0'
4
- data.tar.gz: 9793482a76e42cfab91c5918153f629c66fdfccbaeff49489eb06a9d97d892bb
3
+ metadata.gz: c313ba80bb5c2c04320b78d8b7248c86cb569869f9b9b25b450de62bec97dfde
4
+ data.tar.gz: 8fb0d7c5d581d5407cb68d7a545e8c8a52d377a232cb919804e9218fd4363d48
5
5
  SHA512:
6
- metadata.gz: 7a852cfc08faedf04bac009e0facd471e8387882b2e340ba9428bb71c67b8480cedd626ca6a663262f0269d7c42923caec65c8e0e23ad6980346763a4739a234
7
- data.tar.gz: cc5a90ebd00682fda94ab7898529570ca0fce25f23cb1d1049b2055feedbcf1fe172ec0fed2712f9a1b04ee4c18747297f389079fae0ca281f3110aa68bcad0a
6
+ metadata.gz: d583c8da5c326c3c3e12589f4fa37ed10243d3be91098436e085bf1ccba255a6ae8e27cbe657d022d5740bbb03b0097803ae10ec8c85c327b436f7c3e33fccb4
7
+ data.tar.gz: cac4ea1716084ca36cd0e2a66f3940e34728c0e4731c85b5c569bf7b0aa0e56097c2b71b56e3c0fef5c1a004e8a014abdc1ed448e0ab22201c3aaca80c39cec1
data/CHANGELOG CHANGED
@@ -2,6 +2,43 @@
2
2
 
3
3
  == HEAD
4
4
 
5
+ == Version 1.91.0 (February 22, 2019)
6
+ * WorldPay: Pull CVC and AVS Result from Response [nfarve] #3106
7
+ * Worldpay: Add AVS and CVC Mapping [nfarve] #3107
8
+ * Paymentez: Fixes extra_params field [molbrown] #3108
9
+ * Improved support for account_type using Check class's account_type instead [lancecarlson] #3097
10
+ * USA Epay: Allow quantity to be passed and check custom fields [lancecarlson] #3090
11
+ * Fix usaepay transaction invoice [lancecarlson] #3093
12
+ * Adyen: Handles blank state address field [molbrown] #3113
13
+ * Braintree: Send all country fields [curiousepic] #3112
14
+ * Braintree: Account for empty string countries [curiousepic] #3115
15
+ * Orbital: Support for stored credentials framework [jknipp] #3117
16
+ * Openpay: Fix for marking successful transaction(s) as failed [jknipp] #3121
17
+ * Braintree: Adds support for transaction_source [molbrown] #3120
18
+ * Moneris: Remove redundant card on file guard clause [davidsantoso] #3123
19
+ * Switch order of Romania country codes [molbrown] #3125
20
+ * Blue Snap: Supports Level 2/3 data [molbrown] #3126
21
+ * Blue Snap: Support personal_identification_number [jknipp] #3128
22
+ * ProPay: Send 9 digit zip code without dash [molbrown] #3129
23
+ * Adyen: Extend AVS code mappings [therufs] #3119
24
+ * NMI: Add customer id to authorization on store [curiousepic] #3130
25
+ * Trans First Express: Don't pass blank name field [curiousepic] #3133
26
+ * TrustCommerce: Send full name on ACH transactions [jknipp] #3132
27
+ * Qvalent: Map CVV Result to responses [curiousepic] #3135
28
+ * Card Connect: Handle 401s as responses [curiousepic] #3137
29
+ * Worldpay: Introduce normalized stored credential options [davidsantoso] #3134
30
+ * Worldpay: Adjust use of normalized stored credentials hash [davidsantoso] #3139
31
+ * Adyen: Enable Dynamic 3DS [molbrown] #3138
32
+ * Fat Zebra: Support voids [curiousepic] #3142
33
+ * Blue Snap: Support ACH/ECP payments [jknipp] #3143
34
+ * Blue Snap: Fix Card-on-File field typo [jknipp] #3143
35
+ * Add Bambora gateway [InfraRuby] #3145
36
+ * Bambora Asia-Pacific: Updates Gateway [molbrown] #3145
37
+ * PaymentExpress: Support ClientInfo field [jknipp] #3131
38
+ * Pin Payments: Concatenate card and customer tokens when storing card [therufs] #3144
39
+ * Update Discover regex to allow card numbers longer than 16 digits [prashcr] #3146
40
+ * Merrco partial refunds fix [payfirma1] #3141
41
+
5
42
  == Version 1.90.0 (January 8, 2019)
6
43
  * Mercado Pago: Support "gateway" processing mode [curiousepic] #3087
7
44
  * Braintree: Update gem to latest version [curiousepic] #3091
@@ -12,6 +49,7 @@
12
49
  * Braintree Blue: Refactor line_items field [curiousepic] #3100
13
50
  * TrustCommerce: Use `application_id` [nfarve] #3103
14
51
  * Stripe: Add 3DS Support [nfarve] #3086
52
+ * Cecabank: Append error text to message [therufs] #3127
15
53
 
16
54
  == Version 1.89.0 (December 17, 2018)
17
55
  * Worldpay: handle Visa and MasterCard payouts differently [bpollack] #3068
data/README.md CHANGED
@@ -86,12 +86,13 @@ For more in-depth documentation and tutorials, see [GettingStarted.md](GettingSt
86
86
 
87
87
  ## Supported Payment Gateways
88
88
 
89
- The [ActiveMerchant Wiki](http://github.com/activemerchant/active_merchant/wikis) contains a [table of features supported by each gateway](http://github.com/activemerchant/active_merchant/wikis/gatewayfeaturematrix).
89
+ The [ActiveMerchant Wiki](https://github.com/activemerchant/active_merchant/wikis) contains a [table of features supported by each gateway](https://github.com/activemerchant/active_merchant/wiki/Gateway-Feature-Matrix).
90
90
 
91
91
  * [Authorize.Net CIM](http://www.authorize.net/) - US
92
92
  * [Authorize.Net](http://www.authorize.net/) - AD, AT, AU, BE, BG, CA, CH, CY, CZ, DE, DK, ES, FI, FR, GB, GB, GI, GR, HU, IE, IT, LI, LU, MC, MT, NL, NO, PL, PT, RO, SE, SI, SK, SM, TR, US, VA
93
93
  * [Axcess MS](http://www.axcessms.com/) - AD, AT, BE, BG, BR, CA, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GI, GR, HR, HU, IE, IL, IM, IS, IT, LI, LT, LU, LV, MC, MT, MX, NL, NO, PL, PT, RO, RU, SE, SI, SK, TR, US, VA
94
94
  * [Balanced](https://www.balancedpayments.com/) - US
95
+ * [Bambora Asia-Pacific](http://www.bambora.com/) - AU, NZ
95
96
  * [Bank Frick](http://www.bankfrickacquiring.com/) - LI, US
96
97
  * [Banwire](http://www.banwire.com/) - MX
97
98
  * [Barclays ePDQ Extra Plus](http://www.barclaycard.co.uk/business/accepting-payments/epdq-ecomm/) - GB
@@ -192,7 +193,7 @@ The [ActiveMerchant Wiki](http://github.com/activemerchant/active_merchant/wikis
192
193
  * [Paystation](http://paystation.co.nz) - NZ
193
194
  * [Pay Way](http://www.payway.com.au) - AU
194
195
  * [PayU India](https://www.payu.in/) - IN
195
- * [Pin Payments](http://www.pin.net.au/) - AU
196
+ * [Pin Payments](http://www.pinpayments.com/) - AU
196
197
  * [Plug'n Pay](http://www.plugnpay.com/) - US
197
198
  * [Psigate](http://www.psigate.com/) - CA
198
199
  * [PSL Payment Solutions](http://www.paymentsolutionsltd.com/) - GB
@@ -5,7 +5,7 @@ module ActiveMerchant #:nodoc:
5
5
  CARD_COMPANY_DETECTORS = {
6
6
  'visa' => ->(num) { num =~ /^4\d{12}(\d{3})?(\d{3})?$/ },
7
7
  'master' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), MASTERCARD_RANGES) },
8
- 'discover' => ->(num) { num =~ /^(6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14})$/ },
8
+ 'discover' => ->(num) { num =~ /^(6011|65\d{2}|64[4-9]\d)\d{12,15}|(62\d{14,17})$/ },
9
9
  'american_express' => ->(num) { num =~ /^3[47]\d{13}$/ },
10
10
  'diners_club' => ->(num) { num =~ /^3(0[0-5]|[68]\d)\d{11}$/ },
11
11
  'jcb' => ->(num) { num =~ /^35(28|29|[3-8]\d)\d{12}$/ },
@@ -33,7 +33,7 @@ module ActiveMerchant #:nodoc:
33
33
  end
34
34
 
35
35
  def purchase(money, payment, options={})
36
- if options[:execute_threed]
36
+ if options[:execute_threed] || options[:threed_dynamic]
37
37
  authorize(money, payment, options)
38
38
  else
39
39
  MultiResponse.run do |r|
@@ -52,7 +52,7 @@ module ActiveMerchant #:nodoc:
52
52
  add_shopper_interaction(post, payment, options)
53
53
  add_address(post, options)
54
54
  add_installments(post, options) if options[:installments]
55
- add_3ds(post, options) if options[:execute_threed]
55
+ add_3ds(post, options)
56
56
  commit('authorise', post)
57
57
  end
58
58
 
@@ -110,24 +110,28 @@ module ActiveMerchant #:nodoc:
110
110
 
111
111
  AVS_MAPPING = {
112
112
  '0' => 'R', # Unknown
113
- '1' => 'A', # Address matches, postal code doesn't
114
- '2' => 'N', # Neither postal code nor address match
115
- '3' => 'R', # AVS unavailable
116
- '4' => 'E', # AVS not supported for this card type
117
- '5' => 'U', # No AVS data provided
118
- '6' => 'Z', # Postal code matches, address doesn't match
119
- '7' => 'D', # Both postal code and address match
120
- '8' => 'U', # Address not checked, postal code unknown
121
- '9' => 'B', # Address matches, postal code unknown
122
- '10' => 'N', # Address doesn't match, postal code unknown
123
- '11' => 'U', # Postal code not checked, address unknown
124
- '12' => 'B', # Address matches, postal code not checked
125
- '13' => 'U', # Address doesn't match, postal code not checked
126
- '14' => 'P', # Postal code matches, address unknown
127
- '15' => 'P', # Postal code matches, address not checked
128
- '16' => 'N', # Postal code doesn't match, address unknown
113
+ '1' => 'A', # Address matches, postal code doesn't
114
+ '2' => 'N', # Neither postal code nor address match
115
+ '3' => 'R', # AVS unavailable
116
+ '4' => 'E', # AVS not supported for this card type
117
+ '5' => 'U', # No AVS data provided
118
+ '6' => 'Z', # Postal code matches, address doesn't match
119
+ '7' => 'D', # Both postal code and address match
120
+ '8' => 'U', # Address not checked, postal code unknown
121
+ '9' => 'B', # Address matches, postal code unknown
122
+ '10' => 'N', # Address doesn't match, postal code unknown
123
+ '11' => 'U', # Postal code not checked, address unknown
124
+ '12' => 'B', # Address matches, postal code not checked
125
+ '13' => 'U', # Address doesn't match, postal code not checked
126
+ '14' => 'P', # Postal code matches, address unknown
127
+ '15' => 'P', # Postal code matches, address not checked
128
+ '16' => 'N', # Postal code doesn't match, address unknown
129
129
  '17' => 'U', # Postal code doesn't match, address not checked
130
- '18' => 'I' # Neither postal code nor address were checked
130
+ '18' => 'I', # Neither postal code nor address were checked
131
+ '20' => 'V', # Name, address and postal code matches.
132
+ '23' => 'F', # Postal code matches, name doesn't match.
133
+ '24' => 'H', # Both postal code and address matches, name doesn't match.
134
+ '25' => 'T' # Address matches, name doesn't match.
131
135
  }
132
136
 
133
137
  CVC_MAPPING = {
@@ -187,7 +191,7 @@ module ActiveMerchant #:nodoc:
187
191
  post[:card][:billingAddress][:houseNumberOrName] = address[:address2] || 'N/A'
188
192
  post[:card][:billingAddress][:postalCode] = address[:zip] if address[:zip]
189
193
  post[:card][:billingAddress][:city] = address[:city] || 'N/A'
190
- post[:card][:billingAddress][:stateOrProvince] = address[:state] if address[:state]
194
+ post[:card][:billingAddress][:stateOrProvince] = address[:state] || 'N/A'
191
195
  post[:card][:billingAddress][:country] = address[:country] if address[:country]
192
196
  end
193
197
  end
@@ -272,8 +276,9 @@ module ActiveMerchant #:nodoc:
272
276
  end
273
277
 
274
278
  def add_3ds(post, options)
275
- post[:additionalData] = { executeThreeD: 'true' }
279
+ return unless options[:execute_threed] || options[:threed_dynamic]
276
280
  post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] }
281
+ post[:additionalData] = { executeThreeD: 'true' } if options[:execute_threed]
277
282
  end
278
283
 
279
284
  def parse(body)
@@ -0,0 +1,186 @@
1
+ require 'nokogiri'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class BamboraApacGateway < Gateway
6
+ self.live_url = 'https://www.bambora.co.nz/interface/api/dts.asmx'
7
+ self.test_url = 'https://demo.bambora.co.nz/interface/api/dts.asmx'
8
+
9
+ self.supported_countries = ['AU', 'NZ']
10
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
11
+
12
+ self.homepage_url = 'http://www.bambora.com/'
13
+ self.display_name = 'Bambora Asia-Pacific'
14
+
15
+ self.money_format = :cents
16
+
17
+ STANDARD_ERROR_CODE_MAPPING = {
18
+ '05' => STANDARD_ERROR_CODE[:card_declined],
19
+ '06' => STANDARD_ERROR_CODE[:processing_error],
20
+ '14' => STANDARD_ERROR_CODE[:invalid_number],
21
+ '54' => STANDARD_ERROR_CODE[:expired_card],
22
+ }
23
+
24
+ def initialize(options={})
25
+ requires!(options, :username, :password)
26
+ super
27
+ end
28
+
29
+ def purchase(money, payment, options={})
30
+ commit('SubmitSinglePayment') do |xml|
31
+ xml.Transaction do
32
+ xml.CustRef options[:order_id]
33
+ add_amount(xml, money)
34
+ xml.TrnType '1'
35
+ add_credit_card(xml, payment)
36
+ add_credentials(xml, options)
37
+ xml.TrnSource options[:ip]
38
+ end
39
+ end
40
+ end
41
+
42
+ def authorize(money, payment, options={})
43
+ commit('SubmitSinglePayment') do |xml|
44
+ xml.Transaction do
45
+ xml.CustRef options[:order_id]
46
+ add_amount(xml, money)
47
+ xml.TrnType '2'
48
+ add_credit_card(xml, payment)
49
+ add_credentials(xml, options)
50
+ xml.TrnSource options[:ip]
51
+ end
52
+ end
53
+ end
54
+
55
+ def capture(money, authorization, options={})
56
+ commit('SubmitSingleCapture') do |xml|
57
+ xml.Capture do
58
+ xml.Receipt authorization
59
+ add_amount(xml, money)
60
+ add_credentials(xml, options)
61
+ end
62
+ end
63
+ end
64
+
65
+ def refund(money, authorization, options={})
66
+ commit('SubmitSingleRefund') do |xml|
67
+ xml.Refund do
68
+ xml.Receipt authorization
69
+ add_amount(xml, money)
70
+ add_credentials(xml, options)
71
+ end
72
+ end
73
+ end
74
+
75
+ def void(money, authorization, options={})
76
+ commit('SubmitSingleVoid') do |xml|
77
+ xml.Void do
78
+ xml.Receipt authorization
79
+ add_amount(xml, money)
80
+ add_credentials(xml, options)
81
+ end
82
+ end
83
+ end
84
+
85
+ def supports_scrubbing?
86
+ true
87
+ end
88
+
89
+ def scrub(transcript)
90
+ transcript.
91
+ gsub(%r((<CardNumber>)[^<]+(<))i, '\1[FILTERED]\2').
92
+ gsub(%r((<CVN>)[^<]+(<))i, '\1[FILTERED]\2').
93
+ gsub(%r((<Password>)[^<]+(<))i, '\1[FILTERED]\2')
94
+ end
95
+
96
+ private
97
+
98
+ def add_credentials(xml, options)
99
+ xml.AccountNumber options[:account_number] if options[:account_number]
100
+ xml.Security do
101
+ xml.UserName @options[:username]
102
+ xml.Password @options[:password]
103
+ end
104
+ end
105
+
106
+ def add_amount(xml, money)
107
+ xml.Amount amount(money)
108
+ end
109
+
110
+ def add_credit_card(xml, payment)
111
+ xml.CreditCard :Registered => 'False' do
112
+ xml.CardNumber payment.number
113
+ xml.ExpM format(payment.month, :two_digits)
114
+ xml.ExpY format(payment.year, :four_digits)
115
+ xml.CVN payment.verification_value
116
+ xml.CardHolderName payment.name
117
+ end
118
+ end
119
+
120
+ def parse(body)
121
+ element = Nokogiri::XML(body).root.first_element_child.first_element_child
122
+
123
+ response = {}
124
+ doc = Nokogiri::XML(element)
125
+ doc.root.elements.each do |e|
126
+ response[e.name.underscore.to_sym] = e.inner_text
127
+ end
128
+ response
129
+ end
130
+
131
+ def commit(action, &block)
132
+ headers = {
133
+ 'Content-Type' => 'text/xml; charset=utf-8',
134
+ 'SOAPAction' => "http://www.ippayments.com.au/interface/api/dts/#{action}",
135
+ }
136
+ response = parse(ssl_post(commit_url, new_submit_xml(action, &block), headers))
137
+
138
+ Response.new(
139
+ success_from(response),
140
+ message_from(response),
141
+ response,
142
+ authorization: authorization_from(response),
143
+ error_code: error_code_from(response),
144
+ test: test?
145
+ )
146
+ end
147
+
148
+ def new_submit_xml(action)
149
+ xml = Builder::XmlMarkup.new(indent: 2)
150
+ xml.instruct!
151
+ xml.soap :Envelope, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
152
+ xml.soap :Body do
153
+ xml.__send__(action, 'xmlns' => 'http://www.ippayments.com.au/interface/api/dts') do
154
+ xml.trnXML do
155
+ inner_xml = Builder::XmlMarkup.new(indent: 2)
156
+ yield(inner_xml)
157
+ xml.cdata!(inner_xml.target!)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ xml.target!
163
+ end
164
+
165
+ def commit_url
166
+ test? ? test_url : live_url
167
+ end
168
+
169
+ def success_from(response)
170
+ response[:response_code] == '0'
171
+ end
172
+
173
+ def error_code_from(response)
174
+ STANDARD_ERROR_CODE_MAPPING[response[:declined_code]]
175
+ end
176
+
177
+ def message_from(response)
178
+ response[:declined_message]
179
+ end
180
+
181
+ def authorization_from(response)
182
+ response[:receipt]
183
+ end
184
+ end
185
+ end
186
+ end
@@ -5,7 +5,7 @@ module ActiveMerchant
5
5
  class BlueSnapGateway < Gateway
6
6
  self.test_url = 'https://sandbox.bluesnap.com/services/2'
7
7
  self.live_url = 'https://ws.bluesnap.com/services/2'
8
- self.supported_countries = %w(US CA GB 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)
8
+ self.supported_countries = %w(US CA GB 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 AR BO BR BZ CL CO CR DO EC GF GP GT HN HT MF MQ MX NI PA PE PR PY SV UY VE)
9
9
 
10
10
  self.default_currency = 'USD'
11
11
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
@@ -59,14 +59,27 @@ module ActiveMerchant
59
59
  'line1: N, zip: N, name: N' => 'N',
60
60
  }
61
61
 
62
+ BANK_ACCOUNT_TYPE_MAPPING = {
63
+ 'personal_checking' => 'CONSUMER_CHECKING',
64
+ 'personal_savings' => 'CONSUMER_SAVINGS',
65
+ 'business_checking' => 'CORPORATE_CHECKING',
66
+ 'business_savings' => 'CORPORATE_SAVINGS'
67
+ }
68
+
62
69
  def initialize(options={})
63
70
  requires!(options, :api_username, :api_password)
64
71
  super
65
72
  end
66
73
 
67
74
  def purchase(money, payment_method, options={})
68
- commit(:purchase) do |doc|
69
- add_auth_purchase(doc, money, payment_method, options)
75
+ payment_method_details = PaymentMethodDetails.new(payment_method)
76
+
77
+ commit(:purchase, :post, payment_method_details) do |doc|
78
+ if payment_method_details.alt_transaction?
79
+ add_alt_transaction_purchase(doc, money, payment_method_details, options)
80
+ else
81
+ add_auth_purchase(doc, money, payment_method, options)
82
+ end
70
83
  end
71
84
  end
72
85
 
@@ -102,18 +115,33 @@ module ActiveMerchant
102
115
  authorize(0, payment_method, options)
103
116
  end
104
117
 
105
- def store(credit_card, options = {})
106
- commit(:store) do |doc|
107
- add_personal_info(doc, credit_card, options)
118
+ def store(payment_method, options = {})
119
+ payment_method_details = PaymentMethodDetails.new(payment_method)
120
+
121
+ commit(:store, :post, payment_method_details) do |doc|
122
+ add_personal_info(doc, payment_method, options)
123
+ add_echeck_company(doc, payment_method) if payment_method_details.check?
108
124
  doc.send('payment-sources') do
109
- doc.send('credit-card-info') do
110
- add_credit_card(doc, credit_card)
111
- end
125
+ payment_method_details.check? ? store_echeck(doc, payment_method) : store_credit_card(doc, payment_method)
112
126
  end
113
127
  add_order(doc, options)
114
128
  end
115
129
  end
116
130
 
131
+ def store_credit_card(doc, payment_method)
132
+ doc.send('credit-card-info') do
133
+ add_credit_card(doc, payment_method)
134
+ end
135
+ end
136
+
137
+ def store_echeck(doc, payment_method)
138
+ doc.send('ecp-info') do
139
+ doc.send('ecp') do
140
+ add_echeck(doc, payment_method)
141
+ end
142
+ end
143
+ end
144
+
117
145
  def verify_credentials
118
146
  begin
119
147
  ssl_get(url.to_s, headers)
@@ -132,7 +160,9 @@ module ActiveMerchant
132
160
  transcript.
133
161
  gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
134
162
  gsub(%r((<card-number>).+(</card-number>)), '\1[FILTERED]\2').
135
- gsub(%r((<security-code>).+(</security-code>)), '\1[FILTERED]\2')
163
+ gsub(%r((<security-code>).+(</security-code>)), '\1[FILTERED]\2').
164
+ gsub(%r((<(?:public-)?account-number>).+(</(?:public-)?account-number>)), '\1[FILTERED]\2').
165
+ gsub(%r((<(?:public-)?routing-number>).+(</(?:public-)?routing-number>)), '\1[FILTERED]\2')
136
166
  end
137
167
 
138
168
  private
@@ -140,11 +170,9 @@ module ActiveMerchant
140
170
  def add_auth_purchase(doc, money, payment_method, options)
141
171
  doc.send('recurring-transaction', options[:recurring] ? 'RECURRING' : 'ECOMMERCE')
142
172
  add_order(doc, options)
143
- doc.send('storeCard', options[:store_card] || false)
173
+ doc.send('store-card', options[:store_card] || false)
144
174
  add_amount(doc, money, options)
145
- doc.send('transaction-fraud-info') do
146
- doc.send('shopper-ip-address', options[:ip]) if options[:ip]
147
- end
175
+ add_fraud_info(doc, options)
148
176
 
149
177
  if payment_method.is_a?(String)
150
178
  doc.send('vaulted-shopper-id', payment_method)
@@ -161,9 +189,10 @@ module ActiveMerchant
161
189
  doc.currency(options[:currency] || currency(money))
162
190
  end
163
191
 
164
- def add_personal_info(doc, credit_card, options)
165
- doc.send('first-name', credit_card.first_name)
166
- doc.send('last-name', credit_card.last_name)
192
+ def add_personal_info(doc, payment_method, options)
193
+ doc.send('first-name', payment_method.first_name)
194
+ doc.send('last-name', payment_method.last_name)
195
+ doc.send('personal-identification-number', options[:personal_identification_number]) if options[:personal_identification_number]
167
196
  doc.email(options[:email]) if options[:email]
168
197
  add_address(doc, options)
169
198
  end
@@ -191,6 +220,7 @@ module ActiveMerchant
191
220
  doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
192
221
  doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
193
222
  add_description(doc, options[:description]) if options[:description]
223
+ add_level_3_data(doc, options)
194
224
  end
195
225
 
196
226
  def add_address(doc, options)
@@ -204,10 +234,92 @@ module ActiveMerchant
204
234
  doc.zip(address[:zip]) if address[:zip]
205
235
  end
206
236
 
237
+ def add_level_3_data(doc, options)
238
+ return unless options[:customer_reference_number]
239
+ doc.send('level-3-data') do
240
+ send_when_present(doc, :customer_reference_number, options)
241
+ send_when_present(doc, :sales_tax_amount, options)
242
+ send_when_present(doc, :freight_amount, options)
243
+ send_when_present(doc, :duty_amount, options)
244
+ send_when_present(doc, :destination_zip_code, options)
245
+ send_when_present(doc, :destination_country_code, options)
246
+ send_when_present(doc, :ship_from_zip_code, options)
247
+ send_when_present(doc, :discount_amount, options)
248
+ send_when_present(doc, :tax_amount, options)
249
+ send_when_present(doc, :tax_rate, options)
250
+ add_level_3_data_items(doc, options[:level_3_data_items]) if options[:level_3_data_items]
251
+ end
252
+ end
253
+
254
+ def send_when_present(doc, options_key, options, xml_element_name = nil)
255
+ return unless options[options_key]
256
+ xml_element_name ||= options_key.to_s
257
+
258
+ doc.send(xml_element_name.dasherize, options[options_key])
259
+ end
260
+
261
+ def add_level_3_data_items(doc, items)
262
+ items.each do |item|
263
+ doc.send('level-3-data-item') do
264
+ item.each do |key, value|
265
+ key = key.to_s.dasherize
266
+ doc.send(key, value)
267
+ end
268
+ end
269
+ end
270
+ end
271
+
207
272
  def add_authorization(doc, authorization)
208
273
  doc.send('transaction-id', authorization)
209
274
  end
210
275
 
276
+ def add_fraud_info(doc, options)
277
+ doc.send('transaction-fraud-info') do
278
+ doc.send('shopper-ip-address', options[:ip]) if options[:ip]
279
+ end
280
+ end
281
+
282
+ def add_alt_transaction_purchase(doc, money, payment_method_details, options)
283
+ doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
284
+ doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
285
+ add_amount(doc, money, options)
286
+
287
+ vaulted_shopper_id = payment_method_details.vaulted_shopper_id
288
+ doc.send('vaulted-shopper-id', vaulted_shopper_id) if vaulted_shopper_id
289
+
290
+ if payment_method_details.check?
291
+ add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?)
292
+ end
293
+
294
+ add_fraud_info(doc, options)
295
+ add_description(doc, options)
296
+ end
297
+
298
+ def add_echeck_transaction(doc, check, options, vaulted_shopper)
299
+ unless vaulted_shopper
300
+ doc.send('payer-info') do
301
+ add_personal_info(doc, check, options)
302
+ add_echeck_company(doc, check)
303
+ end
304
+ end
305
+
306
+ doc.send('ecp-transaction') do
307
+ add_echeck(doc, check) unless vaulted_shopper
308
+ end
309
+
310
+ doc.send('authorized-by-shopper', options[:authorized_by_shopper])
311
+ end
312
+
313
+ def add_echeck_company(doc, check)
314
+ doc.send('company-name', truncate(check.name, 50)) if check.account_holder_type = 'business'
315
+ end
316
+
317
+ def add_echeck(doc, check)
318
+ doc.send('account-number', check.account_number)
319
+ doc.send('routing-number', check.routing_number)
320
+ doc.send('account-type', BANK_ACCOUNT_TYPE_MAPPING["#{check.account_holder_type}_#{check.account_type}"])
321
+ end
322
+
211
323
  def parse(response)
212
324
  return bad_authentication_response if response.code.to_i == 401
213
325
  return forbidden_response(response.body) if response.code.to_i == 403
@@ -236,15 +348,15 @@ module ActiveMerchant
236
348
  end
237
349
  end
238
350
 
239
- def api_request(action, request, verb)
240
- ssl_request(verb, url(action), request, headers)
351
+ def api_request(action, request, verb, payment_method_details)
352
+ ssl_request(verb, url(action, payment_method_details), request, headers)
241
353
  rescue ResponseError => e
242
354
  e.response
243
355
  end
244
356
 
245
- def commit(action, verb = :post)
246
- request = build_xml_request(action) { |doc| yield(doc) }
247
- response = api_request(action, request, verb)
357
+ def commit(action, verb = :post, payment_method_details = PaymentMethodDetails.new())
358
+ request = build_xml_request(action, payment_method_details) { |doc| yield(doc) }
359
+ response = api_request(action, request, verb, payment_method_details)
248
360
  parsed = parse(response)
249
361
 
250
362
  succeeded = success_from(action, response)
@@ -252,7 +364,7 @@ module ActiveMerchant
252
364
  succeeded,
253
365
  message_from(succeeded, parsed),
254
366
  parsed,
255
- authorization: authorization_from(action, parsed),
367
+ authorization: authorization_from(action, parsed, payment_method_details),
256
368
  avs_result: avs_result(parsed),
257
369
  cvv_result: cvv_result(parsed),
258
370
  error_code: error_code_from(parsed),
@@ -260,9 +372,9 @@ module ActiveMerchant
260
372
  )
261
373
  end
262
374
 
263
- def url(action = nil)
375
+ def url(action = nil, payment_method_details = PaymentMethodDetails.new())
264
376
  base = test? ? test_url : live_url
265
- resource = action == :store ? 'vaulted-shoppers' : 'transactions'
377
+ resource = action == :store ? 'vaulted-shoppers' : payment_method_details.resource_url
266
378
  "#{base}/#{resource}"
267
379
  end
268
380
 
@@ -287,13 +399,15 @@ module ActiveMerchant
287
399
  parsed_response['description']
288
400
  end
289
401
 
290
- def authorization_from(action, parsed_response)
291
- action == :store ? vaulted_shopper_id(parsed_response) : parsed_response['transaction-id']
402
+ def authorization_from(action, parsed_response, payment_method_details)
403
+ action == :store ? vaulted_shopper_id(parsed_response, payment_method_details) : parsed_response['transaction-id']
292
404
  end
293
405
 
294
- def vaulted_shopper_id(parsed_response)
406
+ def vaulted_shopper_id(parsed_response, payment_method_details)
295
407
  return nil unless parsed_response['content-location-header']
296
- parsed_response['content-location-header'].split('/').last
408
+ vaulted_shopper_id = parsed_response['content-location-header'].split('/').last
409
+ vaulted_shopper_id += "|#{payment_method_details.payment_method_type}" if payment_method_details.alt_transaction?
410
+ vaulted_shopper_id
297
411
  end
298
412
 
299
413
  def error_code_from(parsed_response)
@@ -306,8 +420,8 @@ module ActiveMerchant
306
420
  }
307
421
  end
308
422
 
309
- def root_element(action)
310
- action == :store ? 'vaulted-shopper' : 'card-transaction'
423
+ def root_element(action, payment_method_details)
424
+ action == :store ? 'vaulted-shopper' : payment_method_details.root_element
311
425
  end
312
426
 
313
427
  def headers
@@ -317,10 +431,10 @@ module ActiveMerchant
317
431
  }
318
432
  end
319
433
 
320
- def build_xml_request(action)
434
+ def build_xml_request(action, payment_method_details)
321
435
  builder = Nokogiri::XML::Builder.new
322
- builder.__send__(root_element(action), root_attributes) do |doc|
323
- doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action]
436
+ builder.__send__(root_element(action, payment_method_details), root_attributes) do |doc|
437
+ doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction?
324
438
  yield(doc)
325
439
  end
326
440
  builder.doc.root.to_xml
@@ -343,5 +457,46 @@ module ActiveMerchant
343
457
  { 'description' => body }
344
458
  end
345
459
  end
460
+
461
+ class PaymentMethodDetails
462
+ attr_reader :payment_method, :vaulted_shopper_id, :payment_method_type
463
+
464
+ def initialize(payment_method = nil)
465
+ @payment_method = payment_method
466
+ @payment_method_type = nil
467
+ parse(payment_method)
468
+ end
469
+
470
+ def check?
471
+ @payment_method.is_a?(Check) || @payment_method_type == 'check'
472
+ end
473
+
474
+ def alt_transaction?
475
+ check?
476
+ end
477
+
478
+ def root_element
479
+ alt_transaction? ? 'alt-transaction' : 'card-transaction'
480
+ end
481
+
482
+ def resource_url
483
+ alt_transaction? ? 'alt-transactions' : 'transactions'
484
+ end
485
+
486
+ private
487
+
488
+ def parse(payment_method)
489
+ return unless payment_method
490
+
491
+ if payment_method.is_a?(String)
492
+ @vaulted_shopper_id, payment_method_type = payment_method.split('|')
493
+ @payment_method_type = payment_method_type if payment_method_type.present?
494
+ elsif payment_method.is_a?(Check)
495
+ @payment_method_type = payment_method.type
496
+ else
497
+ @payment_method_type = 'credit_card'
498
+ end
499
+ end
500
+ end
346
501
  end
347
502
  end