activemerchant 1.80.0 → 1.81.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: 2a370014b9b3f630ef74788e39ed2f70166e825b
4
- data.tar.gz: ada89dc3c89779f200933eb9ce9ec03af4e4b68b
3
+ metadata.gz: 9a3a23a8a15d84e0c61a12458a7198886b2ab517
4
+ data.tar.gz: 256e0471468a250883d3b57eb8af909c4ae20a94
5
5
  SHA512:
6
- metadata.gz: 9e98a44739681282c53fd421e39903830656a97bab74dab4c8d7ea34cf103f09b2c091fd6941b97cc72962e43f1aba3f104c89074089b5b2d3c53ae215f26a33
7
- data.tar.gz: 255f0dab0c7aaf55f4d111fac977e5cd058fa5e8a86b4d6f424fe87bdb056d730bc5ea50830887fa1f69f72c58c24929ce3c3bf296f05b3c0196803e5a2702b5
6
+ metadata.gz: 95115d4f69d651b30e3129af8bb20b37af280d4de58c3b4a9fa29954c5bc605b4c0226c3555d08ec2c7f2b41b7cdae16081f634199adf22e5caa69605d733ceb
7
+ data.tar.gz: b711f96170b72145fa4ef79b79778c26c4396a0d51fdfb4af1aaeb7e05a56a7dd7c8a9bd8f03fe4ac3cf08a3a86944c9925a373206ff34a937526c2f4db305e8
data/CHANGELOG CHANGED
@@ -1,6 +1,22 @@
1
1
  = ActiveMerchant CHANGELOG
2
2
 
3
3
  == HEAD
4
+ * GlobalCollect: Don't overwrite contactDetails [curiousepic] #2915
5
+ * Pin Payments: Pass reference for statement desc [curiousepic] #2919
6
+ * FirstData: introduce v27 gateway [shasum] #2912
7
+ * Stripe: Fix contactless magstripe support [abhiin1947] #2917
8
+ * ANET: Expose full response code [curiousepic] #2924
9
+ * Global Collect: Fix customer data field structure [curiousepic] #2929
10
+ * Adyen: Set Default Name for Apple Pay Transactions [nfarve] #2930
11
+ * Beanstream: Update to use api key with login credentials [nfarve] #2934
12
+ * CT Payments: Fix a typo in the live URL scheme [bpollack] #2936
13
+ * CyberSource: Don't throw exceptions on HTML responses [bpollack] #2937
14
+ * CyberSource: Remove extraneous parameter blocking echecks [chriscz] #2861
15
+ * FirstPay: Update Fields For Recurring Payments [nfarve] #2940
16
+ * Remove unused handle_response method [bl] #2309
17
+ * Barclaycard Smartpay: bump API version to v30 [bpollack] #2941
18
+ * Safecharge: Remove duplicate supported country [curiousepic]
19
+ * Payflow Express: Use SHIPTONAME instead of `full_name` for shipping address [filipebarcos] #2945
4
20
 
5
21
  == Version 1.80.0 (July 4, 2018)
6
22
  * Default SSL min_version to TLS 1.1 to comply with June 30 PCI DSS deadline [bdewater] #2909
@@ -34,6 +50,7 @@
34
50
  * Redsys: Fix payments with cc token [Leonardo Diez] #2586
35
51
  * Redsys: Missing cardnumber params in xml_signed_fields [nerburish] #2628
36
52
  * Bogus: allow authorizing with a tokenized card [Azdaroth] #2703
53
+ * CT Payment: Add new gateway [nfarve] #2911
37
54
 
38
55
  == Version 1.79.2 (June 2, 2018)
39
56
  * Fix Gateway#max_version= overwriting min_version [bdewater]
@@ -179,6 +179,7 @@ module ActiveMerchant #:nodoc:
179
179
  }
180
180
 
181
181
  card.delete_if{|k,v| v.blank? }
182
+ card[:holderName] ||= 'Not Provided' if credit_card.is_a?(NetworkTokenizationCreditCard)
182
183
  requires!(card, :expiryMonth, :expiryYear, :holderName, :number)
183
184
  post[:card] = card
184
185
  end
@@ -863,6 +863,10 @@ module ActiveMerchant
863
863
  (empty?(element.content) ? nil : element.content)
864
864
  end
865
865
 
866
+ response[:full_response_code] = if(element = doc.at_xpath('//messages/message/code'))
867
+ (empty?(element.content) ? nil : element.content)
868
+ end
869
+
866
870
  response
867
871
  end
868
872
 
@@ -13,6 +13,8 @@ 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'
17
+
16
18
  def initialize(options = {})
17
19
  requires!(options, :company, :merchant, :password)
18
20
  super
@@ -222,11 +224,11 @@ module ActiveMerchant #:nodoc:
222
224
  def build_url(action)
223
225
  case action
224
226
  when 'store'
225
- "#{test? ? self.test_url : self.live_url}/Recurring/v12/storeToken"
227
+ "#{test? ? self.test_url : self.live_url}/Recurring/#{API_VERSION}/storeToken"
226
228
  when 'finalize3ds'
227
- "#{test? ? self.test_url : self.live_url}/Payment/v12/authorise3d"
229
+ "#{test? ? self.test_url : self.live_url}/Payment/#{API_VERSION}/authorise3d"
228
230
  else
229
- "#{test? ? self.test_url : self.live_url}/Payment/v12/#{action}"
231
+ "#{test? ? self.test_url : self.live_url}/Payment/#{API_VERSION}/#{action}"
230
232
  end
231
233
  end
232
234
 
@@ -199,6 +199,7 @@ module ActiveMerchant #:nodoc:
199
199
  transcript.
200
200
  gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
201
201
  gsub(/(&?password=)[^&\s]*(&?)/, '\1[FILTERED]\2').
202
+ gsub(/(&?passcode=)[^&\s]*(&?)/, '\1[FILTERED]\2').
202
203
  gsub(/(&?trnCardCvd=)\d*(&?)/, '\1[FILTERED]\2').
203
204
  gsub(/(&?trnCardNumber=)\d*(&?)/, '\1[FILTERED]\2')
204
205
  end
@@ -156,7 +156,6 @@ module ActiveMerchant #:nodoc:
156
156
 
157
157
  def capture(money, authorization, options = {})
158
158
  reference, _, _ = split_auth(authorization)
159
-
160
159
  post = {}
161
160
  add_amount(post, money)
162
161
  add_reference(post, reference)
@@ -313,7 +312,6 @@ module ActiveMerchant #:nodoc:
313
312
  post[:serviceVersion] = SP_SERVICE_VERSION
314
313
  post[:responseFormat] = 'QS'
315
314
  post[:cardValidation] = (options[:cardValidation].to_i == 1) || '0'
316
-
317
315
  post[:operationType] = options[:operationType] || options[:operation] || secure_profile_action(:new)
318
316
  post[:customerCode] = options[:billing_id] || options[:vault_id] || false
319
317
  post[:status] = options[:status]
@@ -462,6 +460,7 @@ module ActiveMerchant #:nodoc:
462
460
  params[:username] = @options[:user] if @options[:user]
463
461
  params[:password] = @options[:password] if @options[:password]
464
462
  params[:merchant_id] = @options[:login]
463
+ params[:passcode] = @options[:api_key]
465
464
  end
466
465
  params[:vbvEnabled] = '0'
467
466
  params[:scEnabled] = '0'
@@ -0,0 +1,267 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class CtPaymentGateway < Gateway
4
+ self.test_url = 'https://test.ctpaiement.ca/v1/'
5
+ self.live_url = 'https://www.ctpaiement.com/v1/'
6
+
7
+ self.supported_countries = ['US', 'CA']
8
+ self.default_currency = 'CAD'
9
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
10
+
11
+ self.homepage_url = 'http://www.ct-payment.com/'
12
+ self.display_name = 'CT Payment'
13
+
14
+ STANDARD_ERROR_CODE_MAPPING = {
15
+ '14' => STANDARD_ERROR_CODE[:invalid_number],
16
+ '05' => STANDARD_ERROR_CODE[:card_declined],
17
+ 'M6' => STANDARD_ERROR_CODE[:card_declined],
18
+ '9068' => STANDARD_ERROR_CODE[:incorrect_number],
19
+ '9067' => STANDARD_ERROR_CODE[:incorrect_number]
20
+ }
21
+ CARD_BRAND = {
22
+ 'american_express' => 'A',
23
+ 'master' => 'M',
24
+ 'diners_club' => 'I',
25
+ 'visa' => 'V',
26
+ 'discover' => 'O'
27
+ }
28
+
29
+ def initialize(options={})
30
+ requires!(options, :api_key, :company_number, :merchant_number)
31
+ super
32
+ end
33
+
34
+ def purchase(money, payment, options={})
35
+ requires!(options, :order_id)
36
+ post = {}
37
+ add_terminal_number(post, options)
38
+ add_money(post, money)
39
+ add_operator_id(post, options)
40
+ add_invoice(post, money, options)
41
+ add_payment(post, payment)
42
+ add_address(post, payment, options)
43
+ add_customer_data(post, options)
44
+
45
+ payment.is_a?(String) ? commit('purchaseWithToken', post) : commit('purchase', post)
46
+ end
47
+
48
+ def authorize(money, payment, options={})
49
+ requires!(options, :order_id)
50
+ post = {}
51
+ add_money(post, money)
52
+ add_terminal_number(post, options)
53
+ add_operator_id(post, options)
54
+ add_invoice(post, money, options)
55
+ add_payment(post, payment)
56
+ add_address(post, payment, options)
57
+ add_customer_data(post, options)
58
+
59
+ payment.is_a?(String) ? commit('preAuthorizationWithToken', post) : commit('preAuthorization', post)
60
+ end
61
+
62
+ def capture(money, authorization, options={})
63
+ requires!(options, :order_id)
64
+ post = {}
65
+ add_invoice(post, money, options)
66
+ add_money(post, money)
67
+ add_customer_data(post, options)
68
+ transaction_number, authorization_number, invoice_number = split_authorization(authorization)
69
+ post[:OriginalTransactionNumber] = transaction_number
70
+ post[:OriginalAuthorizationNumber] = authorization_number
71
+ post[:OriginalInvoiceNumber] = invoice_number
72
+
73
+ commit('completion', post)
74
+ end
75
+
76
+ def refund(money, authorization, options={})
77
+ requires!(options, :order_id)
78
+ post = {}
79
+ add_invoice(post, money, options)
80
+ add_money(post, money)
81
+ add_customer_data(post, options)
82
+ transaction_number, _, invoice_number = split_authorization(authorization)
83
+ post[:OriginalTransactionNumber] = transaction_number
84
+ post[:OriginalInvoiceNumber] = invoice_number
85
+
86
+ commit('refundWithoutCard', post)
87
+ end
88
+
89
+ def credit(money, payment, options={})
90
+ requires!(options, :order_id)
91
+ post = {}
92
+ add_terminal_number(post, options)
93
+ add_money(post, money)
94
+ add_operator_id(post, options)
95
+ add_invoice(post, money, options)
96
+ add_payment(post, payment)
97
+ add_address(post, payment, options)
98
+ add_customer_data(post, options)
99
+
100
+ payment.is_a?(String) ? commit('refundWithToken', post) : commit('refund', post)
101
+ end
102
+
103
+ def void(authorization, options={})
104
+ post = {}
105
+ post[:InputType] = 'I'
106
+ post[:LanguageCode] = 'E'
107
+ transaction_number, _, invoice_number = split_authorization(authorization)
108
+ post[:OriginalTransactionNumber] = transaction_number
109
+ post[:OriginalInvoiceNumber] = invoice_number
110
+ add_operator_id(post, options)
111
+ add_customer_data(post, options)
112
+
113
+ commit('void', post)
114
+ end
115
+
116
+ def verify(credit_card, options={})
117
+ requires!(options, :order_id)
118
+ post = {}
119
+ add_terminal_number(post, options)
120
+ add_operator_id(post, options)
121
+ add_invoice(post,0, options)
122
+ add_payment(post, credit_card)
123
+ add_customer_data(post, options)
124
+
125
+ commit('verifyAccount', post)
126
+ end
127
+
128
+ def store(credit_card, options={})
129
+ requires!(options, :email)
130
+ post = {
131
+ LanguageCode: 'E',
132
+ Name: credit_card.name.rjust(50, ' '),
133
+ Email: options[:email].rjust(240, ' ')
134
+ }
135
+ add_operator_id(post, options)
136
+ add_payment(post, credit_card)
137
+ add_customer_data(post, options)
138
+
139
+ commit('recur/AddUser', post)
140
+ end
141
+
142
+ def supports_scrubbing?
143
+ true
144
+ end
145
+
146
+ def scrub(transcript)
147
+ transcript.
148
+ gsub(%r((&?auth-api-key=)[^&]*)i, '\1[FILTERED]').
149
+ gsub(%r((&?payload=)[a-zA-Z%0-9=]+)i, '\1[FILTERED]').
150
+ gsub(%r((&?token:)[^&]*)i, '\1[FILTERED]').
151
+ gsub(%r((&?cardNumber:)[^&]*)i, '\1[FILTERED]')
152
+ end
153
+
154
+ private
155
+
156
+ def add_terminal_number(post, options)
157
+ post[:MerchantTerminalNumber] = options[:merchant_terminal_number] || ' ' * 5
158
+ end
159
+
160
+ def add_money(post, money)
161
+ post[:Amount] = money.to_s.rjust(11,'0')
162
+ end
163
+
164
+ def add_operator_id(post, options)
165
+ post[:OperatorID] = options[:operator_id] || '0' * 8
166
+ end
167
+
168
+ def add_customer_data(post, options)
169
+ post[:CustomerNumber] = options[:customer_number] || '0' * 8
170
+ end
171
+
172
+ def add_address(post, creditcard, options)
173
+ if address = options[:billing_address] || options[:address]
174
+ post[:CardHolderAddress] = ("#{address[:address1]} #{address[:address2]}").rjust(20, ' ')
175
+ post[:CardHolderPostalCode] = address[:zip].gsub(/\s+/, '').rjust(9, ' ')
176
+ end
177
+ end
178
+
179
+ def add_invoice(post, money, options)
180
+ post[:CurrencyCode] = options[:currency] || (currency(money) if money)
181
+ post[:InvoiceNumber] = options[:order_id].rjust(12,'0')
182
+ post[:InputType] = 'I'
183
+ post[:LanguageCode] = 'E'
184
+ end
185
+
186
+ def add_payment(post, payment)
187
+ if payment.is_a?(String)
188
+ post[:Token] = split_authorization(payment)[3].strip
189
+ else
190
+ post[:CardType] = CARD_BRAND[payment.brand] || ' '
191
+ post[:CardNumber] = payment.number.rjust(40,' ')
192
+ post[:ExpirationDate] = expdate(payment)
193
+ post[:Cvv2Cvc2Number] = payment.verification_value
194
+ end
195
+ end
196
+
197
+ def parse(body)
198
+ JSON.parse(body)
199
+ end
200
+
201
+ def split_authorization(authorization)
202
+ authorization.split(';')
203
+ end
204
+
205
+ def commit_raw(action, parameters)
206
+ url = (test? ? test_url : live_url) + action
207
+ response = parse(ssl_post(url, post_data(action, parameters)))
208
+
209
+ final_response = Response.new(
210
+ success_from(response),
211
+ message_from(response),
212
+ response,
213
+ authorization: authorization_from(response),
214
+ avs_result: AVSResult.new(code: response['avsStatus']),
215
+ cvv_result: CVVResult.new(response['cvv2Cvc2Status']),
216
+ test: test?,
217
+ error_code: error_code_from(response)
218
+ )
219
+ end
220
+
221
+ def commit(action, parameters)
222
+ if action == 'void'
223
+ commit_raw(action, parameters)
224
+ else
225
+ MultiResponse.run(true) do |r|
226
+ r.process { commit_raw(action, parameters)}
227
+ r.process {
228
+ split_auth = split_authorization(r.authorization)
229
+ auth = (action.include?('recur')? split_auth[4] : split_auth[0])
230
+ action.include?('recur') ? commit_raw('recur/ack', {ID: auth}) : commit_raw('ack', {TransactionNumber: auth})
231
+ }
232
+ end
233
+ end
234
+ end
235
+
236
+ def success_from(response)
237
+ return true if response['returnCode'] == ' 00'
238
+ return true if response['returnCode'] == 'true'
239
+ return true if response['recurReturnCode'] == ' 00'
240
+ return false
241
+ end
242
+
243
+ def message_from(response)
244
+ response['errorDescription'] || (response['terminalDisp'].strip if response['terminalDisp'])
245
+ end
246
+
247
+ def authorization_from(response)
248
+ "#{response['transactionNumber']};#{response['authorizationNumber']};"\
249
+ "#{response['invoiceNumber']};#{response['token']};#{response['id']}"
250
+ end
251
+
252
+ def post_data(action, parameters = {})
253
+ parameters[:CompanyNumber] = @options[:company_number]
254
+ parameters[:MerchantNumber] = @options[:merchant_number]
255
+ parameters = parameters.collect do |key, value|
256
+ "#{key}=#{value}" unless (value.nil? || value.empty?)
257
+ end.join('&')
258
+ payload = Base64.strict_encode64(parameters)
259
+ "auth-api-key=#{@options[:api_key]}&payload=#{payload}".strip
260
+ end
261
+
262
+ def error_code_from(response)
263
+ STANDARD_ERROR_CODE_MAPPING[response['returnCode'].strip || response['recurReturnCode'.strip]] unless success_from(response)
264
+ end
265
+ end
266
+ end
267
+ end
@@ -356,7 +356,7 @@ module ActiveMerchant #:nodoc:
356
356
  add_subscription(xml, options)
357
357
  if options[:setup_fee]
358
358
  if card_brand(payment_method) == 'check'
359
- add_check_service(xml, options)
359
+ add_check_service(xml)
360
360
  else
361
361
  add_purchase_service(xml, payment_method, options)
362
362
  add_payment_network_token(xml) if network_tokenization?(payment_method)
@@ -708,9 +708,13 @@ module ActiveMerchant #:nodoc:
708
708
  # Response object
709
709
  def commit(request, action, amount, options)
710
710
  begin
711
- response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(request, options)))
711
+ raw_response = ssl_post(test? ? self.test_url : self.live_url, build_request(request, options))
712
712
  rescue ResponseError => e
713
- response = parse(e.response.body)
713
+ raw_response = e.response.body
714
+ end
715
+
716
+ begin
717
+ response = parse(raw_response)
714
718
  rescue REXML::ParseException => e
715
719
  response = { message: e.to_s }
716
720
  end
@@ -93,8 +93,9 @@ module ActiveMerchant #:nodoc:
93
93
  post[:card_exp] = expdate(payment)
94
94
  post[:cvv2] = payment.verification_value
95
95
  post[:recurring] = options[:recurring] if options[:recurring]
96
- post[:recurringStartDate] = options[:recurring_start_date] if options[:recurring_start_date]
97
- post[:recurringEndDate] = options[:recurring_end_date] if options[:recurring_end_date]
96
+ post[:recurring_start_date] = options[:recurring_start_date] if options[:recurring_start_date]
97
+ post[:recurring_end_date] = options[:recurring_end_date] if options[:recurring_end_date]
98
+ post[:recurring_type] = options[:recurring_type] if options[:recurring_type]
98
99
  end
99
100
 
100
101
  def add_reference(post, action, money, authorization)
@@ -0,0 +1,444 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class FirstdataE4V27Gateway < Gateway
4
+ self.test_url = 'https://api.demo.globalgatewaye4.firstdata.com/transaction/v27'
5
+ self.live_url = 'https://api.globalgatewaye4.firstdata.com/transaction/v27'
6
+
7
+ TRANSACTIONS = {
8
+ sale: '00',
9
+ authorization: '01',
10
+ verify: '05',
11
+ capture: '32',
12
+ void: '33',
13
+ credit: '34',
14
+ store: '05'
15
+ }
16
+
17
+ SUCCESS = 'true'
18
+
19
+ SENSITIVE_FIELDS = [:cvdcode, :expiry_date, :card_number]
20
+
21
+ BRANDS = {
22
+ :visa => 'Visa',
23
+ :master => 'Mastercard',
24
+ :american_express => 'American Express',
25
+ :jcb => 'JCB',
26
+ :discover => 'Discover'
27
+ }
28
+
29
+ DEFAULT_ECI = '07'
30
+
31
+ self.supported_cardtypes = BRANDS.keys
32
+ self.supported_countries = ['CA', 'US']
33
+ self.default_currency = 'USD'
34
+ self.homepage_url = 'http://www.firstdata.com'
35
+ self.display_name = 'FirstData Global Gateway e4 v27'
36
+
37
+ STANDARD_ERROR_CODE_MAPPING = {
38
+ # Bank error codes: https://support.payeezy.com/hc/en-us/articles/203730509-First-Data-Global-Gateway-e4-Bank-Response-Codes
39
+ '201' => STANDARD_ERROR_CODE[:incorrect_number],
40
+ '531' => STANDARD_ERROR_CODE[:invalid_cvc],
41
+ '503' => STANDARD_ERROR_CODE[:invalid_cvc],
42
+ '811' => STANDARD_ERROR_CODE[:invalid_cvc],
43
+ '605' => STANDARD_ERROR_CODE[:invalid_expiry_date],
44
+ '522' => STANDARD_ERROR_CODE[:expired_card],
45
+ '303' => STANDARD_ERROR_CODE[:card_declined],
46
+ '530' => STANDARD_ERROR_CODE[:card_declined],
47
+ '401' => STANDARD_ERROR_CODE[:call_issuer],
48
+ '402' => STANDARD_ERROR_CODE[:call_issuer],
49
+ '501' => STANDARD_ERROR_CODE[:pickup_card],
50
+ # Ecommerce error codes: https://support.payeezy.com/hc/en-us/articles/203730499-eCommerce-Response-Codes-ETG-e4-Transaction-Gateway-Codes
51
+ '22' => STANDARD_ERROR_CODE[:invalid_number],
52
+ '25' => STANDARD_ERROR_CODE[:invalid_expiry_date],
53
+ '31' => STANDARD_ERROR_CODE[:incorrect_cvc],
54
+ '44' => STANDARD_ERROR_CODE[:incorrect_zip],
55
+ '42' => STANDARD_ERROR_CODE[:processing_error]
56
+ }
57
+
58
+ def initialize(options = {})
59
+ requires!(options, :login, :password, :key_id, :hmac_key)
60
+ @options = options
61
+
62
+ super
63
+ end
64
+
65
+ def authorize(money, credit_card_or_store_authorization, options = {})
66
+ commit(:authorization, build_sale_or_authorization_request(money, credit_card_or_store_authorization, options))
67
+ end
68
+
69
+ def purchase(money, credit_card_or_store_authorization, options = {})
70
+ commit(:sale, build_sale_or_authorization_request(money, credit_card_or_store_authorization, options))
71
+ end
72
+
73
+ def capture(money, authorization, options = {})
74
+ commit(:capture, build_capture_or_credit_request(money, authorization, options))
75
+ end
76
+
77
+ def void(authorization, options = {})
78
+ commit(:void, build_capture_or_credit_request(money_from_authorization(authorization), authorization, options))
79
+ end
80
+
81
+ def refund(money, authorization, options = {})
82
+ commit(:credit, build_capture_or_credit_request(money, authorization, options))
83
+ end
84
+
85
+ def verify(credit_card, options = {})
86
+ commit(:verify, build_sale_or_authorization_request(0, credit_card, options))
87
+ end
88
+
89
+ # Tokenize a credit card with TransArmor
90
+ #
91
+ # The TransArmor token and other card data necessary for subsequent
92
+ # transactions is stored in the response's +authorization+ attribute.
93
+ # The authorization string may be passed to +authorize+ and +purchase+
94
+ # instead of a +ActiveMerchant::Billing::CreditCard+ instance.
95
+ #
96
+ # TransArmor support must be explicitly activated on your gateway
97
+ # account by FirstData. If your authorization string is empty, contact
98
+ # FirstData support for account setup assistance.
99
+ #
100
+ # https://support.payeezy.com/hc/en-us/articles/203731189-TransArmor-Tokenization
101
+ def store(credit_card, options = {})
102
+ commit(:store, build_store_request(credit_card, options), credit_card)
103
+ end
104
+
105
+ def verify_credentials
106
+ response = void('0')
107
+ response.message != 'Unauthorized Request. Bad or missing credentials.'
108
+ end
109
+
110
+ def supports_scrubbing?
111
+ true
112
+ end
113
+
114
+ def scrub(transcript)
115
+ transcript
116
+ .gsub(%r((<Card_Number>).+(</Card_Number>)), '\1[FILTERED]\2')
117
+ .gsub(%r((<CVDCode>).+(</CVDCode>)), '\1[FILTERED]\2')
118
+ .gsub(%r((<Password>).+(</Password>))i, '\1[FILTERED]\2')
119
+ .gsub(%r((<CAVV>).+(</CAVV>)), '\1[FILTERED]\2')
120
+ .gsub(%r((CARD NUMBER\s+: )#+\d+), '\1[FILTERED]')
121
+ end
122
+
123
+ def supports_network_tokenization?
124
+ true
125
+ end
126
+
127
+ private
128
+
129
+ def build_request(action, body)
130
+ xml = Builder::XmlMarkup.new
131
+
132
+ xml.instruct!
133
+ xml.tag! 'Transaction', xmlns: 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes' do
134
+ add_credentials(xml)
135
+ add_transaction_type(xml, action)
136
+ xml << body
137
+ end
138
+
139
+ xml.target!
140
+ end
141
+
142
+ def build_sale_or_authorization_request(money, credit_card_or_store_authorization, options)
143
+ xml = Builder::XmlMarkup.new
144
+
145
+ add_amount(xml, money, options)
146
+
147
+ if credit_card_or_store_authorization.is_a? String
148
+ add_credit_card_token(xml, credit_card_or_store_authorization, options)
149
+ else
150
+ add_credit_card(xml, credit_card_or_store_authorization, options)
151
+ end
152
+
153
+ add_address(xml, options)
154
+ add_customer_data(xml, options)
155
+ add_invoice(xml, options)
156
+ add_tax_fields(xml, options)
157
+ add_level_3(xml, options)
158
+
159
+ xml.target!
160
+ end
161
+
162
+ def build_capture_or_credit_request(money, identification, options)
163
+ xml = Builder::XmlMarkup.new
164
+
165
+ add_identification(xml, identification)
166
+ add_amount(xml, money, options)
167
+ add_customer_data(xml, options)
168
+ add_card_authentication_data(xml, options)
169
+
170
+ xml.target!
171
+ end
172
+
173
+ def build_store_request(credit_card, options)
174
+ xml = Builder::XmlMarkup.new
175
+
176
+ add_credit_card(xml, credit_card, options)
177
+ add_address(xml, options)
178
+ add_customer_data(xml, options)
179
+
180
+ xml.target!
181
+ end
182
+
183
+ def add_credentials(xml)
184
+ xml.tag! 'ExactID', @options[:login]
185
+ xml.tag! 'Password', @options[:password]
186
+ end
187
+
188
+ def add_transaction_type(xml, action)
189
+ xml.tag! 'Transaction_Type', TRANSACTIONS[action]
190
+ end
191
+
192
+ def add_identification(xml, identification)
193
+ authorization_num, transaction_tag, _ = identification.split(';')
194
+
195
+ xml.tag! 'Authorization_Num', authorization_num
196
+ xml.tag! 'Transaction_Tag', transaction_tag
197
+ end
198
+
199
+ def add_amount(xml, money, options)
200
+ currency_code = options[:currency] || default_currency
201
+ xml.tag! 'DollarAmount', localized_amount(money, currency_code)
202
+ xml.tag! 'Currency', currency_code
203
+ end
204
+
205
+ def add_credit_card(xml, credit_card, options)
206
+ if credit_card.respond_to?(:track_data) && credit_card.track_data.present?
207
+ xml.tag! 'Track1', credit_card.track_data
208
+ xml.tag! 'Ecommerce_Flag', 'R'
209
+ else
210
+ xml.tag! 'Card_Number', credit_card.number
211
+ xml.tag! 'Expiry_Date', expdate(credit_card)
212
+ xml.tag! 'CardHoldersName', credit_card.name
213
+ xml.tag! 'CardType', card_type(credit_card.brand)
214
+
215
+ add_credit_card_eci(xml, credit_card, options)
216
+ add_credit_card_verification_strings(xml, credit_card, options)
217
+ end
218
+ end
219
+
220
+ def add_credit_card_eci(xml, credit_card, options)
221
+ eci = if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == 'discover'
222
+ # Discover requires any Apple Pay transaction, regardless of in-app
223
+ # or web, and regardless of the ECI contained in the PKPaymentToken,
224
+ # to have an ECI value explicitly of 04.
225
+ '04'
226
+ else
227
+ (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI
228
+ end
229
+
230
+ xml.tag! 'Ecommerce_Flag', eci.to_s =~ /^[0-9]+$/ ? eci.to_s.rjust(2, '0') : eci
231
+ end
232
+
233
+ def add_credit_card_verification_strings(xml, credit_card, options)
234
+ if credit_card.is_a?(NetworkTokenizationCreditCard)
235
+ add_network_tokenization_credit_card(xml, credit_card)
236
+ else
237
+ if credit_card.verification_value?
238
+ xml.tag! 'CVD_Presence_Ind', '1'
239
+ xml.tag! 'CVDCode', credit_card.verification_value
240
+ end
241
+
242
+ add_card_authentication_data(xml, options)
243
+ end
244
+ end
245
+
246
+ def add_network_tokenization_credit_card(xml, credit_card)
247
+ case card_brand(credit_card).to_sym
248
+ when :american_express
249
+ cryptogram = Base64.decode64(credit_card.payment_cryptogram)
250
+ xml.tag!('XID', Base64.encode64(cryptogram[20...40]))
251
+ xml.tag!('CAVV', Base64.encode64(cryptogram[0...20]))
252
+ else
253
+ xml.tag!('XID', credit_card.transaction_id) if credit_card.transaction_id
254
+ xml.tag!('CAVV', credit_card.payment_cryptogram)
255
+ end
256
+ end
257
+
258
+ def add_card_authentication_data(xml, options)
259
+ xml.tag! 'CAVV', options[:cavv]
260
+ xml.tag! 'XID', options[:xid]
261
+ end
262
+
263
+ def add_credit_card_token(xml, store_authorization, options)
264
+ params = store_authorization.split(';')
265
+ credit_card = CreditCard.new(
266
+ :brand => params[1],
267
+ :first_name => params[2],
268
+ :last_name => params[3],
269
+ :month => params[4],
270
+ :year => params[5])
271
+
272
+ xml.tag! 'TransarmorToken', params[0]
273
+ xml.tag! 'Expiry_Date', expdate(credit_card)
274
+ xml.tag! 'CardHoldersName', credit_card.name
275
+ xml.tag! 'CardType', card_type(credit_card.brand)
276
+ add_card_authentication_data(xml, options)
277
+ end
278
+
279
+ def add_customer_data(xml, options)
280
+ xml.tag! 'Customer_Ref', options[:customer] if options[:customer]
281
+ xml.tag! 'Client_IP', options[:ip] if options[:ip]
282
+ xml.tag! 'Client_Email', options[:email] if options[:email]
283
+ end
284
+
285
+ def add_address(xml, options)
286
+ if (address = options[:billing_address] || options[:address])
287
+ xml.tag! 'Address' do
288
+ xml.tag! 'Address1', address[:address1]
289
+ xml.tag! 'Address2', address[:address2] if address[:address2]
290
+ xml.tag! 'City', address[:city]
291
+ xml.tag! 'State', address[:state]
292
+ xml.tag! 'Zip', address[:zip]
293
+ xml.tag! 'CountryCode', address[:country]
294
+ end
295
+ xml.tag! 'ZipCode', address[:zip]
296
+ end
297
+ end
298
+
299
+ def add_invoice(xml, options)
300
+ xml.tag! 'Reference_No', options[:order_id]
301
+ xml.tag! 'Reference_3', options[:description] if options[:description]
302
+ end
303
+
304
+ def add_tax_fields(xml, options)
305
+ xml.tag! 'Tax1Amount', options[:tax1_amount] if options[:tax1_amount]
306
+ xml.tag! 'Tax1Number', options[:tax1_number] if options[:tax1_number]
307
+ end
308
+
309
+ def add_level_3(xml, options)
310
+ xml.tag!('Level3') { |x| x << options[:level_3] } if options[:level_3]
311
+ end
312
+
313
+ def expdate(credit_card)
314
+ "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
315
+ end
316
+
317
+ def card_type(credit_card_brand)
318
+ BRANDS[credit_card_brand.to_sym] if credit_card_brand
319
+ end
320
+
321
+ def commit(action, data, credit_card = nil)
322
+ url = (test? ? self.test_url : self.live_url)
323
+ request = build_request(action, data)
324
+ begin
325
+ response = parse(ssl_post(url, request, headers('POST', url, request)))
326
+ rescue ResponseError => e
327
+ response = parse_error(e.response)
328
+ end
329
+
330
+ Response.new(successful?(response), message_from(response), response,
331
+ :test => test?,
332
+ :authorization => successful?(response) ? response_authorization(action, response, credit_card) : '',
333
+ :avs_result => {:code => response[:avs]},
334
+ :cvv_result => response[:cvv2],
335
+ :error_code => standard_error_code(response)
336
+ )
337
+ end
338
+
339
+ def headers(method, url, request)
340
+ content_type = 'application/xml'
341
+ content_digest = Digest::SHA1.hexdigest(request)
342
+ sending_time = Time.now.utc.iso8601
343
+ payload = [method, content_type, content_digest, sending_time, url.split('.com')[1]].join("\n")
344
+ hmac = OpenSSL::HMAC.digest('sha1', @options[:hmac_key], payload)
345
+ encoded = Base64.strict_encode64(hmac)
346
+
347
+ {
348
+ 'x-gge4-date' => sending_time,
349
+ 'x-gge4-content-sha1' => content_digest,
350
+ 'Authorization' => 'GGE4_API ' + @options[:key_id].to_s + ':' + encoded,
351
+ 'Accepts' => content_type,
352
+ 'Content-Type' => content_type
353
+ }
354
+ end
355
+
356
+ def successful?(response)
357
+ response[:transaction_approved] == SUCCESS
358
+ end
359
+
360
+ def response_authorization(action, response, credit_card)
361
+ if action == :store
362
+ store_authorization_from(response, credit_card)
363
+ else
364
+ authorization_from(response)
365
+ end
366
+ end
367
+
368
+ def authorization_from(response)
369
+ if response[:authorization_num] && response[:transaction_tag]
370
+ [
371
+ response[:authorization_num],
372
+ response[:transaction_tag],
373
+ (response[:dollar_amount].to_f * 100).round
374
+ ].join(';')
375
+ else
376
+ ''
377
+ end
378
+ end
379
+
380
+ def store_authorization_from(response, credit_card)
381
+ if response[:transarmor_token].present?
382
+ [
383
+ response[:transarmor_token],
384
+ credit_card.brand,
385
+ credit_card.first_name,
386
+ credit_card.last_name,
387
+ credit_card.month,
388
+ credit_card.year
389
+ ].map { |value| value.to_s.tr(';', '') }.join(';')
390
+ else
391
+ raise StandardError, "TransArmor support is not enabled on your #{display_name} account"
392
+ end
393
+ end
394
+
395
+ def money_from_authorization(auth)
396
+ _, _, amount = auth.split(/;/, 3)
397
+ amount.to_i
398
+ end
399
+
400
+ def message_from(response)
401
+ if(response[:faultcode] && response[:faultstring])
402
+ response[:faultstring]
403
+ elsif(response[:error_number] && response[:error_number] != '0')
404
+ response[:error_description]
405
+ else
406
+ result = (response[:exact_message] || '')
407
+ result << " - #{response[:bank_message]}" if response[:bank_message].present?
408
+ result
409
+ end
410
+ end
411
+
412
+ def parse_error(error)
413
+ {
414
+ :transaction_approved => 'false',
415
+ :error_number => error.code,
416
+ :error_description => error.body,
417
+ :ecommerce_error_code => error.body.gsub(/[^\d]/, '')
418
+ }
419
+ end
420
+
421
+ def standard_error_code(response)
422
+ STANDARD_ERROR_CODE_MAPPING[response[:bank_resp_code] || response[:ecommerce_error_code]]
423
+ end
424
+
425
+ def parse(xml)
426
+ response = {}
427
+ xml = REXML::Document.new(xml)
428
+
429
+ if (root = REXML::XPath.first(xml, '//TransactionResult'))
430
+ parse_elements(response, root)
431
+ end
432
+
433
+ SENSITIVE_FIELDS.each { |key| response.delete(key) }
434
+ response
435
+ end
436
+
437
+ def parse_elements(response, root)
438
+ root.elements.to_a.each do |node|
439
+ response[node.name.gsub(/EXact/, 'Exact').underscore.to_sym] = (node.text || '').strip
440
+ end
441
+ end
442
+ end
443
+ end
444
+ end
@@ -4,7 +4,7 @@ module ActiveMerchant #:nodoc:
4
4
  self.display_name = 'GlobalCollect'
5
5
  self.homepage_url = 'http://www.globalcollect.com/'
6
6
 
7
- self.test_url = 'https://api-sandbox.globalcollect.com/'
7
+ self.test_url = 'https://eu.sandbox.api-ingenico.com/'
8
8
  self.live_url = 'https://api.globalcollect.com/'
9
9
 
10
10
  self.supported_countries = ['AD', 'AE', 'AG', 'AI', 'AL', 'AM', 'AO', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IS', 'IT', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PL', 'PN', 'PS', 'PT', 'PW', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SR', 'ST', 'SV', 'SZ', 'TC', 'TD', 'TG', 'TH', 'TJ', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'US', 'UY', 'UZ', 'VC', 'VE', 'VG', 'VI', 'VN', 'WF', 'WS', 'ZA', 'ZM', 'ZW']
@@ -140,9 +140,6 @@ module ActiveMerchant #:nodoc:
140
140
  end
141
141
 
142
142
  def add_customer_data(post, options, payment = nil)
143
- post['order']['customer'] = {
144
- 'merchantCustomerId' => options[:customer]
145
- }
146
143
  if payment
147
144
  post['order']['customer']['personalInformation'] = {
148
145
  'name' => {
@@ -151,16 +148,11 @@ module ActiveMerchant #:nodoc:
151
148
  }
152
149
  }
153
150
  end
154
- post['order']['companyInformation'] = {
155
- 'name' => options[:company]
156
- }
157
- post['order']['contactDetails'] = {
158
- 'emailAddress' => options[:email]
159
- }
151
+ post['order']['customer']['merchantCustomerId'] = options[:customer] if options[:customer]
152
+ post['order']['customer']['companyInformation']['name'] = options[:company] if options[:company]
153
+ post['order']['customer']['contactDetails']['emailAddress'] = options[:email] if options[:email]
160
154
  if address = options[:billing_address] || options[:address]
161
- post['order']['contactDetails'] = {
162
- 'phoneNumber' => address[:phone]
163
- }
155
+ post['order']['customer']['contactDetails']['phoneNumber'] = address[:phone] if address[:phone]
164
156
  end
165
157
  end
166
158
 
@@ -169,10 +161,10 @@ module ActiveMerchant #:nodoc:
169
161
  post['customer']['address'] = {
170
162
  'countryCode' => address[:country]
171
163
  }
172
- post['customer']['contactDetails'] = {
173
- 'emailAddress' => options[:email],
174
- 'phoneNumber' => address[:phone]
175
- }
164
+ post['customer']['contactDetails']['emailAddress'] = options[:email] if options[:email]
165
+ if address = options[:billing_address] || options[:address]
166
+ post['customer']['contactDetails']['phoneNumber'] = address[:phone] if address[:phone]
167
+ end
176
168
  end
177
169
  end
178
170
 
@@ -103,7 +103,7 @@ module ActiveMerchant #:nodoc:
103
103
  xml.tag! 'Transaction' do
104
104
  xml.tag! 'TranType', 'Credit'
105
105
  xml.tag! 'TranCode', action
106
- if options[:allow_partial_auth] && (action == 'PreAuth' || action == 'Sale')
106
+ if options[:allow_partial_auth] && ['PreAuth', 'Sale'].include?(action)
107
107
  xml.tag! 'PartialAuth', 'Allow'
108
108
  end
109
109
  add_invoice(xml, options[:order_id], nil, options)
@@ -4,26 +4,26 @@ module ActiveMerchant #:nodoc:
4
4
  def email
5
5
  @params['e_mail']
6
6
  end
7
-
7
+
8
8
  def full_name
9
9
  "#{@params['name']} #{@params['lastname']}"
10
10
  end
11
-
11
+
12
12
  def token
13
13
  @params['token']
14
14
  end
15
-
15
+
16
16
  def payer_id
17
17
  @params['payer_id']
18
18
  end
19
-
19
+
20
20
  # Really the shipping country, but it is all the information provided
21
21
  def payer_country
22
22
  address['country']
23
23
  end
24
-
24
+
25
25
  def address
26
- { 'name' => full_name,
26
+ { 'name' => @params['shiptoname'] || full_name,
27
27
  'company' => nil,
28
28
  'address1' => @params['street'],
29
29
  'address2' => @params['shiptostreet2'] || @params['street2'],
@@ -114,6 +114,7 @@ module ActiveMerchant #:nodoc:
114
114
 
115
115
  def add_invoice(post, options)
116
116
  post[:description] = options[:description] || 'Active Merchant Purchase'
117
+ post[:reference] = options[:reference] if options[:reference]
117
118
  end
118
119
 
119
120
  def add_capture(post, options)
@@ -6,7 +6,7 @@ module ActiveMerchant #:nodoc:
6
6
  self.test_url = 'https://process.sandbox.safecharge.com/service.asmx/Process'
7
7
  self.live_url = 'https://process.safecharge.com/service.asmx/Process'
8
8
 
9
- self.supported_countries = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'GR', 'ES', 'FI', 'FR', 'HR', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SE', 'SI', 'SK', 'GB', 'US']
9
+ self.supported_countries = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'GR', 'ES', 'FI', 'FR', 'HR', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'GB', 'US']
10
10
  self.default_currency = 'USD'
11
11
  self.supported_cardtypes = [:visa, :master]
12
12
 
@@ -397,6 +397,7 @@ module ActiveMerchant #:nodoc:
397
397
  if emv_payment?(creditcard)
398
398
  add_emv_creditcard(post, creditcard.icc_data)
399
399
  post[:card][:read_method] = 'contactless' if creditcard.read_method == 'contactless'
400
+ post[:card][:read_method] = 'contactless_magstripe_mode' if creditcard.read_method == 'contactless_magstripe'
400
401
  if creditcard.encrypted_pin_cryptogram.present? && creditcard.encrypted_pin_ksn.present?
401
402
  post[:card][:encrypted_pin] = creditcard.encrypted_pin_cryptogram
402
403
  post[:card][:encrypted_pin_key_id] = creditcard.encrypted_pin_ksn
@@ -177,19 +177,6 @@ module ActiveMerchant
177
177
  end
178
178
  end
179
179
 
180
- def handle_response(response)
181
- if @ignore_http_status then
182
- return response.body
183
- else
184
- case response.code.to_i
185
- when 200...300
186
- response.body
187
- else
188
- raise ResponseError.new(response)
189
- end
190
- end
191
- end
192
-
193
180
  def debug(message, tag = nil)
194
181
  log(:debug, message, tag)
195
182
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveMerchant
2
- VERSION = '1.80.0'
2
+ VERSION = '1.81.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activemerchant
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.80.0
4
+ version: 1.81.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Luetke
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-04 00:00:00.000000000 Z
11
+ date: 2018-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -204,6 +204,7 @@ files:
204
204
  - lib/active_merchant/billing/gateways/conekta.rb
205
205
  - lib/active_merchant/billing/gateways/creditcall.rb
206
206
  - lib/active_merchant/billing/gateways/credorax.rb
207
+ - lib/active_merchant/billing/gateways/ct_payment.rb
207
208
  - lib/active_merchant/billing/gateways/culqi.rb
208
209
  - lib/active_merchant/billing/gateways/cyber_source.rb
209
210
  - lib/active_merchant/billing/gateways/data_cash.rb
@@ -226,6 +227,7 @@ files:
226
227
  - lib/active_merchant/billing/gateways/first_giving.rb
227
228
  - lib/active_merchant/billing/gateways/first_pay.rb
228
229
  - lib/active_merchant/billing/gateways/firstdata_e4.rb
230
+ - lib/active_merchant/billing/gateways/firstdata_e4_v27.rb
229
231
  - lib/active_merchant/billing/gateways/flo2cash.rb
230
232
  - lib/active_merchant/billing/gateways/flo2cash_simple.rb
231
233
  - lib/active_merchant/billing/gateways/forte.rb