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
@@ -0,0 +1,271 @@
1
+ require 'active_support/core_ext/hash/slice'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ # This gateway uses the current Stripe {Payment Intents API}[https://stripe.com/docs/api/payment_intents].
6
+ # For the legacy API, see the Stripe gateway
7
+ class StripePaymentIntentsGateway < StripeGateway
8
+
9
+ self.supported_countries = %w(AT AU BE BR CA CH DE DK EE ES FI FR GB GR HK IE IT JP LT LU LV MX NL NO NZ PL PT SE SG SI SK US)
10
+
11
+ ALLOWED_METHOD_STATES = %w[automatic manual].freeze
12
+ ALLOWED_CANCELLATION_REASONS = %w[duplicate fraudulent requested_by_customer abandoned].freeze
13
+ CREATE_INTENT_ATTRIBUTES = %i[description statement_descriptor receipt_email save_payment_method]
14
+ CONFIRM_INTENT_ATTRIBUTES = %i[receipt_email return_url save_payment_method setup_future_usage off_session]
15
+ UPDATE_INTENT_ATTRIBUTES = %i[description statement_descriptor receipt_email setup_future_usage]
16
+ DEFAULT_API_VERSION = '2019-05-16'
17
+
18
+ def create_intent(money, payment_method, options = {})
19
+ post = {}
20
+ add_amount(post, money, options, true)
21
+ add_capture_method(post, options)
22
+ add_confirmation_method(post, options)
23
+ add_customer(post, options)
24
+ add_payment_method_token(post, payment_method, options)
25
+ add_metadata(post, options)
26
+ add_return_url(post, options)
27
+ add_connected_account(post, options)
28
+ add_shipping_address(post, options)
29
+ setup_future_usage(post, options)
30
+ add_exemption(post, options)
31
+
32
+ CREATE_INTENT_ATTRIBUTES.each do |attribute|
33
+ add_whitelisted_attribute(post, options, attribute)
34
+ end
35
+
36
+ commit(:post, 'payment_intents', post, options)
37
+ end
38
+
39
+ def show_intent(intent_id, options)
40
+ commit(:get, "payment_intents/#{intent_id}", nil, options)
41
+ end
42
+
43
+ def confirm_intent(intent_id, payment_method, options = {})
44
+ post = {}
45
+ add_payment_method_token(post, payment_method, options)
46
+ CONFIRM_INTENT_ATTRIBUTES.each do |attribute|
47
+ add_whitelisted_attribute(post, options, attribute)
48
+ end
49
+
50
+ commit(:post, "payment_intents/#{intent_id}/confirm", post, options)
51
+ end
52
+
53
+ def create_payment_method(payment_method, options = {})
54
+ post = {}
55
+ post[:type] = 'card'
56
+ post[:card] = {}
57
+ post[:card][:number] = payment_method.number
58
+ post[:card][:exp_month] = payment_method.month
59
+ post[:card][:exp_year] = payment_method.year
60
+ post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
61
+ add_billing_address(post, options)
62
+
63
+ commit(:post, 'payment_methods', post, options)
64
+ end
65
+
66
+ def update_intent(money, intent_id, payment_method, options = {})
67
+ post = {}
68
+ post[:amount] = money if money
69
+
70
+ add_payment_method_token(post, payment_method, options)
71
+ add_payment_method_types(post, options)
72
+ add_customer(post, options)
73
+ add_metadata(post, options)
74
+ add_shipping_address(post, options)
75
+ add_connected_account(post, options)
76
+
77
+ UPDATE_INTENT_ATTRIBUTES.each do |attribute|
78
+ add_whitelisted_attribute(post, options, attribute)
79
+ end
80
+
81
+ commit(:post, "payment_intents/#{intent_id}", post, options)
82
+ end
83
+
84
+ def authorize(money, payment_method, options = {})
85
+ create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'manual'))
86
+ end
87
+
88
+ def purchase(money, payment_method, options = {})
89
+ create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'automatic'))
90
+ end
91
+
92
+ def capture(money, intent_id, options = {})
93
+ post = {}
94
+ post[:amount_to_capture] = money
95
+ if options[:transfer_amount]
96
+ post[:transfer_data] = {}
97
+ post[:transfer_data][:amount] = options[:transfer_amount]
98
+ end
99
+ post[:application_fee_amount] = options[:application_fee] if options[:application_fee]
100
+ commit(:post, "payment_intents/#{intent_id}/capture", post, options)
101
+ end
102
+
103
+ def void(intent_id, options = {})
104
+ post = {}
105
+ post[:cancellation_reason] = options[:cancellation_reason] if ALLOWED_CANCELLATION_REASONS.include?(options[:cancellation_reason])
106
+ commit(:post, "payment_intents/#{intent_id}/cancel", post, options)
107
+ end
108
+
109
+ def refund(money, intent_id, options = {})
110
+ intent = commit(:get, "payment_intents/#{intent_id}", nil, options)
111
+ charge_id = intent.params.dig('charges', 'data')[0].dig('id')
112
+ super(money, charge_id, options)
113
+ end
114
+
115
+ # Note: Not all payment methods are currently supported by the {Payment Methods API}[https://stripe.com/docs/payments/payment-methods]
116
+ # Current implementation will create a PaymentMethod object if the method is a token or credit card
117
+ # All other types will default to legacy Stripe store
118
+ def store(payment_method, options = {})
119
+ params = {}
120
+ post = {}
121
+
122
+ # If customer option is provided, create a payment method and attach to customer id
123
+ # Otherwise, create a customer, then attach
124
+ if payment_method.is_a?(StripePaymentToken) || payment_method.is_a?(ActiveMerchant::Billing::CreditCard)
125
+ add_payment_method_token(params, payment_method, options)
126
+ if options[:customer]
127
+ customer_id = options[:customer]
128
+ else
129
+ post[:validate] = options[:validate] unless options[:validate].nil?
130
+ post[:description] = options[:description] if options[:description]
131
+ post[:email] = options[:email] if options[:email]
132
+ customer = commit(:post, 'customers', post, options)
133
+ customer_id = customer.params['id']
134
+ end
135
+ commit(:post, "payment_methods/#{params[:payment_method]}/attach", { customer: customer_id }, options)
136
+ else
137
+ super(payment, options)
138
+ end
139
+ end
140
+
141
+ def unstore(identification, options = {}, deprecated_options = {})
142
+ if identification.include?('pm_')
143
+ _, payment_method = identification.split('|')
144
+ commit(:post, "payment_methods/#{payment_method}/detach", nil, options)
145
+ else
146
+ super(identification, options, deprecated_options)
147
+ end
148
+ end
149
+
150
+ private
151
+
152
+ def add_whitelisted_attribute(post, options, attribute)
153
+ post[attribute] = options[attribute] if options[attribute]
154
+ post
155
+ end
156
+
157
+ def add_capture_method(post, options)
158
+ capture_method = options[:capture_method].to_s
159
+ post[:capture_method] = capture_method if ALLOWED_METHOD_STATES.include?(capture_method)
160
+ post
161
+ end
162
+
163
+ def add_confirmation_method(post, options)
164
+ confirmation_method = options[:confirmation_method].to_s
165
+ post[:confirmation_method] = confirmation_method if ALLOWED_METHOD_STATES.include?(confirmation_method)
166
+ post
167
+ end
168
+
169
+ def add_customer(post, options)
170
+ customer = options[:customer].to_s
171
+ post[:customer] = customer if customer.start_with?('cus_')
172
+ post
173
+ end
174
+
175
+ def add_return_url(post, options)
176
+ return unless options[:confirm]
177
+ post[:confirm] = options[:confirm]
178
+ post[:return_url] = options[:return_url] if options[:return_url]
179
+ post
180
+ end
181
+
182
+ def add_payment_method_token(post, payment_method, options)
183
+ return if payment_method.nil?
184
+
185
+ if payment_method.is_a?(ActiveMerchant::Billing::CreditCard)
186
+ p = create_payment_method(payment_method, options)
187
+ payment_method = p.params['id']
188
+ end
189
+
190
+ if payment_method.is_a?(StripePaymentToken)
191
+ post[:payment_method] = payment_method.payment_data['id']
192
+ elsif payment_method.is_a?(String)
193
+ if payment_method.include?('|')
194
+ customer_id, payment_method_id = payment_method.split('|')
195
+ token = payment_method_id
196
+ post[:customer] = customer_id
197
+ else
198
+ token = payment_method
199
+ end
200
+ post[:payment_method] = token
201
+ end
202
+ end
203
+
204
+ def add_payment_method_types(post, options)
205
+ payment_method_types = options[:payment_method_types] if options[:payment_method_types]
206
+ return if payment_method_types.nil?
207
+
208
+ post[:payment_method_types] = Array(payment_method_types)
209
+ post
210
+ end
211
+
212
+ def add_exemption(post, options = {})
213
+ return unless options[:confirm]
214
+ post[:payment_method_options] ||= {}
215
+ post[:payment_method_options][:card] ||= {}
216
+ post[:payment_method_options][:card][:moto] = true if options[:moto]
217
+ end
218
+
219
+ def setup_future_usage(post, options = {})
220
+ post[:setup_future_usage] = options[:setup_future_usage] if %w( on_session off_session ).include?(options[:setup_future_usage])
221
+ post[:off_session] = options[:off_session] if options[:off_session] && options[:confirm] == true
222
+ post
223
+ end
224
+
225
+ def add_connected_account(post, options = {})
226
+ return unless options[:transfer_destination]
227
+ post[:transfer_data] = {}
228
+ post[:transfer_data][:destination] = options[:transfer_destination]
229
+ post[:transfer_data][:amount] = options[:transfer_amount] if options[:transfer_amount]
230
+ post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of]
231
+ post[:transfer_group] = options[:transfer_group] if options[:transfer_group]
232
+ post[:application_fee_amount] = options[:application_fee] if options[:application_fee]
233
+ post
234
+ end
235
+
236
+ def add_billing_address(post, options = {})
237
+ return unless billing = options[:billing_address] || options[:address]
238
+ post[:billing_details] = {}
239
+ post[:billing_details][:address] = {}
240
+ post[:billing_details][:address][:city] = billing[:city] if billing[:city]
241
+ post[:billing_details][:address][:country] = billing[:country] if billing[:country]
242
+ post[:billing_details][:address][:line1] = billing[:address1] if billing[:address1]
243
+ post[:billing_details][:address][:line2] = billing[:address2] if billing[:address2]
244
+ post[:billing_details][:address][:postal_code] = billing[:zip] if billing[:zip]
245
+ post[:billing_details][:address][:state] = billing[:state] if billing[:state]
246
+ post[:billing_details][:email] = billing[:email] if billing[:email]
247
+ post[:billing_details][:name] = billing[:name] if billing[:name]
248
+ post[:billing_details][:phone] = billing[:phone] if billing[:phone]
249
+ post
250
+ end
251
+
252
+ def add_shipping_address(post, options = {})
253
+ return unless shipping = options[:shipping]
254
+ post[:shipping] = {}
255
+ post[:shipping][:address] = {}
256
+ post[:shipping][:address][:line1] = shipping[:address][:line1]
257
+ post[:shipping][:address][:city] = shipping[:address][:city] if shipping[:address][:city]
258
+ post[:shipping][:address][:country] = shipping[:address][:country] if shipping[:address][:country]
259
+ post[:shipping][:address][:line2] = shipping[:address][:line2] if shipping[:address][:line2]
260
+ post[:shipping][:address][:postal_code] = shipping[:address][:postal_code] if shipping[:address][:postal_code]
261
+ post[:shipping][:address][:state] = shipping[:address][:state] if shipping[:address][:state]
262
+
263
+ post[:shipping][:name] = shipping[:name]
264
+ post[:shipping][:carrier] = shipping[:carrier] if shipping[:carrier]
265
+ post[:shipping][:phone] = shipping[:phone] if shipping[:phone]
266
+ post[:shipping][:tracking_number] = shipping[:tracking_number] if shipping[:tracking_number]
267
+ post
268
+ end
269
+ end
270
+ end
271
+ end
@@ -3,13 +3,18 @@ module ActiveMerchant
3
3
  class TnsGateway < Gateway
4
4
  include MastercardGateway
5
5
 
6
- class_attribute :live_na_url, :live_ap_url, :test_na_url, :test_ap_url
6
+ class_attribute :live_na_url, :live_ap_url, :live_eu_url, :test_na_url, :test_ap_url, :test_eu_url
7
7
 
8
- self.live_na_url = 'https://secure.na.tnspayments.com/api/rest/version/36/'
9
- self.test_na_url = 'https://secure.na.tnspayments.com/api/rest/version/36/'
8
+ VERSION = '52'
10
9
 
11
- self.live_ap_url = 'https://secure.ap.tnspayments.com/api/rest/version/36/'
12
- self.test_ap_url = 'https://secure.ap.tnspayments.com/api/rest/version/36/'
10
+ self.live_na_url = "https://secure.na.tnspayments.com/api/rest/version/#{VERSION}/"
11
+ self.test_na_url = "https://secure.na.tnspayments.com/api/rest/version/#{VERSION}/"
12
+
13
+ self.live_ap_url = "https://secure.ap.tnspayments.com/api/rest/version/#{VERSION}/"
14
+ self.test_ap_url = "https://secure.ap.tnspayments.com/api/rest/version/#{VERSION}/"
15
+
16
+ self.live_eu_url = "https://secure.eu.tnspayments.com/api/rest/version/#{VERSION}/"
17
+ self.test_eu_url = "https://secure.eu.tnspayments.com/api/rest/version/#{VERSION}/"
13
18
 
14
19
  self.display_name = 'TNS'
15
20
  self.homepage_url = 'http://www.tnsi.com/'
@@ -532,7 +532,7 @@ module ActiveMerchant #:nodoc:
532
532
 
533
533
  def add_contact(doc, fullname, options)
534
534
  doc['v1'].contact do
535
- doc['v1'].fullName fullname
535
+ doc['v1'].fullName fullname unless fullname.blank?
536
536
  doc['v1'].coName options[:company_name] if options[:company_name]
537
537
  doc['v1'].title options[:title] if options[:title]
538
538
 
@@ -544,7 +544,7 @@ module ActiveMerchant #:nodoc:
544
544
  end
545
545
  end
546
546
  doc['v1'].addrLn1 billing_address[:address1] if billing_address[:address1]
547
- doc['v1'].addrLn2 billing_address[:address2] if billing_address[:address2]
547
+ doc['v1'].addrLn2 billing_address[:address2] unless billing_address[:address2].blank?
548
548
  doc['v1'].city billing_address[:city] if billing_address[:city]
549
549
  doc['v1'].state billing_address[:state] if billing_address[:state]
550
550
  doc['v1'].zipCode billing_address[:zip] if billing_address[:zip]
@@ -557,9 +557,9 @@ module ActiveMerchant #:nodoc:
557
557
 
558
558
  if (shipping_address = options[:shipping_address])
559
559
  doc['v1'].ship do
560
- doc['v1'].fullName fullname
560
+ doc['v1'].fullName fullname unless fullname.blank?
561
561
  doc['v1'].addrLn1 shipping_address[:address1] if shipping_address[:address1]
562
- doc['v1'].addrLn2 shipping_address[:address2] if shipping_address[:address2]
562
+ doc['v1'].addrLn2 shipping_address[:address2] unless shipping_address[:address2].blank?
563
563
  doc['v1'].city shipping_address[:city] if shipping_address[:city]
564
564
  doc['v1'].state shipping_address[:state] if shipping_address[:state]
565
565
  doc['v1'].zipCode shipping_address[:zip] if shipping_address[:zip]
@@ -572,7 +572,7 @@ module ActiveMerchant #:nodoc:
572
572
 
573
573
  def add_name(doc, payment_method)
574
574
  doc['v1'].contact do
575
- doc['v1'].fullName payment_method.name
575
+ doc['v1'].fullName payment_method.name unless payment_method.name.blank?
576
576
  end
577
577
  end
578
578
 
@@ -104,6 +104,8 @@ module ActiveMerchant #:nodoc:
104
104
  TEST_LOGIN = 'TestMerchant'
105
105
  TEST_PASSWORD = 'password'
106
106
 
107
+ VOIDABLE_ACTIONS = %w(preauth sale postauth credit)
108
+
107
109
  self.money_format = :cents
108
110
  self.supported_cardtypes = [:visa, :master, :discover, :american_express, :diners_club, :jcb]
109
111
  self.supported_countries = ['US']
@@ -157,6 +159,8 @@ module ActiveMerchant #:nodoc:
157
159
  add_customer_data(parameters, options)
158
160
  add_payment_source(parameters, creditcard_or_billing_id)
159
161
  add_addresses(parameters, options)
162
+ add_custom_fields(parameters, options)
163
+
160
164
  commit('preauth', parameters)
161
165
  end
162
166
 
@@ -172,6 +176,8 @@ module ActiveMerchant #:nodoc:
172
176
  add_customer_data(parameters, options)
173
177
  add_payment_source(parameters, creditcard_or_billing_id)
174
178
  add_addresses(parameters, options)
179
+ add_custom_fields(parameters, options)
180
+
175
181
  commit('sale', parameters)
176
182
  end
177
183
 
@@ -179,11 +185,13 @@ module ActiveMerchant #:nodoc:
179
185
  # postauth, we preserve active_merchant's nomenclature of capture() for consistency with the rest of the library. To process
180
186
  # a postauthorization with TC, you need an amount in cents or a money object, and a TC transid.
181
187
  def capture(money, authorization, options = {})
188
+ transaction_id, _ = split_authorization(authorization)
182
189
  parameters = {
183
190
  :amount => amount(money),
184
- :transid => authorization,
191
+ :transid => transaction_id,
185
192
  }
186
193
  add_aggregator(parameters, options)
194
+ add_custom_fields(parameters, options)
187
195
 
188
196
  commit('postauth', parameters)
189
197
  end
@@ -191,11 +199,15 @@ module ActiveMerchant #:nodoc:
191
199
  # refund() allows you to return money to a card that was previously billed. You need to supply the amount, in cents or a money object,
192
200
  # that you want to refund, and a TC transid for the transaction that you are refunding.
193
201
  def refund(money, identification, options = {})
202
+ transaction_id, _ = split_authorization(identification)
203
+
194
204
  parameters = {
195
205
  :amount => amount(money),
196
- :transid => identification
206
+ :transid => transaction_id
197
207
  }
208
+
198
209
  add_aggregator(parameters, options)
210
+ add_custom_fields(parameters, options)
199
211
 
200
212
  commit('credit', parameters)
201
213
  end
@@ -214,18 +226,26 @@ module ActiveMerchant #:nodoc:
214
226
  # TrustCommerce to allow for reversal transactions before you can use this
215
227
  # method.
216
228
  #
229
+ # void() is also used to to cancel a capture (postauth), purchase (sale),
230
+ # or refund (credit) or a before it is sent for settlement.
231
+ #
217
232
  # NOTE: AMEX preauth's cannot be reversed. If you want to clear it more
218
233
  # quickly than the automatic expiration (7-10 days), you will have to
219
234
  # capture it and then immediately issue a credit for the same amount
220
235
  # which should clear the customers credit card with 48 hours according to
221
236
  # TC.
222
237
  def void(authorization, options = {})
238
+ transaction_id, original_action = split_authorization(authorization)
239
+ action = (VOIDABLE_ACTIONS - ['preauth']).include?(original_action) ? 'void' : 'reversal'
240
+
223
241
  parameters = {
224
- :transid => authorization,
242
+ :transid => transaction_id,
225
243
  }
244
+
226
245
  add_aggregator(parameters, options)
246
+ add_custom_fields(parameters, options)
227
247
 
228
- commit('reversal', parameters)
248
+ commit(action, parameters)
229
249
  end
230
250
 
231
251
  # recurring() a TrustCommerce account that is activated for Citadel, TrustCommerce's
@@ -284,6 +304,8 @@ module ActiveMerchant #:nodoc:
284
304
 
285
305
  add_creditcard(parameters, creditcard)
286
306
  add_addresses(parameters, options)
307
+ add_custom_fields(parameters, options)
308
+
287
309
  commit('store', parameters)
288
310
  end
289
311
 
@@ -294,6 +316,8 @@ module ActiveMerchant #:nodoc:
294
316
  :billingid => identification,
295
317
  }
296
318
 
319
+ add_custom_fields(parameters, options)
320
+
297
321
  commit('unstore', parameters)
298
322
  end
299
323
 
@@ -311,6 +335,12 @@ module ActiveMerchant #:nodoc:
311
335
 
312
336
  private
313
337
 
338
+ def add_custom_fields(params, options)
339
+ options[:custom_fields]&.each do |key, value|
340
+ params[key.to_sym] = value
341
+ end
342
+ end
343
+
314
344
  def add_aggregator(params, options)
315
345
  if @options[:aggregator_id] || application_id != Gateway.application_id
316
346
  params[:aggregators] = 1
@@ -333,6 +363,7 @@ module ActiveMerchant #:nodoc:
333
363
  params[:routing] = check.routing_number
334
364
  params[:account] = check.account_number
335
365
  params[:savings] = 'y' if check.account_type == 'savings'
366
+ params[:name] = check.name
336
367
  end
337
368
 
338
369
  def add_creditcard(params, creditcard)
@@ -408,14 +439,14 @@ module ActiveMerchant #:nodoc:
408
439
  TCLink.send(parameters)
409
440
  else
410
441
  parse(ssl_post(self.live_url, post_data(parameters)))
411
- end
442
+ end
412
443
 
413
444
  # to be considered successful, transaction status must be either "approved" or "accepted"
414
445
  success = SUCCESS_TYPES.include?(data['status'])
415
446
  message = message_from(data)
416
447
  Response.new(success, message, data,
417
448
  :test => test?,
418
- :authorization => data['transid'],
449
+ :authorization => authorization_from(action, data),
419
450
  :cvv_result => data['cvv'],
420
451
  :avs_result => { :code => data['avs'] }
421
452
  )
@@ -445,6 +476,15 @@ module ActiveMerchant #:nodoc:
445
476
  end
446
477
  end
447
478
 
479
+ def authorization_from(action, data)
480
+ authorization = data['transid']
481
+ authorization = "#{authorization}|#{action}" if authorization && VOIDABLE_ACTIONS.include?(action)
482
+ authorization
483
+ end
484
+
485
+ def split_authorization(authorization)
486
+ authorization&.split('|')
487
+ end
448
488
  end
449
489
  end
450
490
  end
@@ -33,9 +33,9 @@ module ActiveMerchant #:nodoc:
33
33
  '10110' => STANDARD_ERROR_CODE[:incorrect_address],
34
34
  '10111' => STANDARD_ERROR_CODE[:incorrect_address],
35
35
  '10127' => STANDARD_ERROR_CODE[:card_declined],
36
- '10128' => STANDARD_ERROR_CODE[:processing_error],
37
- '10132' => STANDARD_ERROR_CODE[:processing_error],
38
- '00043' => STANDARD_ERROR_CODE[:call_issuer]
36
+ '00043' => STANDARD_ERROR_CODE[:call_issuer],
37
+ '10205' => STANDARD_ERROR_CODE[:card_declined],
38
+ '10204' => STANDARD_ERROR_CODE[:pickup_card]
39
39
  }
40
40
 
41
41
  def initialize(options = {})
@@ -197,14 +197,19 @@ module ActiveMerchant #:nodoc:
197
197
  end
198
198
 
199
199
  def add_invoice(post, options)
200
- post[:invoice] = options[:order_id]
200
+ post[:invoice] = options[:invoice]
201
+ post[:orderid] = options[:order_id]
201
202
  post[:description] = options[:description]
202
203
  end
203
204
 
204
205
  def add_payment(post, payment, options={})
205
206
  if payment.respond_to?(:routing_number)
206
207
  post[:checkformat] = options[:check_format] if options[:check_format]
207
- post[:accounttype] = options[:account_type] if options[:account_type]
208
+ if payment.account_type
209
+ account_type = payment.account_type.to_s.capitalize
210
+ raise ArgumentError, 'account_type must be checking or savings' unless %w(Checking Savings).include?(account_type)
211
+ post[:accounttype] = account_type
212
+ end
208
213
  post[:account] = payment.account_number
209
214
  post[:routing] = payment.routing_number
210
215
  post[:name] = payment.name unless payment.name.blank?
@@ -258,7 +263,10 @@ module ActiveMerchant #:nodoc:
258
263
  # see: https://wiki.usaepay.com/developer/transactionapi#merchant_defined_custom_fields
259
264
  def add_custom_fields(post, options)
260
265
  return unless options[:custom_fields].is_a?(Hash)
266
+
261
267
  options[:custom_fields].each do |index, custom|
268
+ raise ArgumentError.new('Cannot specify custom field with index 0') if index.to_s.to_i.zero?
269
+
262
270
  post["custom#{index}"] = custom
263
271
  end
264
272
  end
@@ -267,7 +275,7 @@ module ActiveMerchant #:nodoc:
267
275
  def add_line_items(post, options)
268
276
  return unless options[:line_items].is_a?(Array)
269
277
  options[:line_items].each_with_index do |line_item, index|
270
- %w(product_ref_num sku name description taxable tax_rate tax_amount commodity_code discount_rate discount_amount).each do |key|
278
+ %w(product_ref_num sku qty name description taxable tax_rate tax_amount commodity_code discount_rate discount_amount).each do |key|
271
279
  post["line#{index}#{key.delete('_')}"] = line_item[key.to_sym] if line_item.has_key?(key.to_sym)
272
280
  end
273
281
 
@@ -310,12 +318,15 @@ module ActiveMerchant #:nodoc:
310
318
  def commit(action, parameters)
311
319
  url = (test? ? self.test_url : self.live_url)
312
320
  response = parse(ssl_post(url, post_data(action, parameters)))
313
- Response.new(response[:status] == 'Approved', message_from(response), response,
321
+ approved = response[:status] == 'Approved'
322
+ error_code = nil
323
+ error_code = (STANDARD_ERROR_CODE_MAPPING[response[:error_code]] || STANDARD_ERROR_CODE[:processing_error]) unless approved
324
+ Response.new(approved, message_from(response), response,
314
325
  :test => test?,
315
326
  :authorization => response[:ref_num],
316
327
  :cvv_result => response[:cvv2_result_code],
317
328
  :avs_result => { :code => response[:avs_result_code] },
318
- :error_code => STANDARD_ERROR_CODE_MAPPING[response[:error_code]]
329
+ :error_code => error_code
319
330
  )
320
331
  end
321
332
 
@@ -57,7 +57,10 @@ module ActiveMerchant #:nodoc:
57
57
  response = commit('cancelDeposit', params, options)
58
58
  return response if response.success? || split_authorization(authorization).length == 1 || !options[:force_full_refund_if_unsettled]
59
59
 
60
- # Attempt RefundSingleTransaction if unsettled
60
+ # Attempt RefundSingleTransaction if unsettled (and stash the original
61
+ # response message so it will be included it in the follow-up response
62
+ # message)
63
+ options[:error_message] = response.message
61
64
  prepare_refund_data(params, authorization, options)
62
65
  commit('refund', params, options)
63
66
  end
@@ -197,15 +200,24 @@ module ActiveMerchant #:nodoc:
197
200
  end
198
201
 
199
202
  def message_from(response, options, action)
200
- if empty?(response['errorMessage']) || response['errorMessage'] == '[ ]'
201
- action == 'refund' ? "#{response['data']['DSC_COD_ACCION']}, #{options[:error_message]}" : response['data']['DSC_COD_ACCION']
202
- elsif action == 'refund'
203
- message = "#{response['errorMessage']}, #{options[:error_message]}"
204
- options[:error_message] = response['errorMessage']
205
- message
206
- else
207
- response['errorMessage']
208
- end
203
+ message_from_messages(
204
+ response['errorMessage'],
205
+ action_code_description(response),
206
+ options[:error_message]
207
+ )
208
+ end
209
+
210
+ def message_from_messages(*args)
211
+ args.reject { |m| error_message_empty?(m) }.join(' | ')
212
+ end
213
+
214
+ def action_code_description(response)
215
+ return nil unless response['data']
216
+ response['data']['DSC_COD_ACCION']
217
+ end
218
+
219
+ def error_message_empty?(error_message)
220
+ empty?(error_message) || error_message == '[ ]'
209
221
  end
210
222
 
211
223
  def response_error(raw_response, options, action)