adyen_jpiqueras 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +30 -0
  4. data/CHANGELOG.md +128 -0
  5. data/CONTRIBUTING.md +85 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE +21 -0
  8. data/README.md +31 -0
  9. data/Rakefile +54 -0
  10. data/adyen_jpiqueras.gemspec +44 -0
  11. data/config.ru +5 -0
  12. data/lib/adyen.rb +16 -0
  13. data/lib/adyen/api.rb +424 -0
  14. data/lib/adyen/api/cacert.pem +3894 -0
  15. data/lib/adyen/api/payment_service.rb +374 -0
  16. data/lib/adyen/api/recurring_service.rb +188 -0
  17. data/lib/adyen/api/response.rb +61 -0
  18. data/lib/adyen/api/simple_soap_client.rb +134 -0
  19. data/lib/adyen/api/templates/payment_service.rb +159 -0
  20. data/lib/adyen/api/templates/recurring_service.rb +71 -0
  21. data/lib/adyen/api/test_helpers.rb +133 -0
  22. data/lib/adyen/api/xml_querier.rb +137 -0
  23. data/lib/adyen/base.rb +17 -0
  24. data/lib/adyen/configuration.rb +179 -0
  25. data/lib/adyen/form.rb +419 -0
  26. data/lib/adyen/hpp.rb +27 -0
  27. data/lib/adyen/hpp/request.rb +192 -0
  28. data/lib/adyen/hpp/response.rb +52 -0
  29. data/lib/adyen/hpp/signature.rb +34 -0
  30. data/lib/adyen/matchers.rb +92 -0
  31. data/lib/adyen/notification_generator.rb +30 -0
  32. data/lib/adyen/railtie.rb +13 -0
  33. data/lib/adyen/rest.rb +67 -0
  34. data/lib/adyen/rest/authorise_payment.rb +234 -0
  35. data/lib/adyen/rest/authorise_recurring_payment.rb +46 -0
  36. data/lib/adyen/rest/client.rb +127 -0
  37. data/lib/adyen/rest/errors.rb +33 -0
  38. data/lib/adyen/rest/modify_payment.rb +89 -0
  39. data/lib/adyen/rest/payout.rb +89 -0
  40. data/lib/adyen/rest/request.rb +104 -0
  41. data/lib/adyen/rest/response.rb +80 -0
  42. data/lib/adyen/rest/signature.rb +27 -0
  43. data/lib/adyen/signature.rb +76 -0
  44. data/lib/adyen/templates/notification_migration.rb +29 -0
  45. data/lib/adyen/templates/notification_model.rb +69 -0
  46. data/lib/adyen/util.rb +147 -0
  47. data/lib/adyen/version.rb +5 -0
  48. data/spec/api/api_spec.rb +231 -0
  49. data/spec/api/payment_service_spec.rb +505 -0
  50. data/spec/api/recurring_service_spec.rb +236 -0
  51. data/spec/api/response_spec.rb +59 -0
  52. data/spec/api/simple_soap_client_spec.rb +133 -0
  53. data/spec/api/spec_helper.rb +463 -0
  54. data/spec/api/test_helpers_spec.rb +84 -0
  55. data/spec/functional/api_spec.rb +117 -0
  56. data/spec/functional/initializer.rb.ci +3 -0
  57. data/spec/functional/initializer.rb.sample +3 -0
  58. data/spec/spec_helper.rb +8 -0
  59. data/test/form_test.rb +303 -0
  60. data/test/functional/payment_authorisation_api_test.rb +107 -0
  61. data/test/functional/payment_modification_api_test.rb +58 -0
  62. data/test/functional/payout_api_test.rb +93 -0
  63. data/test/helpers/capybara.rb +12 -0
  64. data/test/helpers/configure_adyen.rb +6 -0
  65. data/test/helpers/example_server.rb +136 -0
  66. data/test/helpers/public/adyen.encrypt.js +679 -0
  67. data/test/helpers/public/adyen.encrypt.min.js +14 -0
  68. data/test/helpers/test_cards.rb +20 -0
  69. data/test/helpers/views/authorized.erb +7 -0
  70. data/test/helpers/views/hpp.erb +20 -0
  71. data/test/helpers/views/index.erb +6 -0
  72. data/test/helpers/views/pay.erb +36 -0
  73. data/test/helpers/views/redirect_shopper.erb +18 -0
  74. data/test/hpp/signature_test.rb +37 -0
  75. data/test/hpp_test.rb +250 -0
  76. data/test/integration/hpp_integration_test.rb +52 -0
  77. data/test/integration/payment_using_3d_secure_integration_test.rb +41 -0
  78. data/test/integration/payment_with_client_side_encryption_integration_test.rb +26 -0
  79. data/test/rest/signature_test.rb +36 -0
  80. data/test/rest_list_recurring_details_response_test.rb +22 -0
  81. data/test/rest_request_test.rb +43 -0
  82. data/test/rest_response_test.rb +19 -0
  83. data/test/signature_test.rb +76 -0
  84. data/test/test_helper.rb +45 -0
  85. data/test/util_test.rb +78 -0
  86. data/yard_extensions.rb +16 -0
  87. 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