activemerchant 1.80.0 → 1.81.0

Sign up to get free protection for your applications and to get access to all the features.
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