adyen_jpiqueras 2.3.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 +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +30 -0
- data/CHANGELOG.md +128 -0
- data/CONTRIBUTING.md +85 -0
- data/Gemfile +11 -0
- data/LICENSE +21 -0
- data/README.md +31 -0
- data/Rakefile +54 -0
- data/adyen_jpiqueras.gemspec +44 -0
- data/config.ru +5 -0
- data/lib/adyen.rb +16 -0
- data/lib/adyen/api.rb +424 -0
- data/lib/adyen/api/cacert.pem +3894 -0
- data/lib/adyen/api/payment_service.rb +374 -0
- data/lib/adyen/api/recurring_service.rb +188 -0
- data/lib/adyen/api/response.rb +61 -0
- data/lib/adyen/api/simple_soap_client.rb +134 -0
- data/lib/adyen/api/templates/payment_service.rb +159 -0
- data/lib/adyen/api/templates/recurring_service.rb +71 -0
- data/lib/adyen/api/test_helpers.rb +133 -0
- data/lib/adyen/api/xml_querier.rb +137 -0
- data/lib/adyen/base.rb +17 -0
- data/lib/adyen/configuration.rb +179 -0
- data/lib/adyen/form.rb +419 -0
- data/lib/adyen/hpp.rb +27 -0
- data/lib/adyen/hpp/request.rb +192 -0
- data/lib/adyen/hpp/response.rb +52 -0
- data/lib/adyen/hpp/signature.rb +34 -0
- data/lib/adyen/matchers.rb +92 -0
- data/lib/adyen/notification_generator.rb +30 -0
- data/lib/adyen/railtie.rb +13 -0
- data/lib/adyen/rest.rb +67 -0
- data/lib/adyen/rest/authorise_payment.rb +234 -0
- data/lib/adyen/rest/authorise_recurring_payment.rb +46 -0
- data/lib/adyen/rest/client.rb +127 -0
- data/lib/adyen/rest/errors.rb +33 -0
- data/lib/adyen/rest/modify_payment.rb +89 -0
- data/lib/adyen/rest/payout.rb +89 -0
- data/lib/adyen/rest/request.rb +104 -0
- data/lib/adyen/rest/response.rb +80 -0
- data/lib/adyen/rest/signature.rb +27 -0
- data/lib/adyen/signature.rb +76 -0
- data/lib/adyen/templates/notification_migration.rb +29 -0
- data/lib/adyen/templates/notification_model.rb +69 -0
- data/lib/adyen/util.rb +147 -0
- data/lib/adyen/version.rb +5 -0
- data/spec/api/api_spec.rb +231 -0
- data/spec/api/payment_service_spec.rb +505 -0
- data/spec/api/recurring_service_spec.rb +236 -0
- data/spec/api/response_spec.rb +59 -0
- data/spec/api/simple_soap_client_spec.rb +133 -0
- data/spec/api/spec_helper.rb +463 -0
- data/spec/api/test_helpers_spec.rb +84 -0
- data/spec/functional/api_spec.rb +117 -0
- data/spec/functional/initializer.rb.ci +3 -0
- data/spec/functional/initializer.rb.sample +3 -0
- data/spec/spec_helper.rb +8 -0
- data/test/form_test.rb +303 -0
- data/test/functional/payment_authorisation_api_test.rb +107 -0
- data/test/functional/payment_modification_api_test.rb +58 -0
- data/test/functional/payout_api_test.rb +93 -0
- data/test/helpers/capybara.rb +12 -0
- data/test/helpers/configure_adyen.rb +6 -0
- data/test/helpers/example_server.rb +136 -0
- data/test/helpers/public/adyen.encrypt.js +679 -0
- data/test/helpers/public/adyen.encrypt.min.js +14 -0
- data/test/helpers/test_cards.rb +20 -0
- data/test/helpers/views/authorized.erb +7 -0
- data/test/helpers/views/hpp.erb +20 -0
- data/test/helpers/views/index.erb +6 -0
- data/test/helpers/views/pay.erb +36 -0
- data/test/helpers/views/redirect_shopper.erb +18 -0
- data/test/hpp/signature_test.rb +37 -0
- data/test/hpp_test.rb +250 -0
- data/test/integration/hpp_integration_test.rb +52 -0
- data/test/integration/payment_using_3d_secure_integration_test.rb +41 -0
- data/test/integration/payment_with_client_side_encryption_integration_test.rb +26 -0
- data/test/rest/signature_test.rb +36 -0
- data/test/rest_list_recurring_details_response_test.rb +22 -0
- data/test/rest_request_test.rb +43 -0
- data/test/rest_response_test.rb +19 -0
- data/test/signature_test.rb +76 -0
- data/test/test_helper.rb +45 -0
- data/test/util_test.rb +78 -0
- data/yard_extensions.rb +16 -0
- metadata +308 -0
@@ -0,0 +1,374 @@
|
|
1
|
+
require 'adyen/api/simple_soap_client'
|
2
|
+
require 'adyen/api/templates/payment_service'
|
3
|
+
|
4
|
+
module Adyen
|
5
|
+
module API
|
6
|
+
# This is the class that maps actions to Adyen’s Payment SOAP service.
|
7
|
+
#
|
8
|
+
# It’s encouraged to use the shortcut methods on the {API} module, which abstracts away the
|
9
|
+
# difference between this service and the {RecurringService}. Henceforth, for extensive
|
10
|
+
# documentation you should look at the {API} documentation.
|
11
|
+
#
|
12
|
+
# The most important difference is that you instantiate a {PaymentService} with the parameters
|
13
|
+
# that are needed for the call that you will eventually make.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# payment = Adyen::API::PaymentService.new({
|
17
|
+
# :reference => invoice.id,
|
18
|
+
# :amount => {
|
19
|
+
# :currency => 'EUR',
|
20
|
+
# :value => invoice.amount,
|
21
|
+
# },
|
22
|
+
# :shopper => {
|
23
|
+
# :email => user.email,
|
24
|
+
# :reference => user.id,
|
25
|
+
# :ip => request.ip,
|
26
|
+
# :statement => 'Invoice number 123456'
|
27
|
+
# },
|
28
|
+
# :card => {
|
29
|
+
# :expiry_month => 12,
|
30
|
+
# :expiry_year => 2012,
|
31
|
+
# :holder_name => 'Simon Hopper',
|
32
|
+
# :number => '4444333322221111',
|
33
|
+
# :cvc => '737'
|
34
|
+
# }
|
35
|
+
# })
|
36
|
+
# response = payment.authorise_payment
|
37
|
+
# response.authorised? # => true
|
38
|
+
#
|
39
|
+
class PaymentService < SimpleSOAPClient
|
40
|
+
# The Adyen Payment SOAP service endpoint uri.
|
41
|
+
ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Payment'
|
42
|
+
|
43
|
+
# @see API.generate_billet
|
44
|
+
def generate_billet
|
45
|
+
make_payment_request(generate_billet_request_body, BilletResponse)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @see API.authorise_payment
|
49
|
+
def authorise_payment
|
50
|
+
make_payment_request(authorise_payment_request_body, AuthorisationResponse)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @see API.authorise_recurring_payment
|
54
|
+
def authorise_recurring_payment
|
55
|
+
make_payment_request(authorise_recurring_payment_request_body, AuthorisationResponse)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @see API.authorise_one_click_payment
|
59
|
+
def authorise_one_click_payment
|
60
|
+
make_payment_request(authorise_one_click_payment_request_body, AuthorisationResponse)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @see API.capture_payment
|
64
|
+
def capture
|
65
|
+
make_payment_request(capture_request_body, CaptureResponse)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @see API.refund_payment
|
69
|
+
def refund
|
70
|
+
make_payment_request(refund_request_body, RefundResponse)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @see API.cancel_payment
|
74
|
+
def cancel
|
75
|
+
make_payment_request(cancel_request_body, CancelResponse)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @see API.cancel_or_refund_payment
|
79
|
+
def cancel_or_refund
|
80
|
+
make_payment_request(cancel_or_refund_request_body, CancelOrRefundResponse)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def make_payment_request(data, response_class)
|
86
|
+
call_webservice_action('authorise', data, response_class)
|
87
|
+
end
|
88
|
+
|
89
|
+
def authorise_payment_request_body
|
90
|
+
content = card_partial
|
91
|
+
if @params[:recurring]
|
92
|
+
validate_parameters!(:shopper => [:email, :reference])
|
93
|
+
content << ENABLE_RECURRING_CONTRACTS_PARTIAL
|
94
|
+
end
|
95
|
+
payment_request_body(content)
|
96
|
+
end
|
97
|
+
|
98
|
+
def authorise_recurring_payment_request_body
|
99
|
+
validate_parameters!(:shopper => [:email, :reference])
|
100
|
+
content = RECURRING_PAYMENT_BODY_PARTIAL % (@params[:recurring_detail_reference] || 'LATEST')
|
101
|
+
payment_request_body(content)
|
102
|
+
end
|
103
|
+
|
104
|
+
def authorise_one_click_payment_request_body
|
105
|
+
validate_parameters!(:recurring_detail_reference,
|
106
|
+
:shopper => [:email, :reference])
|
107
|
+
content = one_click_card_partial
|
108
|
+
content << ONE_CLICK_PAYMENT_BODY_PARTIAL % [@params[:recurring_detail_reference]]
|
109
|
+
payment_request_body(content)
|
110
|
+
end
|
111
|
+
|
112
|
+
def payment_request_body(content)
|
113
|
+
validate_parameters!(:merchant_account, :reference, :amount => [:currency, :value])
|
114
|
+
content << amount_partial
|
115
|
+
content << installments_partial if @params[:installments]
|
116
|
+
content << shopper_partial if @params[:shopper]
|
117
|
+
content << fraud_offset_partial if @params[:fraud_offset]
|
118
|
+
content << capture_delay_partial if @params[:instant_capture]
|
119
|
+
LAYOUT % [@params[:merchant_account], @params[:reference], content]
|
120
|
+
end
|
121
|
+
|
122
|
+
def generate_billet_request_body
|
123
|
+
validate_parameters!(:merchant_account, :reference, :amount => [:currency, :value])
|
124
|
+
content = amount_partial
|
125
|
+
content << social_security_number_partial if @params[:social_security_number]
|
126
|
+
content << shopper_name_partial if @params[:shopper_name]
|
127
|
+
content << delivery_date_partial if @params[:delivery_date]
|
128
|
+
content << selected_brand_partial if @params[:selected_brand]
|
129
|
+
LAYOUT % [@params[:merchant_account], @params[:reference], content]
|
130
|
+
end
|
131
|
+
|
132
|
+
def capture_request_body
|
133
|
+
CAPTURE_LAYOUT % capture_and_refund_params
|
134
|
+
end
|
135
|
+
|
136
|
+
def refund_request_body
|
137
|
+
REFUND_LAYOUT % capture_and_refund_params
|
138
|
+
end
|
139
|
+
|
140
|
+
def cancel_or_refund_request_body
|
141
|
+
validate_parameters!(:merchant_account, :psp_reference)
|
142
|
+
CANCEL_OR_REFUND_LAYOUT % [@params[:merchant_account], @params[:psp_reference]]
|
143
|
+
end
|
144
|
+
|
145
|
+
def cancel_request_body
|
146
|
+
validate_parameters!(:merchant_account, :psp_reference)
|
147
|
+
CANCEL_LAYOUT % [@params[:merchant_account], @params[:psp_reference]]
|
148
|
+
end
|
149
|
+
|
150
|
+
def capture_and_refund_params
|
151
|
+
validate_parameters!(:merchant_account, :psp_reference, :amount => [:currency, :value])
|
152
|
+
[@params[:merchant_account], @params[:psp_reference], *@params[:amount].values_at(:currency, :value)]
|
153
|
+
end
|
154
|
+
|
155
|
+
def amount_partial
|
156
|
+
AMOUNT_PARTIAL % @params[:amount].values_at(:currency, :value)
|
157
|
+
end
|
158
|
+
|
159
|
+
def one_click_card_partial
|
160
|
+
if @params[:card] && @params[:card][:encrypted] && @params[:card][:encrypted][:json]
|
161
|
+
ENCRYPTED_CARD_PARTIAL % [@params[:card][:encrypted][:json]]
|
162
|
+
else
|
163
|
+
validate_parameters!(:card => [:cvc])
|
164
|
+
card = @params[:card].values_at(:cvc)
|
165
|
+
ONE_CLICK_CARD_PARTIAL % card
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def shopper_name_partial
|
170
|
+
SHOPPER_NAME_PARTIAL % @params[:shopper_name].values_at(:first_name, :last_name)
|
171
|
+
end
|
172
|
+
|
173
|
+
def card_partial
|
174
|
+
if @params[:card] && @params[:card][:encrypted] && @params[:card][:encrypted][:json]
|
175
|
+
ENCRYPTED_CARD_PARTIAL % [@params[:card][:encrypted][:json]]
|
176
|
+
else
|
177
|
+
validate_parameters!(:card => [:holder_name, :number, :expiry_year, :expiry_month])
|
178
|
+
card = @params[:card].values_at(:holder_name, :number, :expiry_year)
|
179
|
+
card << @params[:card][:expiry_month].to_i
|
180
|
+
card << (['', nil].include?(@params[:card][:cvc]) ? '' : (CARD_CVC_PARTIAL % @params[:card][:cvc]))
|
181
|
+
CARD_PARTIAL % card
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def installments_partial
|
186
|
+
if @params[:installments] && @params[:installments][:value]
|
187
|
+
INSTALLMENTS_PARTIAL % @params[:installments].values_at(:value)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def social_security_number_partial
|
192
|
+
if @params[:social_security_number]
|
193
|
+
SOCIAL_SECURITY_NUMBER_PARTIAL % @params[:social_security_number]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def selected_brand_partial
|
198
|
+
if @params[:selected_brand]
|
199
|
+
SELECTED_BRAND_PARTIAL % @params[:selected_brand]
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def delivery_date_partial
|
204
|
+
if @params[:delivery_date]
|
205
|
+
DELIVERY_DATE_PARTIAL % @params[:delivery_date]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def shopper_partial
|
210
|
+
@params[:shopper].map { |k, v| SHOPPER_PARTIALS[k] % v }.join("\n")
|
211
|
+
end
|
212
|
+
|
213
|
+
def fraud_offset_partial
|
214
|
+
validate_parameters!(:fraud_offset)
|
215
|
+
FRAUD_OFFSET_PARTIAL % @params[:fraud_offset]
|
216
|
+
end
|
217
|
+
|
218
|
+
def capture_delay_partial(delay = 0)
|
219
|
+
CAPTURE_DELAY_PARTIAL % delay
|
220
|
+
end
|
221
|
+
|
222
|
+
class BilletResponse < Response
|
223
|
+
RECEIVED = "Received"
|
224
|
+
|
225
|
+
response_attrs :result_code, :billet_url, :psp_reference
|
226
|
+
|
227
|
+
def success?
|
228
|
+
super && params[:result_code] == RECEIVED
|
229
|
+
end
|
230
|
+
|
231
|
+
def params
|
232
|
+
@params ||= xml_querier.xpath('//payment:authoriseResponse/payment:paymentResult') do |result|
|
233
|
+
{
|
234
|
+
:psp_reference => result.text('./payment:pspReference'),
|
235
|
+
:result_code => result_code = result.text('./payment:resultCode'),
|
236
|
+
:billet_url => (result_code == RECEIVED) ? result.children[0].children[0].children[1].text : ""
|
237
|
+
}
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def invalid_request?
|
242
|
+
!fault_message.nil?
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
class AuthorisationResponse < Response
|
247
|
+
ERRORS = {
|
248
|
+
"validation 101 Invalid card number" => [:number, 'is not a valid creditcard number'],
|
249
|
+
"validation 103 CVC is not the right length" => [:cvc, 'is not the right length'],
|
250
|
+
"validation 128 Card Holder Missing" => [:holder_name, "can't be blank"],
|
251
|
+
"validation Couldn't parse expiry year" => [:expiry_year, 'could not be recognized'],
|
252
|
+
"validation Expiry month should be between 1 and 12 inclusive" => [:expiry_month, 'could not be recognized'],
|
253
|
+
}
|
254
|
+
|
255
|
+
AUTHORISED = 'Authorised'
|
256
|
+
REFUSED = 'Refused'
|
257
|
+
|
258
|
+
response_attrs :result_code, :auth_code, :refusal_reason, :psp_reference,
|
259
|
+
:additional_data
|
260
|
+
|
261
|
+
def success?
|
262
|
+
super && params[:result_code] == AUTHORISED
|
263
|
+
end
|
264
|
+
|
265
|
+
def refused?
|
266
|
+
params[:result_code] == REFUSED
|
267
|
+
end
|
268
|
+
|
269
|
+
alias_method :authorised?, :success?
|
270
|
+
alias_method :authorized?, :success?
|
271
|
+
|
272
|
+
# @return [Boolean] Returns whether or not the request was valid.
|
273
|
+
def invalid_request?
|
274
|
+
!fault_message.nil?
|
275
|
+
end
|
276
|
+
|
277
|
+
# In the case of a validation error, or SOAP fault message, this method will return an
|
278
|
+
# array describing what attribute failed validation and the accompanying message. If the
|
279
|
+
# errors is not of the common user validation errors, then the attribute is +:base+ and the
|
280
|
+
# full original message is returned.
|
281
|
+
#
|
282
|
+
# An optional +prefix+ can be given so you can seamlessly integrate this in your
|
283
|
+
# ActiveRecord model and copy over errors.
|
284
|
+
#
|
285
|
+
# @param [String,Symbol] prefix A string that should be used to prefix the error key.
|
286
|
+
# @return [Array<Symbol, String>] A name-message pair of the attribute with an error.
|
287
|
+
def error(prefix = nil)
|
288
|
+
if error = ERRORS[fault_message]
|
289
|
+
prefix ? ["#{prefix}_#{error[0]}".to_sym, error[1]] : error
|
290
|
+
elsif fault_message
|
291
|
+
[:base, fault_message]
|
292
|
+
elsif refused?
|
293
|
+
[:base, 'Transaction was refused.']
|
294
|
+
else
|
295
|
+
[:base, 'Transaction failed for unkown reasons.']
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def params
|
300
|
+
@params ||= xml_querier.xpath('//payment:authoriseResponse/payment:paymentResult') do |result|
|
301
|
+
{
|
302
|
+
:psp_reference => result.text('./payment:pspReference'),
|
303
|
+
:result_code => result.text('./payment:resultCode'),
|
304
|
+
:auth_code => result.text('./payment:authCode'),
|
305
|
+
:additional_data => parse_additional_data(result.xpath('.//payment:additionalData')),
|
306
|
+
:refusal_reason => (invalid_request? ? fault_message : result.text('./payment:refusalReason'))
|
307
|
+
}
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
private
|
312
|
+
def parse_additional_data(xpath)
|
313
|
+
if xpath.empty?
|
314
|
+
{}
|
315
|
+
else
|
316
|
+
results = {}
|
317
|
+
|
318
|
+
xpath.map do |node|
|
319
|
+
key = node.text('./payment:entry/payment:key')
|
320
|
+
value = node.text('./payment:entry/payment:value')
|
321
|
+
results[key] = value unless key.empty?
|
322
|
+
end
|
323
|
+
|
324
|
+
results
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
class ModificationResponse < Response
|
330
|
+
class << self
|
331
|
+
# @private
|
332
|
+
attr_accessor :request_received_value, :base_xpath
|
333
|
+
end
|
334
|
+
|
335
|
+
response_attrs :psp_reference, :response
|
336
|
+
|
337
|
+
# This only returns whether or not the request has been successfully received. Check the
|
338
|
+
# subsequent notification to see if the payment was actually mutated.
|
339
|
+
def success?
|
340
|
+
super && params[:response] == self.class.request_received_value
|
341
|
+
end
|
342
|
+
|
343
|
+
def params
|
344
|
+
@params ||= xml_querier.xpath(self.class.base_xpath) do |result|
|
345
|
+
{
|
346
|
+
:psp_reference => result.text('./payment:pspReference'),
|
347
|
+
:response => result.text('./payment:response')
|
348
|
+
}
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
class CaptureResponse < ModificationResponse
|
354
|
+
self.request_received_value = '[capture-received]'
|
355
|
+
self.base_xpath = '//payment:captureResponse/payment:captureResult'
|
356
|
+
end
|
357
|
+
|
358
|
+
class RefundResponse < ModificationResponse
|
359
|
+
self.request_received_value = '[refund-received]'
|
360
|
+
self.base_xpath = '//payment:refundResponse/payment:refundResult'
|
361
|
+
end
|
362
|
+
|
363
|
+
class CancelResponse < ModificationResponse
|
364
|
+
self.request_received_value = '[cancel-received]'
|
365
|
+
self.base_xpath = '//payment:cancelResponse/payment:cancelResult'
|
366
|
+
end
|
367
|
+
|
368
|
+
class CancelOrRefundResponse < ModificationResponse
|
369
|
+
self.request_received_value = '[cancelOrRefund-received]'
|
370
|
+
self.base_xpath = '//payment:cancelOrRefundResponse/payment:cancelOrRefundResult'
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'adyen/api/simple_soap_client'
|
2
|
+
require 'adyen/api/templates/recurring_service'
|
3
|
+
|
4
|
+
module Adyen
|
5
|
+
module API
|
6
|
+
# This is the class that maps actions to Adyen’s Recurring SOAP service.
|
7
|
+
#
|
8
|
+
# It’s encouraged to use the shortcut methods on the {API} module, which abstracts away the
|
9
|
+
# difference between this service and the {PaymentService}. Henceforth, for extensive
|
10
|
+
# documentation you should look at the {API} documentation.
|
11
|
+
#
|
12
|
+
# The most important difference is that you instantiate a {RecurringService} with the parameters
|
13
|
+
# that are needed for the call that you will eventually make.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# recurring = Adyen::API::RecurringService.new(:shopper => { :reference => user.id })
|
17
|
+
# response = recurring.disable
|
18
|
+
# response.success? # => true
|
19
|
+
#
|
20
|
+
class RecurringService < SimpleSOAPClient
|
21
|
+
# The Adyen Recurring SOAP service endpoint uri.
|
22
|
+
ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Recurring'
|
23
|
+
|
24
|
+
# @see API.list_recurring_details
|
25
|
+
def list
|
26
|
+
call_webservice_action('listRecurringDetails', list_request_body, ListResponse)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @see API.disable_recurring_contract
|
30
|
+
def disable
|
31
|
+
call_webservice_action('disable', disable_request_body, DisableResponse)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @see API.store_recurring_token
|
35
|
+
def store_token
|
36
|
+
call_webservice_action('storeToken', store_token_request_body, StoreTokenResponse)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# The card's CVC isn't needed when tokenising details, so insert `nil'.
|
42
|
+
def card_partial
|
43
|
+
validate_parameters!(:card => [:holder_name, :number, :expiry_year, :expiry_month])
|
44
|
+
card = @params[:card].values_at(:holder_name, :number, :cvc, :expiry_year)
|
45
|
+
card << @params[:card][:expiry_month].to_i
|
46
|
+
CARD_PARTIAL % card
|
47
|
+
end
|
48
|
+
|
49
|
+
ELV_ATTRS = [:bank_location, :bank_name, :bank_location_id, :holder_name, :number]
|
50
|
+
# The ELV - (Elektronisches Lastschriftverfahren) does not require bank_location, so insert 'nil'.
|
51
|
+
def elv_partial
|
52
|
+
validate_parameters!(:elv => ELV_ATTRS)
|
53
|
+
elv = @params[:elv].values_at(*ELV_ATTRS)
|
54
|
+
ELV_PARTIAL % elv
|
55
|
+
end
|
56
|
+
|
57
|
+
def list_request_body
|
58
|
+
validate_parameters!(:merchant_account, :shopper => [:reference])
|
59
|
+
LIST_LAYOUT % [@params[:merchant_account], @params[:shopper][:reference]]
|
60
|
+
end
|
61
|
+
|
62
|
+
def disable_request_body
|
63
|
+
validate_parameters!(:merchant_account, :shopper => [:reference])
|
64
|
+
if reference = @params[:recurring_detail_reference]
|
65
|
+
reference = RECURRING_DETAIL_PARTIAL % reference
|
66
|
+
end
|
67
|
+
DISABLE_LAYOUT % [@params[:merchant_account], @params[:shopper][:reference], reference || '']
|
68
|
+
end
|
69
|
+
|
70
|
+
def store_token_request_body
|
71
|
+
validate_parameters!(:merchant_account, :shopper => [:email, :reference])
|
72
|
+
content = []
|
73
|
+
content << card_partial unless @params[:card].nil?
|
74
|
+
content << elv_partial unless @params[:elv].nil?
|
75
|
+
raise ArgumentError, "The required parameter 'card' or 'elv' is missing." if content.empty?
|
76
|
+
STORE_TOKEN_LAYOUT % [@params[:merchant_account], @params[:shopper][:reference], @params[:shopper][:email], content.join]
|
77
|
+
end
|
78
|
+
|
79
|
+
class DisableResponse < Response
|
80
|
+
DISABLED_RESPONSES = %w{ [detail-successfully-disabled] [all-details-successfully-disabled] }
|
81
|
+
|
82
|
+
response_attrs :response
|
83
|
+
|
84
|
+
def success?
|
85
|
+
super && DISABLED_RESPONSES.include?(params[:response])
|
86
|
+
end
|
87
|
+
|
88
|
+
alias_method :disabled?, :success?
|
89
|
+
|
90
|
+
def params
|
91
|
+
@params ||= { :response => xml_querier.text('//recurring:disableResponse/recurring:result/recurring:response') }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class ListResponse < Response
|
96
|
+
response_attrs :details, :last_known_shopper_email, :shopper_reference, :creation_date
|
97
|
+
|
98
|
+
def references
|
99
|
+
details ? details.map { |d| d[:recurring_detail_reference] } : []
|
100
|
+
end
|
101
|
+
|
102
|
+
def params
|
103
|
+
@params ||= xml_querier.xpath('//recurring:listRecurringDetailsResponse/recurring:result') do |result|
|
104
|
+
details = result.xpath('.//recurring:RecurringDetail')
|
105
|
+
details.empty? ? {} : {
|
106
|
+
:creation_date => DateTime.parse(result.text('./recurring:creationDate')),
|
107
|
+
:details => details.map { |node| parse_recurring_detail(node) },
|
108
|
+
:last_known_shopper_email => result.text('./recurring:lastKnownShopperEmail'),
|
109
|
+
:shopper_reference => result.text('./recurring:shopperReference')
|
110
|
+
}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def parse_recurring_detail(node)
|
117
|
+
result = {
|
118
|
+
:recurring_detail_reference => node.text('./recurring:recurringDetailReference'),
|
119
|
+
:variant => node.text('./recurring:variant'),
|
120
|
+
:creation_date => DateTime.parse(node.text('./recurring:creationDate'))
|
121
|
+
}
|
122
|
+
|
123
|
+
card = node.xpath('./recurring:card')
|
124
|
+
elv = node.xpath('./recurring:elv')
|
125
|
+
bank = node.xpath('./recurring:bank')
|
126
|
+
|
127
|
+
if !card.children.empty?
|
128
|
+
result[:card] = parse_card_details(card)
|
129
|
+
elsif !elv.children.empty?
|
130
|
+
result[:elv] = parse_elv_details(elv)
|
131
|
+
else
|
132
|
+
result[:bank] = parse_bank_details(bank)
|
133
|
+
end
|
134
|
+
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
def parse_card_details(card)
|
139
|
+
{
|
140
|
+
:expiry_date => Date.new(card.text('./payment:expiryYear').to_i, card.text('./payment:expiryMonth').to_i, -1),
|
141
|
+
:holder_name => card.text('./payment:holderName'),
|
142
|
+
:number => card.text('./payment:number')
|
143
|
+
}
|
144
|
+
end
|
145
|
+
|
146
|
+
def parse_elv_details(elv)
|
147
|
+
{
|
148
|
+
:holder_name => elv.text('./payment:accountHolderName'),
|
149
|
+
:number => elv.text('./payment:bankAccountNumber'),
|
150
|
+
:bank_location => elv.text('./payment:bankLocation'),
|
151
|
+
:bank_location_id => elv.text('./payment:bankLocationId'),
|
152
|
+
:bank_name => elv.text('./payment:bankName')
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
def parse_bank_details(bank)
|
157
|
+
{
|
158
|
+
:number => bank.text('./payment:bankAccountNumber'),
|
159
|
+
:bank_location_id => bank.text('./payment:bankLocationId'),
|
160
|
+
:bank_name => bank.text('./payment:bankName'),
|
161
|
+
:bic => bank.text('./payment:bic'),
|
162
|
+
:country_code => bank.text('./payment:countryCode'),
|
163
|
+
:iban => bank.text('./payment:iban'),
|
164
|
+
:holder_name => bank.text('./payment:ownerName')
|
165
|
+
}
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class StoreTokenResponse < Response
|
170
|
+
response_attrs :response, :recurring_detail_reference
|
171
|
+
|
172
|
+
def success?
|
173
|
+
super && response == 'Success'
|
174
|
+
end
|
175
|
+
|
176
|
+
alias_method :stored?, :success?
|
177
|
+
|
178
|
+
def params
|
179
|
+
@params ||= {
|
180
|
+
:response => xml_querier.text('//recurring:storeTokenResponse/recurring:result/recurring:result'),
|
181
|
+
:reference => xml_querier.text('//recurring:storeTokenResponse/recurring:result/recurring:rechargeReference'),
|
182
|
+
:recurring_detail_reference => xml_querier.text('//recurring:storeTokenResponse/recurring:result/recurring:recurringDetailReference')
|
183
|
+
}
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|