activemerchant 1.131.0 → 1.133.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
  SHA256:
3
- metadata.gz: 353c5193fc7b56d5ad6aee9a6ac5d21a691235f5ec3ef324af88e83b8333bffd
4
- data.tar.gz: 7961954d7d9efd01188a87131576407c9e5f4cb5058b5541efe5b41e7547a431
3
+ metadata.gz: 5d705df588bf311375b4dfc46da6912709e0aaecc7bd1800e69af7b61fcfa23b
4
+ data.tar.gz: 80ae4869bcbf875c2ef0bd7b5b257641c1810bc5110958a66d1cda956e6a994a
5
5
  SHA512:
6
- metadata.gz: 56778ad0daa66ed3807ba7c099fbaf4d0686f87a940dafdf5d30faba63024ea6c4c3cf11b853333d0febdf78d4396ba311bd401f92e50ef23fd56977276eea0a
7
- data.tar.gz: d5e52402becff2de0a529bd3de9f70c49e4e4dafa09e797dd54eccb51624de71264d8e961bc1ccc5e5c32ef47ab5b1f8abd3db04fefbfef2cdfd772c5493d05e
6
+ metadata.gz: ba783e41a2872b825b73e5b20c6084e2d4b7200ab899a91ee6aa31977c6e4b3da651f96cd3c895417a8e3e70fdd84cd082a7450eb8a2d35c48b4fc2410ea2c02
7
+ data.tar.gz: b6c1718e9160fdf16745eff1758f5ffc25cbdd6a4bb219c4e13510e3a40b998f9f01b1426985fcb8b987cf73c1c5e8a760a4ffb8b506459ce4501ddb242bedb4
data/CHANGELOG CHANGED
@@ -3,6 +3,28 @@
3
3
 
4
4
  == HEAD
5
5
 
6
+ == Version 1.133.0 (July 20, 2023)
7
+ * CyberSource: remove credentials from tests [bbraschi] #4836
8
+
9
+ == Version 1.132.0 (July 20, 2023)
10
+ * Stripe Payment Intents: Add support for new card on file field [aenand] #4807
11
+ * Commerce Hub: Add `physicalGoodsIndicator` and `schemeReferenceTransactionId` GSFs [sinourain] #4786
12
+ * Nuvei (formerly SafeCharge): Add customer details to credit action [yunnydang] #4820
13
+ * IPG: Update live url to correct endpoint [curiousepic] #4121
14
+ * VPos: Adding Panal Credit Card type [jherreraa] #4814
15
+ * Stripe PI: Update parameters for creation of customer [almalee24] #4782
16
+ * WorldPay: Update xml tag for Credit Cards [almalee24] #4797
17
+ * PaywayDotCom: update `live_url` [jcreiff] #4824
18
+ * Stripe & Stripe PI: Update login key validation [almalee24] #4816
19
+ * CheckoutV2: Parse the AVS and CVV checks more often [aenand] #4822
20
+ * NMI: Add shipping_firstname, shipping_lastname, shipping_email, and surcharge fields [jcreiff] #4825
21
+ * Borgun: Update authorization_from & message_from [almalee24] #4826
22
+ * Kushki: Add Brazil as supported country [almalee24] #4829
23
+ * Adyen: Add additional data for airline and lodging [javierpedrozaing] #4815
24
+ * MIT: Changed how the payload was sent to the gateway [alejandrofloresm] #4655
25
+ * SafeCharge: Add unreferenced_refund field [yunnydang] #4831
26
+ * CyberSource: include `paymentSolution` for ApplePay and GooglePay [bbraschi] #4835
27
+
6
28
  == Version 1.131.0 (June 21, 2023)
7
29
  * Redsys: Add supported countries [jcreiff] #4811
8
30
  * Authorize.net: Truncate nameOnAccount for bank refunds [jcreiff] #4808
@@ -38,6 +38,7 @@ module ActiveMerchant #:nodoc:
38
38
  # * Edenred
39
39
  # * Anda
40
40
  # * Creditos directos (Tarjeta D)
41
+ # * Panal
41
42
  #
42
43
  # For testing purposes, use the 'bogus' credit card brand. This skips the vast majority of
43
44
  # validations, allowing you to focus on your core concerns until you're ready to be more concerned
@@ -130,6 +131,7 @@ module ActiveMerchant #:nodoc:
130
131
  # * +'edenred'+
131
132
  # * +'anda'+
132
133
  # * +'tarjeta-d'+
134
+ # * +'panal'+
133
135
  #
134
136
  # Or, if you wish to test your implementation, +'bogus'+.
135
137
  #
@@ -46,7 +46,8 @@ module ActiveMerchant #:nodoc:
46
46
  'edenred' => ->(num) { num =~ /^637483\d{10}$/ },
47
47
  'anda' => ->(num) { num =~ /^603199\d{10}$/ },
48
48
  'tarjeta-d' => ->(num) { num =~ /^601828\d{10}$/ },
49
- 'hipercard' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), HIPERCARD_RANGES) }
49
+ 'hipercard' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), HIPERCARD_RANGES) },
50
+ 'panal' => ->(num) { num&.size == 16 && in_bin_range?(num.slice(0, 6), PANAL_RANGES) }
50
51
  }
51
52
 
52
53
  SODEXO_NO_LUHN = ->(num) { num =~ /^(505864|505865)\d{10}$/ }
@@ -182,7 +183,8 @@ module ActiveMerchant #:nodoc:
182
183
  (601256..601276),
183
184
  (601640..601652),
184
185
  (601689..601700),
185
- (602011..602050),
186
+ (602011..602048),
187
+ [602050],
186
188
  (630400..630499),
187
189
  (639000..639099),
188
190
  (670000..679999),
@@ -247,6 +249,8 @@ module ActiveMerchant #:nodoc:
247
249
  637568..637568, 637599..637599, 637609..637609, 637612..637612
248
250
  ]
249
251
 
252
+ PANAL_RANGES = [[602049]]
253
+
250
254
  def self.included(base)
251
255
  base.extend(ClassMethods)
252
256
  end
@@ -68,6 +68,8 @@ module ActiveMerchant #:nodoc:
68
68
  add_application_info(post, options)
69
69
  add_level_2_data(post, options)
70
70
  add_level_3_data(post, options)
71
+ add_data_airline(post, options)
72
+ add_data_lodging(post, options)
71
73
  commit('authorise', post, options)
72
74
  end
73
75
 
@@ -291,6 +293,84 @@ module ActiveMerchant #:nodoc:
291
293
  post[:additionalData].compact!
292
294
  end
293
295
 
296
+ def add_data_airline(post, options)
297
+ return unless options[:additional_data_airline]
298
+
299
+ mapper = %w[
300
+ agency_invoice_number
301
+ agency_plan_name
302
+ airline_code
303
+ airline_designator_code
304
+ boarding_fee
305
+ computerized_reservation_system
306
+ customer_reference_number
307
+ document_type
308
+ flight_date
309
+ ticket_issue_address
310
+ ticket_number
311
+ travel_agency_code
312
+ travel_agency_name
313
+ passenger_name
314
+ ].each_with_object({}) { |value, hash| hash["airline.#{value}"] = value }
315
+
316
+ post[:additionalData].merge!(extract_and_transform(mapper, options[:additional_data_airline]))
317
+
318
+ if options[:additional_data_airline][:leg].present?
319
+ leg_data = %w[
320
+ carrier_code
321
+ class_of_travel
322
+ date_of_travel
323
+ depart_airport
324
+ depart_tax
325
+ destination_code
326
+ fare_base_code
327
+ flight_number
328
+ stop_over_code
329
+ ].each_with_object({}) { |value, hash| hash["airline.leg.#{value}"] = value }
330
+
331
+ post[:additionalData].merge!(extract_and_transform(leg_data, options[:additional_data_airline][:leg]))
332
+ end
333
+
334
+ if options[:additional_data_airline][:passenger].present?
335
+ passenger_data = %w[
336
+ date_of_birth
337
+ first_name
338
+ last_name
339
+ telephone_number
340
+ traveller_type
341
+ ].each_with_object({}) { |value, hash| hash["airline.passenger.#{value}"] = value }
342
+
343
+ post[:additionalData].merge!(extract_and_transform(passenger_data, options[:additional_data_airline][:passenger]))
344
+ end
345
+ post[:additionalData].compact!
346
+ end
347
+
348
+ def add_data_lodging(post, options)
349
+ return unless options[:additional_data_lodging]
350
+
351
+ mapper = {
352
+ 'lodging.checkInDate': 'check_in_date',
353
+ 'lodging.checkOutDate': 'check_out_date',
354
+ 'lodging.customerServiceTollFreeNumber': 'customer_service_toll_free_number',
355
+ 'lodging.fireSafetyActIndicator': 'fire_safety_act_indicator',
356
+ 'lodging.folioCashAdvances': 'folio_cash_advances',
357
+ 'lodging.folioNumber': 'folio_number',
358
+ 'lodging.foodBeverageCharges': 'food_beverage_charges',
359
+ 'lodging.noShowIndicator': 'no_show_indicator',
360
+ 'lodging.prepaidExpenses': 'prepaid_expenses',
361
+ 'lodging.propertyPhoneNumber': 'property_phone_number',
362
+ 'lodging.room1.numberOfNights': 'number_of_nights',
363
+ 'lodging.room1.rate': 'rate',
364
+ 'lodging.totalRoomTax': 'total_room_tax',
365
+ 'lodging.totalTax': 'totalTax',
366
+ 'travelEntertainmentAuthData.duration': 'duration',
367
+ 'travelEntertainmentAuthData.market': 'market'
368
+ }
369
+
370
+ post[:additionalData].merge!(extract_and_transform(mapper, options[:additional_data_lodging]))
371
+ post[:additionalData].compact!
372
+ end
373
+
294
374
  def add_shopper_data(post, options)
295
375
  post[:shopperEmail] = options[:email] if options[:email]
296
376
  post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
@@ -172,7 +172,7 @@ module ActiveMerchant #:nodoc:
172
172
  success,
173
173
  message_from(success, pairs),
174
174
  pairs,
175
- authorization: authorization_from(pairs),
175
+ authorization: authorization_from(pairs, options),
176
176
  test: test?
177
177
  )
178
178
  end
@@ -185,12 +185,12 @@ module ActiveMerchant #:nodoc:
185
185
  if succeeded
186
186
  'Succeeded'
187
187
  else
188
- response[:message] || "Error with ActionCode=#{response[:actioncode]}"
188
+ response[:message] || response[:status_errormessage] || "Error with ActionCode=#{response[:actioncode]}"
189
189
  end
190
190
  end
191
191
 
192
- def authorization_from(response)
193
- [
192
+ def authorization_from(response, options)
193
+ authorization = [
194
194
  response[:dateandtime],
195
195
  response[:batch],
196
196
  response[:transaction],
@@ -200,6 +200,8 @@ module ActiveMerchant #:nodoc:
200
200
  response[:tramount],
201
201
  response[:trcurrency]
202
202
  ].join('|')
203
+
204
+ authorization == '|||||||' ? nil : authorization
203
205
  end
204
206
 
205
207
  def split_authorization(authorization)
@@ -363,9 +363,6 @@ module ActiveMerchant #:nodoc:
363
363
  end
364
364
 
365
365
  def response(action, succeeded, response, source_id = nil)
366
- successful_response = succeeded && action == :purchase || action == :authorize
367
- avs_result = successful_response ? avs_result(response) : nil
368
- cvv_result = successful_response ? cvv_result(response) : nil
369
366
  authorization = authorization_from(response) unless action == :unstore
370
367
  body = action == :unstore ? { response_code: response.to_s } : response
371
368
  Response.new(
@@ -375,8 +372,8 @@ module ActiveMerchant #:nodoc:
375
372
  authorization: authorization,
376
373
  error_code: error_code_from(succeeded, body),
377
374
  test: test?,
378
- avs_result: avs_result,
379
- cvv_result: cvv_result
375
+ avs_result: avs_result(response),
376
+ cvv_result: cvv_result(response)
380
377
  )
381
378
  end
382
379
 
@@ -427,11 +424,11 @@ module ActiveMerchant #:nodoc:
427
424
  end
428
425
 
429
426
  def avs_result(response)
430
- response['source'] && response['source']['avs_check'] ? AVSResult.new(code: response['source']['avs_check']) : nil
427
+ response.respond_to?(:dig) && response.dig('source', 'avs_check') ? AVSResult.new(code: response['source']['avs_check']) : nil
431
428
  end
432
429
 
433
430
  def cvv_result(response)
434
- response['source'] && response['source']['cvv_check'] ? CVVResult.new(response['source']['cvv_check']) : nil
431
+ response.respond_to?(:dig) && response.dig('source', 'cvv_check') ? CVVResult.new(response['source']['cvv_check']) : nil
435
432
  end
436
433
 
437
434
  def parse(body, error: nil)
@@ -120,7 +120,11 @@ module ActiveMerchant #:nodoc:
120
120
  end
121
121
 
122
122
  def add_transaction_details(post, options, action = nil)
123
- details = { captureFlag: options[:capture_flag], createToken: options[:create_token] }
123
+ details = {
124
+ captureFlag: options[:capture_flag],
125
+ createToken: options[:create_token],
126
+ physicalGoodsIndicator: [true, 'true'].include?(options[:physical_goods_indicator])
127
+ }
124
128
 
125
129
  if options[:order_id].present? && action == 'sale'
126
130
  details[:merchantOrderId] = options[:order_id]
@@ -214,7 +218,7 @@ module ActiveMerchant #:nodoc:
214
218
  post[:storedCredentials][:sequence] = stored_credential[:initial_transaction] ? 'FIRST' : 'SUBSEQUENT'
215
219
  post[:storedCredentials][:initiator] = stored_credential[:initiator] == 'merchant' ? 'MERCHANT' : 'CARD_HOLDER'
216
220
  post[:storedCredentials][:scheduled] = SCHEDULED_REASON_TYPES.include?(stored_credential[:reason_type])
217
- post[:storedCredentials][:schemeReferenceTransactionId] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
221
+ post[:storedCredentials][:schemeReferenceTransactionId] = options[:scheme_reference_transaction_id] || stored_credential[:network_transaction_id]
218
222
  end
219
223
 
220
224
  def add_credit_card(source, payment, options)
@@ -132,6 +132,11 @@ module ActiveMerchant #:nodoc:
132
132
  r703: 'Export hostname_country/ip_country match'
133
133
  }
134
134
 
135
+ @@payment_solution = {
136
+ apple_pay: '001',
137
+ google_pay: '012'
138
+ }
139
+
135
140
  # These are the options that can be used when creating a new CyberSource
136
141
  # Gateway object.
137
142
  #
@@ -322,6 +327,7 @@ module ActiveMerchant #:nodoc:
322
327
  add_airline_data(xml, options)
323
328
  add_sales_slip_number(xml, options)
324
329
  add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference)
330
+ add_payment_solution(xml, creditcard_or_reference.source) if network_tokenization?(creditcard_or_reference)
325
331
  add_tax_management_indicator(xml, options)
326
332
  add_stored_credential_subsequent_auth(xml, options)
327
333
  add_issuer_additional_data(xml, options)
@@ -393,6 +399,7 @@ module ActiveMerchant #:nodoc:
393
399
  add_airline_data(xml, options)
394
400
  add_sales_slip_number(xml, options)
395
401
  add_payment_network_token(xml) if network_tokenization?(payment_method_or_reference)
402
+ add_payment_solution(xml, payment_method_or_reference.source) if network_tokenization?(payment_method_or_reference)
396
403
  add_tax_management_indicator(xml, options)
397
404
  add_stored_credential_subsequent_auth(xml, options)
398
405
  add_issuer_additional_data(xml, options)
@@ -670,6 +677,12 @@ module ActiveMerchant #:nodoc:
670
677
  end
671
678
  end
672
679
 
680
+ def add_payment_solution(xml, source)
681
+ return unless (payment_solution = @@payment_solution[source])
682
+
683
+ xml.tag! 'paymentSolution', payment_solution
684
+ end
685
+
673
686
  def add_issuer_additional_data(xml, options)
674
687
  return unless options[:issuer_additional_data]
675
688
 
@@ -2,7 +2,7 @@ module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class IpgGateway < Gateway
4
4
  self.test_url = 'https://test.ipg-online.com/ipgapi/services'
5
- self.live_url = 'https://www5.ipg-online.com'
5
+ self.live_url = 'https://www5.ipg-online.com/ipgapi/services'
6
6
 
7
7
  self.supported_countries = %w(AR)
8
8
  self.default_currency = 'ARS'
@@ -7,7 +7,7 @@ module ActiveMerchant #:nodoc:
7
7
  self.test_url = 'https://api-uat.kushkipagos.com/'
8
8
  self.live_url = 'https://api.kushkipagos.com/'
9
9
 
10
- self.supported_countries = %w[CL CO EC MX PE]
10
+ self.supported_countries = %w[BR CL CO EC MX PE]
11
11
  self.default_currency = 'USD'
12
12
  self.money_format = :dollars
13
13
  self.supported_cardtypes = %i[visa master american_express discover diners_club alia]
@@ -93,8 +93,7 @@ module ActiveMerchant #:nodoc:
93
93
  post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
94
94
 
95
95
  final_post = '<authorization>' + post_to_json_encrypt + '</authorization><dataID>' + @options[:user] + '</dataID>'
96
- json_post = {}
97
- json_post[:payload] = final_post
96
+ json_post = final_post
98
97
  commit('sale', json_post)
99
98
  end
100
99
 
@@ -114,8 +113,7 @@ module ActiveMerchant #:nodoc:
114
113
  post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
115
114
 
116
115
  final_post = '<capture>' + post_to_json_encrypt + '</capture><dataID>' + @options[:user] + '</dataID>'
117
- json_post = {}
118
- json_post[:payload] = final_post
116
+ json_post = final_post
119
117
  commit('capture', json_post)
120
118
  end
121
119
 
@@ -136,8 +134,7 @@ module ActiveMerchant #:nodoc:
136
134
  post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
137
135
 
138
136
  final_post = '<refund>' + post_to_json_encrypt + '</refund><dataID>' + @options[:user] + '</dataID>'
139
- json_post = {}
140
- json_post[:payload] = final_post
137
+ json_post = final_post
141
138
  commit('refund', json_post)
142
139
  end
143
140
 
@@ -145,10 +142,18 @@ module ActiveMerchant #:nodoc:
145
142
  true
146
143
  end
147
144
 
145
+ def extract_mit_responses_from_transcript(transcript)
146
+ groups = transcript.scan(/reading \d+ bytes(.*?)read \d+ bytes/m)
147
+ groups.map do |group|
148
+ group.first.scan(/-> "(.*?)"/).flatten.map(&:strip).join('')
149
+ end
150
+ end
151
+
148
152
  def scrub(transcript)
149
153
  ret_transcript = transcript
150
154
  auth_origin = ret_transcript[/<authorization>(.*?)<\/authorization>/, 1]
151
155
  unless auth_origin.nil?
156
+ auth_origin = auth_origin.gsub('\n', '')
152
157
  auth_decrypted = decrypt(auth_origin, @options[:key_session])
153
158
  auth_json = JSON.parse(auth_decrypted)
154
159
  auth_json['card'] = '[FILTERED]'
@@ -162,6 +167,7 @@ module ActiveMerchant #:nodoc:
162
167
 
163
168
  cap_origin = ret_transcript[/<capture>(.*?)<\/capture>/, 1]
164
169
  unless cap_origin.nil?
170
+ cap_origin = cap_origin.gsub('\n', '')
165
171
  cap_decrypted = decrypt(cap_origin, @options[:key_session])
166
172
  cap_json = JSON.parse(cap_decrypted)
167
173
  cap_json['apikey'] = '[FILTERED]'
@@ -173,6 +179,7 @@ module ActiveMerchant #:nodoc:
173
179
 
174
180
  ref_origin = ret_transcript[/<refund>(.*?)<\/refund>/, 1]
175
181
  unless ref_origin.nil?
182
+ ref_origin = ref_origin.gsub('\n', '')
176
183
  ref_decrypted = decrypt(ref_origin, @options[:key_session])
177
184
  ref_json = JSON.parse(ref_decrypted)
178
185
  ref_json['apikey'] = '[FILTERED]'
@@ -182,15 +189,10 @@ module ActiveMerchant #:nodoc:
182
189
  ret_transcript = ret_transcript.gsub(/<refund>(.*?)<\/refund>/, ref_tagged)
183
190
  end
184
191
 
185
- res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
186
- loop do
187
- break if res_origin.nil?
188
-
189
- resp_origin = res_origin[/#{Regexp.escape('"')}(.*?)#{Regexp.escape('"')}/m, 1]
190
- resp_decrypted = decrypt(resp_origin, @options[:key_session])
191
- ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1] = resp_decrypted
192
- ret_transcript = ret_transcript.sub('reading ', 'response: ')
193
- res_origin = ret_transcript[/#{Regexp.escape('reading ')}(.*?)#{Regexp.escape('read')}/m, 1]
192
+ groups = extract_mit_responses_from_transcript(transcript)
193
+ groups.each do |group|
194
+ group_decrypted = decrypt(group, @options[:key_session])
195
+ ret_transcript = ret_transcript.gsub('Conn close', "\n" + group_decrypted + "\nConn close")
194
196
  end
195
197
 
196
198
  ret_transcript
@@ -219,9 +221,7 @@ module ActiveMerchant #:nodoc:
219
221
  end
220
222
 
221
223
  def commit(action, parameters)
222
- json_str = JSON.generate(parameters)
223
- cleaned_str = json_str.gsub('\n', '')
224
- raw_response = ssl_post(live_url, cleaned_str, { 'Content-type' => 'application/json' })
224
+ raw_response = ssl_post(live_url, parameters, { 'Content-type' => 'text/plain' })
225
225
  response = JSON.parse(decrypt(raw_response, @options[:key_session]))
226
226
 
227
227
  Response.new(
@@ -149,6 +149,7 @@ module ActiveMerchant #:nodoc:
149
149
 
150
150
  def add_invoice(post, money, options)
151
151
  post[:amount] = amount(money)
152
+ post[:surcharge] = options[:surcharge] if options[:surcharge]
152
153
  post[:orderid] = options[:order_id]
153
154
  post[:orderdescription] = options[:description]
154
155
  post[:currency] = options[:currency] || currency(money)
@@ -232,6 +233,9 @@ module ActiveMerchant #:nodoc:
232
233
  end
233
234
 
234
235
  if (shipping_address = options[:shipping_address])
236
+ first_name, last_name = split_names(shipping_address[:name])
237
+ post[:shipping_firstname] = first_name if first_name
238
+ post[:shipping_lastname] = last_name if last_name
235
239
  post[:shipping_company] = shipping_address[:company]
236
240
  post[:shipping_address1] = shipping_address[:address1]
237
241
  post[:shipping_address2] = shipping_address[:address2]
@@ -240,6 +244,7 @@ module ActiveMerchant #:nodoc:
240
244
  post[:shipping_country] = shipping_address[:country]
241
245
  post[:shipping_zip] = shipping_address[:zip]
242
246
  post[:shipping_phone] = shipping_address[:phone]
247
+ post[:shipping_email] = options[:shipping_email] if options[:shipping_email]
243
248
  end
244
249
 
245
250
  if (descriptor = options[:descriptors])
@@ -2,7 +2,7 @@ module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class PaywayDotComGateway < Gateway
4
4
  self.test_url = 'https://paywaywsdev.com/PaywayWS/Payment/CreditCard'
5
- self.live_url = 'https://paywayws.com/PaywayWS/Payment/CreditCard'
5
+ self.live_url = 'https://paywayws.net/PaywayWS/Payment/CreditCard'
6
6
 
7
7
  self.supported_countries = %w[US CA]
8
8
  self.default_currency = 'USD'
@@ -73,10 +73,10 @@ module ActiveMerchant #:nodoc:
73
73
  add_transaction_data('Credit', post, money, options.merge!({ currency: original_currency }))
74
74
  post[:sg_CreditType] = 2
75
75
  post[:sg_AuthCode] = auth
76
- post[:sg_TransactionID] = transaction_id
77
76
  post[:sg_CCToken] = token
78
77
  post[:sg_ExpMonth] = exp_month
79
78
  post[:sg_ExpYear] = exp_year
79
+ post[:sg_TransactionID] = transaction_id unless options[:unreferenced_refund]
80
80
 
81
81
  commit(post)
82
82
  end
@@ -86,6 +86,7 @@ module ActiveMerchant #:nodoc:
86
86
 
87
87
  add_payment(post, payment, options)
88
88
  add_transaction_data('Credit', post, money, options)
89
+ add_customer_details(post, payment, options)
89
90
 
90
91
  post[:sg_CreditType] = 1
91
92
 
@@ -696,7 +696,7 @@ module ActiveMerchant #:nodoc:
696
696
  def commit(method, url, parameters = nil, options = {})
697
697
  add_expand_parameters(parameters, options) if parameters
698
698
 
699
- return Response.new(false, 'Invalid API Key provided') if test? && !key(options).start_with?('sk_test')
699
+ return Response.new(false, 'Invalid API Key provided') unless key_valid?(options)
700
700
 
701
701
  response = api_request(method, url, parameters, options)
702
702
  response['webhook_id'] = options[:webhook_id] if options[:webhook_id]
@@ -716,6 +716,18 @@ module ActiveMerchant #:nodoc:
716
716
  error_code: success ? nil : error_code_from(response))
717
717
  end
718
718
 
719
+ def key_valid?(options)
720
+ return true unless test?
721
+
722
+ %w(sk rk).each do |k|
723
+ if key(options).start_with?(k)
724
+ return false unless key(options).start_with?("#{k}_test")
725
+ end
726
+ end
727
+
728
+ true
729
+ end
730
+
719
731
  def authorization_from(success, url, method, response)
720
732
  return response.fetch('error', {})['charge'] unless success
721
733
 
@@ -34,9 +34,9 @@ module ActiveMerchant #:nodoc:
34
34
  add_connected_account(post, options)
35
35
  add_radar_data(post, options)
36
36
  add_shipping_address(post, options)
37
+ add_stored_credentials(post, options)
37
38
  setup_future_usage(post, options)
38
39
  add_exemption(post, options)
39
- add_stored_credentials(post, options)
40
40
  add_ntid(post, options)
41
41
  add_claim_without_transaction_id(post, options)
42
42
  add_error_on_requires_action(post, options)
@@ -76,22 +76,27 @@ module ActiveMerchant #:nodoc:
76
76
 
77
77
  def create_payment_method(payment_method, options = {})
78
78
  post_data = add_payment_method_data(payment_method, options)
79
-
80
79
  options = format_idempotency_key(options, 'pm')
81
80
  commit(:post, 'payment_methods', post_data, options)
82
81
  end
83
82
 
84
83
  def add_payment_method_data(payment_method, options = {})
85
- post_data = {}
86
- post_data[:type] = 'card'
87
- post_data[:card] = {}
88
- post_data[:card][:number] = payment_method.number
89
- post_data[:card][:exp_month] = payment_method.month
90
- post_data[:card][:exp_year] = payment_method.year
91
- post_data[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
92
- add_billing_address(post_data, options)
93
- add_name_only(post_data, payment_method) if post_data[:billing_details].nil?
94
- post_data
84
+ post = {
85
+ type: 'card',
86
+ card: {
87
+ number: payment_method.number,
88
+ exp_month: payment_method.month,
89
+ exp_year: payment_method.year
90
+ }
91
+ }
92
+
93
+ post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
94
+ if billing = options[:billing_address] || options[:address]
95
+ post[:billing_details] = add_address(billing, options)
96
+ end
97
+
98
+ add_name_only(post, payment_method) if post[:billing_details].nil?
99
+ post
95
100
  end
96
101
 
97
102
  def add_payment_method_card_data_token(post_data, payment_method)
@@ -212,16 +217,7 @@ module ActiveMerchant #:nodoc:
212
217
  result = add_payment_method_token(params, payment_method, options)
213
218
  return result if result.is_a?(ActiveMerchant::Billing::Response)
214
219
 
215
- if options[:customer]
216
- customer_id = options[:customer]
217
- else
218
- post[:description] = options[:description] if options[:description]
219
- post[:email] = options[:email] if options[:email]
220
- options = format_idempotency_key(options, 'customer')
221
- post[:expand] = [:sources]
222
- customer = commit(:post, 'customers', post, options)
223
- customer_id = customer.params['id']
224
- end
220
+ customer_id = options[:customer] || customer(post, payment_method, options).params['id']
225
221
  options = format_idempotency_key(options, 'attach')
226
222
  attach_parameters = { customer: customer_id }
227
223
  attach_parameters[:validate] = options[:validate] unless options[:validate].nil?
@@ -231,6 +227,23 @@ module ActiveMerchant #:nodoc:
231
227
  end
232
228
  end
233
229
 
230
+ def customer(post, payment, options)
231
+ post[:description] = options[:description] if options[:description]
232
+ post[:expand] = [:sources]
233
+ post[:email] = options[:email]
234
+
235
+ if billing = options[:billing_address] || options[:address]
236
+ post.merge!(add_address(billing, options))
237
+ end
238
+
239
+ if shipping = options[:shipping_address]
240
+ post[:shipping] = add_address(shipping, options).except(:email)
241
+ end
242
+
243
+ options = format_idempotency_key(options, 'customer')
244
+ commit(:post, 'customers', post, options)
245
+ end
246
+
234
247
  def unstore(identification, options = {}, deprecated_options = {})
235
248
  if identification.include?('pm_')
236
249
  _, payment_method = identification.split('|')
@@ -399,17 +412,19 @@ module ActiveMerchant #:nodoc:
399
412
  post[:payment_method_options][:card][:moto] = true if options[:moto]
400
413
  end
401
414
 
402
- # Stripe Payment Intents does not pass any parameters for cardholder/merchant initiated
403
- # it also does not support installments for any country other than Mexico (reason for this is unknown)
404
- # The only thing that Stripe PI requires for stored credentials to work currently is the network_transaction_id
405
- # network_transaction_id is created when the card is authenticated using the field `setup_for_future_usage` with the value `off_session` see def setup_future_usage below
415
+ # Stripe Payment Intents now supports specifying on a transaction level basis stored credential information.
416
+ # The feature is currently gated but is listed as `stored_credential_transaction_type` inside the
417
+ # `post[:payment_method_options][:card]` hash. Since this is a beta field adding an extra check to use
418
+ # the existing logic by default. To be able to utilize this field, you must reach out to Stripe.
406
419
 
407
420
  def add_stored_credentials(post, options = {})
408
421
  return unless options[:stored_credential] && !options[:stored_credential].values.all?(&:nil?)
409
422
 
410
- stored_credential = options[:stored_credential]
411
423
  post[:payment_method_options] ||= {}
412
424
  post[:payment_method_options][:card] ||= {}
425
+ add_stored_credential_transaction_type(post, options) if options[:stored_credential_transaction_type]
426
+
427
+ stored_credential = options[:stored_credential]
413
428
  post[:payment_method_options][:card][:mit_exemption] = {}
414
429
 
415
430
  # Stripe PI accepts network_transaction_id and ds_transaction_id via mit field under card.
@@ -419,6 +434,50 @@ module ActiveMerchant #:nodoc:
419
434
  post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
420
435
  end
421
436
 
437
+ def add_stored_credential_transaction_type(post, options = {})
438
+ stored_credential = options[:stored_credential]
439
+ # Do not add anything unless these are present.
440
+ return unless stored_credential[:reason_type] && stored_credential[:initiator]
441
+
442
+ # Not compatible with off_session parameter.
443
+ options.delete(:off_session)
444
+ if stored_credential[:initial_transaction]
445
+ # Initial transactions must by CIT
446
+ return unless stored_credential[:initiator] == 'cardholder'
447
+
448
+ initial_transaction_stored_credential(post, stored_credential[:reason_type])
449
+ else
450
+ # Subsequent transaction
451
+ subsequent_transaction_stored_credential(post, stored_credential[:initiator], stored_credential[:reason_type])
452
+ end
453
+ end
454
+
455
+ def initial_transaction_stored_credential(post, reason_type)
456
+ if reason_type == 'unscheduled'
457
+ # Charge on-session and store card for future one-off payment use
458
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_off_session_unscheduled'
459
+ elsif reason_type == 'recurring'
460
+ # Charge on-session and store card for future recurring payment use
461
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_off_session_recurring'
462
+ else
463
+ # Charge on-session and store card for future on-session payment use.
464
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'setup_on_session'
465
+ end
466
+ end
467
+
468
+ def subsequent_transaction_stored_credential(post, initiator, reason_type)
469
+ if initiator == 'cardholder'
470
+ # Charge on-session customer using previously stored card.
471
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'stored_on_session'
472
+ elsif reason_type == 'recurring'
473
+ # Charge off-session customer using previously stored card for recurring transaction
474
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'stored_off_session_recurring'
475
+ else
476
+ # Charge off-session customer using previously stored card for one-off transaction
477
+ post[:payment_method_options][:card][:stored_credential_transaction_type] = 'stored_off_session_unscheduled'
478
+ end
479
+ end
480
+
422
481
  def add_ntid(post, options = {})
423
482
  return unless options[:network_transaction_id]
424
483
 
@@ -478,30 +537,35 @@ module ActiveMerchant #:nodoc:
478
537
  def add_billing_address_for_card_tokenization(post, options = {})
479
538
  return unless (billing = options[:billing_address] || options[:address])
480
539
 
481
- post[:card][:address_city] = billing[:city] if billing[:city]
482
- post[:card][:address_country] = billing[:country] if billing[:country]
483
- post[:card][:address_line1] = billing[:address1] if billing[:address1]
484
- post[:card][:address_line2] = billing[:address2] if billing[:address2]
485
- post[:card][:address_zip] = billing[:zip] if billing[:zip]
486
- post[:card][:address_state] = billing[:state] if billing[:state]
540
+ billing = add_address(billing, options)
541
+ billing[:address].transform_keys! { |k| k == :postal_code ? :address_zip : k.to_s.prepend('address_').to_sym }
542
+
543
+ post[:card][:name] = billing[:name]
544
+ post[:card].merge!(billing[:address])
487
545
  end
488
546
 
489
- def add_billing_address(post, options = {})
490
- return unless billing = options[:billing_address] || options[:address]
547
+ def add_shipping_address(post, options = {})
548
+ return unless shipping = options[:shipping_address]
491
549
 
492
- email = billing[:email] || options[:email]
550
+ post[:shipping] = add_address(shipping, options).except(:email)
551
+ post[:shipping][:carrier] = (shipping[:carrier] || options[:shipping_carrier]) if shipping[:carrier] || options[:shipping_carrier]
552
+ post[:shipping][:tracking_number] = (shipping[:tracking_number] || options[:shipping_tracking_number]) if shipping[:tracking_number] || options[:shipping_tracking_number]
553
+ end
493
554
 
494
- post[:billing_details] = {}
495
- post[:billing_details][:address] = {}
496
- post[:billing_details][:address][:city] = billing[:city] if billing[:city]
497
- post[:billing_details][:address][:country] = billing[:country] if billing[:country]
498
- post[:billing_details][:address][:line1] = billing[:address1] if billing[:address1]
499
- post[:billing_details][:address][:line2] = billing[:address2] if billing[:address2]
500
- post[:billing_details][:address][:postal_code] = billing[:zip] if billing[:zip]
501
- post[:billing_details][:address][:state] = billing[:state] if billing[:state]
502
- post[:billing_details][:email] = email if email
503
- post[:billing_details][:name] = billing[:name] if billing[:name]
504
- post[:billing_details][:phone] = billing[:phone] if billing[:phone]
555
+ def add_address(address, options)
556
+ {
557
+ address: {
558
+ city: address[:city],
559
+ country: address[:country],
560
+ line1: address[:address1],
561
+ line2: address[:address2],
562
+ postal_code: address[:zip],
563
+ state: address[:state]
564
+ }.compact,
565
+ email: address[:email] || options[:email],
566
+ phone: address[:phone] || address[:phone_number],
567
+ name: address[:name]
568
+ }.compact
505
569
  end
506
570
 
507
571
  def add_name_only(post, payment_method)
@@ -511,27 +575,6 @@ module ActiveMerchant #:nodoc:
511
575
  post[:billing_details][:name] = name
512
576
  end
513
577
 
514
- def add_shipping_address(post, options = {})
515
- return unless shipping = options[:shipping_address]
516
-
517
- post[:shipping] = {}
518
-
519
- # fields required by Stripe PI
520
- post[:shipping][:address] = {}
521
- post[:shipping][:address][:line1] = shipping[:address1]
522
- post[:shipping][:name] = shipping[:name]
523
-
524
- # fields considered optional by Stripe PI
525
- post[:shipping][:address][:city] = shipping[:city] if shipping[:city]
526
- post[:shipping][:address][:country] = shipping[:country] if shipping[:country]
527
- post[:shipping][:address][:line2] = shipping[:address2] if shipping[:address2]
528
- post[:shipping][:address][:postal_code] = shipping[:zip] if shipping[:zip]
529
- post[:shipping][:address][:state] = shipping[:state] if shipping[:state]
530
- post[:shipping][:phone] = shipping[:phone_number] if shipping[:phone_number]
531
- post[:shipping][:carrier] = (shipping[:carrier] || options[:shipping_carrier]) if shipping[:carrier] || options[:shipping_carrier]
532
- post[:shipping][:tracking_number] = (shipping[:tracking_number] || options[:shipping_tracking_number]) if shipping[:tracking_number] || options[:shipping_tracking_number]
533
- end
534
-
535
578
  def format_idempotency_key(options, suffix)
536
579
  return options unless options[:idempotency_key]
537
580
 
@@ -9,7 +9,7 @@ module ActiveMerchant #:nodoc:
9
9
 
10
10
  self.supported_countries = ['PY']
11
11
  self.default_currency = 'PYG'
12
- self.supported_cardtypes = %i[visa master]
12
+ self.supported_cardtypes = %i[visa master panal]
13
13
 
14
14
  self.homepage_url = 'https://comercios.bancard.com.py'
15
15
  self.display_name = 'vPOS'
@@ -28,21 +28,6 @@ module ActiveMerchant #:nodoc:
28
28
  network_token: 'NETWORKTOKEN'
29
29
  }
30
30
 
31
- CARD_CODES = {
32
- 'visa' => 'VISA-SSL',
33
- 'master' => 'ECMC-SSL',
34
- 'discover' => 'DISCOVER-SSL',
35
- 'american_express' => 'AMEX-SSL',
36
- 'jcb' => 'JCB-SSL',
37
- 'maestro' => 'MAESTRO-SSL',
38
- 'diners_club' => 'DINERS-SSL',
39
- 'elo' => 'ELO-SSL',
40
- 'naranja' => 'NARANJA-SSL',
41
- 'cabal' => 'CABAL-SSL',
42
- 'unionpay' => 'CHINAUNIONPAY-SSL',
43
- 'unknown' => 'CARD-SSL'
44
- }
45
-
46
31
  AVS_CODE_MAP = {
47
32
  'A' => 'M', # Match
48
33
  'B' => 'P', # Postcode matches, address not verified
@@ -646,7 +631,7 @@ module ActiveMerchant #:nodoc:
646
631
  end
647
632
 
648
633
  def add_card_details(xml, payment_method, options)
649
- xml.tag! card_code_for(payment_method) do
634
+ xml.tag! 'CARD-SSL' do
650
635
  add_card(xml, payment_method, options)
651
636
  end
652
637
  end
@@ -1034,10 +1019,6 @@ module ActiveMerchant #:nodoc:
1034
1019
  return 2
1035
1020
  end
1036
1021
 
1037
- def card_code_for(payment_method)
1038
- CARD_CODES[card_brand(payment_method)] || CARD_CODES['unknown']
1039
- end
1040
-
1041
1022
  def eligible_for_0_auth?(payment_method, options = {})
1042
1023
  payment_method.is_a?(CreditCard) && %w(visa master).include?(payment_method.brand) && options[:zero_dollar_auth]
1043
1024
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveMerchant
2
- VERSION = '1.131.0'
2
+ VERSION = '1.133.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.131.0
4
+ version: 1.133.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: 2023-06-21 00:00:00.000000000 Z
11
+ date: 2023-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -480,7 +480,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
480
480
  - !ruby/object:Gem::Version
481
481
  version: '0'
482
482
  requirements: []
483
- rubygems_version: 3.4.14
483
+ rubygems_version: 3.4.16
484
484
  signing_key:
485
485
  specification_version: 4
486
486
  summary: Framework and tools for dealing with credit card transactions.