activemerchant 1.121.0 → 1.125.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +217 -0
  3. data/README.md +1 -1
  4. data/lib/active_merchant/billing/check.rb +13 -19
  5. data/lib/active_merchant/billing/credit_card.rb +13 -0
  6. data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
  7. data/lib/active_merchant/billing/credit_card_methods.rb +24 -12
  8. data/lib/active_merchant/billing/gateway.rb +1 -1
  9. data/lib/active_merchant/billing/gateways/adyen.rb +75 -27
  10. data/lib/active_merchant/billing/gateways/authorize_net.rb +10 -8
  11. data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
  12. data/lib/active_merchant/billing/gateways/blue_snap.rb +2 -2
  13. data/lib/active_merchant/billing/gateways/braintree_blue.rb +6 -3
  14. data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
  15. data/lib/active_merchant/billing/gateways/cashnet.rb +15 -5
  16. data/lib/active_merchant/billing/gateways/checkout_v2.rb +33 -4
  17. data/lib/active_merchant/billing/gateways/credorax.rb +2 -1
  18. data/lib/active_merchant/billing/gateways/cyber_source.rb +41 -6
  19. data/lib/active_merchant/billing/gateways/d_local.rb +12 -6
  20. data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
  21. data/lib/active_merchant/billing/gateways/decidir_plus.rb +173 -0
  22. data/lib/active_merchant/billing/gateways/ebanx.rb +16 -1
  23. data/lib/active_merchant/billing/gateways/elavon.rb +65 -30
  24. data/lib/active_merchant/billing/gateways/element.rb +22 -2
  25. data/lib/active_merchant/billing/gateways/global_collect.rb +130 -26
  26. data/lib/active_merchant/billing/gateways/ipg.rb +416 -0
  27. data/lib/active_merchant/billing/gateways/kushki.rb +30 -0
  28. data/lib/active_merchant/billing/gateways/mercado_pago.rb +6 -3
  29. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
  30. data/lib/active_merchant/billing/gateways/mit.rb +260 -0
  31. data/lib/active_merchant/billing/gateways/moka.rb +290 -0
  32. data/lib/active_merchant/billing/gateways/monei.rb +228 -144
  33. data/lib/active_merchant/billing/gateways/mundipagg.rb +22 -11
  34. data/lib/active_merchant/billing/gateways/nmi.rb +29 -10
  35. data/lib/active_merchant/billing/gateways/orbital.rb +46 -8
  36. data/lib/active_merchant/billing/gateways/pay_arc.rb +392 -0
  37. data/lib/active_merchant/billing/gateways/pay_conex.rb +3 -1
  38. data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
  39. data/lib/active_merchant/billing/gateways/payeezy.rb +4 -0
  40. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
  41. data/lib/active_merchant/billing/gateways/payflow.rb +21 -4
  42. data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
  43. data/lib/active_merchant/billing/gateways/paymentez.rb +14 -2
  44. data/lib/active_merchant/billing/gateways/paysafe.rb +412 -0
  45. data/lib/active_merchant/billing/gateways/payu_latam.rb +9 -4
  46. data/lib/active_merchant/billing/gateways/payway_dot_com.rb +3 -3
  47. data/lib/active_merchant/billing/gateways/pin.rb +31 -4
  48. data/lib/active_merchant/billing/gateways/priority.rb +347 -0
  49. data/lib/active_merchant/billing/gateways/realex.rb +18 -0
  50. data/lib/active_merchant/billing/gateways/redsys.rb +35 -32
  51. data/lib/active_merchant/billing/gateways/safe_charge.rb +8 -2
  52. data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
  53. data/lib/active_merchant/billing/gateways/stripe.rb +27 -7
  54. data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +115 -39
  55. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
  56. data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
  57. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +21 -7
  58. data/lib/active_merchant/billing/gateways/vpos.rb +49 -6
  59. data/lib/active_merchant/billing/gateways/wompi.rb +193 -0
  60. data/lib/active_merchant/billing/gateways/worldpay.rb +226 -62
  61. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
  62. data/lib/active_merchant/billing/response.rb +4 -0
  63. data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
  64. data/lib/active_merchant/billing.rb +1 -0
  65. data/lib/active_merchant/version.rb +1 -1
  66. metadata +13 -3
@@ -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,8 @@ 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)
85
+ add_metadata(post, options)
81
86
 
82
87
  commit(action, post)
83
88
  end
@@ -88,6 +93,9 @@ module ActiveMerchant #:nodoc:
88
93
  post = {}
89
94
  add_reference(post, authorization, options)
90
95
  add_invoice(action, post, amount, options)
96
+ add_contact_details(post, options[:contact_details]) if options[:contact_details]
97
+ add_full_response(post, options)
98
+ add_metadata(post, options)
91
99
 
92
100
  commit(action, post)
93
101
  end
@@ -98,6 +106,8 @@ module ActiveMerchant #:nodoc:
98
106
  post = {}
99
107
  add_reference(post, authorization, options)
100
108
  add_invoice(action, post, amount, options)
109
+ add_full_response(post, options)
110
+ add_metadata(post, options)
101
111
 
102
112
  commit(action, post)
103
113
  end
@@ -154,6 +164,26 @@ module ActiveMerchant #:nodoc:
154
164
  post[:token] = authorization
155
165
  end
156
166
 
167
+ def add_contact_details(post, contact_details_options)
168
+ contact_details = {}
169
+ contact_details[:documentType] = contact_details_options[:document_type] if contact_details_options[:document_type]
170
+ contact_details[:documentNumber] = contact_details_options[:document_number] if contact_details_options[:document_number]
171
+ contact_details[:email] = contact_details_options[:email] if contact_details_options[:email]
172
+ contact_details[:firstName] = contact_details_options[:first_name] if contact_details_options[:first_name]
173
+ contact_details[:lastName] = contact_details_options[:last_name] if contact_details_options[:last_name]
174
+ contact_details[:secondLastName] = contact_details_options[:second_last_name] if contact_details_options[:second_last_name]
175
+ contact_details[:phoneNumber] = contact_details_options[:phone_number] if contact_details_options[:phone_number]
176
+ post[:contactDetails] = contact_details
177
+ end
178
+
179
+ def add_full_response(post, options)
180
+ post[:fullResponse] = options[:full_response].to_s.casecmp('true').zero? if options[:full_response]
181
+ end
182
+
183
+ def add_metadata(post, options)
184
+ post[:metadata] = options[:metadata] if options[:metadata]
185
+ end
186
+
157
187
  ENDPOINT = {
158
188
  'tokenize' => 'tokens',
159
189
  'charge' => 'charges',
@@ -53,8 +53,10 @@ module ActiveMerchant #:nodoc:
53
53
  end
54
54
 
55
55
  def verify(credit_card, options = {})
56
+ verify_amount = 100
57
+ verify_amount = options[:amount].to_i if options[:amount]
56
58
  MultiResponse.run(:use_first_response) do |r|
57
- r.process { authorize(100, credit_card, options) }
59
+ r.process { authorize(verify_amount, credit_card, options) }
58
60
  r.process(:ignore_result) { void(r.authorization, options) }
59
61
  end
60
62
  end
@@ -129,6 +131,7 @@ module ActiveMerchant #:nodoc:
129
131
 
130
132
  def add_additional_data(post, options)
131
133
  post[:sponsor_id] = options[:sponsor_id]
134
+ post[:metadata] = options[:metadata] if options[:metadata]
132
135
  post[:device_id] = options[:device_id] if options[:device_id]
133
136
  post[:additional_info] = {
134
137
  ip_address: options[:ip_address]
@@ -143,7 +146,7 @@ module ActiveMerchant #:nodoc:
143
146
  email: options[:email],
144
147
  first_name: payment.first_name,
145
148
  last_name: payment.last_name
146
- }
149
+ }.merge(options[:payer] || {})
147
150
  end
148
151
 
149
152
  def add_address(post, options)
@@ -191,7 +194,7 @@ module ActiveMerchant #:nodoc:
191
194
  post[:description] = options[:description]
192
195
  post[:installments] = options[:installments] ? options[:installments].to_i : 1
193
196
  post[:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor]
194
- post[:external_reference] = options[:order_id] || SecureRandom.hex(16)
197
+ post[:external_reference] = options[:order_id] || options[:external_reference] || SecureRandom.hex(16)
195
198
  end
196
199
 
197
200
  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,260 @@
1
+ require 'json'
2
+ require 'openssl'
3
+ require 'digest'
4
+ require 'base64'
5
+
6
+ module ActiveMerchant #:nodoc:
7
+ module Billing #:nodoc:
8
+ class MitGateway < Gateway
9
+ self.live_url = 'https://wpy.mitec.com.mx/ModuloUtilWS/activeCDP.htm'
10
+
11
+ self.supported_countries = ['MX']
12
+ self.default_currency = 'MXN'
13
+ self.supported_cardtypes = %i[visa master]
14
+
15
+ self.homepage_url = 'http://www.centrodepagos.com.mx/'
16
+ self.display_name = 'MIT Centro de pagos'
17
+
18
+ self.money_format = :dollars
19
+
20
+ def initialize(options = {})
21
+ requires!(options, :commerce_id, :user, :api_key, :key_session)
22
+ super
23
+ end
24
+
25
+ def purchase(money, payment, options = {})
26
+ MultiResponse.run do |r|
27
+ r.process { authorize(money, payment, options) }
28
+ r.process { capture(money, r.authorization, options) }
29
+ end
30
+ end
31
+
32
+ def cipher_key
33
+ @options[:key_session]
34
+ end
35
+
36
+ def decrypt(val, keyinhex)
37
+ # Splits the first 16 bytes (the IV bytes) in array format
38
+ unpacked = val.unpack('m')
39
+ iv_base64 = unpacked[0].bytes.slice(0, 16)
40
+ # Splits the second bytes (the encrypted text bytes) these would be the
41
+ # original message
42
+ full_data = unpacked[0].bytes.slice(16, unpacked[0].bytes.length)
43
+ # Creates the engine
44
+ engine = OpenSSL::Cipher::AES128.new(:CBC)
45
+ # Set engine as decrypt mode
46
+ engine.decrypt
47
+ # Converts the key from hex to bytes
48
+ engine.key = [keyinhex].pack('H*')
49
+ # Converts the ivBase64 array into bytes
50
+ engine.iv = iv_base64.pack('c*')
51
+ # Decrypts the texts and returns the original string
52
+ engine.update(full_data.pack('c*')) + engine.final
53
+ end
54
+
55
+ def encrypt(val, keyinhex)
56
+ # Creates the engine motor
57
+ engine = OpenSSL::Cipher::AES128.new(:CBC)
58
+ # Set engine as encrypt mode
59
+ engine.encrypt
60
+ # Converts the key from hex to bytes
61
+ engine.key = [keyinhex].pack('H*')
62
+ # Generates a random iv with this settings
63
+ iv_rand = engine.random_iv
64
+ # Packs IV as a Base64 string
65
+ iv_base64 = [iv_rand].pack('m')
66
+ # Converts the packed key into bytes
67
+ unpacked = iv_base64.unpack('m')
68
+ iv = unpacked[0]
69
+ # Sets the IV into the engine
70
+ engine.iv = iv
71
+ # Encrypts the texts and stores the bytes
72
+ encrypted_bytes = engine.update(val) + engine.final
73
+ # Concatenates the (a) IV bytes and (b) the encrypted bytes then returns a
74
+ # base64 representation
75
+ [iv << encrypted_bytes].pack('m')
76
+ end
77
+
78
+ def authorize(money, payment, options = {})
79
+ post = {
80
+ operation: 'Authorize',
81
+ commerce_id: @options[:commerce_id],
82
+ user: @options[:user],
83
+ apikey: @options[:api_key],
84
+ testMode: (test? ? 'YES' : 'NO')
85
+ }
86
+ add_invoice(post, money, options)
87
+ # Payments contains the card information
88
+ add_payment(post, payment)
89
+ add_customer_data(post, options)
90
+ post[:key_session] = @options[:key_session]
91
+
92
+ post_to_json = post.to_json
93
+ post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
94
+
95
+ final_post = '<authorization>' + post_to_json_encrypt + '</authorization><dataID>' + @options[:user] + '</dataID>'
96
+ json_post = {}
97
+ json_post[:payload] = final_post
98
+ commit('sale', json_post)
99
+ end
100
+
101
+ def capture(money, authorization, options = {})
102
+ post = {
103
+ operation: 'Capture',
104
+ commerce_id: @options[:commerce_id],
105
+ user: @options[:user],
106
+ apikey: @options[:api_key],
107
+ testMode: (test? ? 'YES' : 'NO'),
108
+ transaction_id: authorization,
109
+ amount: amount(money)
110
+ }
111
+ post[:key_session] = @options[:key_session]
112
+
113
+ post_to_json = post.to_json
114
+ post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
115
+
116
+ final_post = '<capture>' + post_to_json_encrypt + '</capture><dataID>' + @options[:user] + '</dataID>'
117
+ json_post = {}
118
+ json_post[:payload] = final_post
119
+ commit('capture', json_post)
120
+ end
121
+
122
+ def refund(money, authorization, options = {})
123
+ post = {
124
+ operation: 'Refund',
125
+ commerce_id: @options[:commerce_id],
126
+ user: @options[:user],
127
+ apikey: @options[:api_key],
128
+ testMode: (test? ? 'YES' : 'NO'),
129
+ transaction_id: authorization,
130
+ auth: authorization,
131
+ amount: amount(money)
132
+ }
133
+ post[:key_session] = @options[:key_session]
134
+
135
+ post_to_json = post.to_json
136
+ post_to_json_encrypt = encrypt(post_to_json, @options[:key_session])
137
+
138
+ final_post = '<refund>' + post_to_json_encrypt + '</refund><dataID>' + @options[:user] + '</dataID>'
139
+ json_post = {}
140
+ json_post[:payload] = final_post
141
+ commit('refund', json_post)
142
+ end
143
+
144
+ def supports_scrubbing?
145
+ true
146
+ end
147
+
148
+ def scrub(transcript)
149
+ ret_transcript = transcript
150
+ auth_origin = ret_transcript[/<authorization>(.*?)<\/authorization>/, 1]
151
+ unless auth_origin.nil?
152
+ auth_decrypted = decrypt(auth_origin, @options[:key_session])
153
+ auth_json = JSON.parse(auth_decrypted)
154
+ auth_json['card'] = '[FILTERED]'
155
+ auth_json['cvv'] = '[FILTERED]'
156
+ auth_json['apikey'] = '[FILTERED]'
157
+ auth_json['key_session'] = '[FILTERED]'
158
+ auth_to_json = auth_json.to_json
159
+ auth_tagged = '<authorization>' + auth_to_json + '</authorization>'
160
+ ret_transcript = ret_transcript.gsub(/<authorization>(.*?)<\/authorization>/, auth_tagged)
161
+ end
162
+
163
+ cap_origin = ret_transcript[/<capture>(.*?)<\/capture>/, 1]
164
+ unless cap_origin.nil?
165
+ cap_decrypted = decrypt(cap_origin, @options[:key_session])
166
+ cap_json = JSON.parse(cap_decrypted)
167
+ cap_json['apikey'] = '[FILTERED]'
168
+ cap_json['key_session'] = '[FILTERED]'
169
+ cap_to_json = cap_json.to_json
170
+ cap_tagged = '<capture>' + cap_to_json + '</capture>'
171
+ ret_transcript = ret_transcript.gsub(/<capture>(.*?)<\/capture>/, cap_tagged)
172
+ end
173
+
174
+ ref_origin = ret_transcript[/<refund>(.*?)<\/refund>/, 1]
175
+ unless ref_origin.nil?
176
+ ref_decrypted = decrypt(ref_origin, @options[:key_session])
177
+ ref_json = JSON.parse(ref_decrypted)
178
+ ref_json['apikey'] = '[FILTERED]'
179
+ ref_json['key_session'] = '[FILTERED]'
180
+ ref_to_json = ref_json.to_json
181
+ ref_tagged = '<refund>' + ref_to_json + '</refund>'
182
+ ret_transcript = ret_transcript.gsub(/<refund>(.*?)<\/refund>/, ref_tagged)
183
+ end
184
+
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]
194
+ end
195
+
196
+ ret_transcript
197
+ end
198
+
199
+ private
200
+
201
+ def add_customer_data(post, options)
202
+ post[:email] = options[:email] || 'nadie@mit.test'
203
+ end
204
+
205
+ def add_invoice(post, money, options)
206
+ post[:amount] = amount(money)
207
+ post[:currency] = (options[:currency] || currency(money))
208
+ post[:reference] = options[:order_id]
209
+ post[:transaction_id] = options[:order_id]
210
+ end
211
+
212
+ def add_payment(post, payment)
213
+ post[:installments] = 1
214
+ post[:card] = payment.number
215
+ post[:expmonth] = payment.month
216
+ post[:expyear] = payment.year
217
+ post[:cvv] = payment.verification_value
218
+ post[:name_client] = [payment.first_name, payment.last_name].join(' ')
219
+ end
220
+
221
+ 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' })
225
+ response = JSON.parse(decrypt(raw_response, @options[:key_session]))
226
+
227
+ Response.new(
228
+ success_from(response),
229
+ message_from(response),
230
+ response,
231
+ authorization: authorization_from(response),
232
+ avs_result: AVSResult.new(code: response['some_avs_response_key']),
233
+ cvv_result: CVVResult.new(response['some_cvv_response_key']),
234
+ test: test?,
235
+ error_code: error_code_from(response)
236
+ )
237
+ end
238
+
239
+ def success_from(response)
240
+ response['response'] == 'approved'
241
+ end
242
+
243
+ def message_from(response)
244
+ response['response']
245
+ end
246
+
247
+ def authorization_from(response)
248
+ response['reference']
249
+ end
250
+
251
+ def post_data(action, parameters = {})
252
+ parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
253
+ end
254
+
255
+ def error_code_from(response)
256
+ response['message'].split(' -- ', 2)[0] unless success_from(response)
257
+ end
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,290 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class MokaGateway < Gateway
4
+ self.test_url = 'https://service.refmoka.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
+ add_3ds_data(post, options) if options[:execute_threed]
59
+
60
+ action = options[:execute_threed] ? 'three_ds_purchase' : 'purchase'
61
+ commit(action, post)
62
+ end
63
+
64
+ def authorize(money, payment, options = {})
65
+ post = {}
66
+ post[:PaymentDealerRequest] = {}
67
+ options[:pre_auth] = 1
68
+ add_auth_purchase(post, money, payment, options)
69
+ add_3ds_data(post, options) if options[:execute_threed]
70
+
71
+ action = options[:execute_threed] ? 'three_ds_authorize' : 'authorize'
72
+ commit(action, post)
73
+ end
74
+
75
+ def capture(money, authorization, options = {})
76
+ post = {}
77
+ post[:PaymentDealerRequest] = {}
78
+ add_payment_dealer_authentication(post)
79
+ add_transaction_reference(post, authorization)
80
+ add_invoice(post, money, options)
81
+
82
+ commit('capture', post)
83
+ end
84
+
85
+ def refund(money, authorization, options = {})
86
+ post = {}
87
+ post[:PaymentDealerRequest] = {}
88
+ add_payment_dealer_authentication(post)
89
+ add_transaction_reference(post, authorization)
90
+ add_void_refund_reason(post)
91
+ add_amount(post, money)
92
+
93
+ commit('refund', post)
94
+ end
95
+
96
+ def void(authorization, options = {})
97
+ post = {}
98
+ post[:PaymentDealerRequest] = {}
99
+ add_payment_dealer_authentication(post)
100
+ add_transaction_reference(post, authorization)
101
+ add_void_refund_reason(post)
102
+
103
+ commit('void', post)
104
+ end
105
+
106
+ def verify(credit_card, options = {})
107
+ MultiResponse.run(:use_first_response) do |r|
108
+ r.process { authorize(100, credit_card, options) }
109
+ r.process(:ignore_result) { void(r.authorization, options) }
110
+ end
111
+ end
112
+
113
+ def supports_scrubbing?
114
+ true
115
+ end
116
+
117
+ def scrub(transcript)
118
+ transcript.
119
+ gsub(%r(("CardNumber\\?":\\?")[^"]*)i, '\1[FILTERED]').
120
+ gsub(%r(("CvcNumber\\?":\\?")[^"]*)i, '\1[FILTERED]').
121
+ gsub(%r(("DealerCode\\?":\\?"?)[^"?]*)i, '\1[FILTERED]').
122
+ gsub(%r(("Username\\?":\\?")[^"]*)i, '\1[FILTERED]').
123
+ gsub(%r(("Password\\?":\\?")[^"]*)i, '\1[FILTERED]').
124
+ gsub(%r(("CheckKey\\?":\\?")[^"]*)i, '\1[FILTERED]')
125
+ end
126
+
127
+ private
128
+
129
+ def add_auth_purchase(post, money, payment, options)
130
+ add_payment_dealer_authentication(post)
131
+ add_invoice(post, money, options)
132
+ add_payment(post, payment)
133
+ add_additional_auth_purchase_data(post, options)
134
+ add_additional_transaction_data(post, options)
135
+ add_buyer_information(post, payment, options)
136
+ add_basket_product(post, options[:basket_product]) if options[:basket_product]
137
+ end
138
+
139
+ def add_3ds_data(post, options)
140
+ post[:PaymentDealerRequest][:ReturnHash] = 1
141
+ post[:PaymentDealerRequest][:RedirectUrl] = options[:redirect_url] || ''
142
+ post[:PaymentDealerRequest][:RedirectType] = options[:redirect_type] || 0
143
+ end
144
+
145
+ def add_payment_dealer_authentication(post)
146
+ post[:PaymentDealerAuthentication] = {
147
+ DealerCode: @options[:dealer_code],
148
+ Username: @options[:username],
149
+ Password: @options[:password],
150
+ CheckKey: check_key
151
+ }
152
+ end
153
+
154
+ def check_key
155
+ str = "#{@options[:dealer_code]}MK#{@options[:username]}PD#{@options[:password]}"
156
+ Digest::SHA256.hexdigest(str)
157
+ end
158
+
159
+ def add_invoice(post, money, options)
160
+ post[:PaymentDealerRequest][:Amount] = amount(money) || 0
161
+ post[:PaymentDealerRequest][:Currency] = options[:currency] || 'TL'
162
+ end
163
+
164
+ def add_payment(post, card)
165
+ post[:PaymentDealerRequest][:CardHolderFullName] = card.name
166
+ post[:PaymentDealerRequest][:CardNumber] = card.number
167
+ post[:PaymentDealerRequest][:ExpMonth] = card.month.to_s.rjust(2, '0')
168
+ post[:PaymentDealerRequest][:ExpYear] = card.year
169
+ post[:PaymentDealerRequest][:CvcNumber] = card.verification_value || ''
170
+ end
171
+
172
+ def add_amount(post, money)
173
+ post[:PaymentDealerRequest][:Amount] = amount(money) || 0
174
+ end
175
+
176
+ def add_additional_auth_purchase_data(post, options)
177
+ post[:PaymentDealerRequest][:IsPreAuth] = options[:pre_auth]
178
+ post[:PaymentDealerRequest][:Description] = options[:description] if options[:description]
179
+ post[:PaymentDealerRequest][:InstallmentNumber] = options[:installment_number].to_i if options[:installment_number]
180
+ post[:SubMerchantName] = options[:sub_merchant_name] if options[:sub_merchant_name]
181
+ post[:IsPoolPayment] = options[:is_pool_payment] || 0
182
+ end
183
+
184
+ def add_buyer_information(post, card, options)
185
+ obj = {}
186
+
187
+ obj[:BuyerFullName] = card.name || ''
188
+ obj[:BuyerEmail] = options[:email] if options[:email]
189
+ obj[:BuyerAddress] = options[:billing_address][:address1] if options[:billing_address]
190
+ obj[:BuyerGsmNumber] = options[:billing_address][:phone] if options[:billing_address]
191
+
192
+ post[:PaymentDealerRequest][:BuyerInformation] = obj
193
+ end
194
+
195
+ def add_basket_product(post, basket_options)
196
+ basket = []
197
+
198
+ basket_options.each do |product|
199
+ obj = {}
200
+ obj[:ProductId] = product[:product_id] if product[:product_id]
201
+ obj[:ProductCode] = product[:product_code] if product[:product_code]
202
+ obj[:UnitPrice] = amount(product[:unit_price]) if product[:unit_price]
203
+ obj[:Quantity] = product[:quantity] if product[:quantity]
204
+ basket << obj
205
+ end
206
+
207
+ post[:PaymentDealerRequest][:BasketProduct] = basket
208
+ end
209
+
210
+ def add_additional_transaction_data(post, options)
211
+ post[:PaymentDealerRequest][:ClientIP] = options[:ip] if options[:ip]
212
+ post[:PaymentDealerRequest][:OtherTrxCode] = options[:order_id] if options[:order_id]
213
+ end
214
+
215
+ def add_transaction_reference(post, authorization)
216
+ post[:PaymentDealerRequest][:VirtualPosOrderId] = authorization
217
+ end
218
+
219
+ def add_void_refund_reason(post)
220
+ post[:PaymentDealerRequest][:VoidRefundReason] = 2
221
+ end
222
+
223
+ def commit(action, parameters)
224
+ response = parse(ssl_post(url(action), post_data(parameters), request_headers))
225
+ Response.new(
226
+ success_from(response),
227
+ message_from(response),
228
+ response,
229
+ authorization: authorization_from(response),
230
+ test: test?,
231
+ error_code: error_code_from(response)
232
+ )
233
+ end
234
+
235
+ def url(action)
236
+ host = (test? ? test_url : live_url)
237
+ endpoint = endpoint(action)
238
+
239
+ "#{host}/PaymentDealer/#{endpoint}"
240
+ end
241
+
242
+ def endpoint(action)
243
+ case action
244
+ when 'three_ds_authorize', 'three_ds_purchase'
245
+ 'DoDirectPaymentThreeD'
246
+ when 'purchase', 'authorize'
247
+ 'DoDirectPayment'
248
+ when 'capture'
249
+ 'DoCapture'
250
+ when 'void'
251
+ 'DoVoid'
252
+ when 'refund'
253
+ 'DoCreateRefundRequest'
254
+ end
255
+ end
256
+
257
+ def request_headers
258
+ { 'Content-Type' => 'application/json' }
259
+ end
260
+
261
+ def post_data(parameters = {})
262
+ JSON.generate(parameters)
263
+ end
264
+
265
+ def parse(body)
266
+ JSON.parse(body)
267
+ end
268
+
269
+ def success_from(response)
270
+ return response.dig('Data', 'IsSuccessful') if response.dig('Data', 'IsSuccessful').to_s.present?
271
+
272
+ response['ResultCode']&.casecmp('success') == 0
273
+ end
274
+
275
+ def message_from(response)
276
+ response.dig('Data', 'ResultMessage').presence || response['ResultCode']
277
+ end
278
+
279
+ def authorization_from(response)
280
+ response.dig('Data', 'VirtualPosOrderId')
281
+ end
282
+
283
+ def error_code_from(response)
284
+ codes = [response['ResultCode'], response.dig('Data', 'ResultCode')].flatten
285
+ codes.reject! { |code| code.blank? || code.casecmp('success').zero? }
286
+ codes.map { |code| ERROR_CODE_MAPPING[code] || code }.join(', ')
287
+ end
288
+ end
289
+ end
290
+ end