activemerchant 1.90.0 → 1.99.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 +202 -0
- data/README.md +6 -2
- data/lib/active_merchant/billing/avs_result.rb +4 -5
- data/lib/active_merchant/billing/credit_card.rb +8 -0
- data/lib/active_merchant/billing/credit_card_methods.rb +81 -5
- data/lib/active_merchant/billing/gateway.rb +10 -0
- data/lib/active_merchant/billing/gateways/adyen.rb +206 -54
- data/lib/active_merchant/billing/gateways/bambora_apac.rb +226 -0
- data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +43 -10
- data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +3 -0
- data/lib/active_merchant/billing/gateways/beanstream.rb +11 -6
- data/lib/active_merchant/billing/gateways/blue_pay.rb +10 -8
- data/lib/active_merchant/billing/gateways/blue_snap.rb +211 -36
- data/lib/active_merchant/billing/gateways/bpoint.rb +4 -4
- data/lib/active_merchant/billing/gateways/braintree_blue.rb +79 -18
- data/lib/active_merchant/billing/gateways/card_connect.rb +6 -1
- data/lib/active_merchant/billing/gateways/cecabank.rb +20 -9
- data/lib/active_merchant/billing/gateways/checkout_v2.rb +98 -61
- data/lib/active_merchant/billing/gateways/credorax.rb +69 -4
- data/lib/active_merchant/billing/gateways/cyber_source.rb +85 -14
- data/lib/active_merchant/billing/gateways/d_local.rb +3 -3
- data/lib/active_merchant/billing/gateways/decidir.rb +245 -0
- data/lib/active_merchant/billing/gateways/elavon.rb +9 -0
- data/lib/active_merchant/billing/gateways/epay.rb +13 -2
- data/lib/active_merchant/billing/gateways/eway_rapid.rb +42 -12
- data/lib/active_merchant/billing/gateways/fat_zebra.rb +26 -7
- data/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb +42 -3
- data/lib/active_merchant/billing/gateways/global_collect.rb +3 -7
- data/lib/active_merchant/billing/gateways/hps.rb +46 -1
- data/lib/active_merchant/billing/gateways/ipp.rb +1 -0
- data/lib/active_merchant/billing/gateways/kushki.rb +1 -1
- data/lib/active_merchant/billing/gateways/litle.rb +61 -3
- data/lib/active_merchant/billing/gateways/mastercard.rb +30 -5
- data/lib/active_merchant/billing/gateways/mercado_pago.rb +1 -1
- data/lib/active_merchant/billing/gateways/migs.rb +8 -0
- data/lib/active_merchant/billing/gateways/monei.rb +31 -0
- data/lib/active_merchant/billing/gateways/moneris.rb +3 -4
- data/lib/active_merchant/billing/gateways/mundipagg.rb +37 -9
- data/lib/active_merchant/billing/gateways/nab_transact.rb +1 -1
- data/lib/active_merchant/billing/gateways/netbanx.rb +4 -0
- data/lib/active_merchant/billing/gateways/nmi.rb +45 -5
- data/lib/active_merchant/billing/gateways/openpay.rb +1 -1
- data/lib/active_merchant/billing/gateways/opp.rb +20 -1
- data/lib/active_merchant/billing/gateways/orbital.rb +92 -11
- data/lib/active_merchant/billing/gateways/payflow.rb +64 -14
- data/lib/active_merchant/billing/gateways/payment_express.rb +7 -0
- data/lib/active_merchant/billing/gateways/paymentez.rb +7 -11
- data/lib/active_merchant/billing/gateways/paymill.rb +5 -0
- data/lib/active_merchant/billing/gateways/paypal.rb +14 -1
- data/lib/active_merchant/billing/gateways/paypal_express.rb +3 -1
- data/lib/active_merchant/billing/gateways/payu_latam.rb +6 -2
- data/lib/active_merchant/billing/gateways/pin.rb +19 -6
- data/lib/active_merchant/billing/gateways/pro_pay.rb +1 -1
- data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +7 -1
- data/lib/active_merchant/billing/gateways/qvalent.rb +54 -1
- data/lib/active_merchant/billing/gateways/realex.rb +42 -5
- data/lib/active_merchant/billing/gateways/redsys.rb +113 -30
- data/lib/active_merchant/billing/gateways/spreedly_core.rb +43 -29
- data/lib/active_merchant/billing/gateways/stripe.rb +66 -34
- data/lib/active_merchant/billing/gateways/stripe_payment_intents.rb +271 -0
- data/lib/active_merchant/billing/gateways/tns.rb +10 -5
- data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +5 -5
- data/lib/active_merchant/billing/gateways/trust_commerce.rb +46 -6
- data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +19 -8
- data/lib/active_merchant/billing/gateways/visanet_peru.rb +22 -10
- data/lib/active_merchant/billing/gateways/worldpay.rb +237 -34
- data/lib/active_merchant/country.rb +2 -1
- data/lib/active_merchant/version.rb +1 -1
- metadata +20 -4
|
@@ -4,18 +4,21 @@ module ActiveMerchant #:nodoc:
|
|
|
4
4
|
|
|
5
5
|
# we recommend setting up merchant-specific endpoints.
|
|
6
6
|
# https://docs.adyen.com/developers/api-manual#apiendpoints
|
|
7
|
-
self.test_url = 'https://pal-test.adyen.com/pal/servlet/Payment/
|
|
8
|
-
self.live_url = 'https://pal-live.adyen.com/pal/servlet/Payment/
|
|
7
|
+
self.test_url = 'https://pal-test.adyen.com/pal/servlet/Payment/'
|
|
8
|
+
self.live_url = 'https://pal-live.adyen.com/pal/servlet/Payment/'
|
|
9
9
|
|
|
10
10
|
self.supported_countries = ['AT', 'AU', 'BE', 'BG', 'BR', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GI', 'GR', 'HK', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'MX', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SG', 'SK', 'SI', 'US']
|
|
11
11
|
self.default_currency = 'USD'
|
|
12
|
-
self.
|
|
12
|
+
self.currencies_without_fractions = %w(CVE DJF GNF IDR JPY KMF KRW PYG RWF UGX VND VUV XAF XOF XPF)
|
|
13
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb, :dankort, :maestro, :discover, :elo, :naranja, :cabal]
|
|
13
14
|
|
|
14
15
|
self.money_format = :cents
|
|
15
16
|
|
|
16
17
|
self.homepage_url = 'https://www.adyen.com/'
|
|
17
18
|
self.display_name = 'Adyen'
|
|
18
19
|
|
|
20
|
+
API_VERSION = 'v40'
|
|
21
|
+
|
|
19
22
|
STANDARD_ERROR_CODE_MAPPING = {
|
|
20
23
|
'101' => STANDARD_ERROR_CODE[:incorrect_number],
|
|
21
24
|
'103' => STANDARD_ERROR_CODE[:invalid_cvc],
|
|
@@ -33,12 +36,12 @@ module ActiveMerchant #:nodoc:
|
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
def purchase(money, payment, options={})
|
|
36
|
-
if options[:execute_threed]
|
|
39
|
+
if options[:execute_threed] || options[:threed_dynamic]
|
|
37
40
|
authorize(money, payment, options)
|
|
38
41
|
else
|
|
39
42
|
MultiResponse.run do |r|
|
|
40
43
|
r.process { authorize(money, payment, options) }
|
|
41
|
-
r.process { capture(money, r.authorization, options) }
|
|
44
|
+
r.process { capture(money, r.authorization, capture_options(options)) }
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
47
|
end
|
|
@@ -49,31 +52,40 @@ module ActiveMerchant #:nodoc:
|
|
|
49
52
|
add_invoice(post, money, options)
|
|
50
53
|
add_payment(post, payment)
|
|
51
54
|
add_extra_data(post, payment, options)
|
|
52
|
-
|
|
55
|
+
add_stored_credentials(post, payment, options)
|
|
53
56
|
add_address(post, options)
|
|
54
57
|
add_installments(post, options) if options[:installments]
|
|
55
|
-
add_3ds(post, options)
|
|
56
|
-
|
|
58
|
+
add_3ds(post, options)
|
|
59
|
+
add_3ds_authenticated_data(post, options)
|
|
60
|
+
commit('authorise', post, options)
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
def capture(money, authorization, options={})
|
|
60
64
|
post = init_post(options)
|
|
61
65
|
add_invoice_for_modification(post, money, options)
|
|
62
66
|
add_reference(post, authorization, options)
|
|
63
|
-
commit('capture', post)
|
|
67
|
+
commit('capture', post, options)
|
|
64
68
|
end
|
|
65
69
|
|
|
66
70
|
def refund(money, authorization, options={})
|
|
67
71
|
post = init_post(options)
|
|
68
72
|
add_invoice_for_modification(post, money, options)
|
|
69
73
|
add_original_reference(post, authorization, options)
|
|
70
|
-
commit('refund', post)
|
|
74
|
+
commit('refund', post, options)
|
|
71
75
|
end
|
|
72
76
|
|
|
73
77
|
def void(authorization, options={})
|
|
74
78
|
post = init_post(options)
|
|
75
79
|
add_reference(post, authorization, options)
|
|
76
|
-
commit('cancel', post)
|
|
80
|
+
commit('cancel', post, options)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def adjust(money, authorization, options={})
|
|
84
|
+
post = init_post(options)
|
|
85
|
+
add_invoice_for_modification(post, money, options)
|
|
86
|
+
add_reference(post, authorization, options)
|
|
87
|
+
add_extra_data(post, nil, options)
|
|
88
|
+
commit('adjustAuthorisation', post, options)
|
|
77
89
|
end
|
|
78
90
|
|
|
79
91
|
def store(credit_card, options={})
|
|
@@ -82,14 +94,23 @@ module ActiveMerchant #:nodoc:
|
|
|
82
94
|
add_invoice(post, 0, options)
|
|
83
95
|
add_payment(post, credit_card)
|
|
84
96
|
add_extra_data(post, credit_card, options)
|
|
97
|
+
add_stored_credentials(post, credit_card, options)
|
|
85
98
|
add_recurring_contract(post, options)
|
|
86
99
|
add_address(post, options)
|
|
87
|
-
|
|
100
|
+
|
|
101
|
+
initial_response = commit('authorise', post, options)
|
|
102
|
+
|
|
103
|
+
if initial_response.success? && card_not_stored?(initial_response)
|
|
104
|
+
unsupported_failure_response(initial_response)
|
|
105
|
+
else
|
|
106
|
+
initial_response
|
|
107
|
+
end
|
|
88
108
|
end
|
|
89
109
|
|
|
90
110
|
def verify(credit_card, options={})
|
|
91
111
|
MultiResponse.run(:use_first_response) do |r|
|
|
92
112
|
r.process { authorize(0, credit_card, options) }
|
|
113
|
+
options[:idempotency_key] = nil
|
|
93
114
|
r.process(:ignore_result) { void(r.authorization, options) }
|
|
94
115
|
end
|
|
95
116
|
end
|
|
@@ -110,24 +131,32 @@ module ActiveMerchant #:nodoc:
|
|
|
110
131
|
|
|
111
132
|
AVS_MAPPING = {
|
|
112
133
|
'0' => 'R', # Unknown
|
|
113
|
-
'1' => 'A',
|
|
114
|
-
'2' => 'N',
|
|
115
|
-
'3' => 'R',
|
|
116
|
-
'4' => 'E',
|
|
117
|
-
'5' => 'U',
|
|
118
|
-
'6' => 'Z',
|
|
119
|
-
'7' => 'D',
|
|
120
|
-
'8' => 'U',
|
|
121
|
-
'9' => 'B',
|
|
122
|
-
'10' => 'N',
|
|
123
|
-
'11' => 'U',
|
|
124
|
-
'12' => 'B',
|
|
125
|
-
'13' => 'U',
|
|
126
|
-
'14' => 'P',
|
|
127
|
-
'15' => 'P',
|
|
128
|
-
'16' => 'N',
|
|
134
|
+
'1' => 'A', # Address matches, postal code doesn't
|
|
135
|
+
'2' => 'N', # Neither postal code nor address match
|
|
136
|
+
'3' => 'R', # AVS unavailable
|
|
137
|
+
'4' => 'E', # AVS not supported for this card type
|
|
138
|
+
'5' => 'U', # No AVS data provided
|
|
139
|
+
'6' => 'Z', # Postal code matches, address doesn't match
|
|
140
|
+
'7' => 'D', # Both postal code and address match
|
|
141
|
+
'8' => 'U', # Address not checked, postal code unknown
|
|
142
|
+
'9' => 'B', # Address matches, postal code unknown
|
|
143
|
+
'10' => 'N', # Address doesn't match, postal code unknown
|
|
144
|
+
'11' => 'U', # Postal code not checked, address unknown
|
|
145
|
+
'12' => 'B', # Address matches, postal code not checked
|
|
146
|
+
'13' => 'U', # Address doesn't match, postal code not checked
|
|
147
|
+
'14' => 'P', # Postal code matches, address unknown
|
|
148
|
+
'15' => 'P', # Postal code matches, address not checked
|
|
149
|
+
'16' => 'N', # Postal code doesn't match, address unknown
|
|
129
150
|
'17' => 'U', # Postal code doesn't match, address not checked
|
|
130
|
-
'18' => 'I'
|
|
151
|
+
'18' => 'I', # Neither postal code nor address were checked
|
|
152
|
+
'19' => 'L', # Name and postal code matches.
|
|
153
|
+
'20' => 'V', # Name, address and postal code matches.
|
|
154
|
+
'21' => 'O', # Name and address matches.
|
|
155
|
+
'22' => 'K', # Name matches.
|
|
156
|
+
'23' => 'F', # Postal code matches, name doesn't match.
|
|
157
|
+
'24' => 'H', # Both postal code and address matches, name doesn't match.
|
|
158
|
+
'25' => 'T', # Address matches, name doesn't match.
|
|
159
|
+
'26' => 'N' # Neither postal code, address nor name matches.
|
|
131
160
|
}
|
|
132
161
|
|
|
133
162
|
CVC_MAPPING = {
|
|
@@ -147,9 +176,11 @@ module ActiveMerchant #:nodoc:
|
|
|
147
176
|
}
|
|
148
177
|
|
|
149
178
|
def add_extra_data(post, payment, options)
|
|
179
|
+
post[:telephoneNumber] = options[:billing_address][:phone] if options.dig(:billing_address, :phone)
|
|
150
180
|
post[:shopperEmail] = options[:shopper_email] if options[:shopper_email]
|
|
151
181
|
post[:shopperIP] = options[:shopper_ip] if options[:shopper_ip]
|
|
152
182
|
post[:shopperReference] = options[:shopper_reference] if options[:shopper_reference]
|
|
183
|
+
post[:shopperStatement] = options[:shopper_statement] if options[:shopper_statement]
|
|
153
184
|
post[:fraudOffset] = options[:fraud_offset] if options[:fraud_offset]
|
|
154
185
|
post[:selectedBrand] = options[:selected_brand] if options[:selected_brand]
|
|
155
186
|
post[:selectedBrand] ||= NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard)
|
|
@@ -159,18 +190,29 @@ module ActiveMerchant #:nodoc:
|
|
|
159
190
|
post[:additionalData][:overwriteBrand] = normalize(options[:overwrite_brand]) if options[:overwrite_brand]
|
|
160
191
|
post[:additionalData][:customRoutingFlag] = options[:custom_routing_flag] if options[:custom_routing_flag]
|
|
161
192
|
post[:additionalData]['paymentdatasource.type'] = NETWORK_TOKENIZATION_CARD_SOURCE[payment.source.to_s] if payment.is_a?(NetworkTokenizationCreditCard)
|
|
193
|
+
post[:additionalData][:authorisationType] = options[:authorisation_type] if options[:authorisation_type]
|
|
194
|
+
post[:additionalData][:adjustAuthorisationData] = options[:adjust_authorisation_data] if options[:adjust_authorisation_data]
|
|
195
|
+
post[:additionalData][:industryUsage] = options[:industry_usage] if options[:industry_usage]
|
|
196
|
+
post[:additionalData][:updateShopperStatement] = options[:update_shopper_statement] if options[:update_shopper_statement]
|
|
197
|
+
post[:additionalData][:RequestedTestAcquirerResponseCode] = options[:requested_test_acquirer_response_code] if options[:requested_test_acquirer_response_code] && test?
|
|
198
|
+
post[:deviceFingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
|
|
162
199
|
add_risk_data(post, options)
|
|
163
200
|
end
|
|
164
201
|
|
|
165
202
|
def add_risk_data(post, options)
|
|
166
|
-
risk_data =
|
|
167
|
-
|
|
203
|
+
if (risk_data = options[:risk_data])
|
|
204
|
+
risk_data = Hash[risk_data.map { |k, v| ["riskdata.#{k}", v] }]
|
|
205
|
+
post[:additionalData].merge!(risk_data)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
168
208
|
|
|
169
|
-
|
|
209
|
+
def add_stored_credentials(post, payment, options)
|
|
210
|
+
add_shopper_interaction(post, payment, options)
|
|
211
|
+
add_recurring_processing_model(post, options)
|
|
170
212
|
end
|
|
171
213
|
|
|
172
214
|
def add_shopper_interaction(post, payment, options={})
|
|
173
|
-
if (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard)
|
|
215
|
+
if options.dig(:stored_credential, :initial_transaction) || (payment.respond_to?(:verification_value) && payment.verification_value) || payment.is_a?(NetworkTokenizationCreditCard)
|
|
174
216
|
shopper_interaction = 'Ecommerce'
|
|
175
217
|
else
|
|
176
218
|
shopper_interaction = 'ContAuth'
|
|
@@ -179,32 +221,48 @@ module ActiveMerchant #:nodoc:
|
|
|
179
221
|
post[:shopperInteraction] = options[:shopper_interaction] || shopper_interaction
|
|
180
222
|
end
|
|
181
223
|
|
|
224
|
+
def add_recurring_processing_model(post, options)
|
|
225
|
+
return unless options.dig(:stored_credential, :reason_type) || options[:recurring_processing_model]
|
|
226
|
+
if options.dig(:stored_credential, :reason_type) && options[:stored_credential][:reason_type] == 'unscheduled'
|
|
227
|
+
recurring_processing_model = 'CardOnFile'
|
|
228
|
+
else
|
|
229
|
+
recurring_processing_model = 'Subscription'
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
post[:recurringProcessingModel] = options[:recurring_processing_model] || recurring_processing_model
|
|
233
|
+
end
|
|
234
|
+
|
|
182
235
|
def add_address(post, options)
|
|
183
236
|
return unless post[:card]&.kind_of?(Hash)
|
|
184
237
|
if (address = options[:billing_address] || options[:address]) && address[:country]
|
|
185
|
-
post[:
|
|
186
|
-
post[:
|
|
187
|
-
post[:
|
|
188
|
-
post[:
|
|
189
|
-
post[:
|
|
190
|
-
post[:
|
|
191
|
-
post[:
|
|
238
|
+
post[:billingAddress] = {}
|
|
239
|
+
post[:billingAddress][:street] = address[:address1] || 'NA'
|
|
240
|
+
post[:billingAddress][:houseNumberOrName] = address[:address2] || 'NA'
|
|
241
|
+
post[:billingAddress][:postalCode] = address[:zip] if address[:zip]
|
|
242
|
+
post[:billingAddress][:city] = address[:city] || 'NA'
|
|
243
|
+
post[:billingAddress][:stateOrProvince] = get_state(address)
|
|
244
|
+
post[:billingAddress][:country] = address[:country] if address[:country]
|
|
192
245
|
end
|
|
193
246
|
end
|
|
194
247
|
|
|
248
|
+
def get_state(address)
|
|
249
|
+
address[:state] && !address[:state].blank? ? address[:state] : 'NA'
|
|
250
|
+
end
|
|
251
|
+
|
|
195
252
|
def add_invoice(post, money, options)
|
|
253
|
+
currency = options[:currency] || currency(money)
|
|
196
254
|
amount = {
|
|
197
|
-
value:
|
|
198
|
-
currency:
|
|
255
|
+
value: localized_amount(money, currency),
|
|
256
|
+
currency: currency
|
|
199
257
|
}
|
|
200
258
|
post[:amount] = amount
|
|
201
|
-
post[:recurringProcessingModel] = options[:recurring_processing_model] if options[:recurring_processing_model]
|
|
202
259
|
end
|
|
203
260
|
|
|
204
261
|
def add_invoice_for_modification(post, money, options)
|
|
262
|
+
currency = options[:currency] || currency(money)
|
|
205
263
|
amount = {
|
|
206
|
-
value:
|
|
207
|
-
currency:
|
|
264
|
+
value: localized_amount(money, currency),
|
|
265
|
+
currency: currency
|
|
208
266
|
}
|
|
209
267
|
post[:modificationAmount] = amount
|
|
210
268
|
end
|
|
@@ -235,6 +293,11 @@ module ActiveMerchant #:nodoc:
|
|
|
235
293
|
post[:card] = card
|
|
236
294
|
end
|
|
237
295
|
|
|
296
|
+
def capture_options(options)
|
|
297
|
+
return options.merge(idempotency_key: "#{options[:idempotency_key]}-cap") if options[:idempotency_key]
|
|
298
|
+
options
|
|
299
|
+
end
|
|
300
|
+
|
|
238
301
|
def add_reference(post, authorization, options = {})
|
|
239
302
|
_, psp_reference, _ = authorization.split('#')
|
|
240
303
|
post[:originalReference] = single_reference(authorization) || psp_reference
|
|
@@ -272,8 +335,62 @@ module ActiveMerchant #:nodoc:
|
|
|
272
335
|
end
|
|
273
336
|
|
|
274
337
|
def add_3ds(post, options)
|
|
275
|
-
|
|
276
|
-
|
|
338
|
+
if three_ds_2_options = options[:three_ds_2]
|
|
339
|
+
device_channel = three_ds_2_options[:channel]
|
|
340
|
+
if device_channel == 'app'
|
|
341
|
+
post[:threeDS2RequestData] = { deviceChannel: device_channel }
|
|
342
|
+
else
|
|
343
|
+
add_browser_info(three_ds_2_options[:browser_info], post)
|
|
344
|
+
post[:threeDS2RequestData] = { deviceChannel: device_channel, notificationURL: three_ds_2_options[:notification_url] }
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
if options.has_key?(:execute_threed)
|
|
348
|
+
post[:additionalData][:executeThreeD] = options[:execute_threed]
|
|
349
|
+
post[:additionalData][:scaExemption] = options[:sca_exemption] if options[:sca_exemption]
|
|
350
|
+
end
|
|
351
|
+
else
|
|
352
|
+
return unless options[:execute_threed] || options[:threed_dynamic]
|
|
353
|
+
post[:browserInfo] = { userAgent: options[:user_agent], acceptHeader: options[:accept_header] }
|
|
354
|
+
post[:additionalData] = { executeThreeD: 'true' } if options[:execute_threed]
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def add_3ds_authenticated_data(post, options)
|
|
359
|
+
if options[:three_d_secure] && options[:three_d_secure][:eci] && options[:three_d_secure][:xid]
|
|
360
|
+
add_3ds1_authenticated_data(post, options)
|
|
361
|
+
elsif options[:three_d_secure]
|
|
362
|
+
add_3ds2_authenticated_data(post, options)
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def add_3ds1_authenticated_data(post, options)
|
|
367
|
+
three_d_secure_options = options[:three_d_secure]
|
|
368
|
+
post[:mpiData] = {
|
|
369
|
+
cavv: three_d_secure_options[:cavv],
|
|
370
|
+
cavvAlgorithm: three_d_secure_options[:cavv_algorithm],
|
|
371
|
+
eci: three_d_secure_options[:eci],
|
|
372
|
+
xid: three_d_secure_options[:xid],
|
|
373
|
+
directoryResponse: three_d_secure_options[:directory_response_status],
|
|
374
|
+
authenticationResponse: three_d_secure_options[:authentication_response_status]
|
|
375
|
+
}
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def add_3ds2_authenticated_data(post, options)
|
|
379
|
+
three_d_secure_options = options[:three_d_secure]
|
|
380
|
+
# If the transaction was authenticated in a frictionless flow, send the transStatus from the ARes.
|
|
381
|
+
if(three_d_secure_options[:authentication_response_status].nil?)
|
|
382
|
+
authentication_response = three_d_secure_options[:directory_response_status]
|
|
383
|
+
else
|
|
384
|
+
authentication_response = three_d_secure_options[:authentication_response_status]
|
|
385
|
+
end
|
|
386
|
+
post[:mpiData] = {
|
|
387
|
+
threeDSVersion: three_d_secure_options[:version],
|
|
388
|
+
eci: three_d_secure_options[:eci],
|
|
389
|
+
cavv: three_d_secure_options[:cavv],
|
|
390
|
+
dsTransID: three_d_secure_options[:ds_transaction_id],
|
|
391
|
+
directoryResponse: three_d_secure_options[:directory_response_status],
|
|
392
|
+
authenticationResponse: authentication_response
|
|
393
|
+
}
|
|
277
394
|
end
|
|
278
395
|
|
|
279
396
|
def parse(body)
|
|
@@ -281,9 +398,9 @@ module ActiveMerchant #:nodoc:
|
|
|
281
398
|
JSON.parse(body)
|
|
282
399
|
end
|
|
283
400
|
|
|
284
|
-
def commit(action, parameters)
|
|
401
|
+
def commit(action, parameters, options)
|
|
285
402
|
begin
|
|
286
|
-
raw_response = ssl_post("#{url}/#{action}", post_data(action, parameters), request_headers)
|
|
403
|
+
raw_response = ssl_post("#{url}/#{action}", post_data(action, parameters), request_headers(options))
|
|
287
404
|
response = parse(raw_response)
|
|
288
405
|
rescue ResponseError => e
|
|
289
406
|
raw_response = e.response.body
|
|
@@ -312,11 +429,11 @@ module ActiveMerchant #:nodoc:
|
|
|
312
429
|
|
|
313
430
|
def url
|
|
314
431
|
if test?
|
|
315
|
-
test_url
|
|
432
|
+
"#{test_url}#{API_VERSION}"
|
|
316
433
|
elsif @options[:subdomain]
|
|
317
|
-
"https://#{@options[:subdomain]}-pal-live.adyenpayments.com/pal/servlet/Payment
|
|
434
|
+
"https://#{@options[:subdomain]}-pal-live.adyenpayments.com/pal/servlet/Payment/#{API_VERSION}"
|
|
318
435
|
else
|
|
319
|
-
live_url
|
|
436
|
+
"#{live_url}#{API_VERSION}"
|
|
320
437
|
end
|
|
321
438
|
end
|
|
322
439
|
|
|
@@ -324,11 +441,13 @@ module ActiveMerchant #:nodoc:
|
|
|
324
441
|
Base64.strict_encode64("#{@username}:#{@password}")
|
|
325
442
|
end
|
|
326
443
|
|
|
327
|
-
def request_headers
|
|
328
|
-
{
|
|
444
|
+
def request_headers(options)
|
|
445
|
+
headers = {
|
|
329
446
|
'Content-Type' => 'application/json',
|
|
330
447
|
'Authorization' => "Basic #{basic_auth}"
|
|
331
448
|
}
|
|
449
|
+
headers['Idempotency-Key'] = options[:idempotency_key] if options[:idempotency_key]
|
|
450
|
+
headers
|
|
332
451
|
end
|
|
333
452
|
|
|
334
453
|
def success_from(action, response)
|
|
@@ -337,6 +456,8 @@ module ActiveMerchant #:nodoc:
|
|
|
337
456
|
['Authorised', 'Received', 'RedirectShopper'].include?(response['resultCode'])
|
|
338
457
|
when 'capture', 'refund', 'cancel'
|
|
339
458
|
response['response'] == "[#{action}-received]"
|
|
459
|
+
when 'adjustAuthorisation'
|
|
460
|
+
response['response'] == 'Authorised' || response['response'] == '[adjustAuthorisation-received]'
|
|
340
461
|
else
|
|
341
462
|
false
|
|
342
463
|
end
|
|
@@ -375,6 +496,37 @@ module ActiveMerchant #:nodoc:
|
|
|
375
496
|
def error_code_from(response)
|
|
376
497
|
STANDARD_ERROR_CODE_MAPPING[response['errorCode']]
|
|
377
498
|
end
|
|
499
|
+
|
|
500
|
+
def add_browser_info(browser_info, post)
|
|
501
|
+
return unless browser_info
|
|
502
|
+
post[:browserInfo] = {
|
|
503
|
+
acceptHeader: browser_info[:accept_header],
|
|
504
|
+
colorDepth: browser_info[:depth],
|
|
505
|
+
javaEnabled: browser_info[:java],
|
|
506
|
+
language: browser_info[:language],
|
|
507
|
+
screenHeight: browser_info[:height],
|
|
508
|
+
screenWidth: browser_info[:width],
|
|
509
|
+
timeZoneOffset: browser_info[:timezone],
|
|
510
|
+
userAgent: browser_info[:user_agent]
|
|
511
|
+
}
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def unsupported_failure_response(initial_response)
|
|
515
|
+
Response.new(
|
|
516
|
+
false,
|
|
517
|
+
'Recurring transactions are not supported for this card type.',
|
|
518
|
+
initial_response.params,
|
|
519
|
+
authorization: initial_response.authorization,
|
|
520
|
+
test: initial_response.test,
|
|
521
|
+
error_code: initial_response.error_code,
|
|
522
|
+
avs_result: initial_response.avs_result,
|
|
523
|
+
cvv_result: initial_response.cvv_result[:code]
|
|
524
|
+
)
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
def card_not_stored?(response)
|
|
528
|
+
response.authorization ? response.authorization.split('#')[2].nil? : true
|
|
529
|
+
end
|
|
378
530
|
end
|
|
379
531
|
end
|
|
380
532
|
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
module ActiveMerchant #:nodoc:
|
|
4
|
+
module Billing #:nodoc:
|
|
5
|
+
class BamboraApacGateway < Gateway
|
|
6
|
+
self.live_url = 'https://www.bambora.co.nz/interface/api'
|
|
7
|
+
self.test_url = 'https://demo.bambora.co.nz/interface/api'
|
|
8
|
+
|
|
9
|
+
self.supported_countries = ['AU', 'NZ']
|
|
10
|
+
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
|
|
11
|
+
|
|
12
|
+
self.homepage_url = 'http://www.bambora.com/'
|
|
13
|
+
self.display_name = 'Bambora Asia-Pacific'
|
|
14
|
+
|
|
15
|
+
self.money_format = :cents
|
|
16
|
+
|
|
17
|
+
STANDARD_ERROR_CODE_MAPPING = {
|
|
18
|
+
'05' => STANDARD_ERROR_CODE[:card_declined],
|
|
19
|
+
'06' => STANDARD_ERROR_CODE[:processing_error],
|
|
20
|
+
'14' => STANDARD_ERROR_CODE[:invalid_number],
|
|
21
|
+
'54' => STANDARD_ERROR_CODE[:expired_card],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def initialize(options={})
|
|
25
|
+
requires!(options, :username, :password)
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def purchase(money, payment, options={})
|
|
30
|
+
commit('SubmitSinglePayment') do |xml|
|
|
31
|
+
xml.Transaction do
|
|
32
|
+
xml.CustRef options[:order_id]
|
|
33
|
+
add_amount(xml, money)
|
|
34
|
+
xml.TrnType '1'
|
|
35
|
+
add_payment(xml, payment)
|
|
36
|
+
add_credentials(xml, options)
|
|
37
|
+
xml.TrnSource options[:ip]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def authorize(money, payment, options={})
|
|
43
|
+
commit('SubmitSinglePayment') do |xml|
|
|
44
|
+
xml.Transaction do
|
|
45
|
+
xml.CustRef options[:order_id]
|
|
46
|
+
add_amount(xml, money)
|
|
47
|
+
xml.TrnType '2'
|
|
48
|
+
add_payment(xml, payment)
|
|
49
|
+
add_credentials(xml, options)
|
|
50
|
+
xml.TrnSource options[:ip]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def capture(money, authorization, options={})
|
|
56
|
+
commit('SubmitSingleCapture') do |xml|
|
|
57
|
+
xml.Capture do
|
|
58
|
+
xml.Receipt authorization
|
|
59
|
+
add_amount(xml, money)
|
|
60
|
+
add_credentials(xml, options)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def refund(money, authorization, options={})
|
|
66
|
+
commit('SubmitSingleRefund') do |xml|
|
|
67
|
+
xml.Refund do
|
|
68
|
+
xml.Receipt authorization
|
|
69
|
+
add_amount(xml, money)
|
|
70
|
+
add_credentials(xml, options)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def void(money, authorization, options={})
|
|
76
|
+
commit('SubmitSingleVoid') do |xml|
|
|
77
|
+
xml.Void do
|
|
78
|
+
xml.Receipt authorization
|
|
79
|
+
add_amount(xml, money)
|
|
80
|
+
add_credentials(xml, options)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def store(payment, options={})
|
|
86
|
+
commit('TokeniseCreditCard') do |xml|
|
|
87
|
+
xml.TokeniseCreditCard do
|
|
88
|
+
xml.CardNumber payment.number
|
|
89
|
+
xml.ExpM format(payment.month, :two_digits)
|
|
90
|
+
xml.ExpY format(payment.year, :four_digits)
|
|
91
|
+
xml.TokeniseAlgorithmID options[:tokenise_algorithm_id] || 2
|
|
92
|
+
xml.UserName @options[:username]
|
|
93
|
+
xml.Password @options[:password]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def supports_scrubbing?
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def scrub(transcript)
|
|
103
|
+
transcript.
|
|
104
|
+
gsub(%r((<CardNumber>)[^<]+(<))i, '\1[FILTERED]\2').
|
|
105
|
+
gsub(%r((<CVN>)[^<]+(<))i, '\1[FILTERED]\2').
|
|
106
|
+
gsub(%r((<Password>)[^<]+(<))i, '\1[FILTERED]\2')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def add_credentials(xml, options)
|
|
112
|
+
xml.AccountNumber options[:account_number] if options[:account_number]
|
|
113
|
+
xml.Security do
|
|
114
|
+
xml.UserName @options[:username]
|
|
115
|
+
xml.Password @options[:password]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def add_amount(xml, money)
|
|
120
|
+
xml.Amount amount(money)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def add_payment(xml, payment)
|
|
124
|
+
if payment.is_a?(String)
|
|
125
|
+
add_token(xml, payment)
|
|
126
|
+
else
|
|
127
|
+
add_credit_card(xml, payment)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def add_token(xml, payment)
|
|
132
|
+
xml.CreditCard do
|
|
133
|
+
xml.TokeniseAlgorithmID options[:tokenise_algorithm_id] || 2
|
|
134
|
+
xml.CardNumber payment
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def add_credit_card(xml, payment)
|
|
139
|
+
xml.CreditCard :Registered => 'False' do
|
|
140
|
+
xml.CardNumber payment.number
|
|
141
|
+
xml.ExpM format(payment.month, :two_digits)
|
|
142
|
+
xml.ExpY format(payment.year, :four_digits)
|
|
143
|
+
xml.CVN payment.verification_value
|
|
144
|
+
xml.CardHolderName payment.name
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def parse(body)
|
|
149
|
+
element = Nokogiri::XML(body).root.first_element_child.first_element_child
|
|
150
|
+
|
|
151
|
+
response = {}
|
|
152
|
+
doc = Nokogiri::XML(element)
|
|
153
|
+
doc.root.elements.each do |e|
|
|
154
|
+
response[e.name.underscore.to_sym] = e.inner_text
|
|
155
|
+
end
|
|
156
|
+
response
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def commit(action, &block)
|
|
160
|
+
headers = {
|
|
161
|
+
'Content-Type' => 'text/xml; charset=utf-8',
|
|
162
|
+
'SOAPAction' => "http://www.ippayments.com.au/interface/api/#{endpoint(action)}/#{action}"
|
|
163
|
+
}
|
|
164
|
+
response = parse(ssl_post("#{commit_url}/#{endpoint(action)}.asmx", new_submit_xml(action, &block), headers))
|
|
165
|
+
|
|
166
|
+
Response.new(
|
|
167
|
+
success_from(response),
|
|
168
|
+
message_from(response),
|
|
169
|
+
response,
|
|
170
|
+
authorization: authorization_from(response),
|
|
171
|
+
error_code: error_code_from(response),
|
|
172
|
+
test: test?
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def new_submit_xml(action)
|
|
177
|
+
xml = Builder::XmlMarkup.new(indent: 2)
|
|
178
|
+
xml.instruct!
|
|
179
|
+
xml.soap :Envelope, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
|
|
180
|
+
xml.soap :Body do
|
|
181
|
+
xml.__send__(action, 'xmlns' => "http://www.ippayments.com.au/interface/api/#{endpoint(action)}") do
|
|
182
|
+
if action == 'TokeniseCreditCard'
|
|
183
|
+
xml.tokeniseCreditCardXML do
|
|
184
|
+
inner_xml = Builder::XmlMarkup.new(indent: 2)
|
|
185
|
+
yield(inner_xml)
|
|
186
|
+
xml.cdata!(inner_xml.target!)
|
|
187
|
+
end
|
|
188
|
+
else
|
|
189
|
+
xml.trnXML do
|
|
190
|
+
inner_xml = Builder::XmlMarkup.new(indent: 2)
|
|
191
|
+
yield(inner_xml)
|
|
192
|
+
xml.cdata!(inner_xml.target!)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
xml.target!
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def endpoint(action)
|
|
202
|
+
action == 'TokeniseCreditCard' ? 'sipp' : 'dts'
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def commit_url
|
|
206
|
+
test? ? test_url : live_url
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def success_from(response)
|
|
210
|
+
response[:response_code] == '0' || response[:return_value] == '0'
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def error_code_from(response)
|
|
214
|
+
STANDARD_ERROR_CODE_MAPPING[response[:declined_code]]
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def message_from(response)
|
|
218
|
+
success_from(response) ? 'Succeeded' : response[:declined_message]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def authorization_from(response)
|
|
222
|
+
response[:receipt] || response[:token]
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|