activemerchant 1.131.0 → 1.133.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
  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.