activemerchant 1.90.0 → 1.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +202 -0
  3. data/README.md +6 -2
  4. data/lib/active_merchant/billing/avs_result.rb +4 -5
  5. data/lib/active_merchant/billing/credit_card.rb +8 -0
  6. data/lib/active_merchant/billing/credit_card_methods.rb +81 -5
  7. data/lib/active_merchant/billing/gateway.rb +10 -0
  8. data/lib/active_merchant/billing/gateways/adyen.rb +206 -54
  9. data/lib/active_merchant/billing/gateways/bambora_apac.rb +226 -0
  10. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +43 -10
  11. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +3 -0
  12. data/lib/active_merchant/billing/gateways/beanstream.rb +11 -6
  13. data/lib/active_merchant/billing/gateways/blue_pay.rb +10 -8
  14. data/lib/active_merchant/billing/gateways/blue_snap.rb +211 -36
  15. data/lib/active_merchant/billing/gateways/bpoint.rb +4 -4
  16. data/lib/active_merchant/billing/gateways/braintree_blue.rb +79 -18
  17. data/lib/active_merchant/billing/gateways/card_connect.rb +6 -1
  18. data/lib/active_merchant/billing/gateways/cecabank.rb +20 -9
  19. data/lib/active_merchant/billing/gateways/checkout_v2.rb +98 -61
  20. data/lib/active_merchant/billing/gateways/credorax.rb +69 -4
  21. data/lib/active_merchant/billing/gateways/cyber_source.rb +85 -14
  22. data/lib/active_merchant/billing/gateways/d_local.rb +3 -3
  23. data/lib/active_merchant/billing/gateways/decidir.rb +245 -0
  24. data/lib/active_merchant/billing/gateways/elavon.rb +9 -0
  25. data/lib/active_merchant/billing/gateways/epay.rb +13 -2
  26. data/lib/active_merchant/billing/gateways/eway_rapid.rb +42 -12
  27. data/lib/active_merchant/billing/gateways/fat_zebra.rb +26 -7
  28. data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +42 -3
  29. data/lib/active_merchant/billing/gateways/global_collect.rb +3 -7
  30. data/lib/active_merchant/billing/gateways/hps.rb +46 -1
  31. data/lib/active_merchant/billing/gateways/ipp.rb +1 -0
  32. data/lib/active_merchant/billing/gateways/kushki.rb +1 -1
  33. data/lib/active_merchant/billing/gateways/litle.rb +61 -3
  34. data/lib/active_merchant/billing/gateways/mastercard.rb +30 -5
  35. data/lib/active_merchant/billing/gateways/mercado_pago.rb +1 -1
  36. data/lib/active_merchant/billing/gateways/migs.rb +8 -0
  37. data/lib/active_merchant/billing/gateways/monei.rb +31 -0
  38. data/lib/active_merchant/billing/gateways/moneris.rb +3 -4
  39. data/lib/active_merchant/billing/gateways/mundipagg.rb +37 -9
  40. data/lib/active_merchant/billing/gateways/nab_transact.rb +1 -1
  41. data/lib/active_merchant/billing/gateways/netbanx.rb +4 -0
  42. data/lib/active_merchant/billing/gateways/nmi.rb +45 -5
  43. data/lib/active_merchant/billing/gateways/openpay.rb +1 -1
  44. data/lib/active_merchant/billing/gateways/opp.rb +20 -1
  45. data/lib/active_merchant/billing/gateways/orbital.rb +92 -11
  46. data/lib/active_merchant/billing/gateways/payflow.rb +64 -14
  47. data/lib/active_merchant/billing/gateways/payment_express.rb +7 -0
  48. data/lib/active_merchant/billing/gateways/paymentez.rb +7 -11
  49. data/lib/active_merchant/billing/gateways/paymill.rb +5 -0
  50. data/lib/active_merchant/billing/gateways/paypal.rb +14 -1
  51. data/lib/active_merchant/billing/gateways/paypal_express.rb +3 -1
  52. data/lib/active_merchant/billing/gateways/payu_latam.rb +6 -2
  53. data/lib/active_merchant/billing/gateways/pin.rb +19 -6
  54. data/lib/active_merchant/billing/gateways/pro_pay.rb +1 -1
  55. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +7 -1
  56. data/lib/active_merchant/billing/gateways/qvalent.rb +54 -1
  57. data/lib/active_merchant/billing/gateways/realex.rb +42 -5
  58. data/lib/active_merchant/billing/gateways/redsys.rb +113 -30
  59. data/lib/active_merchant/billing/gateways/spreedly_core.rb +43 -29
  60. data/lib/active_merchant/billing/gateways/stripe.rb +66 -34
  61. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +271 -0
  62. data/lib/active_merchant/billing/gateways/tns.rb +10 -5
  63. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +5 -5
  64. data/lib/active_merchant/billing/gateways/trust_commerce.rb +46 -6
  65. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +19 -8
  66. data/lib/active_merchant/billing/gateways/visanet_peru.rb +22 -10
  67. data/lib/active_merchant/billing/gateways/worldpay.rb +237 -34
  68. data/lib/active_merchant/country.rb +2 -1
  69. data/lib/active_merchant/version.rb +1 -1
  70. metadata +20 -4
@@ -13,7 +13,7 @@ module ActiveMerchant #:nodoc:
13
13
  self.homepage_url = 'https://www.barclaycardsmartpay.com/'
14
14
  self.display_name = 'Barclaycard Smartpay'
15
15
 
16
- API_VERSION = 'v30'
16
+ API_VERSION = 'v40'
17
17
 
18
18
  def initialize(options = {})
19
19
  requires!(options, :company, :merchant, :password)
@@ -37,7 +37,9 @@ module ActiveMerchant #:nodoc:
37
37
  post[:card] = credit_card_hash(creditcard)
38
38
  post[:billingAddress] = billing_address_hash(options) if options[:billing_address]
39
39
  post[:deliveryAddress] = shipping_address_hash(options) if options[:shipping_address]
40
- add_3ds(post, options) if options[:execute_threed]
40
+ post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
41
+
42
+ add_3ds(post, options)
41
43
  commit('authorise', post)
42
44
  end
43
45
 
@@ -186,7 +188,7 @@ module ActiveMerchant #:nodoc:
186
188
  end
187
189
 
188
190
  def parse_avs_code(response)
189
- AVS_MAPPING[response['avsResult'][0..1].strip] if response['avsResult']
191
+ AVS_MAPPING[response['additionalData']['avsResult'][0..1].strip] if response.dig('additionalData', 'avsResult')
190
192
  end
191
193
 
192
194
  def flatten_hash(hash, prefix = nil)
@@ -210,12 +212,18 @@ module ActiveMerchant #:nodoc:
210
212
  end
211
213
 
212
214
  def parse(response)
213
- Hash[
214
- response.split('&').map do |x|
215
- key, val = x.split('=', 2)
216
- [key.split('.').last, CGI.unescape(val)]
215
+ parsed_response = {}
216
+ params = CGI.parse(response)
217
+ params.each do |key, value|
218
+ parsed_key = key.split('.', 2)
219
+ if parsed_key.size > 1
220
+ parsed_response[parsed_key[0]] ||= {}
221
+ parsed_response[parsed_key[0]][parsed_key[1]] = value[0]
222
+ else
223
+ parsed_response[parsed_key[0]] = value[0]
217
224
  end
218
- ]
225
+ end
226
+ parsed_response
219
227
  end
220
228
 
221
229
  def post_data(data)
@@ -343,8 +351,33 @@ module ActiveMerchant #:nodoc:
343
351
  end
344
352
 
345
353
  def add_3ds(post, options)
346
- post[:additionalData] = { executeThreeD: 'true' }
347
- post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] }
354
+ if three_ds_2_options = options[:three_ds_2]
355
+ device_channel = three_ds_2_options[:channel]
356
+ if device_channel == 'app'
357
+ post[:threeDS2RequestData] = { deviceChannel: device_channel }
358
+ else
359
+ add_browser_info(three_ds_2_options[:browser_info], post)
360
+ post[:threeDS2RequestData] = { deviceChannel: device_channel, notificationURL: three_ds_2_options[:notification_url] }
361
+ end
362
+ else
363
+ return unless options[:execute_threed] || options[:threed_dynamic]
364
+ post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] }
365
+ post[:additionalData] = { executeThreeD: 'true' } if options[:execute_threed]
366
+ end
367
+ end
368
+
369
+ def add_browser_info(browser_info, post)
370
+ return unless browser_info
371
+ post[:browserInfo] = {
372
+ acceptHeader: browser_info[:accept_header],
373
+ colorDepth: browser_info[:depth],
374
+ javaEnabled: browser_info[:java],
375
+ language: browser_info[:language],
376
+ screenHeight: browser_info[:height],
377
+ screenWidth: browser_info[:width],
378
+ timeZoneOffset: browser_info[:timezone],
379
+ userAgent: browser_info[:user_agent]
380
+ }
348
381
  end
349
382
  end
350
383
  end
@@ -315,6 +315,9 @@ module ActiveMerchant #:nodoc:
315
315
  post[:operationType] = options[:operationType] || options[:operation] || secure_profile_action(:new)
316
316
  post[:customerCode] = options[:billing_id] || options[:vault_id] || false
317
317
  post[:status] = options[:status]
318
+
319
+ billing_address = options[:billing_address] || options[:address]
320
+ post[:trnCardOwner] = billing_address ? billing_address[:name] : nil
318
321
  end
319
322
 
320
323
  def add_recurring_amount(post, money)
@@ -93,12 +93,15 @@ module ActiveMerchant #:nodoc:
93
93
 
94
94
  def void(authorization, options = {})
95
95
  reference, amount, type = split_auth(authorization)
96
-
97
- post = {}
98
- add_reference(post, reference)
99
- add_original_amount(post, amount)
100
- add_transaction_type(post, void_action(type))
101
- commit(post)
96
+ if type == TRANSACTIONS[:authorization]
97
+ capture(0, authorization, options)
98
+ else
99
+ post = {}
100
+ add_reference(post, reference)
101
+ add_original_amount(post, amount)
102
+ add_transaction_type(post, void_action(type))
103
+ commit(post)
104
+ end
102
105
  end
103
106
 
104
107
  def verify(source, options={})
@@ -153,6 +156,8 @@ module ActiveMerchant #:nodoc:
153
156
 
154
157
  # To match the other stored-value gateways, like TrustCommerce,
155
158
  # store and unstore need to be defined
159
+ #
160
+ # When passing a single-use token the :name option is required
156
161
  def store(payment_method, options = {})
157
162
  post = {}
158
163
  add_address(post, options)
@@ -24,7 +24,7 @@ module ActiveMerchant #:nodoc:
24
24
  'REBID' => :rebid,
25
25
  'TRANS_TYPE' => :trans_type,
26
26
  'PAYMENT_ACCOUNT_MASK' => :acct_mask,
27
- 'CARD_TYPE' => :card_type,
27
+ 'CARD_TYPE' => :card_type
28
28
  }
29
29
 
30
30
  REBILL_FIELD_MAP = {
@@ -41,6 +41,7 @@ module ActiveMerchant #:nodoc:
41
41
  'REB_AMOUNT' => :rebill_amount,
42
42
  'NEXT_AMOUNT' => :next_amount,
43
43
  'USUAL_DATE' => :undoc_usual_date, # Not found in the bp20rebadmin API doc.
44
+ 'CUST_TOKEN' => :cust_token
44
45
  }
45
46
 
46
47
  self.supported_countries = ['US', 'CA']
@@ -84,7 +85,7 @@ module ActiveMerchant #:nodoc:
84
85
  add_rebill(post, options) if options[:rebill]
85
86
  add_duplicate_override(post, options)
86
87
  post[:TRANS_TYPE] = 'AUTH'
87
- commit('AUTH_ONLY', money, post)
88
+ commit('AUTH_ONLY', money, post, options)
88
89
  end
89
90
 
90
91
  # Perform a purchase, which is essentially an authorization and capture in a single operation.
@@ -107,7 +108,7 @@ module ActiveMerchant #:nodoc:
107
108
  add_rebill(post, options) if options[:rebill]
108
109
  add_duplicate_override(post, options)
109
110
  post[:TRANS_TYPE] = 'SALE'
110
- commit('AUTH_CAPTURE', money, post)
111
+ commit('AUTH_CAPTURE', money, post, options)
111
112
  end
112
113
 
113
114
  # Captures the funds from an authorize transaction.
@@ -123,7 +124,7 @@ module ActiveMerchant #:nodoc:
123
124
  add_customer_data(post, options)
124
125
  post[:MASTER_ID] = identification
125
126
  post[:TRANS_TYPE] = 'CAPTURE'
126
- commit('PRIOR_AUTH_CAPTURE', money, post)
127
+ commit('PRIOR_AUTH_CAPTURE', money, post, options)
127
128
  end
128
129
 
129
130
  # Void a previous transaction
@@ -136,7 +137,7 @@ module ActiveMerchant #:nodoc:
136
137
  post = {}
137
138
  post[:MASTER_ID] = identification
138
139
  post[:TRANS_TYPE] = 'VOID'
139
- commit('VOID', nil, post)
140
+ commit('VOID', nil, post, options)
140
141
  end
141
142
 
142
143
  # Performs a credit.
@@ -169,7 +170,7 @@ module ActiveMerchant #:nodoc:
169
170
  add_invoice(post, options)
170
171
  add_address(post, options)
171
172
  add_customer_data(post, options)
172
- commit('CREDIT', money, post)
173
+ commit('CREDIT', money, post, options)
173
174
  end
174
175
 
175
176
  def credit(money, payment_object, options = {})
@@ -189,7 +190,7 @@ module ActiveMerchant #:nodoc:
189
190
  add_invoice(post, options)
190
191
  add_address(post, options)
191
192
  add_customer_data(post, options)
192
- commit('CREDIT', money, post)
193
+ commit('CREDIT', money, post, options)
193
194
  end
194
195
 
195
196
  # Create a new recurring payment.
@@ -313,10 +314,11 @@ module ActiveMerchant #:nodoc:
313
314
 
314
315
  private
315
316
 
316
- def commit(action, money, fields)
317
+ def commit(action, money, fields, options = {})
317
318
  fields[:AMOUNT] = amount(money) unless(fields[:TRANS_TYPE] == 'VOID' || action == 'rebill')
318
319
  fields[:MODE] = (test? ? 'TEST' : 'LIVE')
319
320
  fields[:ACCOUNT_ID] = @options[:login]
321
+ fields[:CUSTOMER_IP] = options[:ip] if options[:ip]
320
322
 
321
323
  if action == 'rebill'
322
324
  url = rebilling_url
@@ -5,10 +5,10 @@ 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
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
11
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro, :naranja, :cabal]
12
12
 
13
13
  self.homepage_url = 'https://home.bluesnap.com/'
14
14
  self.display_name = 'BlueSnap'
@@ -59,14 +59,29 @@ 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
+
69
+ STATE_CODE_COUNTRIES = %w(US CA)
70
+
62
71
  def initialize(options={})
63
72
  requires!(options, :api_username, :api_password)
64
73
  super
65
74
  end
66
75
 
67
76
  def purchase(money, payment_method, options={})
68
- commit(:purchase) do |doc|
69
- add_auth_purchase(doc, money, payment_method, options)
77
+ payment_method_details = PaymentMethodDetails.new(payment_method)
78
+
79
+ commit(:purchase, :post, payment_method_details) do |doc|
80
+ if payment_method_details.alt_transaction?
81
+ add_alt_transaction_purchase(doc, money, payment_method_details, options)
82
+ else
83
+ add_auth_purchase(doc, money, payment_method, options)
84
+ end
70
85
  end
71
86
  end
72
87
 
@@ -80,6 +95,7 @@ module ActiveMerchant
80
95
  commit(:capture, :put) do |doc|
81
96
  add_authorization(doc, authorization)
82
97
  add_order(doc, options)
98
+ add_amount(doc, money, options) if options[:include_capture_amount] == true
83
99
  end
84
100
  end
85
101
 
@@ -102,18 +118,33 @@ module ActiveMerchant
102
118
  authorize(0, payment_method, options)
103
119
  end
104
120
 
105
- def store(credit_card, options = {})
106
- commit(:store) do |doc|
107
- add_personal_info(doc, credit_card, options)
121
+ def store(payment_method, options = {})
122
+ payment_method_details = PaymentMethodDetails.new(payment_method)
123
+
124
+ commit(:store, :post, payment_method_details) do |doc|
125
+ add_personal_info(doc, payment_method, options)
126
+ add_echeck_company(doc, payment_method) if payment_method_details.check?
108
127
  doc.send('payment-sources') do
109
- doc.send('credit-card-info') do
110
- add_credit_card(doc, credit_card)
111
- end
128
+ payment_method_details.check? ? store_echeck(doc, payment_method) : store_credit_card(doc, payment_method)
112
129
  end
113
130
  add_order(doc, options)
114
131
  end
115
132
  end
116
133
 
134
+ def store_credit_card(doc, payment_method)
135
+ doc.send('credit-card-info') do
136
+ add_credit_card(doc, payment_method)
137
+ end
138
+ end
139
+
140
+ def store_echeck(doc, payment_method)
141
+ doc.send('ecp-info') do
142
+ doc.send('ecp') do
143
+ add_echeck(doc, payment_method)
144
+ end
145
+ end
146
+ end
147
+
117
148
  def verify_credentials
118
149
  begin
119
150
  ssl_get(url.to_s, headers)
@@ -132,7 +163,9 @@ module ActiveMerchant
132
163
  transcript.
133
164
  gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
134
165
  gsub(%r((<card-number>).+(</card-number>)), '\1[FILTERED]\2').
135
- gsub(%r((<security-code>).+(</security-code>)), '\1[FILTERED]\2')
166
+ gsub(%r((<security-code>).+(</security-code>)), '\1[FILTERED]\2').
167
+ gsub(%r((<(?:public-)?account-number>).+(</(?:public-)?account-number>)), '\1[FILTERED]\2').
168
+ gsub(%r((<(?:public-)?routing-number>).+(</(?:public-)?routing-number>)), '\1[FILTERED]\2')
136
169
  end
137
170
 
138
171
  private
@@ -140,11 +173,9 @@ module ActiveMerchant
140
173
  def add_auth_purchase(doc, money, payment_method, options)
141
174
  doc.send('recurring-transaction', options[:recurring] ? 'RECURRING' : 'ECOMMERCE')
142
175
  add_order(doc, options)
143
- doc.send('storeCard', options[:store_card] || false)
176
+ doc.send('store-card', options[:store_card] || false)
144
177
  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
178
+ add_fraud_info(doc, options)
148
179
 
149
180
  if payment_method.is_a?(String)
150
181
  doc.send('vaulted-shopper-id', payment_method)
@@ -161,9 +192,10 @@ module ActiveMerchant
161
192
  doc.currency(options[:currency] || currency(money))
162
193
  end
163
194
 
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)
195
+ def add_personal_info(doc, payment_method, options)
196
+ doc.send('first-name', payment_method.first_name)
197
+ doc.send('last-name', payment_method.last_name)
198
+ doc.send('personal-identification-number', options[:personal_identification_number]) if options[:personal_identification_number]
167
199
  doc.email(options[:email]) if options[:email]
168
200
  add_address(doc, options)
169
201
  end
@@ -191,6 +223,8 @@ module ActiveMerchant
191
223
  doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
192
224
  doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
193
225
  add_description(doc, options[:description]) if options[:description]
226
+ add_3ds(doc, options[:three_d_secure]) if options[:three_d_secure]
227
+ add_level_3_data(doc, options)
194
228
  end
195
229
 
196
230
  def add_address(doc, options)
@@ -198,16 +232,114 @@ module ActiveMerchant
198
232
  return unless address
199
233
 
200
234
  doc.country(address[:country]) if address[:country]
201
- doc.state(address[:state]) if address[:state]
235
+ doc.state(address[:state]) if address[:state] && STATE_CODE_COUNTRIES.include?(address[:country])
202
236
  doc.address(address[:address]) if address[:address]
203
237
  doc.city(address[:city]) if address[:city]
204
238
  doc.zip(address[:zip]) if address[:zip]
205
239
  end
206
240
 
241
+ def add_3ds(doc, three_d_secure_options)
242
+ eci = three_d_secure_options[:eci]
243
+ cavv = three_d_secure_options[:cavv]
244
+ xid = three_d_secure_options[:xid]
245
+ ds_transaction_id = three_d_secure_options[:ds_transaction_id]
246
+ version = three_d_secure_options[:version]
247
+
248
+ doc.send('three-d-secure') do
249
+ doc.eci(eci) if eci
250
+ doc.cavv(cavv) if cavv
251
+ doc.xid(xid) if xid
252
+ doc.send('three-d-secure-version', version) if version
253
+ doc.send('ds-transaction-id', ds_transaction_id) if ds_transaction_id
254
+ end
255
+ end
256
+
257
+ def add_level_3_data(doc, options)
258
+ return unless options[:customer_reference_number]
259
+ doc.send('level-3-data') do
260
+ send_when_present(doc, :customer_reference_number, options)
261
+ send_when_present(doc, :sales_tax_amount, options)
262
+ send_when_present(doc, :freight_amount, options)
263
+ send_when_present(doc, :duty_amount, options)
264
+ send_when_present(doc, :destination_zip_code, options)
265
+ send_when_present(doc, :destination_country_code, options)
266
+ send_when_present(doc, :ship_from_zip_code, options)
267
+ send_when_present(doc, :discount_amount, options)
268
+ send_when_present(doc, :tax_amount, options)
269
+ send_when_present(doc, :tax_rate, options)
270
+ add_level_3_data_items(doc, options[:level_3_data_items]) if options[:level_3_data_items]
271
+ end
272
+ end
273
+
274
+ def send_when_present(doc, options_key, options, xml_element_name = nil)
275
+ return unless options[options_key]
276
+ xml_element_name ||= options_key.to_s
277
+
278
+ doc.send(xml_element_name.dasherize, options[options_key])
279
+ end
280
+
281
+ def add_level_3_data_items(doc, items)
282
+ items.each do |item|
283
+ doc.send('level-3-data-item') do
284
+ item.each do |key, value|
285
+ key = key.to_s.dasherize
286
+ doc.send(key, value)
287
+ end
288
+ end
289
+ end
290
+ end
291
+
207
292
  def add_authorization(doc, authorization)
208
293
  doc.send('transaction-id', authorization)
209
294
  end
210
295
 
296
+ def add_fraud_info(doc, options)
297
+ doc.send('transaction-fraud-info') do
298
+ doc.send('shopper-ip-address', options[:ip]) if options[:ip]
299
+ end
300
+ end
301
+
302
+ def add_alt_transaction_purchase(doc, money, payment_method_details, options)
303
+ doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
304
+ doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
305
+ add_amount(doc, money, options)
306
+
307
+ vaulted_shopper_id = payment_method_details.vaulted_shopper_id
308
+ doc.send('vaulted-shopper-id', vaulted_shopper_id) if vaulted_shopper_id
309
+
310
+ if payment_method_details.check?
311
+ add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?)
312
+ end
313
+
314
+ add_fraud_info(doc, options)
315
+ add_description(doc, options)
316
+ end
317
+
318
+ def add_echeck_transaction(doc, check, options, vaulted_shopper)
319
+ unless vaulted_shopper
320
+ doc.send('payer-info') do
321
+ add_personal_info(doc, check, options)
322
+ add_echeck_company(doc, check)
323
+ end
324
+ end
325
+
326
+ doc.send('ecp-transaction') do
327
+ add_echeck(doc, check) unless vaulted_shopper
328
+ end
329
+
330
+ doc.send('authorized-by-shopper', options[:authorized_by_shopper])
331
+ end
332
+
333
+ def add_echeck_company(doc, check)
334
+ doc.send('company-name', truncate(check.name, 50)) if check.account_holder_type = 'business'
335
+ end
336
+
337
+ def add_echeck(doc, check)
338
+ doc.send('account-number', check.account_number)
339
+ doc.send('routing-number', check.routing_number)
340
+ doc.send('account-type', BANK_ACCOUNT_TYPE_MAPPING["#{check.account_holder_type}_#{check.account_type}"])
341
+ end
342
+
211
343
  def parse(response)
212
344
  return bad_authentication_response if response.code.to_i == 401
213
345
  return forbidden_response(response.body) if response.code.to_i == 403
@@ -236,15 +368,15 @@ module ActiveMerchant
236
368
  end
237
369
  end
238
370
 
239
- def api_request(action, request, verb)
240
- ssl_request(verb, url(action), request, headers)
371
+ def api_request(action, request, verb, payment_method_details)
372
+ ssl_request(verb, url(action, payment_method_details), request, headers)
241
373
  rescue ResponseError => e
242
374
  e.response
243
375
  end
244
376
 
245
- def commit(action, verb = :post)
246
- request = build_xml_request(action) { |doc| yield(doc) }
247
- response = api_request(action, request, verb)
377
+ def commit(action, verb = :post, payment_method_details = PaymentMethodDetails.new())
378
+ request = build_xml_request(action, payment_method_details) { |doc| yield(doc) }
379
+ response = api_request(action, request, verb, payment_method_details)
248
380
  parsed = parse(response)
249
381
 
250
382
  succeeded = success_from(action, response)
@@ -252,7 +384,7 @@ module ActiveMerchant
252
384
  succeeded,
253
385
  message_from(succeeded, parsed),
254
386
  parsed,
255
- authorization: authorization_from(action, parsed),
387
+ authorization: authorization_from(action, parsed, payment_method_details),
256
388
  avs_result: avs_result(parsed),
257
389
  cvv_result: cvv_result(parsed),
258
390
  error_code: error_code_from(parsed),
@@ -260,9 +392,9 @@ module ActiveMerchant
260
392
  )
261
393
  end
262
394
 
263
- def url(action = nil)
395
+ def url(action = nil, payment_method_details = PaymentMethodDetails.new())
264
396
  base = test? ? test_url : live_url
265
- resource = action == :store ? 'vaulted-shoppers' : 'transactions'
397
+ resource = action == :store ? 'vaulted-shoppers' : payment_method_details.resource_url
266
398
  "#{base}/#{resource}"
267
399
  end
268
400
 
@@ -287,13 +419,15 @@ module ActiveMerchant
287
419
  parsed_response['description']
288
420
  end
289
421
 
290
- def authorization_from(action, parsed_response)
291
- action == :store ? vaulted_shopper_id(parsed_response) : parsed_response['transaction-id']
422
+ def authorization_from(action, parsed_response, payment_method_details)
423
+ action == :store ? vaulted_shopper_id(parsed_response, payment_method_details) : parsed_response['transaction-id']
292
424
  end
293
425
 
294
- def vaulted_shopper_id(parsed_response)
426
+ def vaulted_shopper_id(parsed_response, payment_method_details)
295
427
  return nil unless parsed_response['content-location-header']
296
- parsed_response['content-location-header'].split('/').last
428
+ vaulted_shopper_id = parsed_response['content-location-header'].split('/').last
429
+ vaulted_shopper_id += "|#{payment_method_details.payment_method_type}" if payment_method_details.alt_transaction?
430
+ vaulted_shopper_id
297
431
  end
298
432
 
299
433
  def error_code_from(parsed_response)
@@ -306,8 +440,8 @@ module ActiveMerchant
306
440
  }
307
441
  end
308
442
 
309
- def root_element(action)
310
- action == :store ? 'vaulted-shopper' : 'card-transaction'
443
+ def root_element(action, payment_method_details)
444
+ action == :store ? 'vaulted-shopper' : payment_method_details.root_element
311
445
  end
312
446
 
313
447
  def headers
@@ -317,10 +451,10 @@ module ActiveMerchant
317
451
  }
318
452
  end
319
453
 
320
- def build_xml_request(action)
454
+ def build_xml_request(action, payment_method_details)
321
455
  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]
456
+ builder.__send__(root_element(action, payment_method_details), root_attributes) do |doc|
457
+ doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction?
324
458
  yield(doc)
325
459
  end
326
460
  builder.doc.root.to_xml
@@ -343,5 +477,46 @@ module ActiveMerchant
343
477
  { 'description' => body }
344
478
  end
345
479
  end
480
+
481
+ class PaymentMethodDetails
482
+ attr_reader :payment_method, :vaulted_shopper_id, :payment_method_type
483
+
484
+ def initialize(payment_method = nil)
485
+ @payment_method = payment_method
486
+ @payment_method_type = nil
487
+ parse(payment_method)
488
+ end
489
+
490
+ def check?
491
+ @payment_method.is_a?(Check) || @payment_method_type == 'check'
492
+ end
493
+
494
+ def alt_transaction?
495
+ check?
496
+ end
497
+
498
+ def root_element
499
+ alt_transaction? ? 'alt-transaction' : 'card-transaction'
500
+ end
501
+
502
+ def resource_url
503
+ alt_transaction? ? 'alt-transactions' : 'transactions'
504
+ end
505
+
506
+ private
507
+
508
+ def parse(payment_method)
509
+ return unless payment_method
510
+
511
+ if payment_method.is_a?(String)
512
+ @vaulted_shopper_id, payment_method_type = payment_method.split('|')
513
+ @payment_method_type = payment_method_type if payment_method_type.present?
514
+ elsif payment_method.is_a?(Check)
515
+ @payment_method_type = payment_method.type
516
+ else
517
+ @payment_method_type = 'credit_card'
518
+ end
519
+ end
520
+ end
346
521
  end
347
522
  end
@@ -163,10 +163,10 @@ module ActiveMerchant #:nodoc:
163
163
  xml.send('PaymentType', payment_type)
164
164
  xml.send('TxnType', 'WEB_SHOP')
165
165
  xml.send('BillerCode', options.fetch(:biller_code, ''))
166
- xml.send('MerchantReference', '')
167
- xml.send('CRN1', '')
168
- xml.send('CRN2', '')
169
- xml.send('CRN3', '')
166
+ xml.send('MerchantReference', options[:order_id]) if options[:order_id]
167
+ xml.send('CRN1', options[:crn1]) if options[:crn1]
168
+ xml.send('CRN2', options[:crn2]) if options[:crn2]
169
+ xml.send('CRN3', options[:crn3]) if options[:crn3]
170
170
  xml.send('Amount', amount)
171
171
  end
172
172