activemerchant 1.119.0 → 1.124.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 +216 -1
- data/README.md +4 -2
- data/lib/active_merchant/billing/check.rb +19 -12
- data/lib/active_merchant/billing/credit_card.rb +3 -0
- data/lib/active_merchant/billing/credit_card_formatting.rb +1 -0
- data/lib/active_merchant/billing/credit_card_methods.rb +32 -14
- data/lib/active_merchant/billing/gateways/adyen.rb +94 -25
- data/lib/active_merchant/billing/gateways/authorize_net.rb +19 -11
- data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +3 -0
- 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 +52 -8
- data/lib/active_merchant/billing/gateways/card_stream.rb +17 -13
- data/lib/active_merchant/billing/gateways/cashnet.rb +7 -2
- data/lib/active_merchant/billing/gateways/checkout_v2.rb +31 -0
- data/lib/active_merchant/billing/gateways/credorax.rb +15 -9
- data/lib/active_merchant/billing/gateways/cyber_source.rb +53 -6
- data/lib/active_merchant/billing/gateways/d_local.rb +9 -2
- data/lib/active_merchant/billing/gateways/decidir.rb +7 -1
- data/lib/active_merchant/billing/gateways/elavon.rb +70 -28
- data/lib/active_merchant/billing/gateways/element.rb +2 -0
- data/lib/active_merchant/billing/gateways/forte.rb +12 -0
- data/lib/active_merchant/billing/gateways/global_collect.rb +24 -10
- data/lib/active_merchant/billing/gateways/hps.rb +55 -1
- data/lib/active_merchant/billing/gateways/kushki.rb +23 -0
- data/lib/active_merchant/billing/gateways/litle.rb +1 -1
- data/lib/active_merchant/billing/gateways/mercado_pago.rb +5 -4
- 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 +14 -5
- data/lib/active_merchant/billing/gateways/netbanx.rb +26 -2
- data/lib/active_merchant/billing/gateways/nmi.rb +27 -9
- data/lib/active_merchant/billing/gateways/orbital.rb +99 -59
- 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 +34 -6
- 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 +5 -5
- data/lib/active_merchant/billing/gateways/paymentez.rb +5 -0
- data/lib/active_merchant/billing/gateways/paypal_express.rb +1 -0
- data/lib/active_merchant/billing/gateways/paysafe.rb +376 -0
- data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -3
- data/lib/active_merchant/billing/gateways/payway_dot_com.rb +253 -0
- data/lib/active_merchant/billing/gateways/qvalent.rb +23 -9
- data/lib/active_merchant/billing/gateways/realex.rb +18 -0
- data/lib/active_merchant/billing/gateways/redsys.rb +42 -24
- data/lib/active_merchant/billing/gateways/safe_charge.rb +25 -13
- data/lib/active_merchant/billing/gateways/spreedly_core.rb +13 -4
- data/lib/active_merchant/billing/gateways/stripe.rb +18 -8
- data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +126 -48
- 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 +1 -1
- data/lib/active_merchant/billing/gateways/vpos.rb +220 -0
- data/lib/active_merchant/billing/gateways/worldpay.rb +78 -18
- 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
- data/lib/certs/cacert.pem +1582 -2431
- metadata +11 -3
@@ -20,16 +20,20 @@ module ActiveMerchant #:nodoc:
|
|
20
20
|
add_capture_method(post, options)
|
21
21
|
add_confirmation_method(post, options)
|
22
22
|
add_customer(post, options)
|
23
|
-
|
24
|
-
return
|
23
|
+
result = add_payment_method_token(post, payment_method, options)
|
24
|
+
return result if result.is_a?(ActiveMerchant::Billing::Response)
|
25
25
|
|
26
26
|
add_external_three_d_secure_auth_data(post, options)
|
27
27
|
add_metadata(post, options)
|
28
28
|
add_return_url(post, options)
|
29
29
|
add_connected_account(post, options)
|
30
|
+
add_radar_data(post, options)
|
30
31
|
add_shipping_address(post, options)
|
31
32
|
setup_future_usage(post, options)
|
32
33
|
add_exemption(post, options)
|
34
|
+
add_stored_credentials(post, options)
|
35
|
+
add_ntid(post, options)
|
36
|
+
add_claim_without_transaction_id(post, options)
|
33
37
|
add_error_on_requires_action(post, options)
|
34
38
|
request_three_d_secure(post, options)
|
35
39
|
|
@@ -46,8 +50,8 @@ module ActiveMerchant #:nodoc:
|
|
46
50
|
|
47
51
|
def confirm_intent(intent_id, payment_method, options = {})
|
48
52
|
post = {}
|
49
|
-
|
50
|
-
return
|
53
|
+
result = add_payment_method_token(post, payment_method, options)
|
54
|
+
return result if result.is_a?(ActiveMerchant::Billing::Response)
|
51
55
|
|
52
56
|
CONFIRM_INTENT_ATTRIBUTES.each do |attribute|
|
53
57
|
add_whitelisted_attribute(post, options, attribute)
|
@@ -56,24 +60,31 @@ module ActiveMerchant #:nodoc:
|
|
56
60
|
end
|
57
61
|
|
58
62
|
def create_payment_method(payment_method, options = {})
|
59
|
-
|
60
|
-
|
61
|
-
post[:card] = {}
|
62
|
-
post[:card][:number] = payment_method.number
|
63
|
-
post[:card][:exp_month] = payment_method.month
|
64
|
-
post[:card][:exp_year] = payment_method.year
|
65
|
-
post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
|
66
|
-
add_billing_address(post, options)
|
63
|
+
post_data = add_payment_method_data(payment_method, options)
|
64
|
+
|
67
65
|
options = format_idempotency_key(options, 'pm')
|
68
|
-
commit(:post, 'payment_methods',
|
66
|
+
commit(:post, 'payment_methods', post_data, options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_payment_method_data(payment_method, options = {})
|
70
|
+
post_data = {}
|
71
|
+
post_data[:type] = 'card'
|
72
|
+
post_data[:card] = {}
|
73
|
+
post_data[:card][:number] = payment_method.number
|
74
|
+
post_data[:card][:exp_month] = payment_method.month
|
75
|
+
post_data[:card][:exp_year] = payment_method.year
|
76
|
+
post_data[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
|
77
|
+
add_billing_address(post_data, options)
|
78
|
+
add_name_only(post_data, payment_method) if post_data[:billing_details].nil?
|
79
|
+
post_data
|
69
80
|
end
|
70
81
|
|
71
82
|
def update_intent(money, intent_id, payment_method, options = {})
|
72
83
|
post = {}
|
73
84
|
add_amount(post, money, options)
|
74
85
|
|
75
|
-
|
76
|
-
return
|
86
|
+
result = add_payment_method_token(post, payment_method, options)
|
87
|
+
return result if result.is_a?(ActiveMerchant::Billing::Response)
|
77
88
|
|
78
89
|
add_payment_method_types(post, options)
|
79
90
|
add_customer(post, options)
|
@@ -90,8 +101,8 @@ module ActiveMerchant #:nodoc:
|
|
90
101
|
def create_setup_intent(payment_method, options = {})
|
91
102
|
post = {}
|
92
103
|
add_customer(post, options)
|
93
|
-
|
94
|
-
return
|
104
|
+
result = add_payment_method_token(post, payment_method, options)
|
105
|
+
return result if result.is_a?(ActiveMerchant::Billing::Response)
|
95
106
|
|
96
107
|
add_metadata(post, options)
|
97
108
|
add_return_url(post, options)
|
@@ -102,6 +113,17 @@ module ActiveMerchant #:nodoc:
|
|
102
113
|
commit(:post, 'setup_intents', post, options)
|
103
114
|
end
|
104
115
|
|
116
|
+
def retrieve_setup_intent(setup_intent_id)
|
117
|
+
# Retrieving a setup_intent passing 'expand[]=latest_attempt' allows the caller to
|
118
|
+
# check for a network_transaction_id and ds_transaction_id
|
119
|
+
# eg (latest_attempt -> payment_method_details -> card -> network_transaction_id)
|
120
|
+
#
|
121
|
+
# Being able to retrieve these fields enables payment flows that rely on MIT exemptions, e.g: off_session
|
122
|
+
commit(:post, "setup_intents/#{setup_intent_id}", {
|
123
|
+
'expand[]': 'latest_attempt'
|
124
|
+
}, {})
|
125
|
+
end
|
126
|
+
|
105
127
|
def authorize(money, payment_method, options = {})
|
106
128
|
create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'manual'))
|
107
129
|
end
|
@@ -159,13 +181,12 @@ module ActiveMerchant #:nodoc:
|
|
159
181
|
# If customer option is provided, create a payment method and attach to customer id
|
160
182
|
# Otherwise, create a customer, then attach
|
161
183
|
if payment_method.is_a?(StripePaymentToken) || payment_method.is_a?(ActiveMerchant::Billing::CreditCard)
|
162
|
-
|
163
|
-
return
|
184
|
+
result = add_payment_method_token(params, payment_method, options)
|
185
|
+
return result if result.is_a?(ActiveMerchant::Billing::Response)
|
164
186
|
|
165
187
|
if options[:customer]
|
166
188
|
customer_id = options[:customer]
|
167
189
|
else
|
168
|
-
post[:validate] = options[:validate] unless options[:validate].nil?
|
169
190
|
post[:description] = options[:description] if options[:description]
|
170
191
|
post[:email] = options[:email] if options[:email]
|
171
192
|
options = format_idempotency_key(options, 'customer')
|
@@ -173,7 +194,9 @@ module ActiveMerchant #:nodoc:
|
|
173
194
|
customer_id = customer.params['id']
|
174
195
|
end
|
175
196
|
options = format_idempotency_key(options, 'attach')
|
176
|
-
|
197
|
+
attach_parameters = { customer: customer_id }
|
198
|
+
attach_parameters[:validate] = options[:validate] unless options[:validate].nil?
|
199
|
+
commit(:post, "payment_methods/#{params[:payment_method]}/attach", attach_parameters, options)
|
177
200
|
else
|
178
201
|
super(payment_method, options)
|
179
202
|
end
|
@@ -194,6 +217,10 @@ module ActiveMerchant #:nodoc:
|
|
194
217
|
|
195
218
|
private
|
196
219
|
|
220
|
+
def off_session_request?(options = {})
|
221
|
+
(options[:off_session] || options[:setup_future_usage]) && options[:confirm] == true
|
222
|
+
end
|
223
|
+
|
197
224
|
def add_connected_account(post, options = {})
|
198
225
|
super(post, options)
|
199
226
|
post[:application_fee_amount] = options[:application_fee] if options[:application_fee]
|
@@ -201,25 +228,21 @@ module ActiveMerchant #:nodoc:
|
|
201
228
|
|
202
229
|
def add_whitelisted_attribute(post, options, attribute)
|
203
230
|
post[attribute] = options[attribute] if options[attribute]
|
204
|
-
post
|
205
231
|
end
|
206
232
|
|
207
233
|
def add_capture_method(post, options)
|
208
234
|
capture_method = options[:capture_method].to_s
|
209
235
|
post[:capture_method] = capture_method if ALLOWED_METHOD_STATES.include?(capture_method)
|
210
|
-
post
|
211
236
|
end
|
212
237
|
|
213
238
|
def add_confirmation_method(post, options)
|
214
239
|
confirmation_method = options[:confirmation_method].to_s
|
215
240
|
post[:confirmation_method] = confirmation_method if ALLOWED_METHOD_STATES.include?(confirmation_method)
|
216
|
-
post
|
217
241
|
end
|
218
242
|
|
219
243
|
def add_customer(post, options)
|
220
244
|
customer = options[:customer].to_s
|
221
245
|
post[:customer] = customer if customer.start_with?('cus_')
|
222
|
-
post
|
223
246
|
end
|
224
247
|
|
225
248
|
def add_return_url(post, options)
|
@@ -227,31 +250,39 @@ module ActiveMerchant #:nodoc:
|
|
227
250
|
|
228
251
|
post[:confirm] = options[:confirm]
|
229
252
|
post[:return_url] = options[:return_url] if options[:return_url]
|
230
|
-
post
|
231
253
|
end
|
232
254
|
|
233
255
|
def add_payment_method_token(post, payment_method, options)
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
payment_method
|
256
|
+
case payment_method
|
257
|
+
when StripePaymentToken
|
258
|
+
post[:payment_method] = payment_method.payment_data['id']
|
259
|
+
when String
|
260
|
+
extract_token_from_string_and_maybe_add_customer_id(post, payment_method)
|
261
|
+
when ActiveMerchant::Billing::CreditCard
|
262
|
+
get_payment_method_data_from_card(post, payment_method, options)
|
241
263
|
end
|
264
|
+
end
|
242
265
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
customer_id, payment_method_id = payment_method.split('|')
|
248
|
-
token = payment_method_id
|
249
|
-
post[:customer] = customer_id
|
250
|
-
else
|
251
|
-
token = payment_method
|
252
|
-
end
|
253
|
-
post[:payment_method] = token
|
266
|
+
def extract_token_from_string_and_maybe_add_customer_id(post, payment_method)
|
267
|
+
if payment_method.include?('|')
|
268
|
+
customer_id, payment_method = payment_method.split('|')
|
269
|
+
post[:customer] = customer_id
|
254
270
|
end
|
271
|
+
|
272
|
+
post[:payment_method] = payment_method
|
273
|
+
end
|
274
|
+
|
275
|
+
def get_payment_method_data_from_card(post, payment_method, options)
|
276
|
+
return create_payment_method_and_extract_token(post, payment_method, options) unless off_session_request?(options)
|
277
|
+
|
278
|
+
post[:payment_method_data] = add_payment_method_data(payment_method, options)
|
279
|
+
end
|
280
|
+
|
281
|
+
def create_payment_method_and_extract_token(post, payment_method, options)
|
282
|
+
payment_method_response = create_payment_method(payment_method, options)
|
283
|
+
return payment_method_response if payment_method_response.failure?
|
284
|
+
|
285
|
+
add_payment_method_token(post, payment_method_response.params['id'], options)
|
255
286
|
end
|
256
287
|
|
257
288
|
def add_payment_method_types(post, options)
|
@@ -259,7 +290,6 @@ module ActiveMerchant #:nodoc:
|
|
259
290
|
return if payment_method_types.nil?
|
260
291
|
|
261
292
|
post[:payment_method_types] = Array(payment_method_types)
|
262
|
-
post
|
263
293
|
end
|
264
294
|
|
265
295
|
def add_exemption(post, options = {})
|
@@ -270,6 +300,49 @@ module ActiveMerchant #:nodoc:
|
|
270
300
|
post[:payment_method_options][:card][:moto] = true if options[:moto]
|
271
301
|
end
|
272
302
|
|
303
|
+
# Stripe Payment Intents does not pass any parameters for cardholder/merchant initiated
|
304
|
+
# it also does not support installments for any country other than Mexico (reason for this is unknown)
|
305
|
+
# The only thing that Stripe PI requires for stored credentials to work currently is the network_transaction_id
|
306
|
+
# 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
|
307
|
+
|
308
|
+
def add_stored_credentials(post, options = {})
|
309
|
+
return unless options[:stored_credential] && !options[:stored_credential].values.all?(&:nil?)
|
310
|
+
|
311
|
+
stored_credential = options[:stored_credential]
|
312
|
+
post[:payment_method_options] ||= {}
|
313
|
+
post[:payment_method_options][:card] ||= {}
|
314
|
+
post[:payment_method_options][:card][:mit_exemption] = {}
|
315
|
+
|
316
|
+
# Stripe PI accepts network_transaction_id and ds_transaction_id via mit field under card.
|
317
|
+
# The network_transaction_id can be sent in nested under stored credentials OR as its own field (add_ntid handles when it is sent in on its own)
|
318
|
+
# If it is sent is as its own field AND under stored credentials, the value sent under its own field is what will send.
|
319
|
+
post[:payment_method_options][:card][:mit_exemption][:ds_transaction_id] = stored_credential[:ds_transaction_id] if stored_credential[:ds_transaction_id]
|
320
|
+
post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
|
321
|
+
end
|
322
|
+
|
323
|
+
def add_ntid(post, options = {})
|
324
|
+
return unless options[:network_transaction_id]
|
325
|
+
|
326
|
+
post[:payment_method_options] ||= {}
|
327
|
+
post[:payment_method_options][:card] ||= {}
|
328
|
+
post[:payment_method_options][:card][:mit_exemption] = {}
|
329
|
+
|
330
|
+
post[:payment_method_options][:card][:mit_exemption][:network_transaction_id] = options[:network_transaction_id] if options[:network_transaction_id]
|
331
|
+
end
|
332
|
+
|
333
|
+
def add_claim_without_transaction_id(post, options = {})
|
334
|
+
return if options[:stored_credential] || options[:network_transaction_id] || options[:ds_transaction_id]
|
335
|
+
return unless options[:claim_without_transaction_id]
|
336
|
+
|
337
|
+
post[:payment_method_options] ||= {}
|
338
|
+
post[:payment_method_options][:card] ||= {}
|
339
|
+
post[:payment_method_options][:card][:mit_exemption] = {}
|
340
|
+
|
341
|
+
# Stripe PI accepts claim_without_transaction_id for transactions without transaction ids.
|
342
|
+
# Gateway validation for this field occurs through a different service, before the transaction request is sent to the gateway.
|
343
|
+
post[:payment_method_options][:card][:mit_exemption][:claim_without_transaction_id] = options[:claim_without_transaction_id]
|
344
|
+
end
|
345
|
+
|
273
346
|
def add_error_on_requires_action(post, options = {})
|
274
347
|
return unless options[:confirm]
|
275
348
|
|
@@ -299,7 +372,7 @@ module ActiveMerchant #:nodoc:
|
|
299
372
|
|
300
373
|
def setup_future_usage(post, options = {})
|
301
374
|
post[:setup_future_usage] = options[:setup_future_usage] if %w(on_session off_session).include?(options[:setup_future_usage])
|
302
|
-
post[:off_session] = options[:off_session] if options
|
375
|
+
post[:off_session] = options[:off_session] if off_session_request?(options)
|
303
376
|
post
|
304
377
|
end
|
305
378
|
|
@@ -317,7 +390,13 @@ module ActiveMerchant #:nodoc:
|
|
317
390
|
post[:billing_details][:email] = billing[:email] if billing[:email]
|
318
391
|
post[:billing_details][:name] = billing[:name] if billing[:name]
|
319
392
|
post[:billing_details][:phone] = billing[:phone] if billing[:phone]
|
320
|
-
|
393
|
+
end
|
394
|
+
|
395
|
+
def add_name_only(post, payment_method)
|
396
|
+
post[:billing_details] = {} unless post[:billing_details]
|
397
|
+
|
398
|
+
name = [payment_method.first_name, payment_method.last_name].compact.join(' ')
|
399
|
+
post[:billing_details][:name] = name
|
321
400
|
end
|
322
401
|
|
323
402
|
def add_shipping_address(post, options = {})
|
@@ -336,7 +415,6 @@ module ActiveMerchant #:nodoc:
|
|
336
415
|
post[:shipping][:carrier] = shipping[:carrier] if shipping[:carrier]
|
337
416
|
post[:shipping][:phone] = shipping[:phone] if shipping[:phone]
|
338
417
|
post[:shipping][:tracking_number] = shipping[:tracking_number] if shipping[:tracking_number]
|
339
|
-
post
|
340
418
|
end
|
341
419
|
|
342
420
|
def format_idempotency_key(options, suffix)
|
@@ -317,7 +317,8 @@ module ActiveMerchant #:nodoc:
|
|
317
317
|
gsub(%r((<[^>]+pan>)[^<]+(<))i, '\1[FILTERED]\2').
|
318
318
|
gsub(%r((<[^>]+sec>)[^<]+(<))i, '\1[FILTERED]\2').
|
319
319
|
gsub(%r((<[^>]+id>)[^<]+(<))i, '\1[FILTERED]\2').
|
320
|
-
gsub(%r((<[^>]+regKey>)[^<]+(<))i, '\1[FILTERED]\2')
|
320
|
+
gsub(%r((<[^>]+regKey>)[^<]+(<))i, '\1[FILTERED]\2').
|
321
|
+
gsub(%r((<[^>]+acctNr>)[^<]+(<))i, '\1[FILTERED]\2')
|
321
322
|
end
|
322
323
|
|
323
324
|
private
|
@@ -331,7 +331,8 @@ module ActiveMerchant #:nodoc:
|
|
331
331
|
gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
|
332
332
|
gsub(%r((&?cc=)\d*(&?)), '\1[FILTERED]\2').
|
333
333
|
gsub(%r((&?password=)[^&]+(&?)), '\1[FILTERED]\2').
|
334
|
-
gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2')
|
334
|
+
gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2').
|
335
|
+
gsub(%r((&?account=)\d*(&?)), '\1[FILTERED]\2')
|
335
336
|
end
|
336
337
|
|
337
338
|
private
|
@@ -343,7 +343,7 @@ module ActiveMerchant #:nodoc:
|
|
343
343
|
parameters[:software] = 'Active Merchant'
|
344
344
|
parameters[:testmode] = (@options[:test] ? 1 : 0) unless parameters.has_key?(:testmode)
|
345
345
|
seed = SecureRandom.hex(32).upcase
|
346
|
-
hash = Digest::SHA1.hexdigest("#{parameters[:command]}:#{@options[:password]}:#{parameters[:amount]}:#{parameters[:invoice]}:#{seed}")
|
346
|
+
hash = Digest::SHA1.hexdigest("#{parameters[:command]}:#{@options[:pin] || @options[:password]}:#{parameters[:amount]}:#{parameters[:invoice]}:#{seed}")
|
347
347
|
parameters[:hash] = "s/#{seed}/#{hash}/n"
|
348
348
|
|
349
349
|
parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join('&')
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'jwe'
|
3
|
+
|
4
|
+
module ActiveMerchant #:nodoc:
|
5
|
+
module Billing #:nodoc:
|
6
|
+
class VposGateway < Gateway
|
7
|
+
self.test_url = 'https://vpos.infonet.com.py:8888'
|
8
|
+
self.live_url = 'https://vpos.infonet.com.py'
|
9
|
+
|
10
|
+
self.supported_countries = ['PY']
|
11
|
+
self.default_currency = 'PYG'
|
12
|
+
self.supported_cardtypes = %i[visa master]
|
13
|
+
|
14
|
+
self.homepage_url = 'https://comercios.bancard.com.py'
|
15
|
+
self.display_name = 'vPOS'
|
16
|
+
|
17
|
+
self.money_format = :dollars
|
18
|
+
|
19
|
+
ENDPOINTS = {
|
20
|
+
pci_encryption_key: '/vpos/api/0.3/application/encryption-key',
|
21
|
+
pay_pci_buy_encrypted: '/vpos/api/0.3/pci/encrypted',
|
22
|
+
pci_buy_rollback: '/vpos/api/0.3/pci_buy/rollback',
|
23
|
+
refund: '/vpos/api/0.3/refunds'
|
24
|
+
}
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
requires!(options, :private_key, :public_key)
|
28
|
+
@private_key = options[:private_key]
|
29
|
+
@public_key = options[:public_key]
|
30
|
+
@shop_process_id = options[:shop_process_id] || SecureRandom.random_number(10**15)
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
def purchase(money, payment, options = {})
|
35
|
+
commerce = options[:commerce] || @options[:commerce]
|
36
|
+
commerce_branch = options[:commerce_branch] || @options[:commerce_branch]
|
37
|
+
shop_process_id = options[:shop_process_id] || @shop_process_id
|
38
|
+
|
39
|
+
token = generate_token(shop_process_id, 'pay_pci', commerce, commerce_branch, amount(money), currency(money))
|
40
|
+
|
41
|
+
post = {}
|
42
|
+
post[:token] = token
|
43
|
+
post[:commerce] = commerce.to_s
|
44
|
+
post[:commerce_branch] = commerce_branch.to_s
|
45
|
+
post[:shop_process_id] = shop_process_id
|
46
|
+
post[:number_of_payments] = options[:number_of_payments] || 1
|
47
|
+
post[:recursive] = options[:recursive] || false
|
48
|
+
|
49
|
+
add_invoice(post, money, options)
|
50
|
+
add_card_data(post, payment)
|
51
|
+
add_customer_data(post, options)
|
52
|
+
|
53
|
+
commit(:pay_pci_buy_encrypted, post)
|
54
|
+
end
|
55
|
+
|
56
|
+
def void(authorization, options = {})
|
57
|
+
_, shop_process_id = authorization.to_s.split('#')
|
58
|
+
token = generate_token(shop_process_id, 'rollback', '0.00')
|
59
|
+
post = {
|
60
|
+
token: token,
|
61
|
+
shop_process_id: shop_process_id
|
62
|
+
}
|
63
|
+
commit(:pci_buy_rollback, post)
|
64
|
+
end
|
65
|
+
|
66
|
+
def credit(money, payment, options = {})
|
67
|
+
# Not permitted for foreign cards.
|
68
|
+
commerce = options[:commerce] || @options[:commerce]
|
69
|
+
commerce_branch = options[:commerce_branch] || @options[:commerce_branch]
|
70
|
+
|
71
|
+
token = generate_token(@shop_process_id, 'refund', commerce, commerce_branch, amount(money), currency(money))
|
72
|
+
post = {}
|
73
|
+
post[:token] = token
|
74
|
+
post[:commerce] = commerce.to_i
|
75
|
+
post[:commerce_branch] = commerce_branch.to_i
|
76
|
+
post[:shop_process_id] = @shop_process_id
|
77
|
+
add_invoice(post, money, options)
|
78
|
+
add_card_data(post, payment)
|
79
|
+
add_customer_data(post, options)
|
80
|
+
post[:origin_shop_process_id] = options[:original_shop_process_id] if options[:original_shop_process_id]
|
81
|
+
commit(:refund, post)
|
82
|
+
end
|
83
|
+
|
84
|
+
def refund(money, authorization, options = {})
|
85
|
+
commerce = options[:commerce] || @options[:commerce]
|
86
|
+
commerce_branch = options[:commerce_branch] || @options[:commerce_branch]
|
87
|
+
shop_process_id = options[:shop_process_id] || @shop_process_id
|
88
|
+
_, original_shop_process_id = authorization.to_s.split('#')
|
89
|
+
|
90
|
+
token = generate_token(shop_process_id, 'refund', commerce, commerce_branch, amount(money), currency(money))
|
91
|
+
post = {}
|
92
|
+
post[:token] = token
|
93
|
+
post[:commerce] = commerce.to_i
|
94
|
+
post[:commerce_branch] = commerce_branch.to_i
|
95
|
+
post[:shop_process_id] = shop_process_id
|
96
|
+
add_invoice(post, money, options)
|
97
|
+
add_customer_data(post, options)
|
98
|
+
post[:origin_shop_process_id] = original_shop_process_id || options[:original_shop_process_id]
|
99
|
+
commit(:refund, post)
|
100
|
+
end
|
101
|
+
|
102
|
+
def supports_scrubbing?
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def scrub(transcript)
|
107
|
+
clean_transcript = remove_invalid_utf_8_byte_sequences(transcript)
|
108
|
+
clean_transcript.
|
109
|
+
gsub(/(token\\":\\")[.\-\w]+/, '\1[FILTERED]').
|
110
|
+
gsub(/(card_encrypted_data\\":\\")[.\-\w]+/, '\1[FILTERED]')
|
111
|
+
end
|
112
|
+
|
113
|
+
def remove_invalid_utf_8_byte_sequences(transcript)
|
114
|
+
transcript.encode('UTF-8', 'binary', undef: :replace, replace: '')
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# Required to encrypt PAN data.
|
120
|
+
def one_time_public_key
|
121
|
+
token = generate_token('get_encription_public_key', @public_key)
|
122
|
+
response = commit(:pci_encryption_key, token: token)
|
123
|
+
OpenSSL::PKey::RSA.new(response.params['encryption_key'])
|
124
|
+
end
|
125
|
+
|
126
|
+
def generate_token(*elements)
|
127
|
+
Digest::MD5.hexdigest(@private_key + elements.join)
|
128
|
+
end
|
129
|
+
|
130
|
+
def add_invoice(post, money, options)
|
131
|
+
post[:amount] = amount(money)
|
132
|
+
post[:currency] = options[:currency] || currency(money)
|
133
|
+
end
|
134
|
+
|
135
|
+
def add_card_data(post, payment)
|
136
|
+
card_number = payment.number
|
137
|
+
cvv = payment.verification_value
|
138
|
+
|
139
|
+
payload = { card_number: card_number, 'cvv': cvv }.to_json
|
140
|
+
|
141
|
+
post[:card_encrypted_data] = JWE.encrypt(payload, one_time_public_key)
|
142
|
+
post[:card_month_expiration] = format(payment.month, :two_digits)
|
143
|
+
post[:card_year_expiration] = format(payment.year, :two_digits)
|
144
|
+
end
|
145
|
+
|
146
|
+
def add_customer_data(post, options)
|
147
|
+
post[:additional_data] = options[:additional_data] || '' # must be passed even if empty
|
148
|
+
end
|
149
|
+
|
150
|
+
def parse(body)
|
151
|
+
JSON.parse(body)
|
152
|
+
end
|
153
|
+
|
154
|
+
def commit(action, parameters)
|
155
|
+
url = build_request_url(action)
|
156
|
+
begin
|
157
|
+
response = parse(ssl_post(url, post_data(parameters)))
|
158
|
+
rescue ResponseError => response
|
159
|
+
# Errors are returned with helpful data,
|
160
|
+
# but get filtered out by `ssl_post` because of their HTTP status.
|
161
|
+
response = parse(response.response.body)
|
162
|
+
end
|
163
|
+
|
164
|
+
Response.new(
|
165
|
+
success_from(response),
|
166
|
+
message_from(response),
|
167
|
+
response,
|
168
|
+
authorization: authorization_from(response),
|
169
|
+
avs_result: nil,
|
170
|
+
cvv_result: nil,
|
171
|
+
test: test?,
|
172
|
+
error_code: error_code_from(response)
|
173
|
+
)
|
174
|
+
end
|
175
|
+
|
176
|
+
def success_from(response)
|
177
|
+
if code = response.dig('confirmation', 'response_code')
|
178
|
+
code == '00'
|
179
|
+
else
|
180
|
+
response['status'] == 'success'
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def message_from(response)
|
185
|
+
%w(confirmation refund).each do |m|
|
186
|
+
message =
|
187
|
+
response.dig(m, 'extended_response_description') ||
|
188
|
+
response.dig(m, 'response_description') ||
|
189
|
+
response.dig(m, 'response_details')
|
190
|
+
return message if message
|
191
|
+
end
|
192
|
+
[response.dig('messages', 0, 'key'), response.dig('messages', 0, 'dsc')].join(':')
|
193
|
+
end
|
194
|
+
|
195
|
+
def authorization_from(response)
|
196
|
+
response_body = response.dig('confirmation') || response.dig('refund')
|
197
|
+
return unless response_body
|
198
|
+
|
199
|
+
authorization_number = response_body.dig('authorization_number') || response_body.dig('authorization_code')
|
200
|
+
shop_process_id = response_body.dig('shop_process_id')
|
201
|
+
|
202
|
+
"#{authorization_number}##{shop_process_id}"
|
203
|
+
end
|
204
|
+
|
205
|
+
def error_code_from(response)
|
206
|
+
response.dig('confirmation', 'response_code') unless success_from(response)
|
207
|
+
end
|
208
|
+
|
209
|
+
def build_request_url(action)
|
210
|
+
base_url = (test? ? test_url : live_url)
|
211
|
+
base_url + ENDPOINTS[action]
|
212
|
+
end
|
213
|
+
|
214
|
+
def post_data(data)
|
215
|
+
{ public_key: @public_key,
|
216
|
+
operation: data }.compact.to_json
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|