activemerchant 1.66.0 → 1.67.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5c4bbd0ee52bf39713dad270b4e4b923d24d193c
4
- data.tar.gz: 304ee11c40f4c5c68d108ccbab8a6eceaddb0315
3
+ metadata.gz: 8d1e9a2bf41a0e836aba8e3d1296464f62d7765e
4
+ data.tar.gz: 6cd30371be55f49a0beef1eb5a3a8796cf226db0
5
5
  SHA512:
6
- metadata.gz: 69f9b2b2082147a44ec23c987b1f6ffaf139b44a2773e84c4c30e70b18812e02677c37714f36de005db44ebe66237b1617ddce74f58fae8731544d990accf216
7
- data.tar.gz: 394bc190204e8e3cb01b1b1b5f5a27c6af430672c51767a760b2f816d0c07e29ef5cb0d9bb3346a616a0ba3083d954d43034d7d8333b721a5ce44bb06ef5e940
6
+ metadata.gz: 4c0426a9d705e5b7c29031ac35ccdb1a2bf062efe8693760b485f9ece001e6149f64bbfa11dcc8cc23310ac5a13586b66683762cb0db98bd300d3eb5c8cd27d7
7
+ data.tar.gz: 54b90b7a5e2235c16e5ea1e91a161437b50081b2b19ccd7938f35f8398f65eafffee05eca2317c19f4cda4007986b59f9daea99db36e184b2a357a4aed437ebf
data/CHANGELOG CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  == HEAD
4
4
 
5
+ == Version 1.67.0 (June 8, 2017)
6
+ * Acapture: Pass 3D Secure fields [davidsantoso] #2451
7
+ * Authorize.net: Pass Level 2 Data Fields [curiousepic] #2444
8
+ * Credorax: Add 3D Secure authentication fields [davidsantoso] #2446
9
+ * Ebanx: Add gateway support [davidsantoso] #2447
10
+ * Ebanx: Reduce supported countries to Brazil and Mexico [davidsantoso]
11
+ * FirstData Payeezy: Set default ECI value for auth/purchase transactions [jasonwebster] #2448
12
+ * JetPay V2: Add new gateway [shasum] #2442
13
+ * JetPay V2: Add optional tax data to capture calls [shasum] #2445
14
+ * NMI: Add Network Tokenization support [shasum] #2431
15
+ * Orbital: Pass soft descriptors from options hash [curiousepic]
16
+ * Orbital: Update test and production urls [jcowhigjr] #2436
17
+ * Payeezy: Add client_email field for telecheck [davidsantoso] #2455
18
+ * Payeezy: Add customer_id_type and customer_id_number fields [davidsantoso] #2454
19
+ * Quickpay V10: Fix store and token use for recurring payments [wsmoak] #2180
20
+
5
21
  == Version 1.66.0 (May 4, 2017)
6
22
  * Support Rails 5.1 [jhawthorn] #2407
7
23
  * ProPay: Add Canada as supported country [davidsantoso]
@@ -10,6 +26,18 @@
10
26
  * WePay: Add scrub method [shasum] #2406
11
27
  * iVeri: Add gateway support [curiousepic] #2400
12
28
  * iVeri: Support 3DSecure data fields [davidsantoso] #2412
29
+ * Opp: Fix transaction success criteria and clean up options [shasum] #2414
30
+ * Elavon: Support custom fields [curiousepic] #2416
31
+ * WePay: Support risk headers [shasum] #2419
32
+ * WePay: Add Canada as supported country [shasum] #2419
33
+ * Fat Zebra: Fix xid 3D Secure field [curiousepic]
34
+ * SafeCharge: Mark support for European countries [curiousepic]
35
+ * Checkout V2: Pass customer ip option [curiousepic]
36
+ * Realex: Map AVS and CVV response codes [davidsantoso] #2424
37
+ * Opp: Send disable3DSecure custom parameter if present [davidsantoso] #2432
38
+ * SafeCharge: Map standard Active Merchant order_id field [davidsantoso] #2434
39
+ * Payeezy: Default check number to 001 if not present [davidsantoso] #2439
40
+ * Opp: Fix incorrect customParameter key to disable 3DS [davidsantoso]
13
41
 
14
42
  == Version 1.65.0 (April 26, 2017)
15
43
  * Adyen: Add Adyen v18 gateway [adyenpayments] #2272
@@ -166,7 +166,7 @@ module ActiveMerchant
166
166
  xml.amount(amount(amount))
167
167
 
168
168
  add_payment_source(xml, payment)
169
- add_invoice(xml, options)
169
+ add_invoice(xml, 'refundTransaction', options)
170
170
  add_customer_data(xml, payment, options)
171
171
  add_settings(xml, payment, options)
172
172
  add_user_fields(xml, amount, options)
@@ -244,7 +244,12 @@ module ActiveMerchant
244
244
  xml.transactionType(transaction_type)
245
245
  xml.amount(amount(amount))
246
246
  add_payment_source(xml, payment)
247
- add_invoice(xml, options)
247
+ add_invoice(xml, transaction_type, options)
248
+ add_tax_fields(xml, options)
249
+ add_duty_fields(xml, options)
250
+ add_shipping_fields(xml, options)
251
+ add_tax_exempt_status(xml, options)
252
+ add_po_number(xml, options)
248
253
  add_customer_data(xml, payment, options)
249
254
  add_market_type_device_type(xml, payment, options)
250
255
  add_settings(xml, payment, options)
@@ -257,8 +262,12 @@ module ActiveMerchant
257
262
  xml.transaction do
258
263
  xml.send(transaction_type) do
259
264
  xml.amount(amount(amount))
265
+ add_tax_fields(xml, options)
266
+ add_shipping_fields(xml, options)
267
+ add_duty_fields(xml, options)
260
268
  add_payment_source(xml, payment)
261
- add_invoice(xml, options)
269
+ add_invoice(xml, transaction_type, options)
270
+ add_tax_exempt_status(xml, options)
262
271
  end
263
272
  end
264
273
  end
@@ -269,6 +278,9 @@ module ActiveMerchant
269
278
  xml.transaction do
270
279
  xml.profileTransPriorAuthCapture do
271
280
  xml.amount(amount(amount))
281
+ add_tax_fields(xml, options)
282
+ add_shipping_fields(xml, options)
283
+ add_duty_fields(xml, options)
272
284
  xml.transId(transaction_id_from(authorization))
273
285
  end
274
286
  end
@@ -281,8 +293,13 @@ module ActiveMerchant
281
293
  xml.transactionRequest do
282
294
  xml.transactionType('priorAuthCaptureTransaction')
283
295
  xml.amount(amount(amount))
296
+ add_tax_fields(xml, options)
297
+ add_duty_fields(xml, options)
298
+ add_shipping_fields(xml, options)
299
+ add_tax_exempt_status(xml, options)
300
+ add_po_number(xml, options)
284
301
  xml.refTransId(transaction_id_from(authorization))
285
- add_invoice(xml, options)
302
+ add_invoice(xml, "capture", options)
286
303
  add_user_fields(xml, amount, options)
287
304
  end
288
305
  end
@@ -296,8 +313,11 @@ module ActiveMerchant
296
313
  xml.transaction do
297
314
  xml.profileTransRefund do
298
315
  xml.amount(amount(amount))
316
+ add_tax_fields(xml, options)
317
+ add_shipping_fields(xml, options)
318
+ add_duty_fields(xml, options)
299
319
  xml.creditCardNumberMasked(card_number)
300
- add_invoice(xml, options)
320
+ add_invoice(xml, "profileTransRefund", options)
301
321
  xml.transId(transaction_id)
302
322
  end
303
323
  end
@@ -319,7 +339,12 @@ module ActiveMerchant
319
339
  end
320
340
  xml.refTransId(transaction_id)
321
341
 
322
- add_invoice(xml, options)
342
+ add_invoice(xml, 'refundTransaction', options)
343
+ add_tax_fields(xml, options)
344
+ add_duty_fields(xml, options)
345
+ add_shipping_fields(xml, options)
346
+ add_tax_exempt_status(xml, options)
347
+ add_po_number(xml, options)
323
348
  add_customer_data(xml, nil, options)
324
349
  add_user_fields(xml, amount, options)
325
350
  end
@@ -570,10 +595,11 @@ module ActiveMerchant
570
595
  xml.refId(truncate(options[:order_id], 20))
571
596
  end
572
597
 
573
- def add_invoice(xml, options)
598
+ def add_invoice(xml, transaction_type, options)
574
599
  xml.order do
575
600
  xml.invoiceNumber(truncate(options[:order_id], 20))
576
601
  xml.description(truncate(options[:description], 255))
602
+ xml.purchaseOrderNumber(options[:po_number]) if options[:po_number] && transaction_type.start_with?("profileTrans")
577
603
  end
578
604
 
579
605
  # Authorize.net API requires lineItems to be placed directly after order tag
@@ -590,6 +616,47 @@ module ActiveMerchant
590
616
  end
591
617
  end
592
618
 
619
+ def add_tax_fields(xml, options)
620
+ tax = options[:tax]
621
+ if tax.is_a?(Hash)
622
+ xml.tax do
623
+ xml.amount(amount(tax[:amount].to_i))
624
+ xml.name(tax[:name])
625
+ xml.description(tax[:description])
626
+ end
627
+ end
628
+ end
629
+
630
+ def add_duty_fields(xml, options)
631
+ duty = options[:duty]
632
+ if duty.is_a?(Hash)
633
+ xml.duty do
634
+ xml.amount(amount(duty[:amount].to_i))
635
+ xml.name(duty[:name])
636
+ xml.description(duty[:description])
637
+ end
638
+ end
639
+ end
640
+
641
+ def add_shipping_fields(xml, options)
642
+ shipping = options[:shipping]
643
+ if shipping.is_a?(Hash)
644
+ xml.shipping do
645
+ xml.amount(amount(shipping[:amount].to_i))
646
+ xml.name(shipping[:name])
647
+ xml.description(shipping[:description])
648
+ end
649
+ end
650
+ end
651
+
652
+ def add_tax_exempt_status(xml, options)
653
+ xml.taxExempt(options[:tax_exempt]) if options[:tax_exempt]
654
+ end
655
+
656
+ def add_po_number(xml, options)
657
+ xml.poNumber(options[:po_number]) if options[:po_number]
658
+ end
659
+
593
660
  def create_customer_payment_profile(credit_card, options)
594
661
  commit(:cim_store_update) do |xml|
595
662
  xml.customerProfileId options[:customer_profile_id]
@@ -94,6 +94,7 @@ module ActiveMerchant #:nodoc:
94
94
 
95
95
  def add_customer_data(post, options)
96
96
  post[:email] = options[:email] || "unspecified@example.com"
97
+ post[:customerIp] = options[:ip] if options[:ip]
97
98
  address = options[:billing_address]
98
99
  if(address && post[:card])
99
100
  post[:card][:billingDetails] = {}
@@ -108,6 +108,7 @@ module ActiveMerchant #:nodoc:
108
108
  add_payment_method(post, payment_method)
109
109
  add_customer_data(post, options)
110
110
  add_email(post, options)
111
+ add_3d_secure(post, options)
111
112
  add_echo(post, options)
112
113
 
113
114
  commit(:purchase, post)
@@ -119,6 +120,7 @@ module ActiveMerchant #:nodoc:
119
120
  add_payment_method(post, payment_method)
120
121
  add_customer_data(post, options)
121
122
  add_email(post, options)
123
+ add_3d_secure(post, options)
122
124
  add_echo(post, options)
123
125
 
124
126
  commit(:authorize, post)
@@ -230,6 +232,11 @@ module ActiveMerchant #:nodoc:
230
232
  post[:c3] = options[:email] || 'unspecified@example.com'
231
233
  end
232
234
 
235
+ def add_3d_secure(post, options)
236
+ return unless options[:eci] && options[:xid]
237
+ post[:i8] = "#{options[:eci]}:#{(options[:cavv] || "none")}:#{options[:xid]}"
238
+ end
239
+
233
240
  def add_echo(post, options)
234
241
  # The d2 parameter is used during the certification process
235
242
  # See remote tests for full certification test suite
@@ -240,7 +247,7 @@ module ActiveMerchant #:nodoc:
240
247
  purchase: '1',
241
248
  authorize: '2',
242
249
  capture: '3',
243
- authorize_void:'4',
250
+ authorize_void: '4',
244
251
  refund: '5',
245
252
  credit: '6',
246
253
  purchase_void: '7',
@@ -691,7 +691,8 @@ module ActiveMerchant #:nodoc:
691
691
  end
692
692
 
693
693
  success = response[:decision] == "ACCEPT"
694
- message = @@response_codes[('r' + response[:reasonCode]).to_sym] rescue response[:message]
694
+ message = response[:message]
695
+
695
696
  authorization = success ? [ options[:order_id], response[:requestID], response[:requestToken], action, amount, options[:currency]].compact.join(";") : nil
696
697
 
697
698
  Response.new(success, message, response,
@@ -709,9 +710,10 @@ module ActiveMerchant #:nodoc:
709
710
  xml = REXML::Document.new(xml)
710
711
  if root = REXML::XPath.first(xml, "//c:replyMessage")
711
712
  root.elements.to_a.each do |node|
712
- case node.name
713
+ case node.expanded_name
713
714
  when 'c:reasonCode'
714
- reply[:message] = reply(node.text)
715
+ reply[:reasonCode] = node.text
716
+ reply[:message] = reason_message(node.text)
715
717
  else
716
718
  parse_element(reply, node)
717
719
  end
@@ -728,14 +730,19 @@ module ActiveMerchant #:nodoc:
728
730
  node.elements.each{|e| parse_element(reply, e) }
729
731
  else
730
732
  if node.parent.name =~ /item/
731
- parent = node.parent.name + (node.parent.attributes["id"] ? "_" + node.parent.attributes["id"] : '')
732
- reply[(parent + '_' + node.name).to_sym] = node.text
733
- else
734
- reply[node.name.to_sym] = node.text
733
+ parent = node.parent.name
734
+ parent += '_' + node.parent.attributes["id"] if node.parent.attributes["id"]
735
+ parent += '_'
735
736
  end
737
+ reply["#{parent}#{node.name}".to_sym] ||= node.text
736
738
  end
737
739
  return reply
738
740
  end
741
+
742
+ def reason_message(reason_code)
743
+ return if reason_code.blank?
744
+ @@response_codes[:"r#{reason_code}"]
745
+ end
739
746
  end
740
747
  end
741
748
  end
@@ -0,0 +1,234 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class EbanxGateway < Gateway
4
+ self.test_url = 'https://sandbox.ebanx.com/ws/'
5
+ self.live_url = 'https://api.ebanx.com/ws/'
6
+
7
+ self.supported_countries = ['BR', 'MX']
8
+ self.default_currency = 'USD'
9
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
10
+
11
+ self.homepage_url = 'http://www.ebanx.com/'
12
+ self.display_name = 'Ebanx'
13
+
14
+ CARD_BRAND = {
15
+ visa: "visa",
16
+ master: "master_card",
17
+ american_express: "amex",
18
+ discover: "discover",
19
+ diners_club: "diners"
20
+ }
21
+
22
+ URL_MAP = {
23
+ purchase: "direct",
24
+ authorize: "direct",
25
+ capture: "capture",
26
+ refund: "refund",
27
+ void: "cancel"
28
+ }
29
+
30
+ HTTP_METHOD = {
31
+ purchase: :post,
32
+ authorize: :post,
33
+ capture: :get,
34
+ refund: :post,
35
+ void: :get
36
+ }
37
+
38
+ def initialize(options={})
39
+ requires!(options, :integration_key)
40
+ super
41
+ end
42
+
43
+ def purchase(money, payment, options={})
44
+ post = { payment: {} }
45
+ add_integration_key(post)
46
+ add_operation(post)
47
+ add_invoice(post, money, options)
48
+ add_customer_data(post, payment, options)
49
+ add_payment(post, payment)
50
+ add_address(post, options)
51
+
52
+ commit(:purchase, post)
53
+ end
54
+
55
+ def authorize(money, payment, options={})
56
+ post = { payment: {} }
57
+ add_integration_key(post)
58
+ add_operation(post)
59
+ add_invoice(post, money, options)
60
+ add_customer_data(post, payment, options)
61
+ add_payment(post, payment)
62
+ add_address(post, options)
63
+ post[:payment][:creditcard][:auto_capture] = false
64
+
65
+ commit(:authorize, post)
66
+ end
67
+
68
+ def capture(money, authorization, options={})
69
+ post = {}
70
+ add_integration_key(post)
71
+ post[:hash] = authorization
72
+ post[:amount] = amount(money)
73
+
74
+ commit(:capture, post)
75
+ end
76
+
77
+ def refund(money, authorization, options={})
78
+ post = {}
79
+ add_integration_key(post)
80
+ add_operation(post)
81
+ add_authorization(post, authorization)
82
+ post[:amount] = amount(money)
83
+ post[:description] = options[:description]
84
+
85
+ commit(:refund, post)
86
+ end
87
+
88
+ def void(authorization, options={})
89
+ post = {}
90
+ add_integration_key(post)
91
+ add_authorization(post, authorization)
92
+
93
+ commit(:void, post)
94
+ end
95
+
96
+ def verify(credit_card, options={})
97
+ MultiResponse.run(:use_first_response) do |r|
98
+ r.process { authorize(100, credit_card, options) }
99
+ r.process(:ignore_result) { void(r.authorization, options) }
100
+ end
101
+ end
102
+
103
+ def supports_scrubbing?
104
+ true
105
+ end
106
+
107
+ def scrub(transcript)
108
+ transcript.
109
+ gsub(/(integration_key\\?":\\?")(\d*)/, '\1[FILTERED]').
110
+ gsub(/(card_number\\?":\\?")(\d*)/, '\1[FILTERED]').
111
+ gsub(/(card_cvv\\?":\\?")(\d*)/, '\1[FILTERED]')
112
+ end
113
+
114
+ private
115
+
116
+ def add_integration_key(post)
117
+ post[:integration_key] = @options[:integration_key].to_s
118
+ end
119
+
120
+ def add_operation(post)
121
+ post[:operation] = "request"
122
+ end
123
+
124
+ def add_authorization(post, authorization)
125
+ post[:hash] = authorization
126
+ end
127
+
128
+ def add_customer_data(post, payment, options)
129
+ post[:payment][:name] = payment.name
130
+ post[:payment][:email] = options[:email] || "unspecified@example.com"
131
+ post[:payment][:document] = options[:document]
132
+ end
133
+
134
+ def add_address(post, options)
135
+ if address = options[:billing_address] || options[:address]
136
+ post[:payment][:address] = address[:address1].split[1..-1].join(" ") if address[:address1]
137
+ post[:payment][:street_number] = address[:address1].split.first if address[:address1]
138
+ post[:payment][:city] = address[:city]
139
+ post[:payment][:state] = address[:state]
140
+ post[:payment][:zipcode] = address[:zip]
141
+ post[:payment][:country] = address[:country]
142
+ post[:payment][:phone_number] = address[:phone]
143
+ end
144
+ end
145
+
146
+ def add_invoice(post, money, options)
147
+ post[:payment][:amount_total] = amount(money)
148
+ post[:payment][:currency_code] = (options[:currency] || currency(money))
149
+ post[:payment][:merchant_payment_code] = options[:order_id]
150
+ end
151
+
152
+ def add_payment(post, payment)
153
+ post[:payment][:payment_type_code] = CARD_BRAND[payment.brand.to_sym]
154
+ post[:payment][:creditcard] = {
155
+ card_number: payment.number,
156
+ card_name: payment.name,
157
+ card_due_date: "#{payment.month}/#{payment.year}",
158
+ card_cvv: payment.verification_value
159
+ }
160
+ end
161
+
162
+ def parse(body)
163
+ JSON.parse(body)
164
+ end
165
+
166
+ def commit(action, parameters)
167
+ url = url_for((test? ? test_url : live_url), action, parameters)
168
+ response = parse(ssl_request(HTTP_METHOD[action], url, post_data(action, parameters), {}))
169
+
170
+ success = success_from(action, response)
171
+
172
+ Response.new(
173
+ success,
174
+ message_from(response),
175
+ response,
176
+ authorization: authorization_from(response),
177
+ test: test?,
178
+ error_code: error_code_from(response, success)
179
+ )
180
+ end
181
+
182
+ def success_from(action, response)
183
+ if [:purchase, :capture, :refund].include?(action)
184
+ response.try(:[], "payment").try(:[], "status") == "CO"
185
+ elsif action == :authorize
186
+ response.try(:[], "payment").try(:[], "status") == "PE"
187
+ elsif action == :void
188
+ response.try(:[], "payment").try(:[], "status") == "CA"
189
+ else
190
+ false
191
+ end
192
+ end
193
+
194
+ def message_from(response)
195
+ return response["status_message"] if response["status"] == "ERROR"
196
+ response.try(:[], "payment").try(:[], "transaction_status").try(:[], "description")
197
+ end
198
+
199
+ def authorization_from(response)
200
+ response.try(:[], "payment").try(:[], "hash")
201
+ end
202
+
203
+ def post_data(action, parameters = {})
204
+ return nil if requires_http_get(action)
205
+ return convert_to_url_form_encoded(parameters) if action == :refund
206
+ "request_body=#{parameters.to_json}"
207
+ end
208
+
209
+ def url_for(hostname, action, parameters)
210
+ return hostname + URL_MAP[action] + "?#{convert_to_url_form_encoded(parameters)}" if requires_http_get(action)
211
+ hostname + URL_MAP[action]
212
+ end
213
+
214
+ def requires_http_get(action)
215
+ return true if [:capture, :void].include?(action)
216
+ false
217
+ end
218
+
219
+ def convert_to_url_form_encoded(parameters)
220
+ parameters.map do |key, value|
221
+ next if value != false && value.blank?
222
+ "#{key}=#{value}"
223
+ end.compact.join("&")
224
+ end
225
+
226
+ def error_code_from(response, success)
227
+ unless success
228
+ return response["status_code"] if response["status"] == "ERROR"
229
+ response.try(:[], "payment").try(:[], "transaction_status").try(:[], "code")
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end