activemerchant 1.117.0 → 1.123.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +217 -0
  3. data/README.md +5 -3
  4. data/lib/active_merchant/billing/check.rb +19 -12
  5. data/lib/active_merchant/billing/credit_card.rb +6 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +96 -22
  8. data/lib/active_merchant/billing/gateways/adyen.rb +38 -21
  9. data/lib/active_merchant/billing/gateways/authorize_net.rb +19 -11
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +4 -0
  11. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +5 -3
  13. data/lib/active_merchant/billing/gateways/braintree_blue.rb +58 -8
  14. data/lib/active_merchant/billing/gateways/cashnet.rb +7 -2
  15. data/lib/active_merchant/billing/gateways/checkout_v2.rb +31 -0
  16. data/lib/active_merchant/billing/gateways/credorax.rb +16 -9
  17. data/lib/active_merchant/billing/gateways/cyber_source.rb +67 -9
  18. data/lib/active_merchant/billing/gateways/d_local.rb +1 -1
  19. data/lib/active_merchant/billing/gateways/decidir.rb +29 -3
  20. data/lib/active_merchant/billing/gateways/elavon.rb +110 -26
  21. data/lib/active_merchant/billing/gateways/element.rb +2 -0
  22. data/lib/active_merchant/billing/gateways/eway_rapid.rb +13 -0
  23. data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +17 -6
  24. data/lib/active_merchant/billing/gateways/forte.rb +12 -0
  25. data/lib/active_merchant/billing/gateways/global_collect.rb +25 -16
  26. data/lib/active_merchant/billing/gateways/hps.rb +65 -2
  27. data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
  28. data/lib/active_merchant/billing/gateways/litle.rb +9 -4
  29. data/lib/active_merchant/billing/gateways/mercado_pago.rb +5 -4
  30. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  31. data/lib/active_merchant/billing/gateways/moka.rb +277 -0
  32. data/lib/active_merchant/billing/gateways/monei.rb +228 -144
  33. data/lib/active_merchant/billing/gateways/mundipagg.rb +14 -5
  34. data/lib/active_merchant/billing/gateways/netbanx.rb +37 -2
  35. data/lib/active_merchant/billing/gateways/nmi.rb +14 -9
  36. data/lib/active_merchant/billing/gateways/orbital.rb +202 -47
  37. data/lib/active_merchant/billing/gateways/pay_arc.rb +390 -0
  38. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  39. data/lib/active_merchant/billing/gateways/payeezy.rb +57 -11
  40. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  41. data/lib/active_merchant/billing/gateways/payflow.rb +9 -0
  42. data/lib/active_merchant/billing/gateways/payment_express.rb +10 -5
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +26 -1
  44. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +1 -0
  45. data/lib/active_merchant/billing/gateways/paypal.rb +10 -2
  46. data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -0
  47. data/lib/active_merchant/billing/gateways/paysafe.rb +291 -0
  48. data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
  49. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +253 -0
  50. data/lib/active_merchant/billing/gateways/pin.rb +11 -0
  51. data/lib/active_merchant/billing/gateways/qvalent.rb +23 -9
  52. data/lib/active_merchant/billing/gateways/redsys.rb +78 -30
  53. data/lib/active_merchant/billing/gateways/safe_charge.rb +19 -8
  54. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  55. data/lib/active_merchant/billing/gateways/stripe.rb +8 -8
  56. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +86 -25
  57. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -1
  58. data/lib/active_merchant/billing/gateways/vpos.rb +220 -0
  59. data/lib/active_merchant/billing/gateways/worldpay.rb +68 -20
  60. data/lib/active_merchant/billing/response.rb +2 -1
  61. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  62. data/lib/active_merchant/billing.rb +1 -0
  63. data/lib/active_merchant/version.rb +1 -1
  64. data/lib/certs/cacert.pem +1582 -2431
  65. metadata +10 -3
@@ -33,7 +33,7 @@ module ActiveMerchant #:nodoc:
33
33
  add_creator_info(post, options)
34
34
  add_fraud_fields(post, options)
35
35
  add_external_cardholder_authentication_data(post, options)
36
- commit(:authorize, post)
36
+ commit(:authorize, post, options: options)
37
37
  end
38
38
 
39
39
  def capture(money, authorization, options = {})
@@ -41,7 +41,7 @@ module ActiveMerchant #:nodoc:
41
41
  add_order(post, money, options, capture: true)
42
42
  add_customer_data(post, options)
43
43
  add_creator_info(post, options)
44
- commit(:capture, post, authorization)
44
+ commit(:capture, post, authorization: authorization)
45
45
  end
46
46
 
47
47
  def refund(money, authorization, options = {})
@@ -49,13 +49,13 @@ module ActiveMerchant #:nodoc:
49
49
  add_amount(post, money, options)
50
50
  add_refund_customer_data(post, options)
51
51
  add_creator_info(post, options)
52
- commit(:refund, post, authorization)
52
+ commit(:refund, post, authorization: authorization)
53
53
  end
54
54
 
55
55
  def void(authorization, options = {})
56
56
  post = nestable_hash
57
57
  add_creator_info(post, options)
58
- commit(:void, post, authorization)
58
+ commit(:void, post, authorization: authorization)
59
59
  end
60
60
 
61
61
  def verify(payment, options = {})
@@ -201,21 +201,21 @@ module ActiveMerchant #:nodoc:
201
201
  shipping_address = options[:shipping_address]
202
202
  if billing_address = options[:billing_address] || options[:address]
203
203
  post['order']['customer']['billingAddress'] = {
204
- 'street' => billing_address[:address1],
205
- 'additionalInfo' => billing_address[:address2],
204
+ 'street' => truncate(billing_address[:address1], 50),
205
+ 'additionalInfo' => truncate(billing_address[:address2], 50),
206
206
  'zip' => billing_address[:zip],
207
207
  'city' => billing_address[:city],
208
- 'state' => billing_address[:state],
208
+ 'state' => truncate(billing_address[:state], 35),
209
209
  'countryCode' => billing_address[:country]
210
210
  }
211
211
  end
212
212
  if shipping_address
213
213
  post['order']['customer']['shippingAddress'] = {
214
- 'street' => shipping_address[:address1],
215
- 'additionalInfo' => shipping_address[:address2],
214
+ 'street' => truncate(shipping_address[:address1], 50),
215
+ 'additionalInfo' => truncate(shipping_address[:address2], 50),
216
216
  'zip' => shipping_address[:zip],
217
217
  'city' => shipping_address[:city],
218
- 'state' => shipping_address[:state],
218
+ 'state' => truncate(shipping_address[:state], 35),
219
219
  'countryCode' => shipping_address[:country]
220
220
  }
221
221
  post['order']['customer']['shippingAddress']['name'] = {
@@ -277,9 +277,13 @@ module ActiveMerchant #:nodoc:
277
277
  end
278
278
  end
279
279
 
280
- def commit(action, post, authorization = nil)
280
+ def idempotency_key_for_signature(options)
281
+ "x-gcs-idempotence-key:#{options[:idempotency_key]}" if options[:idempotency_key]
282
+ end
283
+
284
+ def commit(action, post, authorization: nil, options: {})
281
285
  begin
282
- raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization))
286
+ raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization, options))
283
287
  response = parse(raw_response)
284
288
  rescue ResponseError => e
285
289
  response = parse(e.response.body) if e.response.code.to_i >= 400
@@ -306,21 +310,26 @@ module ActiveMerchant #:nodoc:
306
310
  }
307
311
  end
308
312
 
309
- def headers(action, post, authorization = nil)
310
- {
313
+ def headers(action, post, authorization = nil, options = {})
314
+ headers = {
311
315
  'Content-Type' => content_type,
312
- 'Authorization' => auth_digest(action, post, authorization),
316
+ 'Authorization' => auth_digest(action, post, authorization, options),
313
317
  'Date' => date
314
318
  }
319
+
320
+ headers['X-GCS-Idempotence-Key'] = options[:idempotency_key] if options[:idempotency_key]
321
+ headers
315
322
  end
316
323
 
317
- def auth_digest(action, post, authorization = nil)
324
+ def auth_digest(action, post, authorization = nil, options = {})
318
325
  data = <<~REQUEST
319
326
  POST
320
327
  #{content_type}
321
328
  #{date}
329
+ #{idempotency_key_for_signature(options)}
322
330
  #{uri(action, authorization)}
323
331
  REQUEST
332
+ data = data.each_line.reject { |line| line.strip == '' }.join
324
333
  digest = OpenSSL::Digest.new('sha256')
325
334
  key = @options[:secret_api_key]
326
335
  "GCS v1HMAC:#{@options[:api_key_id]}:#{Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data))}"
@@ -39,6 +39,7 @@ module ActiveMerchant #:nodoc:
39
39
  add_descriptor_name(xml, options)
40
40
  add_card_or_token_payment(xml, card_or_token, options)
41
41
  add_three_d_secure(xml, card_or_token, options)
42
+ add_stored_credentials(xml, options)
42
43
  end
43
44
  end
44
45
 
@@ -52,6 +53,8 @@ module ActiveMerchant #:nodoc:
52
53
  def purchase(money, payment_method, options = {})
53
54
  if payment_method.is_a?(Check)
54
55
  commit_check_sale(money, payment_method, options)
56
+ elsif options.dig(:stored_credential, :reason_type) == 'recurring'
57
+ commit_recurring_billing_sale(money, payment_method, options)
55
58
  else
56
59
  commit_credit_sale(money, payment_method, options)
57
60
  end
@@ -67,6 +70,15 @@ module ActiveMerchant #:nodoc:
67
70
  end
68
71
  end
69
72
 
73
+ def credit(money, payment_method, options = {})
74
+ commit('CreditReturn') do |xml|
75
+ add_amount(xml, money)
76
+ add_allow_dup(xml)
77
+ add_card_or_token_payment(xml, payment_method, options)
78
+ add_details(xml, options)
79
+ end
80
+ end
81
+
70
82
  def verify(card_or_token, options = {})
71
83
  commit('CreditAccountVerify') do |xml|
72
84
  add_card_or_token_customer_data(xml, card_or_token, options)
@@ -122,6 +134,21 @@ module ActiveMerchant #:nodoc:
122
134
  add_descriptor_name(xml, options)
123
135
  add_card_or_token_payment(xml, card_or_token, options)
124
136
  add_three_d_secure(xml, card_or_token, options)
137
+ add_stored_credentials(xml, options)
138
+ end
139
+ end
140
+
141
+ def commit_recurring_billing_sale(money, card_or_token, options)
142
+ commit('RecurringBilling') do |xml|
143
+ add_amount(xml, money)
144
+ add_allow_dup(xml)
145
+ add_card_or_token_customer_data(xml, card_or_token, options)
146
+ add_details(xml, options)
147
+ add_descriptor_name(xml, options)
148
+ add_card_or_token_payment(xml, card_or_token, options)
149
+ add_three_d_secure(xml, card_or_token, options)
150
+ add_stored_credentials(xml, options)
151
+ add_stored_credentials_for_recurring_billing(xml, options)
125
152
  end
126
153
  end
127
154
 
@@ -148,7 +175,7 @@ module ActiveMerchant #:nodoc:
148
175
  xml.hps :CardHolderAddr, billing_address[:address1] if billing_address[:address1]
149
176
  xml.hps :CardHolderCity, billing_address[:city] if billing_address[:city]
150
177
  xml.hps :CardHolderState, billing_address[:state] if billing_address[:state]
151
- xml.hps :CardHolderZip, billing_address[:zip] if billing_address[:zip]
178
+ xml.hps :CardHolderZip, alphanumeric_zip(billing_address[:zip]) if billing_address[:zip]
152
179
  end
153
180
  end
154
181
  end
@@ -210,7 +237,7 @@ module ActiveMerchant #:nodoc:
210
237
  def add_details(xml, options)
211
238
  xml.hps :AdditionalTxnFields do
212
239
  xml.hps :Description, options[:description] if options[:description]
213
- xml.hps :InvoiceNbr, options[:order_id] if options[:order_id]
240
+ xml.hps :InvoiceNbr, options[:order_id][0..59] if options[:order_id]
214
241
  xml.hps :CustomerID, options[:customer_id] if options[:customer_id]
215
242
  end
216
243
  end
@@ -256,6 +283,38 @@ module ActiveMerchant #:nodoc:
256
283
  end
257
284
  end
258
285
 
286
+ # We do not currently support installments on this gateway.
287
+ # The HPS gateway treats recurring transactions as a seperate transaction type
288
+ def add_stored_credentials(xml, options)
289
+ return unless options[:stored_credential]
290
+
291
+ xml.hps :CardOnFileData do
292
+ if options[:stored_credential][:initiator] == 'customer'
293
+ xml.hps :CardOnFile, 'C'
294
+ elsif options[:stored_credential][:initiator] == 'merchant'
295
+ xml.hps :CardOnFile, 'M'
296
+ else
297
+ return
298
+ end
299
+
300
+ if options[:stored_credential][:network_transaction_id]
301
+ xml.hps :CardBrandTxnId, options[:stored_credential][:network_transaction_id]
302
+ else
303
+ return
304
+ end
305
+ end
306
+ end
307
+
308
+ def add_stored_credentials_for_recurring_billing(xml, options)
309
+ xml.hps :RecurringData do
310
+ if options[:stored_credential][:reason_type] = 'recurring'
311
+ xml.hps :OneTime, 'N'
312
+ else
313
+ xml.hps :OneTime, 'Y'
314
+ end
315
+ end
316
+ end
317
+
259
318
  def strip_leading_zero(value)
260
319
  return value unless value[0] == '0'
261
320
 
@@ -380,6 +439,10 @@ module ActiveMerchant #:nodoc:
380
439
  @options[:secret_api_key]&.include?('_cert_')
381
440
  end
382
441
 
442
+ def alphanumeric_zip(zip)
443
+ zip.gsub(/[^0-9a-z]/i, '')
444
+ end
445
+
383
446
  ISSUER_MESSAGES = {
384
447
  '13' => 'Must be greater than or equal 0.',
385
448
  '14' => 'The card number is incorrect.',
@@ -37,6 +37,7 @@ module ActiveMerchant #:nodoc:
37
37
  post = {}
38
38
  post[:ticketNumber] = authorization
39
39
  add_invoice(action, post, amount, options)
40
+ add_full_response(post, options)
40
41
 
41
42
  commit(action, post)
42
43
  end
@@ -46,6 +47,7 @@ module ActiveMerchant #:nodoc:
46
47
 
47
48
  post = {}
48
49
  post[:ticketNumber] = authorization
50
+ add_full_response(post, options)
49
51
 
50
52
  commit(action, post)
51
53
  end
@@ -55,6 +57,7 @@ module ActiveMerchant #:nodoc:
55
57
 
56
58
  post = {}
57
59
  post[:ticketNumber] = authorization
60
+ add_full_response(post, options)
58
61
 
59
62
  commit(action, post)
60
63
  end
@@ -78,6 +81,7 @@ module ActiveMerchant #:nodoc:
78
81
  post = {}
79
82
  add_invoice(action, post, amount, options)
80
83
  add_payment_method(post, payment_method, options)
84
+ add_full_response(post, options)
81
85
 
82
86
  commit(action, post)
83
87
  end
@@ -88,6 +92,8 @@ module ActiveMerchant #:nodoc:
88
92
  post = {}
89
93
  add_reference(post, authorization, options)
90
94
  add_invoice(action, post, amount, options)
95
+ add_contact_details(post, options[:contact_details]) if options[:contact_details]
96
+ add_full_response(post, options)
91
97
 
92
98
  commit(action, post)
93
99
  end
@@ -98,6 +104,7 @@ module ActiveMerchant #:nodoc:
98
104
  post = {}
99
105
  add_reference(post, authorization, options)
100
106
  add_invoice(action, post, amount, options)
107
+ add_full_response(post, options)
101
108
 
102
109
  commit(action, post)
103
110
  end
@@ -154,6 +161,22 @@ module ActiveMerchant #:nodoc:
154
161
  post[:token] = authorization
155
162
  end
156
163
 
164
+ def add_contact_details(post, contact_details_options)
165
+ contact_details = {}
166
+ contact_details[:documentType] = contact_details_options[:document_type] if contact_details_options[:document_type]
167
+ contact_details[:documentNumber] = contact_details_options[:document_number] if contact_details_options[:document_number]
168
+ contact_details[:email] = contact_details_options[:email] if contact_details_options[:email]
169
+ contact_details[:firstName] = contact_details_options[:first_name] if contact_details_options[:first_name]
170
+ contact_details[:lastName] = contact_details_options[:last_name] if contact_details_options[:last_name]
171
+ contact_details[:secondLastName] = contact_details_options[:second_last_name] if contact_details_options[:second_last_name]
172
+ contact_details[:phoneNumber] = contact_details_options[:phone_number] if contact_details_options[:phone_number]
173
+ post[:contactDetails] = contact_details
174
+ end
175
+
176
+ def add_full_response(post, options)
177
+ post[:fullResponse] = options[:full_response].to_s.casecmp('true').zero? if options[:full_response]
178
+ end
179
+
157
180
  ENDPOINT = {
158
181
  'tokenize' => 'tokens',
159
182
  'charge' => 'charges',
@@ -5,7 +5,10 @@ module ActiveMerchant #:nodoc:
5
5
  class LitleGateway < Gateway
6
6
  SCHEMA_VERSION = '9.14'
7
7
 
8
+ class_attribute :postlive_url
9
+
8
10
  self.test_url = 'https://www.testvantivcnp.com/sandbox/communicator/online'
11
+ self.postlive_url = 'https://payments.vantivpostlive.com/vap/communicator/online'
9
12
  self.live_url = 'https://payments.vantivcnp.com/vap/communicator/online'
10
13
 
11
14
  self.supported_countries = ['US']
@@ -368,9 +371,9 @@ module ActiveMerchant #:nodoc:
368
371
  return unless address
369
372
 
370
373
  doc.companyName(address[:company]) unless address[:company].blank?
371
- doc.addressLine1(address[:address1]) unless address[:address1].blank?
372
- doc.addressLine2(address[:address2]) unless address[:address2].blank?
373
- doc.city(address[:city]) unless address[:city].blank?
374
+ doc.addressLine1(truncate(address[:address1], 35)) unless address[:address1].blank?
375
+ doc.addressLine2(truncate(address[:address2], 35)) unless address[:address2].blank?
376
+ doc.city(truncate(address[:city], 35)) unless address[:city].blank?
374
377
  doc.state(address[:state]) unless address[:state].blank?
375
378
  doc.zip(address[:zip]) unless address[:zip].blank?
376
379
  doc.country(address[:country]) unless address[:country].blank?
@@ -491,7 +494,7 @@ module ActiveMerchant #:nodoc:
491
494
  attributes = {}
492
495
  attributes[:id] = truncate(options[:id] || options[:order_id], 24)
493
496
  attributes[:reportGroup] = options[:merchant] || 'Default Report Group'
494
- attributes[:customerId] = options[:customer]
497
+ attributes[:customerId] = options[:customer_id]
495
498
  attributes.delete_if { |_key, value| value == nil }
496
499
  attributes
497
500
  end
@@ -513,6 +516,8 @@ module ActiveMerchant #:nodoc:
513
516
  end
514
517
 
515
518
  def url
519
+ return postlive_url if @options[:url_override].to_s == 'postlive'
520
+
516
521
  test? ? test_url : live_url
517
522
  end
518
523
 
@@ -4,7 +4,7 @@ module ActiveMerchant #:nodoc:
4
4
  self.live_url = self.test_url = 'https://api.mercadopago.com/v1'
5
5
 
6
6
  self.supported_countries = %w[AR BR CL CO MX PE UY]
7
- self.supported_cardtypes = %i[visa master american_express elo cabal naranja]
7
+ self.supported_cardtypes = %i[visa master american_express elo cabal naranja creditel]
8
8
 
9
9
  self.homepage_url = 'https://www.mercadopago.com/'
10
10
  self.display_name = 'Mercado Pago'
@@ -105,7 +105,7 @@ module ActiveMerchant #:nodoc:
105
105
 
106
106
  def authorize_request(money, payment, options = {})
107
107
  post = purchase_request(money, payment, options)
108
- post[:capture] = false
108
+ post[:capture] = options[:capture] || false
109
109
  post
110
110
  end
111
111
 
@@ -129,6 +129,7 @@ module ActiveMerchant #:nodoc:
129
129
 
130
130
  def add_additional_data(post, options)
131
131
  post[:sponsor_id] = options[:sponsor_id]
132
+ post[:metadata] = options[:metadata] if options[:metadata]
132
133
  post[:device_id] = options[:device_id] if options[:device_id]
133
134
  post[:additional_info] = {
134
135
  ip_address: options[:ip_address]
@@ -143,7 +144,7 @@ module ActiveMerchant #:nodoc:
143
144
  email: options[:email],
144
145
  first_name: payment.first_name,
145
146
  last_name: payment.last_name
146
- }
147
+ }.merge(options[:payer] || {})
147
148
  end
148
149
 
149
150
  def add_address(post, options)
@@ -191,7 +192,7 @@ module ActiveMerchant #:nodoc:
191
192
  post[:description] = options[:description]
192
193
  post[:installments] = options[:installments] ? options[:installments].to_i : 1
193
194
  post[:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor]
194
- post[:external_reference] = options[:order_id] || SecureRandom.hex(16)
195
+ post[:external_reference] = options[:order_id] || options[:external_reference] || SecureRandom.hex(16)
195
196
  end
196
197
 
197
198
  def add_payment(post, options)
@@ -187,6 +187,8 @@ module ActiveMerchant #:nodoc:
187
187
  def parse(body)
188
188
  xml = REXML::Document.new(body)
189
189
 
190
+ return { response_message: 'Invalid gateway response' } unless xml.root.present?
191
+
190
192
  response = {}
191
193
  xml.root.elements.to_a.each do |node|
192
194
  parse_element(response, node)
@@ -0,0 +1,277 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class MokaGateway < Gateway
4
+ self.test_url = 'https://service.testmoka.com'
5
+ self.live_url = 'https://service.moka.com'
6
+
7
+ self.supported_countries = %w[GB TR US]
8
+ self.default_currency = 'TRY'
9
+ self.money_format = :dollars
10
+ self.supported_cardtypes = %i[visa master american_express discover]
11
+
12
+ self.homepage_url = 'http://developer.moka.com/'
13
+ self.display_name = 'Moka'
14
+
15
+ ERROR_CODE_MAPPING = {
16
+ '000' => 'General error',
17
+ '001' => '3D Not authenticated',
18
+ '002' => 'Limit is insufficient',
19
+ '003' => 'Credit card number format is wrong',
20
+ '004' => 'General decline',
21
+ '005' => 'This process is invalid for the card owner',
22
+ '006' => 'Expiration date is wrong',
23
+ '007' => 'Invalid transaction',
24
+ '008' => 'Connection with the bank not established',
25
+ '009' => 'Undefined error code',
26
+ '010' => 'Bank SSL error',
27
+ '011' => 'Call the bank for the manual authentication',
28
+ '012' => 'Card info is wrong - Kart Number or CVV2',
29
+ '013' => '3D secure is not supported other than Visa MC cards',
30
+ '014' => 'Invalid account number',
31
+ '015' => 'CVV is wrong',
32
+ '016' => 'Authentication process is not present',
33
+ '017' => 'System error',
34
+ '018' => 'Stolen card',
35
+ '019' => 'Lost card',
36
+ '020' => 'Card with limited properties',
37
+ '021' => 'Timeout',
38
+ '022' => 'Invalid merchant',
39
+ '023' => 'False authentication',
40
+ '024' => '3D authorization is successful but the process cannot be completed',
41
+ '025' => '3D authorization failure',
42
+ '026' => 'Either the issuer bank or the card is not enrolled to the 3D process',
43
+ '027' => 'The bank did not allow the process',
44
+ '028' => 'Fraud suspect',
45
+ '029' => 'The card is closed to the e-commerce operations'
46
+ }
47
+
48
+ def initialize(options = {})
49
+ requires!(options, :dealer_code, :username, :password)
50
+ super
51
+ end
52
+
53
+ def purchase(money, payment, options = {})
54
+ post = {}
55
+ post[:PaymentDealerRequest] = {}
56
+ options[:pre_auth] = 0
57
+ add_auth_purchase(post, money, payment, options)
58
+
59
+ commit('purchase', post)
60
+ end
61
+
62
+ def authorize(money, payment, options = {})
63
+ post = {}
64
+ post[:PaymentDealerRequest] = {}
65
+ options[:pre_auth] = 1
66
+ add_auth_purchase(post, money, payment, options)
67
+
68
+ commit('authorize', post)
69
+ end
70
+
71
+ def capture(money, authorization, options = {})
72
+ post = {}
73
+ post[:PaymentDealerRequest] = {}
74
+ add_payment_dealer_authentication(post)
75
+ add_transaction_reference(post, authorization)
76
+ add_additional_transaction_data(post, options)
77
+
78
+ commit('capture', post)
79
+ end
80
+
81
+ def refund(money, authorization, options = {})
82
+ post = {}
83
+ post[:PaymentDealerRequest] = {}
84
+ add_payment_dealer_authentication(post)
85
+ add_transaction_reference(post, authorization)
86
+ add_additional_transaction_data(post, options)
87
+ add_void_refund_reason(post)
88
+ add_amount(post, money)
89
+
90
+ commit('refund', post)
91
+ end
92
+
93
+ def void(authorization, options = {})
94
+ post = {}
95
+ post[:PaymentDealerRequest] = {}
96
+ add_payment_dealer_authentication(post)
97
+ add_transaction_reference(post, authorization)
98
+ add_additional_transaction_data(post, options)
99
+ add_void_refund_reason(post)
100
+
101
+ commit('void', post)
102
+ end
103
+
104
+ def verify(credit_card, options = {})
105
+ MultiResponse.run(:use_first_response) do |r|
106
+ r.process { authorize(100, credit_card, options) }
107
+ r.process(:ignore_result) { void(r.authorization, options) }
108
+ end
109
+ end
110
+
111
+ def supports_scrubbing?
112
+ true
113
+ end
114
+
115
+ def scrub(transcript)
116
+ transcript.
117
+ gsub(%r(("CardNumber\\?":\\?")[^"]*)i, '\1[FILTERED]').
118
+ gsub(%r(("CvcNumber\\?":\\?")[^"]*)i, '\1[FILTERED]').
119
+ gsub(%r(("DealerCode\\?":\\?"?)[^"?]*)i, '\1[FILTERED]').
120
+ gsub(%r(("Username\\?":\\?")[^"]*)i, '\1[FILTERED]').
121
+ gsub(%r(("Password\\?":\\?")[^"]*)i, '\1[FILTERED]').
122
+ gsub(%r(("CheckKey\\?":\\?")[^"]*)i, '\1[FILTERED]')
123
+ end
124
+
125
+ private
126
+
127
+ def add_auth_purchase(post, money, payment, options)
128
+ add_payment_dealer_authentication(post)
129
+ add_invoice(post, money, options)
130
+ add_payment(post, payment)
131
+ add_additional_auth_purchase_data(post, options)
132
+ add_additional_transaction_data(post, options)
133
+ add_buyer_information(post, payment, options)
134
+ add_basket_product(post, options[:basket_product]) if options[:basket_product]
135
+ end
136
+
137
+ def add_payment_dealer_authentication(post)
138
+ post[:PaymentDealerAuthentication] = {
139
+ DealerCode: @options[:dealer_code],
140
+ Username: @options[:username],
141
+ Password: @options[:password],
142
+ CheckKey: check_key
143
+ }
144
+ end
145
+
146
+ def check_key
147
+ str = "#{@options[:dealer_code]}MK#{@options[:username]}PD#{@options[:password]}"
148
+ Digest::SHA256.hexdigest(str)
149
+ end
150
+
151
+ def add_invoice(post, money, options)
152
+ post[:PaymentDealerRequest][:Amount] = amount(money) || 0
153
+ post[:PaymentDealerRequest][:Currency] = options[:currency] || 'TL'
154
+ end
155
+
156
+ def add_payment(post, card)
157
+ post[:PaymentDealerRequest][:CardHolderFullName] = card.name
158
+ post[:PaymentDealerRequest][:CardNumber] = card.number
159
+ post[:PaymentDealerRequest][:ExpMonth] = card.month
160
+ post[:PaymentDealerRequest][:ExpYear] = card.year
161
+ post[:PaymentDealerRequest][:CvcNumber] = card.verification_value
162
+ end
163
+
164
+ def add_amount(post, money)
165
+ post[:PaymentDealerRequest][:Amount] = money || 0
166
+ end
167
+
168
+ def add_additional_auth_purchase_data(post, options)
169
+ post[:PaymentDealerRequest][:IsPreAuth] = options[:pre_auth]
170
+ post[:PaymentDealerRequest][:Description] = options[:order_id] if options[:order_id]
171
+ post[:SubMerchantName] = options[:sub_merchant_name] if options[:sub_merchant_name]
172
+ post[:IsPoolPayment] = options[:is_pool_payment] || 0
173
+ end
174
+
175
+ def add_buyer_information(post, card, options)
176
+ obj = {}
177
+
178
+ obj[:BuyerFullName] = card.name || ''
179
+ obj[:BuyerEmail] = options[:email] if options[:email]
180
+ obj[:BuyerAddress] = options[:billing_address][:address1] if options[:billing_address]
181
+ obj[:BuyerGsmNumber] = options[:billing_address][:phone] if options[:billing_address]
182
+
183
+ post[:PaymentDealerRequest][:BuyerInformation] = obj
184
+ end
185
+
186
+ def add_basket_product(post, basket_options)
187
+ basket = []
188
+
189
+ basket_options.each do |product|
190
+ obj = {}
191
+ obj[:ProductId] = product[:product_id] if product[:product_id]
192
+ obj[:ProductCode] = product[:product_code] if product[:product_code]
193
+ obj[:UnitPrice] = amount(product[:unit_price]) if product[:unit_price]
194
+ obj[:Quantity] = product[:quantity] if product[:quantity]
195
+ basket << obj
196
+ end
197
+
198
+ post[:PaymentDealerRequest][:BasketProduct] = basket
199
+ end
200
+
201
+ def add_additional_transaction_data(post, options)
202
+ post[:PaymentDealerRequest][:ClientIP] = options[:ip] if options[:ip]
203
+ post[:PaymentDealerRequest][:OtherTrxCode] = options[:order_id] if options[:order_id]
204
+ end
205
+
206
+ def add_transaction_reference(post, authorization)
207
+ post[:PaymentDealerRequest][:VirtualPosOrderId] = authorization
208
+ end
209
+
210
+ def add_void_refund_reason(post)
211
+ post[:PaymentDealerRequest][:VoidRefundReason] = 2
212
+ end
213
+
214
+ def commit(action, parameters)
215
+ response = parse(ssl_post(url(action), post_data(parameters), request_headers))
216
+ Response.new(
217
+ success_from(response),
218
+ message_from(response),
219
+ response,
220
+ authorization: authorization_from(response),
221
+ test: test?,
222
+ error_code: error_code_from(response)
223
+ )
224
+ end
225
+
226
+ def url(action)
227
+ host = (test? ? test_url : live_url)
228
+ endpoint = endpoint(action)
229
+
230
+ "#{host}/PaymentDealer/#{endpoint}"
231
+ end
232
+
233
+ def endpoint(action)
234
+ case action
235
+ when 'purchase', 'authorize'
236
+ 'DoDirectPayment'
237
+ when 'capture'
238
+ 'DoCapture'
239
+ when 'void'
240
+ 'DoVoid'
241
+ when 'refund'
242
+ 'DoCreateRefundRequest'
243
+ end
244
+ end
245
+
246
+ def request_headers
247
+ { 'Content-Type' => 'application/json' }
248
+ end
249
+
250
+ def post_data(parameters = {})
251
+ JSON.generate(parameters)
252
+ end
253
+
254
+ def parse(body)
255
+ JSON.parse(body)
256
+ end
257
+
258
+ def success_from(response)
259
+ response.dig('Data', 'IsSuccessful')
260
+ end
261
+
262
+ def message_from(response)
263
+ response.dig('Data', 'ResultMessage').presence || response['ResultCode']
264
+ end
265
+
266
+ def authorization_from(response)
267
+ response.dig('Data', 'VirtualPosOrderId')
268
+ end
269
+
270
+ def error_code_from(response)
271
+ codes = [response['ResultCode'], response.dig('Data', 'ResultCode')].flatten
272
+ codes.reject! { |code| code.blank? || code.casecmp('success').zero? }
273
+ codes.map { |code| ERROR_CODE_MAPPING[code] || code }.join(', ')
274
+ end
275
+ end
276
+ end
277
+ end