activemerchant 1.126.0 → 1.129.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 +241 -0
- data/lib/active_merchant/billing/check.rb +40 -8
- data/lib/active_merchant/billing/credit_card.rb +28 -1
- data/lib/active_merchant/billing/credit_card_methods.rb +79 -23
- data/lib/active_merchant/billing/gateways/adyen.rb +67 -8
- data/lib/active_merchant/billing/gateways/airwallex.rb +40 -11
- data/lib/active_merchant/billing/gateways/alelo.rb +256 -0
- data/lib/active_merchant/billing/gateways/authorize_net.rb +21 -4
- data/lib/active_merchant/billing/gateways/beanstream.rb +18 -0
- data/lib/active_merchant/billing/gateways/blue_snap.rb +22 -1
- data/lib/active_merchant/billing/gateways/bogus.rb +4 -0
- data/lib/active_merchant/billing/gateways/borgun.rb +56 -16
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +64 -17
- data/lib/active_merchant/billing/gateways/card_connect.rb +27 -9
- data/lib/active_merchant/billing/gateways/card_stream.rb +23 -0
- data/lib/active_merchant/billing/gateways/checkout_v2.rb +228 -57
- data/lib/active_merchant/billing/gateways/commerce_hub.rb +361 -0
- data/lib/active_merchant/billing/gateways/credorax.rb +47 -27
- data/lib/active_merchant/billing/gateways/cyber_source/cyber_source_common.rb +36 -0
- data/lib/active_merchant/billing/gateways/cyber_source.rb +100 -26
- data/lib/active_merchant/billing/gateways/cyber_source_rest.rb +456 -0
- data/lib/active_merchant/billing/gateways/d_local.rb +44 -5
- data/lib/active_merchant/billing/gateways/decidir.rb +15 -4
- data/lib/active_merchant/billing/gateways/ebanx.rb +36 -24
- data/lib/active_merchant/billing/gateways/element.rb +21 -1
- data/lib/active_merchant/billing/gateways/global_collect.rb +73 -22
- data/lib/active_merchant/billing/gateways/ipg.rb +13 -8
- data/lib/active_merchant/billing/gateways/iveri.rb +39 -3
- data/lib/active_merchant/billing/gateways/kushki.rb +21 -1
- data/lib/active_merchant/billing/gateways/litle.rb +25 -5
- data/lib/active_merchant/billing/gateways/mastercard.rb +1 -8
- data/lib/active_merchant/billing/gateways/mercado_pago.rb +17 -0
- data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +44 -10
- data/lib/active_merchant/billing/gateways/monei.rb +2 -0
- data/lib/active_merchant/billing/gateways/moneris.rb +20 -5
- data/lib/active_merchant/billing/gateways/mundipagg.rb +3 -0
- data/lib/active_merchant/billing/gateways/ogone.rb +35 -7
- data/lib/active_merchant/billing/gateways/openpay.rb +20 -3
- data/lib/active_merchant/billing/gateways/orbital.rb +43 -22
- data/lib/active_merchant/billing/gateways/pay_trace.rb +64 -18
- data/lib/active_merchant/billing/gateways/payeezy.rb +59 -4
- data/lib/active_merchant/billing/gateways/paymentez.rb +18 -6
- data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +4 -0
- data/lib/active_merchant/billing/gateways/paysafe.rb +22 -14
- data/lib/active_merchant/billing/gateways/payu_latam.rb +3 -0
- data/lib/active_merchant/billing/gateways/plexo.rb +308 -0
- data/lib/active_merchant/billing/gateways/priority.rb +29 -6
- data/lib/active_merchant/billing/gateways/rapyd.rb +110 -49
- data/lib/active_merchant/billing/gateways/reach.rb +277 -0
- data/lib/active_merchant/billing/gateways/redsys.rb +9 -5
- data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -1
- data/lib/active_merchant/billing/gateways/securion_pay.rb +40 -0
- data/lib/active_merchant/billing/gateways/shift4.rb +342 -0
- data/lib/active_merchant/billing/gateways/simetrik.rb +28 -22
- data/lib/active_merchant/billing/gateways/stripe.rb +21 -1
- data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +62 -22
- data/lib/active_merchant/billing/gateways/tns.rb +2 -5
- data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +1 -1
- data/lib/active_merchant/billing/gateways/trust_commerce.rb +14 -3
- data/lib/active_merchant/billing/gateways/vanco.rb +12 -3
- data/lib/active_merchant/billing/gateways/visanet_peru.rb +1 -1
- data/lib/active_merchant/billing/gateways/vpos.rb +7 -4
- data/lib/active_merchant/billing/gateways/wompi.rb +8 -4
- data/lib/active_merchant/billing/gateways/worldpay.rb +117 -9
- data/lib/active_merchant/billing/response.rb +15 -1
- data/lib/active_merchant/connection.rb +0 -2
- data/lib/active_merchant/country.rb +1 -0
- data/lib/active_merchant/errors.rb +4 -1
- data/lib/active_merchant/version.rb +1 -1
- metadata +24 -3
@@ -0,0 +1,456 @@
|
|
1
|
+
require 'active_merchant/billing/gateways/cyber_source/cyber_source_common'
|
2
|
+
|
3
|
+
module ActiveMerchant #:nodoc:
|
4
|
+
module Billing #:nodoc:
|
5
|
+
class CyberSourceRestGateway < Gateway
|
6
|
+
include ActiveMerchant::Billing::CyberSourceCommon
|
7
|
+
|
8
|
+
self.test_url = 'https://apitest.cybersource.com'
|
9
|
+
self.live_url = 'https://api.cybersource.com'
|
10
|
+
|
11
|
+
self.supported_countries = ActiveMerchant::Billing::CyberSourceGateway.supported_countries
|
12
|
+
self.default_currency = 'USD'
|
13
|
+
self.currencies_without_fractions = ActiveMerchant::Billing::CyberSourceGateway.currencies_without_fractions
|
14
|
+
|
15
|
+
self.supported_cardtypes = %i[visa master american_express discover diners_club jcb maestro elo union_pay cartes_bancaires mada]
|
16
|
+
|
17
|
+
self.homepage_url = 'http://www.cybersource.com'
|
18
|
+
self.display_name = 'Cybersource REST'
|
19
|
+
|
20
|
+
CREDIT_CARD_CODES = {
|
21
|
+
american_express: '003',
|
22
|
+
cartes_bancaires: '036',
|
23
|
+
dankort: '034',
|
24
|
+
diners_club: '005',
|
25
|
+
discover: '004',
|
26
|
+
elo: '054',
|
27
|
+
jcb: '007',
|
28
|
+
maestro: '042',
|
29
|
+
master: '002',
|
30
|
+
unionpay: '062',
|
31
|
+
visa: '001'
|
32
|
+
}
|
33
|
+
|
34
|
+
PAYMENT_SOLUTION = {
|
35
|
+
apple_pay: '001',
|
36
|
+
google_pay: '012'
|
37
|
+
}
|
38
|
+
|
39
|
+
def initialize(options = {})
|
40
|
+
requires!(options, :merchant_id, :public_key, :private_key)
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
def purchase(money, payment, options = {})
|
45
|
+
authorize(money, payment, options, true)
|
46
|
+
end
|
47
|
+
|
48
|
+
def authorize(money, payment, options = {}, capture = false)
|
49
|
+
post = build_auth_request(money, payment, options)
|
50
|
+
post[:processingInformation][:capture] = true if capture
|
51
|
+
|
52
|
+
commit('payments', post, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def capture(money, authorization, options = {})
|
56
|
+
payment = authorization.split('|').first
|
57
|
+
post = build_reference_request(money, options)
|
58
|
+
|
59
|
+
commit("payments/#{payment}/captures", post, options)
|
60
|
+
end
|
61
|
+
|
62
|
+
def refund(money, authorization, options = {})
|
63
|
+
payment = authorization.split('|').first
|
64
|
+
post = build_reference_request(money, options)
|
65
|
+
commit("payments/#{payment}/refunds", post, options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def credit(money, payment, options = {})
|
69
|
+
post = build_credit_request(money, payment, options)
|
70
|
+
commit('credits', post)
|
71
|
+
end
|
72
|
+
|
73
|
+
def void(authorization, options = {})
|
74
|
+
payment, amount = authorization.split('|')
|
75
|
+
post = build_void_request(amount)
|
76
|
+
commit("payments/#{payment}/reversals", post)
|
77
|
+
end
|
78
|
+
|
79
|
+
def verify(credit_card, options = {})
|
80
|
+
amount = eligible_for_zero_auth?(credit_card, options) ? 0 : 100
|
81
|
+
MultiResponse.run(:use_first_response) do |r|
|
82
|
+
r.process { authorize(amount, credit_card, options) }
|
83
|
+
r.process(:ignore_result) { void(r.authorization, options) }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def supports_scrubbing?
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def scrub(transcript)
|
92
|
+
transcript.
|
93
|
+
gsub(/(\\?"number\\?":\\?")\d+/, '\1[FILTERED]').
|
94
|
+
gsub(/(\\?"routingNumber\\?":\\?")\d+/, '\1[FILTERED]').
|
95
|
+
gsub(/(\\?"securityCode\\?":\\?")\d+/, '\1[FILTERED]').
|
96
|
+
gsub(/(signature=")[^"]*/, '\1[FILTERED]').
|
97
|
+
gsub(/(keyid=")[^"]*/, '\1[FILTERED]').
|
98
|
+
gsub(/(Digest: SHA-256=)[\w\/\+=]*/, '\1[FILTERED]')
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def build_void_request(amount = nil)
|
104
|
+
{ reversalInformation: { amountDetails: { totalAmount: nil } } }.tap do |post|
|
105
|
+
add_reversal_amount(post, amount.to_i) if amount.present?
|
106
|
+
end.compact
|
107
|
+
end
|
108
|
+
|
109
|
+
def build_auth_request(amount, payment, options)
|
110
|
+
{ clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post|
|
111
|
+
add_customer_id(post, options)
|
112
|
+
add_code(post, options)
|
113
|
+
add_payment(post, payment, options)
|
114
|
+
add_mdd_fields(post, options)
|
115
|
+
add_amount(post, amount)
|
116
|
+
add_address(post, payment, options[:billing_address], options, :billTo)
|
117
|
+
add_address(post, payment, options[:shipping_address], options, :shipTo)
|
118
|
+
add_business_rules_data(post, payment, options)
|
119
|
+
add_partner_solution_id(post)
|
120
|
+
add_stored_credentials(post, payment, options)
|
121
|
+
end.compact
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_reference_request(amount, options)
|
125
|
+
{ clientReferenceInformation: {}, orderInformation: {} }.tap do |post|
|
126
|
+
add_code(post, options)
|
127
|
+
add_mdd_fields(post, options)
|
128
|
+
add_amount(post, amount)
|
129
|
+
add_partner_solution_id(post)
|
130
|
+
end.compact
|
131
|
+
end
|
132
|
+
|
133
|
+
def build_credit_request(amount, payment, options)
|
134
|
+
{ clientReferenceInformation: {}, paymentInformation: {}, orderInformation: {} }.tap do |post|
|
135
|
+
add_code(post, options)
|
136
|
+
add_credit_card(post, payment)
|
137
|
+
add_mdd_fields(post, options)
|
138
|
+
add_amount(post, amount)
|
139
|
+
add_address(post, payment, options[:billing_address], options, :billTo)
|
140
|
+
add_merchant_description(post, options)
|
141
|
+
end.compact
|
142
|
+
end
|
143
|
+
|
144
|
+
def add_code(post, options)
|
145
|
+
return unless options[:order_id].present?
|
146
|
+
|
147
|
+
post[:clientReferenceInformation][:code] = options[:order_id]
|
148
|
+
end
|
149
|
+
|
150
|
+
def add_customer_id(post, options)
|
151
|
+
return unless options[:customer_id].present?
|
152
|
+
|
153
|
+
post[:paymentInformation][:customer] = { customerId: options[:customer_id] }
|
154
|
+
end
|
155
|
+
|
156
|
+
def add_reversal_amount(post, amount)
|
157
|
+
currency = options[:currency] || currency(amount)
|
158
|
+
|
159
|
+
post[:reversalInformation][:amountDetails] = {
|
160
|
+
totalAmount: localized_amount(amount, currency)
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
def add_amount(post, amount)
|
165
|
+
currency = options[:currency] || currency(amount)
|
166
|
+
post[:orderInformation][:amountDetails] = {
|
167
|
+
totalAmount: localized_amount(amount, currency),
|
168
|
+
currency: currency
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
def add_ach(post, payment)
|
173
|
+
post[:paymentInformation][:bank] = {
|
174
|
+
account: {
|
175
|
+
type: payment.account_type == 'checking' ? 'C' : 'S',
|
176
|
+
number: payment.account_number
|
177
|
+
},
|
178
|
+
routingNumber: payment.routing_number
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
def add_payment(post, payment, options)
|
183
|
+
post[:processingInformation] = {}
|
184
|
+
if payment.is_a?(NetworkTokenizationCreditCard)
|
185
|
+
add_network_tokenization_card(post, payment, options)
|
186
|
+
elsif payment.is_a?(Check)
|
187
|
+
add_ach(post, payment)
|
188
|
+
else
|
189
|
+
add_credit_card(post, payment)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def add_network_tokenization_card(post, payment, options)
|
194
|
+
post[:processingInformation][:paymentSolution] = PAYMENT_SOLUTION[payment.source]
|
195
|
+
post[:processingInformation][:commerceIndicator] = 'internet' unless card_brand(payment) == 'jcb'
|
196
|
+
|
197
|
+
post[:paymentInformation][:tokenizedCard] = {
|
198
|
+
number: payment.number,
|
199
|
+
expirationMonth: payment.month,
|
200
|
+
expirationYear: payment.year,
|
201
|
+
cryptogram: payment.payment_cryptogram,
|
202
|
+
transactionType: '1',
|
203
|
+
type: CREDIT_CARD_CODES[card_brand(payment).to_sym]
|
204
|
+
}
|
205
|
+
|
206
|
+
if card_brand(payment) == 'master'
|
207
|
+
post[:consumerAuthenticationInformation] = {
|
208
|
+
ucafAuthenticationData: payment.payment_cryptogram,
|
209
|
+
ucafCollectionIndicator: '2'
|
210
|
+
}
|
211
|
+
else
|
212
|
+
post[:consumerAuthenticationInformation] = { cavv: payment.payment_cryptogram }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def add_credit_card(post, creditcard)
|
217
|
+
post[:paymentInformation][:card] = {
|
218
|
+
number: creditcard.number,
|
219
|
+
expirationMonth: format(creditcard.month, :two_digits),
|
220
|
+
expirationYear: format(creditcard.year, :four_digits),
|
221
|
+
securityCode: creditcard.verification_value,
|
222
|
+
type: CREDIT_CARD_CODES[card_brand(creditcard).to_sym]
|
223
|
+
}
|
224
|
+
end
|
225
|
+
|
226
|
+
def add_address(post, payment_method, address, options, address_type)
|
227
|
+
return unless address.present?
|
228
|
+
|
229
|
+
first_name, last_name = address_names(address[:name], payment_method)
|
230
|
+
|
231
|
+
post[:orderInformation][address_type] = {
|
232
|
+
firstName: first_name,
|
233
|
+
lastName: last_name,
|
234
|
+
address1: address[:address1],
|
235
|
+
address2: address[:address2],
|
236
|
+
locality: address[:city],
|
237
|
+
administrativeArea: address[:state],
|
238
|
+
postalCode: address[:zip],
|
239
|
+
country: lookup_country_code(address[:country])&.value,
|
240
|
+
email: options[:email].presence || 'null@cybersource.com',
|
241
|
+
phoneNumber: address[:phone]
|
242
|
+
# merchantTaxID: ship_to ? options[:merchant_tax_id] : nil,
|
243
|
+
# company: address[:company],
|
244
|
+
# companyTaxID: address[:companyTaxID],
|
245
|
+
# ipAddress: options[:ip],
|
246
|
+
# driversLicenseNumber: options[:drivers_license_number],
|
247
|
+
# driversLicenseState: options[:drivers_license_state],
|
248
|
+
}.compact
|
249
|
+
end
|
250
|
+
|
251
|
+
def add_merchant_description(post, options)
|
252
|
+
return unless options[:merchant_descriptor_name] || options[:merchant_descriptor_address1] || options[:merchant_descriptor_locality]
|
253
|
+
|
254
|
+
merchant = post[:merchantInformation][:merchantDescriptor] = {}
|
255
|
+
merchant[:name] = options[:merchant_descriptor_name] if options[:merchant_descriptor_name]
|
256
|
+
merchant[:address1] = options[:merchant_descriptor_address1] if options[:merchant_descriptor_address1]
|
257
|
+
merchant[:locality] = options[:merchant_descriptor_locality] if options[:merchant_descriptor_locality]
|
258
|
+
end
|
259
|
+
|
260
|
+
def add_stored_credentials(post, payment, options)
|
261
|
+
return unless stored_credential = options[:stored_credential]
|
262
|
+
|
263
|
+
options = stored_credential_options(stored_credential, options.fetch(:reason_code, ''))
|
264
|
+
post[:processingInformation][:commerceIndicator] = options.fetch(:transaction_type, 'internet')
|
265
|
+
stored_credential[:initial_transaction] ? initial_transaction(post, options) : subsequent_transaction(post, options)
|
266
|
+
end
|
267
|
+
|
268
|
+
def stored_credential_options(options, reason_code)
|
269
|
+
transaction_type = options[:reason_type]
|
270
|
+
transaction_type = 'install' if transaction_type == 'installment'
|
271
|
+
initiator = options[:initiator] if options[:initiator]
|
272
|
+
initiator = 'customer' if initiator == 'cardholder'
|
273
|
+
stored_on_file = options[:reason_type] == 'recurring'
|
274
|
+
options.merge({
|
275
|
+
transaction_type: transaction_type,
|
276
|
+
initiator: initiator,
|
277
|
+
reason_code: reason_code,
|
278
|
+
stored_on_file: stored_on_file
|
279
|
+
})
|
280
|
+
end
|
281
|
+
|
282
|
+
def add_processing_information(initiator, merchant_initiated_transaction_hash = {})
|
283
|
+
{
|
284
|
+
authorizationOptions: {
|
285
|
+
initiator: {
|
286
|
+
type: initiator,
|
287
|
+
merchantInitiatedTransaction: merchant_initiated_transaction_hash,
|
288
|
+
storedCredentialUsed: true
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}.compact
|
292
|
+
end
|
293
|
+
|
294
|
+
def initial_transaction(post, options)
|
295
|
+
processing_information = add_processing_information(options[:initiator], {
|
296
|
+
reason: options[:reason_code]
|
297
|
+
})
|
298
|
+
|
299
|
+
post[:processingInformation].merge!(processing_information)
|
300
|
+
end
|
301
|
+
|
302
|
+
def subsequent_transaction(post, options)
|
303
|
+
network_transaction_id = options[:network_transaction_id] || options.dig(:stored_credential, :network_transaction_id) || ''
|
304
|
+
processing_information = add_processing_information(options[:initiator], {
|
305
|
+
originalAuthorizedAmount: post.dig(:orderInformation, :amountDetails, :totalAmount),
|
306
|
+
previousTransactionID: network_transaction_id,
|
307
|
+
reason: options[:reason_code],
|
308
|
+
storedCredentialUsed: options[:stored_on_file]
|
309
|
+
})
|
310
|
+
post[:processingInformation].merge!(processing_information)
|
311
|
+
end
|
312
|
+
|
313
|
+
def network_transaction_id_from(response)
|
314
|
+
response.dig('processorInformation', 'networkTransactionId')
|
315
|
+
end
|
316
|
+
|
317
|
+
def url(action)
|
318
|
+
"#{(test? ? test_url : live_url)}/pts/v2/#{action}"
|
319
|
+
end
|
320
|
+
|
321
|
+
def host
|
322
|
+
URI.parse(url('')).host
|
323
|
+
end
|
324
|
+
|
325
|
+
def parse(body)
|
326
|
+
JSON.parse(body)
|
327
|
+
end
|
328
|
+
|
329
|
+
def commit(action, post, options = {})
|
330
|
+
add_reconciliation_id(post, options)
|
331
|
+
add_sec_code(post, options)
|
332
|
+
add_invoice_number(post, options)
|
333
|
+
response = parse(ssl_post(url(action), post.to_json, auth_headers(action, post)))
|
334
|
+
Response.new(
|
335
|
+
success_from(response),
|
336
|
+
message_from(response),
|
337
|
+
response,
|
338
|
+
authorization: authorization_from(response),
|
339
|
+
avs_result: AVSResult.new(code: response.dig('processorInformation', 'avs', 'code')),
|
340
|
+
# cvv_result: CVVResult.new(response['some_cvv_response_key']),
|
341
|
+
network_transaction_id: network_transaction_id_from(response),
|
342
|
+
test: test?,
|
343
|
+
error_code: error_code_from(response)
|
344
|
+
)
|
345
|
+
rescue ActiveMerchant::ResponseError => e
|
346
|
+
response = e.response.body.present? ? parse(e.response.body) : { 'response' => { 'rmsg' => e.response.msg } }
|
347
|
+
message = response.dig('response', 'rmsg') || response.dig('message')
|
348
|
+
Response.new(false, message, response, test: test?)
|
349
|
+
end
|
350
|
+
|
351
|
+
def success_from(response)
|
352
|
+
%w(AUTHORIZED PENDING REVERSED).include?(response['status'])
|
353
|
+
end
|
354
|
+
|
355
|
+
def message_from(response)
|
356
|
+
return response['status'] if success_from(response)
|
357
|
+
|
358
|
+
response['errorInformation']['message'] || response['message']
|
359
|
+
end
|
360
|
+
|
361
|
+
def authorization_from(response)
|
362
|
+
id = response['id']
|
363
|
+
has_amount = response['orderInformation'] && response['orderInformation']['amountDetails'] && response['orderInformation']['amountDetails']['authorizedAmount']
|
364
|
+
amount = response['orderInformation']['amountDetails']['authorizedAmount'].delete('.') if has_amount
|
365
|
+
|
366
|
+
return id if amount.blank?
|
367
|
+
|
368
|
+
[id, amount].join('|')
|
369
|
+
end
|
370
|
+
|
371
|
+
def error_code_from(response)
|
372
|
+
response['errorInformation']['reason'] unless success_from(response)
|
373
|
+
end
|
374
|
+
|
375
|
+
# This implementation follows the Cybersource guide on how create the request signature, see:
|
376
|
+
# https://developer.cybersource.com/docs/cybs/en-us/payments/developer/all/rest/payments/GenerateHeader/httpSignatureAuthentication.html
|
377
|
+
def get_http_signature(resource, digest, http_method = 'post', gmtdatetime = Time.now.httpdate)
|
378
|
+
string_to_sign = {
|
379
|
+
host: host,
|
380
|
+
date: gmtdatetime,
|
381
|
+
"(request-target)": "#{http_method} /pts/v2/#{resource}",
|
382
|
+
digest: digest,
|
383
|
+
"v-c-merchant-id": @options[:merchant_id]
|
384
|
+
}.map { |k, v| "#{k}: #{v}" }.join("\n").force_encoding(Encoding::UTF_8)
|
385
|
+
|
386
|
+
{
|
387
|
+
keyid: @options[:public_key],
|
388
|
+
algorithm: 'HmacSHA256',
|
389
|
+
headers: "host date (request-target)#{digest.present? ? ' digest' : ''} v-c-merchant-id",
|
390
|
+
signature: sign_payload(string_to_sign)
|
391
|
+
}.map { |k, v| %{#{k}="#{v}"} }.join(', ')
|
392
|
+
end
|
393
|
+
|
394
|
+
def sign_payload(payload)
|
395
|
+
decoded_key = Base64.decode64(@options[:private_key])
|
396
|
+
Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', decoded_key, payload))
|
397
|
+
end
|
398
|
+
|
399
|
+
def auth_headers(action, post, http_method = 'post')
|
400
|
+
digest = "SHA-256=#{Digest::SHA256.base64digest(post.to_json)}" if post.present?
|
401
|
+
date = Time.now.httpdate
|
402
|
+
|
403
|
+
{
|
404
|
+
'Accept' => 'application/hal+json;charset=utf-8',
|
405
|
+
'Content-Type' => 'application/json;charset=utf-8',
|
406
|
+
'V-C-Merchant-Id' => @options[:merchant_id],
|
407
|
+
'Date' => date,
|
408
|
+
'Host' => host,
|
409
|
+
'Signature' => get_http_signature(action, digest, http_method, date),
|
410
|
+
'Digest' => digest
|
411
|
+
}
|
412
|
+
end
|
413
|
+
|
414
|
+
def add_business_rules_data(post, payment, options)
|
415
|
+
post[:processingInformation][:authorizationOptions] = {}
|
416
|
+
unless payment.is_a?(NetworkTokenizationCreditCard)
|
417
|
+
post[:processingInformation][:authorizationOptions][:ignoreAvsResult] = 'true' if options[:ignore_avs].to_s == 'true'
|
418
|
+
post[:processingInformation][:authorizationOptions][:ignoreCvResult] = 'true' if options[:ignore_cvv].to_s == 'true'
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
def add_mdd_fields(post, options)
|
423
|
+
mdd_fields = options.select { |k, v| k.to_s.start_with?('mdd_field') && v.present? }
|
424
|
+
return unless mdd_fields.present?
|
425
|
+
|
426
|
+
post[:merchantDefinedInformation] = mdd_fields.map do |key, value|
|
427
|
+
{ key: key, value: value }
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def add_reconciliation_id(post, options)
|
432
|
+
return unless options[:reconciliation_id].present?
|
433
|
+
|
434
|
+
post[:clientReferenceInformation][:reconciliationId] = options[:reconciliation_id]
|
435
|
+
end
|
436
|
+
|
437
|
+
def add_sec_code(post, options)
|
438
|
+
return unless options[:sec_code].present?
|
439
|
+
|
440
|
+
post[:processingInformation][:bankTransferOptions] = { secCode: options[:sec_code] }
|
441
|
+
end
|
442
|
+
|
443
|
+
def add_invoice_number(post, options)
|
444
|
+
return unless options[:invoice_number].present?
|
445
|
+
|
446
|
+
post[:orderInformation][:invoiceDetails] = { invoiceNumber: options[:invoice_number] }
|
447
|
+
end
|
448
|
+
|
449
|
+
def add_partner_solution_id(post)
|
450
|
+
return unless application_id
|
451
|
+
|
452
|
+
post[:clientReferenceInformation][:partner] = { solutionId: application_id }
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
@@ -4,7 +4,7 @@ module ActiveMerchant #:nodoc:
|
|
4
4
|
self.test_url = 'https://sandbox.dlocal.com'
|
5
5
|
self.live_url = 'https://api.dlocal.com'
|
6
6
|
|
7
|
-
self.supported_countries = %w[AR BD BO BR CL CM CN CO CR DO EC EG GH IN ID KE MY MX MA NG PA PY PE PH SN
|
7
|
+
self.supported_countries = %w[AR BD BO BR CL CM CN CO CR DO EC EG GH GT IN ID JP KE MY MX MA NG PA PY PE PH SN SV TH TR TZ UG UY VN ZA]
|
8
8
|
self.default_currency = 'USD'
|
9
9
|
self.supported_cardtypes = %i[visa master american_express discover jcb diners_club maestro naranja cabal elo alia carnet]
|
10
10
|
|
@@ -58,10 +58,21 @@ module ActiveMerchant #:nodoc:
|
|
58
58
|
authorize(0, credit_card, options.merge(verify: 'true'))
|
59
59
|
end
|
60
60
|
|
61
|
+
def inquire(authorization, options = {})
|
62
|
+
post = {}
|
63
|
+
post[:payment_id] = authorization
|
64
|
+
action = authorization ? 'status' : 'orders'
|
65
|
+
commit(action, post, options)
|
66
|
+
end
|
67
|
+
|
61
68
|
def supports_scrubbing?
|
62
69
|
true
|
63
70
|
end
|
64
71
|
|
72
|
+
def supports_network_tokenization?
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
65
76
|
def scrub(transcript)
|
66
77
|
transcript.
|
67
78
|
gsub(%r((X-Trans-Key: )\w+), '\1[FILTERED]').
|
@@ -80,6 +91,7 @@ module ActiveMerchant #:nodoc:
|
|
80
91
|
add_card(post, card, action, options)
|
81
92
|
add_additional_data(post, options)
|
82
93
|
post[:order_id] = options[:order_id] || generate_unique_id
|
94
|
+
post[:original_order_id] = options[:original_order_id] if options[:original_order_id]
|
83
95
|
post[:description] = options[:description] if options[:description]
|
84
96
|
end
|
85
97
|
|
@@ -147,11 +159,30 @@ module ActiveMerchant #:nodoc:
|
|
147
159
|
|
148
160
|
def add_card(post, card, action, options = {})
|
149
161
|
post[:card] = {}
|
162
|
+
if card.is_a?(NetworkTokenizationCreditCard)
|
163
|
+
post[:card][:network_token] = card.number
|
164
|
+
post[:card][:cryptogram] = card.payment_cryptogram
|
165
|
+
post[:card][:eci] = card.eci
|
166
|
+
# used case of Network Token: 'CARD_ON_FILE', 'SUBSCRIPTION', 'UNSCHEDULED_CARD_ON_FILE'
|
167
|
+
if options.dig(:stored_credential, :reason_type) == 'unscheduled'
|
168
|
+
if options.dig(:stored_credential, :initiator) == 'merchant'
|
169
|
+
post[:card][:stored_credential_type] = 'UNSCHEDULED_CARD_ON_FILE'
|
170
|
+
else
|
171
|
+
post[:card][:stored_credential_type] = 'CARD_ON_FILE'
|
172
|
+
end
|
173
|
+
else
|
174
|
+
post[:card][:stored_credential_type] = 'SUBSCRIPTION'
|
175
|
+
end
|
176
|
+
# required for MC debit recurrent in BR 'USED'(subsecuence Payments) . 'FIRST' an inital payment
|
177
|
+
post[:card][:stored_credential_usage] = (options[:stored_credential][:initial_transaction] ? 'FIRST' : 'USED') if options[:stored_credential]
|
178
|
+
else
|
179
|
+
post[:card][:number] = card.number
|
180
|
+
post[:card][:cvv] = card.verification_value
|
181
|
+
end
|
182
|
+
|
150
183
|
post[:card][:holder_name] = card.name
|
151
184
|
post[:card][:expiration_month] = card.month
|
152
185
|
post[:card][:expiration_year] = card.year
|
153
|
-
post[:card][:number] = card.number
|
154
|
-
post[:card][:cvv] = card.verification_value
|
155
186
|
post[:card][:descriptor] = options[:dynamic_descriptor] if options[:dynamic_descriptor]
|
156
187
|
post[:card][:capture] = (action == 'purchase')
|
157
188
|
post[:card][:installments] = options[:installments] if options[:installments]
|
@@ -170,7 +201,11 @@ module ActiveMerchant #:nodoc:
|
|
170
201
|
url = url(action, parameters, options)
|
171
202
|
post = post_data(action, parameters)
|
172
203
|
begin
|
173
|
-
raw =
|
204
|
+
raw = if %w(status orders).include?(action)
|
205
|
+
ssl_get(url, headers(nil, options))
|
206
|
+
else
|
207
|
+
ssl_post(url, post, headers(post, options))
|
208
|
+
end
|
174
209
|
response = parse(raw)
|
175
210
|
rescue ResponseError => e
|
176
211
|
raw = e.response.body
|
@@ -229,6 +264,10 @@ module ActiveMerchant #:nodoc:
|
|
229
264
|
'payments'
|
230
265
|
when 'void'
|
231
266
|
"payments/#{parameters[:authorization_id]}/cancel"
|
267
|
+
when 'status'
|
268
|
+
"payments/#{parameters[:payment_id]}/status"
|
269
|
+
when 'orders'
|
270
|
+
"orders/#{options[:order_id]}"
|
232
271
|
end
|
233
272
|
end
|
234
273
|
|
@@ -242,7 +281,7 @@ module ActiveMerchant #:nodoc:
|
|
242
281
|
'X-Version' => '2.1',
|
243
282
|
'Authorization' => signature(post, timestamp)
|
244
283
|
}
|
245
|
-
headers
|
284
|
+
headers['X-Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key]
|
246
285
|
headers
|
247
286
|
end
|
248
287
|
|
@@ -83,6 +83,11 @@ module ActiveMerchant #:nodoc:
|
|
83
83
|
commit(:post, "payments/#{authorization}/refunds", post)
|
84
84
|
end
|
85
85
|
|
86
|
+
def inquire(authorization, options = {})
|
87
|
+
options[:action] = 'inquire'
|
88
|
+
commit(:get, "payments/#{authorization}", nil, options)
|
89
|
+
end
|
90
|
+
|
86
91
|
def verify(credit_card, options = {})
|
87
92
|
raise ArgumentError, 'Verify is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode]
|
88
93
|
|
@@ -267,7 +272,7 @@ module ActiveMerchant #:nodoc:
|
|
267
272
|
response = parse(raw_response)
|
268
273
|
end
|
269
274
|
|
270
|
-
success = success_from(response)
|
275
|
+
success = success_from(response, options)
|
271
276
|
Response.new(
|
272
277
|
success,
|
273
278
|
message_from(success, response),
|
@@ -279,7 +284,7 @@ module ActiveMerchant #:nodoc:
|
|
279
284
|
end
|
280
285
|
|
281
286
|
def post_data(parameters = {})
|
282
|
-
parameters
|
287
|
+
parameters&.to_json
|
283
288
|
end
|
284
289
|
|
285
290
|
def parse(body)
|
@@ -311,8 +316,14 @@ module ActiveMerchant #:nodoc:
|
|
311
316
|
message
|
312
317
|
end
|
313
318
|
|
314
|
-
def success_from(response)
|
315
|
-
|
319
|
+
def success_from(response, options)
|
320
|
+
status = %w(approved pre_approved)
|
321
|
+
|
322
|
+
if options[:action] == 'inquire'
|
323
|
+
status.include?(response['status']) || response['status'] == 'rejected'
|
324
|
+
else
|
325
|
+
status.include?(response['status'])
|
326
|
+
end
|
316
327
|
end
|
317
328
|
|
318
329
|
def authorization_from(response)
|