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.
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