activemerchant 1.56.0 → 1.66.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +331 -0
  3. data/README.md +9 -9
  4. data/lib/active_merchant/billing/check.rb +3 -0
  5. data/lib/active_merchant/billing/credit_card.rb +8 -3
  6. data/lib/active_merchant/billing/credit_card_methods.rb +41 -1
  7. data/lib/active_merchant/billing/gateway.rb +14 -6
  8. data/lib/active_merchant/billing/gateways/adyen.rb +228 -0
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +157 -44
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +7 -4
  11. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +283 -0
  12. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +68 -2
  13. data/lib/active_merchant/billing/gateways/blue_pay.rb +2 -2
  14. data/lib/active_merchant/billing/gateways/blue_snap.rb +348 -0
  15. data/lib/active_merchant/billing/gateways/bpoint.rb +1 -1
  16. data/lib/active_merchant/billing/gateways/braintree_blue.rb +58 -20
  17. data/lib/active_merchant/billing/gateways/bridge_pay.rb +37 -8
  18. data/lib/active_merchant/billing/gateways/card_stream.rb +161 -40
  19. data/lib/active_merchant/billing/gateways/cashnet.rb +1 -0
  20. data/lib/active_merchant/billing/gateways/checkout_v2.rb +5 -2
  21. data/lib/active_merchant/billing/gateways/citrus_pay.rb +24 -0
  22. data/lib/active_merchant/billing/gateways/clearhaus.rb +24 -40
  23. data/lib/active_merchant/billing/gateways/conekta.rb +6 -1
  24. data/lib/active_merchant/billing/gateways/creditcall.rb +1 -1
  25. data/lib/active_merchant/billing/gateways/credorax.rb +310 -0
  26. data/lib/active_merchant/billing/gateways/culqi.rb +279 -0
  27. data/lib/active_merchant/billing/gateways/cyber_source.rb +80 -64
  28. data/lib/active_merchant/billing/gateways/data_cash.rb +10 -304
  29. data/lib/active_merchant/billing/gateways/digitzs.rb +292 -0
  30. data/lib/active_merchant/billing/gateways/elavon.rb +40 -26
  31. data/lib/active_merchant/billing/gateways/element.rb +356 -0
  32. data/lib/active_merchant/billing/gateways/fat_zebra.rb +16 -2
  33. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +6 -1
  34. data/lib/active_merchant/billing/gateways/forte.rb +10 -2
  35. data/lib/active_merchant/billing/gateways/global_collect.rb +311 -0
  36. data/lib/active_merchant/billing/gateways/global_transport.rb +1 -0
  37. data/lib/active_merchant/billing/gateways/iats_payments.rb +13 -0
  38. data/lib/active_merchant/billing/gateways/in_context_paypal_express.rb +15 -0
  39. data/lib/active_merchant/billing/gateways/iveri.rb +251 -0
  40. data/lib/active_merchant/billing/gateways/jetpay.rb +33 -19
  41. data/lib/active_merchant/billing/gateways/kushki.rb +217 -0
  42. data/lib/active_merchant/billing/gateways/latitude19.rb +416 -0
  43. data/lib/active_merchant/billing/gateways/linkpoint.rb +2 -0
  44. data/lib/active_merchant/billing/gateways/litle.rb +29 -13
  45. data/lib/active_merchant/billing/gateways/mastercard.rb +268 -0
  46. data/lib/active_merchant/billing/gateways/maxipago.rb +145 -122
  47. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +15 -1
  48. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +10 -7
  49. data/lib/active_merchant/billing/gateways/mercury.rb +13 -5
  50. data/lib/active_merchant/billing/gateways/metrics_global.rb +1 -1
  51. data/lib/active_merchant/billing/gateways/migs.rb +23 -1
  52. data/lib/active_merchant/billing/gateways/monei.rb +1 -1
  53. data/lib/active_merchant/billing/gateways/moneris.rb +21 -1
  54. data/lib/active_merchant/billing/gateways/nab_transact.rb +12 -0
  55. data/lib/active_merchant/billing/gateways/ncr_secure_pay.rb +165 -0
  56. data/lib/active_merchant/billing/gateways/netbanx.rb +245 -0
  57. data/lib/active_merchant/billing/gateways/nmi.rb +30 -9
  58. data/lib/active_merchant/billing/gateways/omise.rb +9 -5
  59. data/lib/active_merchant/billing/gateways/openpay.rb +10 -1
  60. data/lib/active_merchant/billing/gateways/opp.rb +362 -0
  61. data/lib/active_merchant/billing/gateways/orbital.rb +28 -7
  62. data/lib/active_merchant/billing/gateways/pagarme.rb +248 -0
  63. data/lib/active_merchant/billing/gateways/pay_junction_v2.rb +190 -0
  64. data/lib/active_merchant/billing/gateways/payeezy.rb +61 -12
  65. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -1
  66. data/lib/active_merchant/billing/gateways/payflow.rb +6 -0
  67. data/lib/active_merchant/billing/gateways/payment_express.rb +1 -1
  68. data/lib/active_merchant/billing/gateways/paymill.rb +29 -11
  69. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -1
  70. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -6
  71. data/lib/active_merchant/billing/gateways/payu_in.rb +3 -2
  72. data/lib/active_merchant/billing/gateways/payu_latam.rb +402 -0
  73. data/lib/active_merchant/billing/gateways/pin.rb +6 -3
  74. data/lib/active_merchant/billing/gateways/pro_pay.rb +326 -0
  75. data/lib/active_merchant/billing/gateways/psl_card.rb +3 -3
  76. data/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb +1 -1
  77. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +0 -2
  78. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb +1 -1
  79. data/lib/active_merchant/billing/gateways/quickpay.rb +3 -3
  80. data/lib/active_merchant/billing/gateways/qvalent.rb +44 -1
  81. data/lib/active_merchant/billing/gateways/redsys.rb +3 -0
  82. data/lib/active_merchant/billing/gateways/s5.rb +8 -5
  83. data/lib/active_merchant/billing/gateways/safe_charge.rb +220 -0
  84. data/lib/active_merchant/billing/gateways/sage.rb +397 -128
  85. data/lib/active_merchant/billing/gateways/sage_pay.rb +45 -20
  86. data/lib/active_merchant/billing/gateways/secure_net.rb +0 -5
  87. data/lib/active_merchant/billing/gateways/secure_pay.rb +1 -1
  88. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +12 -0
  89. data/lib/active_merchant/billing/gateways/securion_pay.rb +46 -17
  90. data/lib/active_merchant/billing/gateways/stripe.rb +125 -29
  91. data/lib/active_merchant/billing/gateways/telr.rb +275 -0
  92. data/lib/active_merchant/billing/gateways/tns.rb +13 -222
  93. data/lib/active_merchant/billing/gateways/trans_first.rb +40 -16
  94. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +606 -0
  95. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +114 -9
  96. data/lib/active_merchant/billing/gateways/vanco.rb +14 -10
  97. data/lib/active_merchant/billing/gateways/visanet_peru.rb +209 -0
  98. data/lib/active_merchant/billing/gateways/wepay.rb +73 -38
  99. data/lib/active_merchant/billing/gateways/wirecard.rb +1 -0
  100. data/lib/active_merchant/billing/gateways/world_net.rb +344 -0
  101. data/lib/active_merchant/billing/gateways/worldpay.rb +48 -16
  102. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +15 -0
  103. data/lib/active_merchant/country.rb +6 -4
  104. data/lib/active_merchant/posts_data.rb +1 -1
  105. data/lib/active_merchant/version.rb +1 -1
  106. metadata +32 -13
  107. data/lib/active_merchant/billing/gateways/app55.rb +0 -176
  108. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +0 -314
  109. data/lib/active_merchant/billing/gateways/certo_direct.rb +0 -278
  110. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +0 -89
  111. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +0 -115
  112. data/lib/active_merchant/billing/gateways/sage/sage_vault.rb +0 -149
  113. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +0 -97
@@ -0,0 +1,228 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class AdyenGateway < Gateway
4
+
5
+ # we recommend setting up merchant-specific endpoints.
6
+ # https://docs.adyen.com/developers/api-manual#apiendpoints
7
+ self.test_url = 'https://pal-test.adyen.com/pal/servlet/Payment/v18'
8
+ self.live_url = 'https://pal-live.adyen.com/pal/servlet/Payment/v18'
9
+
10
+ self.supported_countries = ['AD','AE','AF','AG','AI','AL','AM','AO','AQ','AR','AS','AT','AU','AW','AX','AZ','BA','BB','BD','BE','BF','BG','BH','BI','BJ','BL','BM','BN','BO','BQ','BR','BS','BT','BV','BW','BY','BZ','CA','CC','CD','CF','CG','CH','CI','CK','CL','CM','CN','CO','CR','CU','CV','CW','CX','CY','CZ','DE','DJ','DK','DM','DO','DZ','EC','EE','EG','EH','ER','ES','ET','FI','FJ','FK','FM','FO','FR','GA','GB','GD','GE','GF','GG','GH','GI','GL','GM','GN','GP','GQ','GR','GS','GT','GU','GW','GY','HK','HM','HN','HR','HT','HU','ID','IE','IL','IM','IN','IO','IQ','IR','IS','IT','JE','JM','JO','JP','KE','KG','KH','KI','KM','KN','KP','KR','KW','KY','KZ','LA','LB','LC','LI','LK','LR','LS','LT','LU','LV','LY','MA','MC','MD','ME','MF','MG','MH','MK','ML','MM','MN','MO','MP','MQ','MR','MS','MT','MU','MV','MW','MX','MY','MZ','NA','NC','NE','NF','NG','NI','NL','NO','NP','NR','NU','NZ','OM','PA','PE','PF','PG','PH','PK','PL','PM','PN','PR','PS','PT','PW','PY','QA','RE','RO','RS','RU','RW','SA','SB','SC','SD','SE','SG','SH','SI','SJ','SK','SL','SM','SN','SO','SR','SS','ST','SV','SX','SY','SZ','TC','TD','TF','TG','TH','TJ','TK','TL','TM','TN','TO','TR','TT','TV','TW','TZ','UA','UG','UM','US','UY','UZ','VA','VC','VE','VG','VI','VN','VU','WF','WS','YE','YT','ZA','ZM','ZW']
11
+ self.default_currency = 'USD'
12
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover]
13
+
14
+ self.money_format = :cents
15
+
16
+ self.homepage_url = 'https://www.adyen.com/'
17
+ self.display_name = 'Adyen'
18
+
19
+ STANDARD_ERROR_CODE_MAPPING = {
20
+ '101' => STANDARD_ERROR_CODE[:incorrect_number],
21
+ '103' => STANDARD_ERROR_CODE[:invalid_cvc],
22
+ '131' => STANDARD_ERROR_CODE[:incorrect_address],
23
+ '132' => STANDARD_ERROR_CODE[:incorrect_address],
24
+ '133' => STANDARD_ERROR_CODE[:incorrect_address],
25
+ '134' => STANDARD_ERROR_CODE[:incorrect_address],
26
+ '135' => STANDARD_ERROR_CODE[:incorrect_address],
27
+ }
28
+
29
+ def initialize(options={})
30
+ requires!(options, :username, :password, :merchant_account)
31
+ @username, @password, @merchant_account = options.values_at(:username, :password, :merchant_account)
32
+ super
33
+ end
34
+
35
+ def purchase(money, payment, options={})
36
+ MultiResponse.run do |r|
37
+ r.process{authorize(money, payment, options)}
38
+ r.process{capture(money, r.authorization, options)}
39
+ end
40
+ end
41
+
42
+ def authorize(money, payment, options={})
43
+ requires!(options, :reference)
44
+ post = init_post(options)
45
+ add_invoice(post, money, options)
46
+ add_payment(post, payment)
47
+ add_extra_data(post, options)
48
+ add_address(post, options)
49
+ commit('authorise', post)
50
+ end
51
+
52
+ def capture(money, authorization, options={})
53
+ post = init_post(options)
54
+ add_invoice_for_modification(post, money, authorization, options)
55
+ add_references(post, authorization, options)
56
+ commit('capture', post)
57
+ end
58
+
59
+ def refund(money, authorization, options={})
60
+ post = init_post(options)
61
+ add_invoice_for_modification(post, money, authorization, options)
62
+ add_references(post, authorization, options)
63
+ commit('refund', post)
64
+ end
65
+
66
+ def void(authorization, options={})
67
+ post = init_post(options)
68
+ add_references(post, authorization, options)
69
+ commit('cancel', post)
70
+ end
71
+
72
+ def verify(credit_card, options={})
73
+ MultiResponse.run(:use_first_response) do |r|
74
+ r.process { authorize(100, credit_card, options) }
75
+ r.process(:ignore_result) { void(r.authorization, options) }
76
+ end
77
+ end
78
+
79
+ def supports_scrubbing?
80
+ true
81
+ end
82
+
83
+ def scrub(transcript)
84
+ transcript.
85
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
86
+ gsub(%r(("number\\?":\\?")[^"]*)i, '\1[FILTERED]').
87
+ gsub(%r(("cvc\\?":\\?")[^"]*)i, '\1[FILTERED]')
88
+ end
89
+
90
+ private
91
+
92
+ def add_extra_data(post, options)
93
+ post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
94
+ post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip]
95
+ post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference]
96
+ post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
97
+ post[:selectedBrand] = options[:selected_brand] if options[:selected_brand]
98
+ post[:deliveryDate] = options[:delivery_date] if options[:delivery_date]
99
+ post[:merchantOrderReference] = options[:merchant_order_reference] if options[:merchant_order_reference]
100
+ post[:shopperInteraction] = options[:shopper_interaction] if options[:shopper_interaction]
101
+ end
102
+
103
+ def add_address(post, options)
104
+ return unless post[:card] && post[:card].kind_of?(Hash)
105
+ if address = options[:billing_address] || options[:address]
106
+ post[:card][:billingAddress] = {}
107
+ post[:card][:billingAddress][:street] = address[:address1] if address[:address1]
108
+ post[:card][:billingAddress][:houseNumberOrName] = address[:address2] if address[:address2]
109
+ post[:card][:billingAddress][:postalCode] = address[:zip] if address[:zip]
110
+ post[:card][:billingAddress][:city] = address[:city] if address[:city]
111
+ post[:card][:billingAddress][:stateOrProvince] = address[:state] if address[:state]
112
+ post[:card][:billingAddress][:country] = address[:country] if address[:country]
113
+ end
114
+ end
115
+
116
+ def add_invoice(post, money, options)
117
+ amount = {
118
+ value: amount(money),
119
+ currency: options[:currency] || currency(money)
120
+ }
121
+ post[:reference] = options[:reference]
122
+ post[:amount] = amount
123
+ end
124
+
125
+ def add_invoice_for_modification(post, money, authorization, options)
126
+ amount = {
127
+ value: amount(money),
128
+ currency: options[:currency] || currency(money)
129
+ }
130
+ post[:modificationAmount] = amount
131
+ end
132
+
133
+ def add_payment(post, payment)
134
+ card = {
135
+ expiryMonth: payment.month,
136
+ expiryYear: payment.year,
137
+ holderName: payment.name,
138
+ number: payment.number,
139
+ cvc: payment.verification_value
140
+ }
141
+ card.delete_if{|k,v| v.blank? }
142
+ requires!(card, :expiryMonth, :expiryYear, :holderName, :number, :cvc)
143
+ post[:card] = card
144
+ end
145
+
146
+ def add_references(post, authorization, options = {})
147
+ post[:originalReference] = authorization
148
+ post[:reference] = options[:reference]
149
+ end
150
+
151
+ def parse(body)
152
+ return {} if body.blank?
153
+ JSON.parse(body)
154
+ end
155
+
156
+ def commit(action, parameters)
157
+ url = (test? ? test_url : live_url)
158
+
159
+ begin
160
+ raw_response = ssl_post("#{url}/#{action.to_s}", post_data(action, parameters), request_headers)
161
+ response = parse(raw_response)
162
+ rescue ResponseError => e
163
+ raw_response = e.response.body
164
+ response = parse(raw_response)
165
+ end
166
+
167
+ success = success_from(action, response)
168
+ Response.new(
169
+ success,
170
+ message_from(action, response),
171
+ response,
172
+ authorization: authorization_from(response),
173
+ test: test?,
174
+ error_code: success ? nil : error_code_from(response)
175
+ )
176
+
177
+ end
178
+
179
+ def basic_auth
180
+ Base64.strict_encode64("#{@username}:#{@password}")
181
+ end
182
+
183
+ def request_headers
184
+ {
185
+ "Content-Type" => "application/json",
186
+ "Authorization" => "Basic #{basic_auth}"
187
+ }
188
+ end
189
+
190
+ def success_from(action, response)
191
+ case action.to_s
192
+ when 'authorise'
193
+ ['Authorised', 'Received', 'RedirectShopper'].include?(response['resultCode'])
194
+ when 'capture', 'refund', 'cancel'
195
+ response['response'] == "[#{action}-received]"
196
+ else
197
+ false
198
+ end
199
+ end
200
+
201
+ def message_from(action, response)
202
+ case action.to_s
203
+ when 'authorise'
204
+ response['refusalReason'] || response['resultCode'] || response['message']
205
+ when 'capture', 'refund', 'cancel'
206
+ response['response'] || response['message']
207
+ end
208
+ end
209
+
210
+ def authorization_from(response)
211
+ response['pspReference']
212
+ end
213
+
214
+ def init_post(options = {})
215
+ {merchantAccount: options[:merchant_account] || @merchant_account}
216
+ end
217
+
218
+ def post_data(action, parameters = {})
219
+ JSON.generate(parameters)
220
+ end
221
+
222
+ def error_code_from(response)
223
+ STANDARD_ERROR_CODE_MAPPING[response['errorCode']]
224
+ end
225
+
226
+ end
227
+ end
228
+ end
@@ -1,14 +1,14 @@
1
1
  require 'nokogiri'
2
2
 
3
- module ActiveMerchant #:nodoc:
4
- module Billing #:nodoc:
3
+ module ActiveMerchant
4
+ module Billing
5
5
  class AuthorizeNetGateway < Gateway
6
6
  include Empty
7
7
 
8
8
  self.test_url = 'https://apitest.authorize.net/xml/v1/request.api'
9
9
  self.live_url = 'https://api2.authorize.net/xml/v1/request.api'
10
10
 
11
- self.supported_countries = %w(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)
11
+ self.supported_countries = %w(AD AT AU BE BG CA CH CY CZ DE DK EE ES FI FR GB GI GR HU IE IL IS IT LI LT LU LV MC MT NL NO PL PT RO SE SI SK SM TR US VA)
12
12
  self.default_currency = 'USD'
13
13
  self.money_format = :dollars
14
14
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro]
@@ -16,25 +16,45 @@ module ActiveMerchant #:nodoc:
16
16
  self.homepage_url = 'http://www.authorize.net/'
17
17
  self.display_name = 'Authorize.Net'
18
18
 
19
+ # Authorize.net has slightly different definitions for returned AVS codes
20
+ # that have been mapped to the closest equivalent AM standard AVSResult codes
21
+ # Authorize.net's descriptions noted below
22
+ STANDARD_AVS_CODE_MAPPING = {
23
+ 'A' => 'A', # Street Address: Match -- First 5 Digits of ZIP: No Match
24
+ 'B' => 'I', # Address not provided for AVS check or street address match, postal code could not be verified
25
+ 'E' => 'E', # AVS Error
26
+ 'G' => 'G', # Non U.S. Card Issuing Bank
27
+ 'N' => 'N', # Street Address: No Match -- First 5 Digits of ZIP: No Match
28
+ 'P' => 'I', # AVS not applicable for this transaction
29
+ 'R' => 'R', # Retry, System Is Unavailable
30
+ 'S' => 'S', # AVS Not Supported by Card Issuing Bank
31
+ 'U' => 'U', # Address Information For This Cardholder Is Unavailable
32
+ 'W' => 'W', # Street Address: No Match -- All 9 Digits of ZIP: Match
33
+ 'X' => 'X', # Street Address: Match -- All 9 Digits of ZIP: Match
34
+ 'Y' => 'Y', # Street Address: Match - First 5 Digits of ZIP: Match
35
+ 'Z' => 'Z' # Street Address: No Match - First 5 Digits of ZIP: Match
36
+ }
37
+
19
38
  STANDARD_ERROR_CODE_MAPPING = {
20
- '36' => STANDARD_ERROR_CODE[:incorrect_number],
21
- '237' => STANDARD_ERROR_CODE[:invalid_number],
22
- '2315' => STANDARD_ERROR_CODE[:invalid_number],
23
- '37' => STANDARD_ERROR_CODE[:invalid_expiry_date],
24
- '2316' => STANDARD_ERROR_CODE[:invalid_expiry_date],
25
- '378' => STANDARD_ERROR_CODE[:invalid_cvc],
26
- '38' => STANDARD_ERROR_CODE[:expired_card],
27
- '2317' => STANDARD_ERROR_CODE[:expired_card],
28
- '244' => STANDARD_ERROR_CODE[:incorrect_cvc],
29
- '227' => STANDARD_ERROR_CODE[:incorrect_address],
30
39
  '2127' => STANDARD_ERROR_CODE[:incorrect_address],
31
40
  '22' => STANDARD_ERROR_CODE[:card_declined],
41
+ '227' => STANDARD_ERROR_CODE[:incorrect_address],
32
42
  '23' => STANDARD_ERROR_CODE[:card_declined],
33
- '3153' => STANDARD_ERROR_CODE[:processing_error],
43
+ '2315' => STANDARD_ERROR_CODE[:invalid_number],
44
+ '2316' => STANDARD_ERROR_CODE[:invalid_expiry_date],
45
+ '2317' => STANDARD_ERROR_CODE[:expired_card],
34
46
  '235' => STANDARD_ERROR_CODE[:processing_error],
47
+ '237' => STANDARD_ERROR_CODE[:invalid_number],
35
48
  '24' => STANDARD_ERROR_CODE[:pickup_card],
49
+ '244' => STANDARD_ERROR_CODE[:incorrect_cvc],
36
50
  '300' => STANDARD_ERROR_CODE[:config_error],
37
- '384' => STANDARD_ERROR_CODE[:config_error]
51
+ '3153' => STANDARD_ERROR_CODE[:processing_error],
52
+ '3155' => STANDARD_ERROR_CODE[:unsupported_feature],
53
+ '36' => STANDARD_ERROR_CODE[:incorrect_number],
54
+ '37' => STANDARD_ERROR_CODE[:invalid_expiry_date],
55
+ '378' => STANDARD_ERROR_CODE[:invalid_cvc],
56
+ '38' => STANDARD_ERROR_CODE[:expired_card],
57
+ '384' => STANDARD_ERROR_CODE[:config_error],
38
58
  }
39
59
 
40
60
  MARKET_TYPE = {
@@ -61,7 +81,7 @@ module ActiveMerchant #:nodoc:
61
81
  TRANSACTION_ALREADY_ACTIONED = %w(310 311)
62
82
 
63
83
  CARD_CODE_ERRORS = %w(N S)
64
- AVS_ERRORS = %w(A E N R W Z)
84
+ AVS_ERRORS = %w(A E I N R W Z)
65
85
  AVS_REASON_CODES = %w(27 45)
66
86
 
67
87
  TRACKS = {
@@ -72,6 +92,7 @@ module ActiveMerchant #:nodoc:
72
92
  APPLE_PAY_DATA_DESCRIPTOR = "COMMON.APPLE.INAPP.PAYMENT"
73
93
 
74
94
  PAYMENT_METHOD_NOT_SUPPORTED_ERROR = "155"
95
+ INELIGIBLE_FOR_ISSUING_CREDIT_ERROR = "54"
75
96
 
76
97
  def initialize(options={})
77
98
  requires!(options, :login, :password)
@@ -111,11 +132,18 @@ module ActiveMerchant #:nodoc:
111
132
  end
112
133
 
113
134
  def refund(amount, authorization, options={})
114
- if auth_was_for_cim?(authorization)
135
+ response = if auth_was_for_cim?(authorization)
115
136
  cim_refund(amount, authorization, options)
116
137
  else
117
138
  normal_refund(amount, authorization, options)
118
139
  end
140
+
141
+ return response if response.success?
142
+ return response unless options[:force_full_refund_if_unsettled]
143
+
144
+ if response.params["response_reason_code"] == INELIGIBLE_FOR_ISSUING_CREDIT_ERROR
145
+ void(authorization, options)
146
+ end
119
147
  end
120
148
 
121
149
  def void(authorization, options={})
@@ -154,28 +182,24 @@ module ActiveMerchant #:nodoc:
154
182
  end
155
183
 
156
184
  def store(credit_card, options = {})
157
- commit(:cim_store) do |xml|
158
- xml.profile do
159
- xml.merchantCustomerId(truncate(options[:merchant_customer_id], 20) || SecureRandom.hex(10))
160
- xml.description(truncate(options[:description], 255)) unless empty?(options[:description])
161
- xml.email(options[:email]) unless empty?(options[:email])
162
-
163
- xml.paymentProfiles do
164
- xml.customerType("individual")
165
- add_billing_address(xml, credit_card, options)
166
- add_shipping_address(xml, options, "shipToList")
167
- xml.payment do
168
- xml.creditCard do
169
- xml.cardNumber(truncate(credit_card.number, 16))
170
- xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits))
171
- xml.cardCode(credit_card.verification_value) if credit_card.verification_value
172
- end
173
- end
174
- end
175
- end
185
+ if options[:customer_profile_id]
186
+ create_customer_payment_profile(credit_card, options)
187
+ else
188
+ create_customer_profile(credit_card, options)
176
189
  end
177
190
  end
178
191
 
192
+ def unstore(authorization)
193
+ customer_profile_id, _, _ = split_authorization(authorization)
194
+
195
+ delete_customer_profile(customer_profile_id)
196
+ end
197
+
198
+ def verify_credentials
199
+ response = commit(:verify_credentials) { }
200
+ response.success?
201
+ end
202
+
179
203
  def supports_scrubbing?
180
204
  true
181
205
  end
@@ -336,9 +360,13 @@ module ActiveMerchant #:nodoc:
336
360
  end
337
361
  end
338
362
 
363
+ def camel_case_lower(key)
364
+ String(key).split('_').inject([]){ |buffer,e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
365
+ end
366
+
339
367
  def add_settings(xml, source, options)
340
368
  xml.transactionSettings do
341
- if !source.is_a?(String) && card_brand(source) == "check" && options[:recurring]
369
+ if options[:recurring]
342
370
  xml.setting do
343
371
  xml.settingName("recurringBilling")
344
372
  xml.settingValue("true")
@@ -356,6 +384,18 @@ module ActiveMerchant #:nodoc:
356
384
  ActiveMerchant.deprecated "Using the duplicate_window class_attribute is deprecated. Use the transaction options hash instead."
357
385
  set_duplicate_window(xml, self.class.duplicate_window)
358
386
  end
387
+ if options[:email_customer]
388
+ xml.setting do
389
+ xml.settingName("emailCustomer")
390
+ xml.settingValue("true")
391
+ end
392
+ end
393
+ if options[:header_email_receipt]
394
+ xml.setting do
395
+ xml.settingName("headerEmailReceipt")
396
+ xml.settingValue(options[:header_email_receipt])
397
+ end
398
+ end
359
399
  end
360
400
  end
361
401
 
@@ -374,7 +414,7 @@ module ActiveMerchant #:nodoc:
374
414
  xml.value(currency)
375
415
  end
376
416
  end
377
- if application_id.present? && application_id != "ActiveMerchant"
417
+ if application_id.present?
378
418
  xml.userField do
379
419
  xml.name("x_solution_id")
380
420
  xml.value(application_id)
@@ -459,8 +499,7 @@ module ActiveMerchant #:nodoc:
459
499
  xml.bankAccount do
460
500
  xml.routingNumber(check.routing_number)
461
501
  xml.accountNumber(check.account_number)
462
- xml.nameOnAccount(check.name)
463
- xml.echeckType("WEB")
502
+ xml.nameOnAccount(truncate(check.name, 22))
464
503
  xml.bankName(check.bank_name)
465
504
  xml.checkNumber(check.number)
466
505
  end
@@ -536,6 +575,64 @@ module ActiveMerchant #:nodoc:
536
575
  xml.invoiceNumber(truncate(options[:order_id], 20))
537
576
  xml.description(truncate(options[:description], 255))
538
577
  end
578
+
579
+ # Authorize.net API requires lineItems to be placed directly after order tag
580
+ if options[:line_items]
581
+ xml.lineItems do
582
+ options[:line_items].each do |line_item|
583
+ xml.lineItem do
584
+ line_item.each do |key, value|
585
+ xml.send(camel_case_lower(key), value)
586
+ end
587
+ end
588
+ end
589
+ end
590
+ end
591
+ end
592
+
593
+ def create_customer_payment_profile(credit_card, options)
594
+ commit(:cim_store_update) do |xml|
595
+ xml.customerProfileId options[:customer_profile_id]
596
+ xml.paymentProfile do
597
+ add_billing_address(xml, credit_card, options)
598
+ xml.payment do
599
+ xml.creditCard do
600
+ xml.cardNumber(truncate(credit_card.number, 16))
601
+ xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits))
602
+ xml.cardCode(credit_card.verification_value) if credit_card.verification_value
603
+ end
604
+ end
605
+ end
606
+ end
607
+ end
608
+
609
+ def create_customer_profile(credit_card, options)
610
+ commit(:cim_store) do |xml|
611
+ xml.profile do
612
+ xml.merchantCustomerId(truncate(options[:merchant_customer_id], 20) || SecureRandom.hex(10))
613
+ xml.description(truncate(options[:description], 255)) unless empty?(options[:description])
614
+ xml.email(options[:email]) unless empty?(options[:email])
615
+
616
+ xml.paymentProfiles do
617
+ xml.customerType("individual")
618
+ add_billing_address(xml, credit_card, options)
619
+ add_shipping_address(xml, options, "shipToList")
620
+ xml.payment do
621
+ xml.creditCard do
622
+ xml.cardNumber(truncate(credit_card.number, 16))
623
+ xml.expirationDate(format(credit_card.year, :four_digits) + '-' + format(credit_card.month, :two_digits))
624
+ xml.cardCode(credit_card.verification_value) if credit_card.verification_value
625
+ end
626
+ end
627
+ end
628
+ end
629
+ end
630
+ end
631
+
632
+ def delete_customer_profile(customer_profile_id)
633
+ commit(:cim_store_delete_customer) do |xml|
634
+ xml.customerProfileId(customer_profile_id)
635
+ end
539
636
  end
540
637
 
541
638
  def names_from(payment_source, address, options)
@@ -556,7 +653,7 @@ module ActiveMerchant #:nodoc:
556
653
  end
557
654
 
558
655
  def parse(action, raw_response)
559
- if is_cim_action?(action)
656
+ if is_cim_action?(action) || action == :verify_credentials
560
657
  parse_cim(raw_response)
561
658
  else
562
659
  parse_normal(action, raw_response)
@@ -567,7 +664,8 @@ module ActiveMerchant #:nodoc:
567
664
  raw_response = ssl_post(url, post_data(action, &payload), headers)
568
665
  response = parse(action, raw_response)
569
666
 
570
- avs_result = AVSResult.new(code: response[:avs_result_code])
667
+ avs_result_code = response[:avs_result_code].upcase if response[:avs_result_code]
668
+ avs_result = AVSResult.new(code: STANDARD_AVS_CODE_MAPPING[avs_result_code])
571
669
  cvv_result = CVVResult.new(response[:card_code])
572
670
  if using_live_gateway_in_test_mode?(response)
573
671
  Response.new(false, "Using a live Authorize.net account in Test Mode is not permitted.")
@@ -602,6 +700,12 @@ module ActiveMerchant #:nodoc:
602
700
  def root_for(action)
603
701
  if action == :cim_store
604
702
  "createCustomerProfileRequest"
703
+ elsif action == :cim_store_update
704
+ "createCustomerPaymentProfileRequest"
705
+ elsif action == :cim_store_delete_customer
706
+ "deleteCustomerProfileRequest"
707
+ elsif action == :verify_credentials
708
+ "authenticateTestRequest"
605
709
  elsif is_cim_action?(action)
606
710
  "createCustomerProfileTransactionRequest"
607
711
  else
@@ -697,6 +801,11 @@ module ActiveMerchant #:nodoc:
697
801
  (empty?(element.content) ? nil : element.content)
698
802
  end
699
803
 
804
+ response[:customer_payment_profile_id] = if(element = doc.at_xpath("//customerPaymentProfileIdList/numericString") ||
805
+ doc.at_xpath("//customerPaymentProfileId"))
806
+ (empty?(element.content) ? nil : element.content)
807
+ end
808
+
700
809
  response[:direct_response] = if(element = doc.at_xpath("//directResponse"))
701
810
  (empty?(element.content) ? nil : element.content)
702
811
  end
@@ -707,7 +816,7 @@ module ActiveMerchant #:nodoc:
707
816
  end
708
817
 
709
818
  def success_from(action, response)
710
- if action == :cim_store
819
+ if cim?(action) || (action == :verify_credentials)
711
820
  response[:result_code] == "Ok"
712
821
  else
713
822
  response[:response_code] == APPROVED && TRANSACTION_ALREADY_ACTIONED.exclude?(response[:response_reason_code])
@@ -727,7 +836,7 @@ module ActiveMerchant #:nodoc:
727
836
  end
728
837
 
729
838
  def authorization_from(action, response)
730
- if action == :cim_store
839
+ if cim?(action)
731
840
  [response[:customer_profile_id], response[:customer_payment_profile_id], action].join("#")
732
841
  else
733
842
  [response[:transaction_id], response[:account_number], action].join("#")
@@ -738,6 +847,10 @@ module ActiveMerchant #:nodoc:
738
847
  authorization.split("#")
739
848
  end
740
849
 
850
+ def cim?(action)
851
+ (action == :cim_store) || (action == :cim_store_update) || (action == :cim_store_delete_customer)
852
+ end
853
+
741
854
  def transaction_id_from(authorization)
742
855
  transaction_id, _, _ = split_authorization(authorization)
743
856
  transaction_id
@@ -574,6 +574,7 @@ module ActiveMerchant #:nodoc:
574
574
  def build_get_customer_payment_profile_request(xml, options)
575
575
  xml.tag!('customerProfileId', options[:customer_profile_id])
576
576
  xml.tag!('customerPaymentProfileId', options[:customer_payment_profile_id])
577
+ xml.tag!('unmaskExpirationDate', options[:unmask_expiration_date]) if options[:unmask_expiration_date]
577
578
  xml.target!
578
579
  end
579
580
 
@@ -863,10 +864,12 @@ module ActiveMerchant #:nodoc:
863
864
  response_params['direct_response'] = parse_direct_response(response_params['direct_response']) if response_params['direct_response']
864
865
  transaction_id = response_params['direct_response']['transaction_id'] if response_params['direct_response']
865
866
 
866
- Response.new(success, message, response_params,
867
- :test => test_mode,
868
- :authorization => transaction_id || response_params['customer_profile_id'] || (response_params['profile'] ? response_params['profile']['customer_profile_id'] : nil)
869
- )
867
+ response_options = {}
868
+ response_options[:test] = test_mode
869
+ response_options[:authorization] = transaction_id || response_params['customer_profile_id'] || (response_params['profile'] ? response_params['profile']['customer_profile_id'] : nil)
870
+ response_options[:error_code] = response_params['messages']['message']['code'] unless success
871
+
872
+ Response.new(success, message, response_params, response_options)
870
873
  end
871
874
 
872
875
  def tag_unless_blank(xml, tag_name, data)