adyen 1.3.1 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,19 @@
1
1
  language: ruby
2
+ script: bundle exec rake
2
3
  rvm:
3
4
  - 1.8.7
4
5
  - 1.9.2
5
6
  - 1.9.3
6
7
  - ruby-head
7
8
  - ree
8
- - rbx
9
- - jruby
9
+ - rbx-18mode
10
+ - rbx-19mode
11
+ - jruby-18mode
12
+ - jruby-19mode
13
+ - jruby-head
14
+ matrix:
15
+ allow_failures:
16
+ - rvm: rbx-19mode
17
+ - rvm: jruby-19mode
18
+ - rvm: jruby-head
19
+ - rvm: ruby-head
@@ -1,14 +1,14 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'adyen'
3
- s.version = "1.3.1"
4
- s.date = "2012-02-20"
3
+ s.version = "1.3.2"
4
+ s.date = "2013-01-14"
5
5
 
6
6
  s.summary = "Integrate Adyen payment services in your Ruby on Rails application."
7
7
  s.description = <<-EOS
8
8
  Package to simplify including the Adyen payments services into a Ruby on Rails application.
9
9
  The package provides functionality to create payment forms, handling and storing notifications
10
10
  sent by Adyen and consuming the SOAP services provided by Adyen. Moreover, it contains helper
11
- methods, mocks and matchers to simpify writing tests/specsfor your code.
11
+ methods, mocks and matchers to simpify writing tests/specs for your code.
12
12
  EOS
13
13
 
14
14
  s.authors = ['Willem van Bergen', 'Michel Barbosa', 'Stefan Borsje', 'Eloy Duran']
@@ -12,7 +12,7 @@ module Adyen
12
12
  # Version constant for the Adyen plugin.
13
13
  # DO NOT CHANGE THIS VALUE BY HAND. It will be updated automatically by
14
14
  # the gem:release rake task.
15
- VERSION = "1.3.1"
15
+ VERSION = "1.3.2"
16
16
 
17
17
  # @return [Configuration] The configuration singleton.
18
18
  def self.configuration
@@ -20,8 +20,14 @@ module Adyen
20
20
  # * {RecurringService} - for handling recurring contract details.
21
21
  #
22
22
  # *However*, direct use of these classes is discouraged in favor of the shortcut methods defined
23
- # on the API module. These methods *expect* that you set the :merchant_account as a default
24
- # parameter.
23
+ # on the API module. These methods *require* that you set the :merchant_account *beforehand* as
24
+ # a default parameter passed in all API calls:
25
+ #
26
+ # Adyen.configuration.default_api_params[:merchant_account] = 'MerchantAccount'
27
+ #
28
+ # For Rails apps, you can also set it `application.rb` config block, like this:
29
+ #
30
+ # config.adyen.default_api_params = { :merchant_account => 'MerchantAccount' }
25
31
  #
26
32
  # Note that you'll need an Adyen notification PSP reference for some of the calls. Because of
27
33
  # this, store all notifications that Adyen sends to you. Moreover, the responses to these calls
@@ -96,7 +96,7 @@ module Adyen
96
96
  response_attrs :details, :last_known_shopper_email, :shopper_reference, :creation_date
97
97
 
98
98
  def references
99
- details.map { |d| d[:recurring_detail_reference] }
99
+ details ? details.map { |d| d[:recurring_detail_reference] } : []
100
100
  end
101
101
 
102
102
  def params
@@ -113,7 +113,6 @@ module Adyen
113
113
 
114
114
  private
115
115
 
116
- # @todo add support for elv
117
116
  def parse_recurring_detail(node)
118
117
  result = {
119
118
  :recurring_detail_reference => node.text('./recurring:recurringDetailReference'),
@@ -146,11 +145,11 @@ module Adyen
146
145
 
147
146
  def parse_elv_details(elv)
148
147
  {
149
- :holder_name => bank.text('./payment:accountHolderName'),
150
- :number => bank.text('./payment:bankAccountNumber'),
151
- :bank_location => bank.text('./payment:bankLocation'),
152
- :bank_location_id => bank.text('./payment:bankLocationId'),
153
- :bank_name => bank.text('./payment:bankName')
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')
154
153
  }
155
154
  end
156
155
 
@@ -36,11 +36,11 @@ class Adyen::Configuration
36
36
  elsif defined?(::RAILS_ENV)
37
37
  ::RAILS_ENV.to_s
38
38
  end
39
-
39
+
40
40
  LIVE_RAILS_ENVIRONMENTS.include?(rails_env) ? 'live' : 'test'
41
41
  end
42
-
43
- # The payment flow URL that’s used to choose the payement process
42
+
43
+ # The payment flow page type that’s used to choose the payment process
44
44
  #
45
45
  # @example
46
46
  # Adyen.configuration.payment_flow = :select
@@ -49,7 +49,15 @@ class Adyen::Configuration
49
49
  #
50
50
  # @return [String]
51
51
  attr_accessor :payment_flow
52
-
52
+
53
+ # The payment flow domain that’s used to choose the payment process
54
+ #
55
+ # @example
56
+ # Adyen.configuration.payment_flow_domain = checkout.mydomain.com
57
+ #
58
+ # @return [String]
59
+ attr_accessor :payment_flow_domain
60
+
53
61
  # The username that’s used to authenticate for the Adyen SOAP services. It should look
54
62
  # something like ‘+ws@AndyInc.SuperShop+’
55
63
  #
@@ -78,13 +86,13 @@ class Adyen::Configuration
78
86
  #
79
87
  # @return [Hash]
80
88
  attr_accessor :default_form_params
81
-
82
- # Username that's set in Notification settings screen in Adyen PSP system and used by notification service to
89
+
90
+ # Username that's set in Notification settings screen in Adyen PSP system and used by notification service to
83
91
  # authenticate instant payment notification requests.
84
92
  #
85
93
  # @return [String]
86
94
  attr_accessor :ipn_username
87
-
95
+
88
96
  # Password used to authenticate notification requests together with '+ipn_username+' configuration attribute.
89
97
  #
90
98
  # @return [String]
@@ -98,10 +106,10 @@ class Adyen::Configuration
98
106
  #
99
107
  # @return [Hash] The hash of registered skins.
100
108
  attr_reader :form_skins
101
-
109
+
102
110
  # Sets the registered skins.
103
111
  #
104
- # @param [Hash<Symbol, Hash>] hash A hash with the skin name as key and the skin parameter hash
112
+ # @param [Hash<Symbol, Hash>] hash A hash with the skin name as key and the skin parameter hash
105
113
  # (which should include +:skin_code+ and +:shared_secret+) as value.
106
114
  #
107
115
  # @see Adyen::Configuration.register_form_skin
@@ -125,8 +133,8 @@ class Adyen::Configuration
125
133
  # @param [Symbol] name The name of the skin.
126
134
  # @param [String] skin_code The skin code for this skin, as defined by Adyen.
127
135
  # @param [String] shared_secret The shared secret used for signature calculation.
128
- def register_form_skin(name, skin_code, shared_secret)
129
- @form_skins[name.to_sym] = { :name => name.to_sym, :skin_code => skin_code, :shared_secret => shared_secret }
136
+ def register_form_skin(name, skin_code, shared_secret, default_form_params = {})
137
+ @form_skins[name.to_sym] = { :name => name.to_sym, :skin_code => skin_code, :shared_secret => shared_secret, :default_form_params => default_form_params}
130
138
  end
131
139
 
132
140
  # Returns a skin information by name.
@@ -2,8 +2,8 @@ require 'cgi'
2
2
 
3
3
  module Adyen
4
4
 
5
- # The Adyen::Form module contains all functionality that is used to send payment requests
6
- # to the Adyen payment system, using either a HTML form (see {Adyen::Form.hidden_fields})
5
+ # The Adyen::Form module contains all functionality that is used to send payment requests
6
+ # to the Adyen payment system, using either a HTML form (see {Adyen::Form.hidden_fields})
7
7
  # or a HTTP redirect (see {Adyen::Form.redirect_url}).
8
8
  #
9
9
  # Moreover, this module contains the method {Adyen::Form.redirect_signature_check} to
@@ -12,7 +12,7 @@ module Adyen
12
12
  #
13
13
  # You can use different skins in Adyen to define different payment environments. You can
14
14
  # register these skins under a custom name in the module. The other methods will automatically
15
- # use this information (i.e. the skin code and the shared secret) if it is available.
15
+ # use this information (i.e. the skin code and the shared secret) if it is available.
16
16
  # Otherwise, you have to provide it yourself for every method call you make. See
17
17
  # {Adyen::Configuration#register_form_skin} for more information.
18
18
  #
@@ -27,22 +27,41 @@ module Adyen
27
27
  # ADYEN FORM URL
28
28
  ######################################################
29
29
 
30
+ # The DOMAIN of the Adyen payment system that still requires the current
31
+ # Adyen enviroment.
32
+ ACTION_DOMAIN = "%s.adyen.com"
33
+
30
34
  # The URL of the Adyen payment system that still requires the current
31
- # Adyen enviroment and payment flow to be filled.
32
- ACTION_URL = "https://%s.adyen.com/hpp/%s.shtml"
35
+ # domain and payment flow to be filled.
36
+ ACTION_URL = "https://%s/hpp/%s.shtml"
37
+
38
+ # Returns the DOMAIN of the Adyen payment system, adjusted for an Adyen environment.
39
+ #
40
+ # @param [String] environment The Adyen environment to use. This parameter can be
41
+ # left out, in which case the 'current' environment will be used.
42
+ # @return [String] The domain of the Adyen payment system that can be used
43
+ # for payment forms or redirects.
44
+ # @see Adyen::Form.environment
45
+ # @see Adyen::Form.redirect_url
46
+ def domain(environment = nil)
47
+ environment ||= Adyen.configuration.environment
48
+ (Adyen.configuration.payment_flow_domain || ACTION_DOMAIN) % [environment.to_s]
49
+ end
33
50
 
34
51
  # Returns the URL of the Adyen payment system, adjusted for an Adyen environment.
35
52
  #
36
- # @param [String] environment The Adyen environment to use. This parameter can be
53
+ # @param [String] environment The Adyen environment to use. This parameter can be
37
54
  # left out, in which case the 'current' environment will be used.
55
+ # @param [String] payment_flow The Adyen payment type to use. This parameter can be
56
+ # left out, in which case the default payment type will be used.
38
57
  # @return [String] The absolute URL of the Adyen payment system that can be used
39
58
  # for payment forms or redirects.
40
59
  # @see Adyen::Form.environment
60
+ # @see Adyen::Form.domain
41
61
  # @see Adyen::Form.redirect_url
42
62
  def url(environment = nil, payment_flow = nil)
43
- environment ||= Adyen.configuration.environment
44
63
  payment_flow ||= Adyen.configuration.payment_flow
45
- Adyen::Form::ACTION_URL % [environment.to_s, payment_flow.to_s]
64
+ Adyen::Form::ACTION_URL % [domain(environment), payment_flow.to_s]
46
65
  end
47
66
 
48
67
  ######################################################
@@ -58,22 +77,24 @@ module Adyen
58
77
  # @param [Hash] parameters The payment parameters hash to transform
59
78
  def do_parameter_transformations!(parameters = {})
60
79
  parameters.replace(Adyen.configuration.default_form_params.merge(parameters))
61
- parameters[:recurring_contract] = 'RECURRING' if parameters.delete(:recurring) == true
62
- parameters[:order_data] = Adyen::Encoding.gzip_base64(parameters.delete(:order_data_raw)) if parameters[:order_data_raw]
63
- parameters[:ship_before_date] = Adyen::Formatter::DateTime.fmt_date(parameters[:ship_before_date])
64
- parameters[:session_validity] = Adyen::Formatter::DateTime.fmt_time(parameters[:session_validity])
65
-
80
+
66
81
  if parameters[:skin]
67
82
  skin = Adyen.configuration.form_skin_by_name(parameters.delete(:skin))
68
83
  parameters[:skin_code] ||= skin[:skin_code]
69
84
  parameters[:shared_secret] ||= skin[:shared_secret]
85
+ parameters.merge!(skin[:default_form_params])
70
86
  end
87
+
88
+ parameters[:recurring_contract] = 'RECURRING' if parameters.delete(:recurring) == true
89
+ parameters[:order_data] = Adyen::Encoding.gzip_base64(parameters.delete(:order_data_raw)) if parameters[:order_data_raw]
90
+ parameters[:ship_before_date] = Adyen::Formatter::DateTime.fmt_date(parameters[:ship_before_date])
91
+ parameters[:session_validity] = Adyen::Formatter::DateTime.fmt_time(parameters[:session_validity])
71
92
  end
72
93
 
73
94
  # Transforms the payment parameters to be in the correct format and calculates the merchant
74
95
  # signature parameter. It also does some basic health checks on the parameters hash.
75
96
  #
76
- # @param [Hash] parameters The payment parameters. The parameters set in the
97
+ # @param [Hash] parameters The payment parameters. The parameters set in the
77
98
  # {Adyen::Configuration#default_form_params} hash will be included automatically.
78
99
  # @param [String] shared_secret The shared secret that should be used to calculate
79
100
  # the payment request signature. This parameter can be left if the skin that is
@@ -83,7 +104,7 @@ module Adyen
83
104
  # @raise [ArgumentError] Thrown if some parameter health check fails.
84
105
  def payment_parameters(parameters = {}, shared_secret = nil)
85
106
  do_parameter_transformations!(parameters)
86
-
107
+
87
108
  raise ArgumentError, "Cannot generate form: :currency code attribute not found!" unless parameters[:currency_code]
88
109
  raise ArgumentError, "Cannot generate form: :payment_amount code attribute not found!" unless parameters[:payment_amount]
89
110
  raise ArgumentError, "Cannot generate form: :merchant_account attribute not found!" unless parameters[:merchant_account]
@@ -93,14 +114,27 @@ module Adyen
93
114
  shared_secret ||= parameters.delete(:shared_secret)
94
115
  raise ArgumentError, "Cannot calculate payment request signature without shared secret!" unless shared_secret
95
116
  parameters[:merchant_sig] = calculate_signature(parameters, shared_secret)
96
-
117
+
118
+ if parameters[:billing_address]
119
+ parameters[:billing_address_sig] = calculate_billing_address_signature(parameters, shared_secret)
120
+ end
121
+
97
122
  return parameters
98
123
  end
99
-
124
+
125
+ # Transforms and flattens payment parameters to be in the correct format which is understood and accepted by adyen
126
+ #
127
+ # @param [Hash] parameters The payment parameters. The parameters set in the
128
+ # {Adyen::Configuration#default_form_params} hash will be included automatically.
129
+ # @return [Hash] The payment parameters flatten, with camelized and prefixed key, stringified value
130
+ def flat_payment_parameters(parameters = {})
131
+ flatten(payment_parameters(parameters))
132
+ end
133
+
100
134
  # Returns an absolute URL to the Adyen payment system, with the payment parameters included
101
135
  # as GET parameters in the URL. The URL also depends on the current Adyen enviroment.
102
136
  #
103
- # The payment parameters that are provided to this method will be merged with the
137
+ # The payment parameters that are provided to this method will be merged with the
104
138
  # {Adyen::Configuration#default_form_params} hash. The default parameter values will be
105
139
  # overrided if another value is provided to this method.
106
140
  #
@@ -108,7 +142,7 @@ module Adyen
108
142
  # if you provide either a registered skin name as the +:skin+ parameter or provide both the
109
143
  # +:skin_code+ and +:shared_secret+ parameters.
110
144
  #
111
- # Note that Internet Explorer has a maximum length for URLs it can handle (2083 characters).
145
+ # Note that Internet Explorer has a maximum length for URLs it can handle (2083 characters).
112
146
  # Make sure that the URL is not longer than this limit if you want your site to work in IE.
113
147
  #
114
148
  # @example
@@ -117,7 +151,7 @@ module Adyen
117
151
  # # Genarate a URL to redirect to Adyen's payment system.
118
152
  # adyen_url = Adyen::Form.redirect_url(:skin => :my_skin, :currency_code => 'USD',
119
153
  # :payment_amount => 1000, merchant_account => 'MyMerchant', ... )
120
- #
154
+ #
121
155
  # respond_to do |format|
122
156
  # format.html { redirect_to(adyen_url) }
123
157
  # end
@@ -126,14 +160,15 @@ module Adyen
126
160
  # @param [Hash] parameters The payment parameters to include in the payment request.
127
161
  # @return [String] An absolute URL to redirect to the Adyen payment system.
128
162
  def redirect_url(parameters = {})
129
- url + '?' + payment_parameters(parameters).map{|k,v| [k.to_s,v] }.sort.map { |(k, v)|
130
- "#{camelize(k)}=#{CGI.escape(v.to_s)}" }.join('&')
163
+ url + '?' + flat_payment_parameters(parameters).map { |(k, v)|
164
+ "#{k}=#{CGI.escape(v)}"
165
+ }.join('&')
131
166
  end
132
-
133
- # Returns a HTML snippet of hidden INPUT tags with the provided payment parameters.
167
+
168
+ # Returns a HTML snippet of hidden INPUT tags with the provided payment parameters.
134
169
  # The snippet can be included in a payment form that POSTs to the Adyen payment system.
135
170
  #
136
- # The payment parameters that are provided to this method will be merged with the
171
+ # The payment parameters that are provided to this method will be merged with the
137
172
  # {Adyen::Configuration#default_form_params} hash. The default parameter values will be
138
173
  # overrided if another value is provided to this method.
139
174
  #
@@ -152,15 +187,15 @@ module Adyen
152
187
  # @return [String] An HTML snippet that can be included in a form that POSTs to the
153
188
  # Adyen payment system.
154
189
  def hidden_fields(parameters = {})
155
-
190
+
156
191
  # Generate a hidden input tag per parameter, join them by newlines.
157
- form_str = payment_parameters(parameters).map { |key, value|
158
- "<input type=\"hidden\" name=\"#{CGI.escapeHTML(camelize(key))}\" value=\"#{CGI.escapeHTML(value.to_s)}\" />"
192
+ form_str = flat_payment_parameters(parameters).map { |key, value|
193
+ "<input type=\"hidden\" name=\"#{CGI.escapeHTML(key)}\" value=\"#{CGI.escapeHTML(value)}\" />"
159
194
  }.join("\n")
160
-
195
+
161
196
  form_str.respond_to?(:html_safe) ? form_str.html_safe : form_str
162
197
  end
163
-
198
+
164
199
  ######################################################
165
200
  # MERCHANT SIGNATURE CALCULATION
166
201
  ######################################################
@@ -181,7 +216,7 @@ module Adyen
181
216
  parameters[:billing_address_type].to_s << parameters[:offset].to_s
182
217
  end
183
218
 
184
- # Calculates the payment request signature for the given payment parameters.
219
+ # Calculates the payment request signature for the given payment parameters.
185
220
  #
186
221
  # This signature is used by Adyen to check whether the request is
187
222
  # genuinely originating from you. The resulting signature should be
@@ -190,15 +225,47 @@ module Adyen
190
225
  #
191
226
  # @param [Hash] parameters The payment parameters for which to calculate
192
227
  # the payment request signature.
193
- # @param [String] shared_secret The shared secret to use for this signature.
194
- # It should correspond with the skin_code parameter. This parameter can be
228
+ # @param [String] shared_secret The shared secret to use for this signature.
229
+ # It should correspond with the skin_code parameter. This parameter can be
195
230
  # left out if the shared_secret is included as key in the parameters.
196
231
  # @return [String] The signature of the payment request
232
+ # @raise [ArgumentError] Thrown if shared_secret is empty
197
233
  def calculate_signature(parameters, shared_secret = nil)
198
234
  shared_secret ||= parameters.delete(:shared_secret)
235
+ raise ArgumentError, "Cannot calculate payment request signature with empty shared_secret" if shared_secret.to_s.empty?
199
236
  Adyen::Encoding.hmac_base64(shared_secret, calculate_signature_string(parameters))
200
237
  end
201
238
 
239
+ # Generates the string that is used to calculate the request signature. This signature
240
+ # is used by Adyen to check whether the request is genuinely originating from you.
241
+ # @param [Hash] parameters The parameters that will be included in the billing address request.
242
+ # @return [String] The string for which the siganture is calculated.
243
+ def calculate_billing_address_signature_string(parameters)
244
+ %w(street house_number_or_name city postal_code state_or_province country).map do |key|
245
+ parameters[key.to_sym]
246
+ end.join
247
+ end
248
+
249
+ # Calculates the billing address request signature for the given billing address parameters.
250
+ #
251
+ # This signature is used by Adyen to check whether the request is
252
+ # genuinely originating from you. The resulting signature should be
253
+ # included in the billing address request parameters as the +billingAddressSig+
254
+ # parameter; the shared secret should of course not be included.
255
+ #
256
+ # @param [Hash] parameters The billing address parameters for which to calculate
257
+ # the billing address request signature.
258
+ # @param [String] shared_secret The shared secret to use for this signature.
259
+ # It should correspond with the skin_code parameter. This parameter can be
260
+ # left out if the shared_secret is included as key in the parameters.
261
+ # @return [String] The signature of the billing address request
262
+ # @raise [ArgumentError] Thrown if shared_secret is empty
263
+ def calculate_billing_address_signature(parameters, shared_secret = nil)
264
+ shared_secret ||= parameters.delete(:shared_secret)
265
+ raise ArgumentError, "Cannot calculate billing address request signature with empty shared_secret" if shared_secret.to_s.empty?
266
+ Adyen::Encoding.hmac_base64(shared_secret, calculate_billing_address_signature_string(parameters[:billing_address]))
267
+ end
268
+
202
269
  ######################################################
203
270
  # REDIRECT SIGNATURE CHECKING
204
271
  ######################################################
@@ -207,20 +274,22 @@ module Adyen
207
274
  # @param [Hash] params A hash of HTTP GET parameters for the redirect request.
208
275
  # @return [String] The signature string.
209
276
  def redirect_signature_string(params)
210
- params[:authResult].to_s + params[:pspReference].to_s + params[:merchantReference].to_s +
277
+ params[:authResult].to_s + params[:pspReference].to_s + params[:merchantReference].to_s +
211
278
  params[:skinCode].to_s + params[:merchantReturnData].to_s
212
279
  end
213
-
214
- # Computes the redirect signature using the request parameters, so that the
280
+
281
+ # Computes the redirect signature using the request parameters, so that the
215
282
  # redirect can be checked for forgery.
216
283
  #
217
284
  # @param [Hash] params A hash of HTTP GET parameters for the redirect request.
218
285
  # @param [String] shared_secret The shared secret for the Adyen skin that was used for
219
- # the original payment form. You can leave this out of the skin is registered
286
+ # the original payment form. You can leave this out of the skin is registered
220
287
  # using the {Adyen::Form.register_skin} method.
221
288
  # @return [String] The redirect signature
289
+ # @raise [ArgumentError] Thrown if shared_secret is empty
222
290
  def redirect_signature(params, shared_secret = nil)
223
291
  shared_secret ||= Adyen.configuration.form_skin_shared_secret_by_code(params[:skinCode])
292
+ raise ArgumentError, "Cannot compute redirect signature with empty shared_secret" if shared_secret.to_s.empty?
224
293
  Adyen::Encoding.hmac_base64(shared_secret, redirect_signature_string(params))
225
294
  end
226
295
 
@@ -235,14 +304,14 @@ module Adyen
235
304
  # @example
236
305
  # class PaymentsController < ApplicationController
237
306
  # before_filter :check_signature, :only => [:return_from_adyen]
238
- #
307
+ #
239
308
  # def return_from_adyen
240
309
  # @invoice = Invoice.find(params[:merchantReference])
241
310
  # @invoice.set_paid! if params[:authResult] == 'AUTHORISED'
242
311
  # end
243
- #
312
+ #
244
313
  # private
245
- #
314
+ #
246
315
  # def check_signature
247
316
  # raise "Forgery!" unless Adyen::Form.redirect_signature_check(params)
248
317
  # end
@@ -251,19 +320,46 @@ module Adyen
251
320
  # @param [Hash] params params A hash of HTTP GET parameters for the redirect request. This
252
321
  # should include the +:merchantSig+ parameter, which contains the signature.
253
322
  # @param [String] shared_secret The shared secret for the Adyen skin that was used for
254
- # the original payment form. You can leave this out of the skin is registered
323
+ # the original payment form. You can leave this out of the skin is registered
255
324
  # using the {Adyen::Configuration#register_form_skin} method.
256
325
  # @return [true, false] Returns true only if the signature in the parameters is correct.
257
326
  def redirect_signature_check(params, shared_secret = nil)
258
327
  params[:merchantSig] == redirect_signature(params, shared_secret)
259
328
  end
260
-
329
+
261
330
  # Returns the camelized version of a string.
262
331
  # @param [:to_s] identifier The identifier to turn to camelcase
263
332
  # @return [String] The camelcase version of the identifier provided.
264
333
  def camelize(identifier)
265
334
  identifier.to_s.gsub(/_(.)/) { $1.upcase }
266
335
  end
267
-
336
+
337
+ # Transforms the nested parameters Hash into a 'flat' Hash which is understood by adyen. This is:
338
+ # * all keys are camelized
339
+ # * all keys are stringified
340
+ # * nested hash is flattened, keys are prefixed with root key
341
+ #
342
+ # @example
343
+ # flatten {:billing_address => { :street => 'My Street'}}
344
+ #
345
+ # # resolves in:
346
+ # {'billingAddress.street' => 'My Street'}
347
+ #
348
+ # @param [Hash] parameters The payment parameters which to transform
349
+ # @param [String] prefix The prefix to add to the key
350
+ # @param [Hash] return_hash The new hash which is retruned (needed for recursive calls)
351
+ # @return [Hash] The return_hash filled with camelized and prefixed key, stringified value
352
+ def flatten(parameters, prefix = "", return_hash = {})
353
+ parameters ||= {}
354
+ parameters.inject(return_hash) do |hash, (key, value)|
355
+ key = "#{prefix}#{camelize(key)}"
356
+ if value.is_a?(Hash)
357
+ flatten(value, "#{key}.", return_hash)
358
+ else
359
+ hash[key] = value.to_s
360
+ end
361
+ hash
362
+ end
363
+ end
268
364
  end
269
365
  end
@@ -5,7 +5,7 @@ class CreateAdyenNotifications < ActiveRecord::Migration
5
5
  create_table :adyen_notifications do |t|
6
6
  t.boolean :live, :null => false, :default => false
7
7
  t.string :event_code, :null => false, :limit => 20
8
- t.string :psp_reference, :null => false, :limit => 30
8
+ t.string :psp_reference, :null => false, :limit => 50
9
9
  t.string :original_reference, :null => true
10
10
  t.string :merchant_reference, :null => false
11
11
  t.string :merchant_account_code, :null => false
@@ -16,20 +16,20 @@
16
16
  # @invoice.set_paid!
17
17
  # end
18
18
  class AdyenNotification < ActiveRecord::Base
19
-
19
+
20
20
  # A notification should always include an event_code
21
21
  validates_presence_of :event_code
22
-
22
+
23
23
  # A notification should always include a psp_reference
24
24
  validates_presence_of :psp_reference
25
-
25
+
26
26
  # A notification should be unique using the composed key of
27
27
  # [:psp_reference, :event_code, :success]
28
28
  validates_uniqueness_of :success, :scope => [:psp_reference, :event_code]
29
-
29
+
30
30
  # Make sure we don't end up with an original_reference with an empty string
31
31
  before_validation { |notification| notification.original_reference = nil if notification.original_reference.blank? }
32
-
32
+
33
33
  # Logs an incoming notification into the database.
34
34
  #
35
35
  # @param [Hash] params The notification parameters that should be stored in the database.
@@ -38,15 +38,16 @@ class AdyenNotification < ActiveRecord::Base
38
38
  # @see Adyen::Notification::HttpPost.log
39
39
  def self.log(params)
40
40
  converted_params = {}
41
-
42
- # Convert each attribute from CamelCase notation to under_score notation
41
+
42
+ # Assign explicit each attribute from CamelCase notation to notification
43
43
  # For example, merchantReference will be converted to merchant_reference
44
- params.each do |key, value|
45
- column_name = key.to_s.underscore
46
- converted_params[column_name] = value if self.column_names.include?(column_name)
44
+ self.new.tap do |notification|
45
+ params.each do |key, value|
46
+ setter = "#{key.to_s.underscore}="
47
+ notification.send(setter, value) if notification.respond_to?(setter)
48
+ end
49
+ notification.save!
47
50
  end
48
-
49
- self.create!(converted_params)
50
51
  end
51
52
 
52
53
  # Returns true if this notification is an AUTHORISATION notification
@@ -57,14 +58,14 @@ class AdyenNotification < ActiveRecord::Base
57
58
  end
58
59
 
59
60
  alias_method :authorization?, :authorisation?
60
-
61
+
61
62
  # Returns true if this notification is an AUTHORISATION notification and
62
63
  # the success status indicates that the authorization was successfull.
63
64
  # @return [true, false] true iff the notification is an authorization
64
65
  # and the authorization was successful according to the success field.
65
66
  def successful_authorisation?
66
- event_code == 'AUTHORISATION' && success?
67
+ authorisation? && success?
67
68
  end
68
-
69
+
69
70
  alias_method :successful_authorization?, :successful_authorisation?
70
71
  end
@@ -88,17 +88,34 @@ describe Adyen::API::RecurringService do
88
88
  :variant => 'IDEAL',
89
89
  :creation_date => DateTime.parse('2009-10-27T11:26:22.216+01:00')
90
90
  },
91
+ {
92
+ :elv => {
93
+ :holder_name => 'S. Hopper',
94
+ :number => '1234567890',
95
+ :bank_location => 'Berlin',
96
+ :bank_location_id => '12345678',
97
+ :bank_name => 'TestBank',
98
+ },
99
+ :recurring_detail_reference => 'RecurringDetailReference3',
100
+ :variant => 'elv',
101
+ :creation_date => DateTime.parse('2009-10-27T11:26:22.216+01:00')
102
+ }
91
103
  ],
92
104
  })
93
105
 
94
106
  it "returns an array with just the detail references" do
95
- @response.references.should == %w{ RecurringDetailReference1 RecurringDetailReference2 }
107
+ @response.references.should == %w{ RecurringDetailReference1 RecurringDetailReference2 RecurringDetailReference3 }
96
108
  end
109
+ end
97
110
 
111
+ describe_response_from :list, LIST_EMPTY_RESPONSE, 'listRecurringDetails' do
98
112
  it "returns an empty hash when there are no details" do
99
- stub_net_http(LIST_EMPTY_RESPONSE)
100
113
  @recurring.list.params.should == {}
101
114
  end
115
+
116
+ it "returns an empty array when there are no references" do
117
+ @response.references.should == []
118
+ end
102
119
  end
103
120
 
104
121
  describe_request_body_of :disable, '//recurring:disable/recurring:request' do
@@ -332,6 +332,21 @@ LIST_RESPONSE = <<EOS
332
332
  <recurringDetailReference>RecurringDetailReference2</recurringDetailReference>
333
333
  <variant>IDEAL</variant>
334
334
  </RecurringDetail>
335
+ <RecurringDetail>
336
+ <card xsi:nil="true"/>
337
+ <bank xsi:nil="true"/>
338
+ <elv>
339
+ <accountHolderName xmlns="http://payment.services.adyen.com">S. Hopper</accountHolderName>
340
+ <bankAccountNumber xmlns="http://payment.services.adyen.com">1234567890</bankAccountNumber>
341
+ <bankLocation xmlns="http://payment.services.adyen.com">Berlin</bankLocation>
342
+ <bankLocationId xmlns="http://payment.services.adyen.com">12345678</bankLocationId>
343
+ <bankName xmlns="http://payment.services.adyen.com">TestBank</bankName>
344
+ </elv>
345
+ <creationDate>2009-10-27T11:26:22.216+01:00</creationDate>
346
+ <name/>
347
+ <recurringDetailReference>RecurringDetailReference3</recurringDetailReference>
348
+ <variant>elv</variant>
349
+ </RecurringDetail>
335
350
  </details>
336
351
  <ns1:lastKnownShopperEmail>s.hopper@example.com</ns1:lastKnownShopperEmail>
337
352
  <ns1:shopperReference>user-id</ns1:shopperReference>
@@ -6,7 +6,7 @@ require 'adyen/form'
6
6
 
7
7
  describe Adyen::Form do
8
8
 
9
- before(:all) do
9
+ before(:each) do
10
10
  Adyen.configuration.register_form_skin(:testing, '4aD37dJA', 'Kah942*$7sdp0)')
11
11
  Adyen.configuration.default_form_params[:merchant_account] = 'TestMerchant'
12
12
  end
@@ -35,21 +35,32 @@ describe Adyen::Form do
35
35
  it "should generate correct live url if explicitely asked for" do
36
36
  Adyen::Form.url(:live).should == 'https://live.adyen.com/hpp/select.shtml'
37
37
  end
38
-
38
+
39
39
  it "should generate correct testing url if the payment flow selection is set to select" do
40
40
  Adyen.configuration.payment_flow = :select
41
41
  Adyen::Form.url.should == 'https://test.adyen.com/hpp/select.shtml'
42
42
  end
43
-
43
+
44
44
  it "should generate correct testing url if the payment flow selection is set to pay" do
45
45
  Adyen.configuration.payment_flow = :pay
46
46
  Adyen::Form.url.should == 'https://test.adyen.com/hpp/pay.shtml'
47
47
  end
48
-
48
+
49
49
  it "should generate correct testing url if the payment flow selection is set to details" do
50
50
  Adyen.configuration.payment_flow = :details
51
51
  Adyen::Form.url.should == 'https://test.adyen.com/hpp/details.shtml'
52
52
  end
53
+
54
+ context "with custom domain" do
55
+ before(:each) do
56
+ Adyen.configuration.payment_flow = :select
57
+ Adyen.configuration.payment_flow_domain = "checkout.mydomain.com"
58
+ end
59
+
60
+ it "should generate correct testing url" do
61
+ Adyen::Form.url.should == 'https://checkout.mydomain.com/hpp/select.shtml'
62
+ end
63
+ end
53
64
  end
54
65
 
55
66
  describe 'redirect signature check' do
@@ -80,6 +91,18 @@ describe Adyen::Form do
80
91
  Adyen::Form.redirect_signature_check(@params).should be_true
81
92
  end
82
93
 
94
+ it "should raise ArgumentError on missing skinCode" do
95
+ expect do
96
+ @params.delete(:skinCode)
97
+ Adyen::Form.redirect_signature_check(@params).should be_false
98
+ end.to raise_error ArgumentError
99
+ end
100
+
101
+ it "should raise ArgumentError on empty input" do
102
+ expect do
103
+ Adyen::Form.redirect_signature_check({}).should be_false
104
+ end.to raise_error ArgumentError
105
+ end
83
106
 
84
107
  it "should detect a tampered field" do
85
108
  Adyen::Form.redirect_signature_check(@params.merge(:pspReference => 'tampered')).should be_false
@@ -99,16 +122,16 @@ describe Adyen::Form do
99
122
 
100
123
  @redirect_url = Adyen::Form.redirect_url(@attributes)
101
124
  end
102
-
125
+
103
126
  it "should return an URL pointing to the adyen server" do
104
127
  @redirect_url.should =~ %r[^#{Adyen::Form.url}]
105
128
  end
106
-
129
+
107
130
  it "should include all provided attributes" do
108
131
  params = @redirect_url.split('?', 2).last.split('&').map { |param| param.split('=', 2).first }
109
132
  params.should include(*(@attributes.keys.map { |k| Adyen::Form.camelize(k) }))
110
133
  end
111
-
134
+
112
135
  it "should include the merchant signature" do
113
136
  params = @redirect_url.split('?', 2).last.split('&').map { |param| param.split('=', 2).first }
114
137
  params.should include('merchantSig')
@@ -116,6 +139,7 @@ describe Adyen::Form do
116
139
  end
117
140
 
118
141
  describe 'hidden fields generation' do
142
+ subject { %Q'<form action="#{CGI.escapeHTML(Adyen::Form.url)}" method="post">#{Adyen::Form.hidden_fields(@attributes)}</form>' }
119
143
 
120
144
  before(:each) do
121
145
  @attributes = { :currency_code => 'GBP', :payment_amount => 10000, :ship_before_date => Date.today,
@@ -123,12 +147,18 @@ describe Adyen::Form do
123
147
  :session_validity => Time.now + 3600 }
124
148
  end
125
149
 
126
- it "should generate a valid payment form" do
127
- html_snippet = <<-HTML
128
- <form action="#{CGI.escapeHTML(Adyen::Form.url)}" method="post">#{Adyen::Form.hidden_fields(@attributes)}</form>
129
- HTML
130
-
131
- html_snippet.should have_adyen_payment_form
150
+ it { should have_adyen_payment_form }
151
+ it { should include('<input type="hidden" name="merchantAccount" value="TestMerchant" />') }
152
+
153
+ context "width default_form_params" do
154
+ before(:each) do
155
+ Adyen.configuration.register_form_skin(:testing, '4aD37dJA', 'Kah942*$7sdp0)', {
156
+ :merchant_account => 'OtherMerchant',
157
+ })
158
+ end
159
+
160
+ it { should include('<input type="hidden" name="merchantAccount" value="OtherMerchant" />') }
161
+ it { should_not include('<input type="hidden" name="merchantAccount" value="TestMerchant" />') }
132
162
  end
133
163
  end
134
164
 
@@ -140,7 +170,16 @@ describe Adyen::Form do
140
170
 
141
171
  @parameters = { :currency_code => 'GBP', :payment_amount => 10000,
142
172
  :ship_before_date => '2007-10-20', :merchant_reference => 'Internet Order 12345',
143
- :skin => :testing, :session_validity => '2007-10-11T11:00:00Z' }
173
+ :skin => :testing, :session_validity => '2007-10-11T11:00:00Z',
174
+ :billing_address => {
175
+ :street => 'Alexanderplatz',
176
+ :house_number_or_name => '0815',
177
+ :city => 'Berlin',
178
+ :postal_code => '10119',
179
+ :state_or_province => 'Berlin',
180
+ :country => 'Germany',
181
+ }
182
+ }
144
183
 
145
184
  Adyen::Form.do_parameter_transformations!(@parameters)
146
185
  end
@@ -148,17 +187,24 @@ describe Adyen::Form do
148
187
  it "should construct the signature base string correctly" do
149
188
  signature_string = Adyen::Form.calculate_signature_string(@parameters)
150
189
  signature_string.should == "10000GBP2007-10-20Internet Order 123454aD37dJATestMerchant2007-10-11T11:00:00Z"
151
-
190
+
152
191
  signature_string = Adyen::Form.calculate_signature_string(@parameters.merge(:merchant_return_data => 'testing123'))
153
192
  signature_string.should == "10000GBP2007-10-20Internet Order 123454aD37dJATestMerchant2007-10-11T11:00:00Ztesting123"
154
-
193
+
155
194
  end
156
-
195
+
157
196
  it "should calculate the signature correctly" do
158
197
  signature = Adyen::Form.calculate_signature(@parameters)
159
198
  signature.should == 'x58ZcRVL1H6y+XSeBGrySJ9ACVo='
160
199
  end
161
200
 
201
+ it "should raise ArgumentError on empty shared_secret" do
202
+ expect do
203
+ @parameters.delete(:shared_secret)
204
+ signature = Adyen::Form.calculate_signature(@parameters)
205
+ end.to raise_error ArgumentError
206
+ end
207
+
162
208
  it "should calculate the signature base string correctly for a recurring payment" do
163
209
  # Add the required recurrent payment attributes
164
210
  @parameters.merge!(:recurring_contract => 'DEFAULT', :shopper_reference => 'grasshopper52', :shopper_email => 'gras.shopper@somewhere.org')
@@ -174,5 +220,44 @@ describe Adyen::Form do
174
220
  signature = Adyen::Form.calculate_signature(@parameters)
175
221
  signature.should == 'F2BQEYbE+EUhiRGuPtcD16Gm7JY='
176
222
  end
223
+
224
+ context 'billing address' do
225
+
226
+ it "should construct the signature base string correctly" do
227
+ signature_string = Adyen::Form.calculate_billing_address_signature_string(@parameters[:billing_address])
228
+ signature_string.should == "Alexanderplatz0815Berlin10119BerlinGermany"
229
+ end
230
+
231
+ it "should calculate the signature correctly" do
232
+ signature = Adyen::Form.calculate_billing_address_signature(@parameters)
233
+ signature.should == '5KQb7VJq4cz75cqp11JDajntCY4='
234
+ end
235
+
236
+ it "should raise ArgumentError on empty shared_secret" do
237
+ expect do
238
+ @parameters.delete(:shared_secret)
239
+ signature = Adyen::Form.calculate_billing_address_signature(@parameters)
240
+ end.to raise_error ArgumentError
241
+ end
242
+ end
243
+
244
+ end
245
+
246
+ describe "flatten" do
247
+ let(:parameters) do
248
+ {
249
+ :billing_address => { :street => 'My Street'}
250
+ }
251
+ end
252
+
253
+ it "returns empty hash for nil input" do
254
+ Adyen::Form.flatten(nil).should == {}
255
+ end
256
+
257
+ it "flattens hash and prefixes keys" do
258
+ Adyen::Form.flatten(parameters).should == {
259
+ 'billingAddress.street' => 'My Street'
260
+ }
261
+ end
177
262
  end
178
- end
263
+ end
metadata CHANGED
@@ -1,14 +1,10 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: adyen
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 1
7
- - 3
8
- - 1
9
- version: 1.3.1
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.3.2
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Willem van Bergen
13
9
  - Michel Barbosa
14
10
  - Stefan Borsje
@@ -16,72 +12,67 @@ authors:
16
12
  autorequire:
17
13
  bindir: bin
18
14
  cert_chain: []
19
-
20
- date: 2012-02-20 00:00:00 -05:00
21
- default_executable:
22
- dependencies:
23
- - !ruby/object:Gem::Dependency
15
+ date: 2013-01-14 00:00:00.000000000 Z
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
24
18
  name: rake
25
- prerelease: false
26
- requirement: &id001 !ruby/object:Gem::Requirement
27
- requirements:
28
- - - ">="
29
- - !ruby/object:Gem::Version
30
- segments:
31
- - 0
32
- version: "0"
19
+ requirement: &70260598824500 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ! '>='
23
+ - !ruby/object:Gem::Version
24
+ version: '0'
33
25
  type: :development
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: rspec
37
26
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
39
- requirements:
27
+ version_requirements: *70260598824500
28
+ - !ruby/object:Gem::Dependency
29
+ name: rspec
30
+ requirement: &70260598798280 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
40
33
  - - ~>
41
- - !ruby/object:Gem::Version
42
- segments:
43
- - 2
44
- version: "2"
34
+ - !ruby/object:Gem::Version
35
+ version: '2'
45
36
  type: :development
46
- version_requirements: *id002
47
- - !ruby/object:Gem::Dependency
48
- name: rails
49
37
  prerelease: false
50
- requirement: &id003 !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- segments:
55
- - 2
56
- - 3
57
- version: "2.3"
38
+ version_requirements: *70260598798280
39
+ - !ruby/object:Gem::Dependency
40
+ name: rails
41
+ requirement: &70260598790820 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '2.3'
58
47
  type: :development
59
- version_requirements: *id003
60
- - !ruby/object:Gem::Dependency
61
- name: nokogiri
62
48
  prerelease: false
63
- requirement: &id004 !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- segments:
68
- - 0
69
- version: "0"
49
+ version_requirements: *70260598790820
50
+ - !ruby/object:Gem::Dependency
51
+ name: nokogiri
52
+ requirement: &70260598661900 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
70
58
  type: :development
71
- version_requirements: *id004
72
- description: " Package to simplify including the Adyen payments services into a Ruby on Rails application.\n The package provides functionality to create payment forms, handling and storing notifications \n sent by Adyen and consuming the SOAP services provided by Adyen. Moreover, it contains helper\n methods, mocks and matchers to simpify writing tests/specsfor your code.\n"
73
- email:
59
+ prerelease: false
60
+ version_requirements: *70260598661900
61
+ description: ! " Package to simplify including the Adyen payments services into
62
+ a Ruby on Rails application.\n The package provides functionality to create payment
63
+ forms, handling and storing notifications \n sent by Adyen and consuming the
64
+ SOAP services provided by Adyen. Moreover, it contains helper\n methods, mocks
65
+ and matchers to simpify writing tests/specs for your code.\n"
66
+ email:
74
67
  - willem@vanbergen.org
75
68
  - cicaboo@gmail.com
76
69
  - mail@sborsje.nl
77
70
  - eloy.de.enige@gmail.com
78
71
  executables: []
79
-
80
72
  extensions: []
81
-
82
- extra_rdoc_files:
73
+ extra_rdoc_files:
83
74
  - README.rdoc
84
- files:
75
+ files:
85
76
  - .gitignore
86
77
  - .kick
87
78
  - .travis.yml
@@ -125,42 +116,38 @@ files:
125
116
  - spec/spec_helper.rb
126
117
  - tasks/github-gem.rake
127
118
  - yard_extensions.rb
128
- has_rdoc: true
129
119
  homepage: http://github.com/wvanbergen/adyen/wiki
130
120
  licenses: []
131
-
132
121
  post_install_message:
133
- rdoc_options:
122
+ rdoc_options:
134
123
  - --title
135
124
  - adyen
136
125
  - --main
137
126
  - README.rdoc
138
127
  - --line-numbers
139
128
  - --inline-source
140
- require_paths:
129
+ require_paths:
141
130
  - lib
142
- required_ruby_version: !ruby/object:Gem::Requirement
143
- requirements:
144
- - - ">="
145
- - !ruby/object:Gem::Version
146
- segments:
147
- - 0
148
- version: "0"
149
- required_rubygems_version: !ruby/object:Gem::Requirement
150
- requirements:
151
- - - ">="
152
- - !ruby/object:Gem::Version
153
- segments:
154
- - 0
155
- version: "0"
156
- requirements:
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ! '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements:
157
144
  - Having Nokogiri installed will speed up XML handling when using the SOAP API.
158
145
  rubyforge_project:
159
- rubygems_version: 1.3.6
146
+ rubygems_version: 1.8.16
160
147
  signing_key:
161
148
  specification_version: 3
162
149
  summary: Integrate Adyen payment services in your Ruby on Rails application.
163
- test_files:
150
+ test_files:
164
151
  - spec/adyen_spec.rb
165
152
  - spec/api/api_spec.rb
166
153
  - spec/api/payment_service_spec.rb