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.
- checksums.yaml +4 -4
- data/CHANGELOG +217 -0
- data/README.md +1 -1
- data/lib/active_merchant/billing/check.rb +13 -19
- data/lib/active_merchant/billing/credit_card.rb +13 -0
- data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
- data/lib/active_merchant/billing/credit_card_methods.rb +24 -12
- data/lib/active_merchant/billing/gateway.rb +1 -1
- data/lib/active_merchant/billing/gateways/adyen.rb +75 -27
- data/lib/active_merchant/billing/gateways/authorize_net.rb +10 -8
- data/lib/active_merchant/billing/gateways/blue_pay.rb +29 -0
- data/lib/active_merchant/billing/gateways/blue_snap.rb +2 -2
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +6 -3
- data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
- data/lib/active_merchant/billing/gateways/cashnet.rb +15 -5
- data/lib/active_merchant/billing/gateways/checkout_v2.rb +33 -4
- data/lib/active_merchant/billing/gateways/credorax.rb +2 -1
- data/lib/active_merchant/billing/gateways/cyber_source.rb +41 -6
- data/lib/active_merchant/billing/gateways/d_local.rb +12 -6
- data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
- data/lib/active_merchant/billing/gateways/decidir_plus.rb +173 -0
- data/lib/active_merchant/billing/gateways/ebanx.rb +16 -1
- data/lib/active_merchant/billing/gateways/elavon.rb +65 -30
- data/lib/active_merchant/billing/gateways/element.rb +22 -2
- data/lib/active_merchant/billing/gateways/global_collect.rb +130 -26
- data/lib/active_merchant/billing/gateways/ipg.rb +416 -0
- data/lib/active_merchant/billing/gateways/kushki.rb +30 -0
- data/lib/active_merchant/billing/gateways/mercado_pago.rb +6 -3
- data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -0
- data/lib/active_merchant/billing/gateways/mit.rb +260 -0
- data/lib/active_merchant/billing/gateways/moka.rb +290 -0
- data/lib/active_merchant/billing/gateways/monei.rb +228 -144
- data/lib/active_merchant/billing/gateways/mundipagg.rb +22 -11
- data/lib/active_merchant/billing/gateways/nmi.rb +29 -10
- data/lib/active_merchant/billing/gateways/orbital.rb +46 -8
- data/lib/active_merchant/billing/gateways/pay_arc.rb +392 -0
- data/lib/active_merchant/billing/gateways/pay_conex.rb +3 -1
- data/lib/active_merchant/billing/gateways/pay_trace.rb +404 -0
- data/lib/active_merchant/billing/gateways/payeezy.rb +4 -0
- data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +1 -0
- data/lib/active_merchant/billing/gateways/payflow.rb +21 -4
- data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
- data/lib/active_merchant/billing/gateways/paymentez.rb +14 -2
- data/lib/active_merchant/billing/gateways/paysafe.rb +412 -0
- data/lib/active_merchant/billing/gateways/payu_latam.rb +9 -4
- data/lib/active_merchant/billing/gateways/payway_dot_com.rb +3 -3
- data/lib/active_merchant/billing/gateways/pin.rb +31 -4
- data/lib/active_merchant/billing/gateways/priority.rb +347 -0
- data/lib/active_merchant/billing/gateways/realex.rb +18 -0
- data/lib/active_merchant/billing/gateways/redsys.rb +35 -32
- data/lib/active_merchant/billing/gateways/safe_charge.rb +8 -2
- data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
- data/lib/active_merchant/billing/gateways/stripe.rb +27 -7
- data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +115 -39
- data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +2 -1
- data/lib/active_merchant/billing/gateways/trust_commerce.rb +2 -1
- data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +21 -7
- data/lib/active_merchant/billing/gateways/vpos.rb +49 -6
- data/lib/active_merchant/billing/gateways/wompi.rb +193 -0
- data/lib/active_merchant/billing/gateways/worldpay.rb +226 -62
- data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
- data/lib/active_merchant/billing/response.rb +4 -0
- data/lib/active_merchant/billing/three_d_secure_eci_mapper.rb +27 -0
- data/lib/active_merchant/billing.rb +1 -0
- data/lib/active_merchant/version.rb +1 -1
- 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(
|
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
|