adyen 2.0.0.pre2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 65289d682bc415347ec65ea427c0d6e7943cb3d3
4
- data.tar.gz: e3a11edb4ac752b412721f3bdf61a0a04b4bf85e
3
+ metadata.gz: 98769e9e8772583d0d35be23a09352f10654095b
4
+ data.tar.gz: 8e8fefa15a40bcd9f871ddf203acb415351df55d
5
5
  SHA512:
6
- metadata.gz: fb6eb78b5a9748124fa432b7105f88d1b0d358ad7fe13817d36ae97036968ebb9514e7986aa8ee071a0210331d16782b67db5179ef90a04733c9d08835b07fcb
7
- data.tar.gz: db40c3ff10543755bbe055f6f2ac677cbba179aea097ea175389d0c39b9958e6434118ff646deba9cb2ce2dd5096bca23ae869ae1ce45766a738e79f4c5ca4bb
6
+ metadata.gz: c3273f3cfb9c26b6e399254e92e068843dd7a75eedd66205166eee82b3cb2761848dc05fec7935d0e08d1d5b45ad00d4903f280c69c00a540dd1b52f35666d56
7
+ data.tar.gz: 629c089709b330bf3bfb9accd30f4ee7a4a8db5d687662eb6c7b2047b4b2d973ab676c9d8ebaa099cfe1790e6d9cec5f4793db9932dc038b1fd920b5a5607567
@@ -26,6 +26,7 @@ end
26
26
  require 'adyen/version'
27
27
  require 'adyen/configuration'
28
28
  require 'adyen/util'
29
+ require 'adyen/hpp/signature'
29
30
  require 'adyen/form'
30
31
  require 'adyen/api'
31
32
  require 'adyen/rest'
@@ -120,10 +120,18 @@ module Adyen
120
120
  parameters[:billing_address_sig] = calculate_billing_address_signature(parameters, shared_secret)
121
121
  end
122
122
 
123
+ if parameters[:delivery_address]
124
+ parameters[:delivery_address_sig] = calculate_delivery_address_signature(parameters, shared_secret)
125
+ end
126
+
123
127
  if parameters[:shopper]
124
128
  parameters[:shopper_sig] = calculate_shopper_signature(parameters, shared_secret)
125
129
  end
126
130
 
131
+ if parameters[:openinvoicedata]
132
+ parameters[:openinvoicedata][:sig] = calculate_open_invoice_signature(parameters, shared_secret)
133
+ end
134
+
127
135
  return parameters
128
136
  end
129
137
 
@@ -225,14 +233,15 @@ module Adyen
225
233
  # @return [String] The string for which the siganture is calculated.
226
234
  def calculate_signature_string(parameters)
227
235
  merchant_sig_string = ""
228
- merchant_sig_string << parameters[:payment_amount].to_s << parameters[:currency_code].to_s <<
229
- parameters[:ship_before_date].to_s << parameters[:merchant_reference].to_s <<
230
- parameters[:skin_code].to_s << parameters[:merchant_account].to_s <<
231
- parameters[:session_validity].to_s << parameters[:shopper_email].to_s <<
232
- parameters[:shopper_reference].to_s << parameters[:recurring_contract].to_s <<
233
- parameters[:allowed_methods].to_s << parameters[:blocked_methods].to_s <<
234
- parameters[:shopper_statement].to_s << parameters[:merchant_return_data].to_s <<
235
- parameters[:billing_address_type].to_s << parameters[:offset].to_s
236
+ merchant_sig_string << parameters[:payment_amount].to_s << parameters[:currency_code].to_s <<
237
+ parameters[:ship_before_date].to_s << parameters[:merchant_reference].to_s <<
238
+ parameters[:skin_code].to_s << parameters[:merchant_account].to_s <<
239
+ parameters[:session_validity].to_s << parameters[:shopper_email].to_s <<
240
+ parameters[:shopper_reference].to_s << parameters[:recurring_contract].to_s <<
241
+ parameters[:allowed_methods].to_s << parameters[:blocked_methods].to_s <<
242
+ parameters[:shopper_statement].to_s << parameters[:merchant_return_data].to_s <<
243
+ parameters[:billing_address_type].to_s << parameters[:delivery_address_type].to_s <<
244
+ parameters[:shopper_type].to_s << parameters[:offset].to_s
236
245
  end
237
246
 
238
247
  # Calculates the payment request signature for the given payment parameters.
@@ -265,6 +274,16 @@ module Adyen
265
274
  end.join
266
275
  end
267
276
 
277
+ # Generates the string that is used to calculate the request signature. This signature
278
+ # is used by Adyen to check whether the request is genuinely originating from you.
279
+ # @param [Hash] parameters The parameters that will be included in the delivery address request.
280
+ # @return [String] The string for which the siganture is calculated.
281
+ def calculate_delivery_address_signature_string(parameters)
282
+ %w(street house_number_or_name city postal_code state_or_province country).map do |key|
283
+ parameters[key.to_sym]
284
+ end.join
285
+ end
286
+
268
287
  # Calculates the billing address request signature for the given billing address parameters.
269
288
  #
270
289
  # This signature is used by Adyen to check whether the request is
@@ -285,6 +304,26 @@ module Adyen
285
304
  Adyen::Util.hmac_base64(shared_secret, calculate_billing_address_signature_string(parameters[:billing_address]))
286
305
  end
287
306
 
307
+ # Calculates the delivery address request signature for the given delivery address parameters.
308
+ #
309
+ # This signature is used by Adyen to check whether the request is
310
+ # genuinely originating from you. The resulting signature should be
311
+ # included in the delivery address request parameters as the +deliveryAddressSig+
312
+ # parameter; the shared secret should of course not be included.
313
+ #
314
+ # @param [Hash] parameters The delivery address parameters for which to calculate
315
+ # the delivery address request signature.
316
+ # @param [String] shared_secret The shared secret to use for this signature.
317
+ # It should correspond with the skin_code parameter. This parameter can be
318
+ # left out if the shared_secret is included as key in the parameters.
319
+ # @return [String] The signature of the delivery address request
320
+ # @raise [ArgumentError] Thrown if shared_secret is empty
321
+ def calculate_delivery_address_signature(parameters, shared_secret = nil)
322
+ shared_secret ||= parameters.delete(:shared_secret)
323
+ raise ArgumentError, "Cannot calculate delivery address request signature with empty shared_secret" if shared_secret.to_s.empty?
324
+ Adyen::Util.hmac_base64(shared_secret, calculate_delivery_address_signature_string(parameters[:delivery_address]))
325
+ end
326
+
288
327
  # shopperSig: shopper.firstName + shopper.infix + shopper.lastName + shopper.gender + shopper.dateOfBirthDayOfMonth + shopper.dateOfBirthMonth + shopper.dateOfBirthYear + shopper.telephoneNumber
289
328
  # (Note that you can send only shopper.firstName and shopper.lastName if you like. Do NOT include shopperSocialSecurityNumber in the shopperSig!)
290
329
  def calculate_shopper_signature_string(parameters)
@@ -299,6 +338,19 @@ module Adyen
299
338
  Adyen::Util.hmac_base64(shared_secret, calculate_shopper_signature_string(parameters[:shopper]))
300
339
  end
301
340
 
341
+ def calculate_open_invoice_signature_string(merchant_sig, parameters)
342
+ flattened = Adyen::Util.flatten(:merchant_sig => merchant_sig, :openinvoicedata => parameters)
343
+ pairs = flattened.to_a.sort
344
+ pairs.transpose.map { |it| it.join(':') }.join('|')
345
+ end
346
+
347
+ def calculate_open_invoice_signature(parameters, shared_secret = nil)
348
+ shared_secret ||= parameters.delete(:shared_secret)
349
+ raise ArgumentError, "Cannot calculate open invoice request signature with empty shared_secret" if shared_secret.to_s.empty?
350
+ merchant_sig = calculate_signature(parameters, shared_secret)
351
+ Adyen::Util.hmac_base64(shared_secret, calculate_open_invoice_signature_string(merchant_sig, parameters[:openinvoicedata]))
352
+ end
353
+
302
354
  ######################################################
303
355
  # REDIRECT SIGNATURE CHECKING
304
356
  ######################################################
@@ -0,0 +1,66 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module Adyen
5
+ module HPP
6
+ # The Signature module can sign and verify HMAC SHA-256 signatures for Hosted Payment Pages
7
+ module Signature
8
+ extend self
9
+
10
+ # Sign the parameters with the given shared secret
11
+ # @param [Hash] params The set of parameters to sign
12
+ # @param [String] shared_secret The shared secret for signing/verification. Can also be sent in the
13
+ # params hash with the `sharedSecret` key.
14
+ # @return [Hash] params The params that were passed in plus a new `merchantSig` param
15
+ def sign(params, shared_secret = nil)
16
+ shared_secret ||= params.delete['sharedSecret']
17
+ raise ArgumentError, "Cannot verify a signature without a shared secret" unless shared_secret
18
+ sig = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), Array(shared_secret).pack("H*"), string_to_sign(params))
19
+ params.merge('merchantSig' => Base64.encode64(sig).strip)
20
+ end
21
+
22
+ # Verify the parameters with the given shared secret
23
+ # @param [Hash] params The set of parameters to verify. Must include a `merchantSig`
24
+ # param that will be compared to the signature we calculate.
25
+ # @param [String] shared_secret The shared secret for signing/verification. Can also be sent in the
26
+ # params hash with the `sharedSecret` key.
27
+ # @return [Boolean] true if the `merchantSig` in the params matches our calculated signature
28
+ def verify(params, shared_secret = nil)
29
+ their_sig = params.delete('merchantSig')
30
+ raise ArgumentError, "params must include 'merchantSig' for verification" if their_sig.empty?
31
+ our_sig = sign(params, shared_secret)['merchantSig']
32
+ secure_compare(their_sig, our_sig)
33
+ end
34
+
35
+ private
36
+
37
+ def string_to_sign(params)
38
+ (sorted_keys(params) + sorted_values(params)).map{ |el| escape_value(el) }.join(':')
39
+ end
40
+
41
+ def sorted_keys(hash)
42
+ hash.sort.map{ |el| el[0] }
43
+ end
44
+
45
+ def sorted_values(hash)
46
+ hash.sort.map{ |el| el[1] }
47
+ end
48
+
49
+ def escape_value(value)
50
+ value.gsub(':', '\\:').gsub('\\', '\\\\')
51
+ end
52
+
53
+ # Constant-time compare for two fixed-length strings
54
+ # Stolen from https://github.com/rails/rails/commit/c8c660002f4b0e9606de96325f20b95248b6ff2d
55
+ def secure_compare(a, b)
56
+ return false unless a.bytesize == b.bytesize
57
+
58
+ l = a.unpack "C#{a.bytesize}"
59
+
60
+ res = 0
61
+ b.each_byte { |byte| res |= byte ^ l.shift }
62
+ res == 0
63
+ end
64
+ end
65
+ end
66
+ end
@@ -16,7 +16,6 @@
16
16
  # @invoice.set_paid!
17
17
  # end
18
18
  class AdyenNotification < ActiveRecord::Base
19
-
20
19
  # A notification should always include an event_code
21
20
  validates_presence_of :event_code
22
21
 
@@ -37,8 +36,6 @@ class AdyenNotification < ActiveRecord::Base
37
36
  # @raise This method will raise an exception if the notification cannot be stored.
38
37
  # @see Adyen::Notification::HttpPost.log
39
38
  def self.log(params)
40
- converted_params = {}
41
-
42
39
  # Assign explicit each attribute from CamelCase notation to notification
43
40
  # For example, merchantReference will be converted to merchant_reference
44
41
  self.new.tap do |notification|
@@ -46,6 +43,7 @@ class AdyenNotification < ActiveRecord::Base
46
43
  setter = "#{key.to_s.underscore}="
47
44
  notification.send(setter, value) if notification.respond_to?(setter)
48
45
  end
46
+
49
47
  notification.save!
50
48
  end
51
49
  end
@@ -1,5 +1,5 @@
1
1
  module Adyen
2
2
  # Version constant for the Adyen plugin.
3
3
  # Set it & commit the change before running rake release.
4
- VERSION = "2.0.0.pre2"
4
+ VERSION = "2.0.0"
5
5
  end
@@ -29,11 +29,32 @@ class FormTest < Minitest::Test
29
29
  :state_or_province => 'Berlin',
30
30
  :country => 'Germany',
31
31
  },
32
+ :delivery_address => {
33
+ :street => 'Pecunialaan',
34
+ :house_number_or_name => '316',
35
+ :city => 'Geldrop',
36
+ :state_or_province => 'None',
37
+ :postal_code => '1234 AB',
38
+ :country => 'Netherlands',
39
+ },
32
40
  :shopper => {
33
41
  :telephone_number => '1234512345',
34
42
  :first_name => 'John',
35
43
  :last_name => 'Doe',
36
44
  :social_security_number => '123-45-1234'
45
+ },
46
+ :openinvoicedata => {
47
+ :number_of_lines => 1,
48
+ :line1 => {
49
+ :number_of_items => 2,
50
+ :item_amount => 4000,
51
+ :currency_code => 'GBP',
52
+ :item_vat_amount => 1000,
53
+ :item_vat_percentage => 2500,
54
+ :item_vat_category => 'High',
55
+ :description => 'Product Awesome'
56
+ },
57
+ :refund_description => 'Refund for 12345'
37
58
  }
38
59
  }
39
60
 
@@ -122,6 +143,12 @@ class FormTest < Minitest::Test
122
143
 
123
144
  signature_string = Adyen::Form.calculate_signature_string(@recurring_payment_attributes)
124
145
  assert_equal "10000GBP2007-10-20Internet Order 12345sk1nC0deOtherMerchant2007-10-11T11:00:00Zgras.shopper@somewhere.orggrasshopper52DEFAULT", signature_string
146
+
147
+ signature_string = Adyen::Form.calculate_signature_string(@payment_attributes.merge(:billing_address_type => '1', :delivery_address_type => '2'))
148
+ assert_equal "10000GBP2007-10-20Internet Order 123454aD37dJATestMerchant2007-10-11T11:00:00Z12", signature_string
149
+
150
+ signature_string = Adyen::Form.calculate_signature_string(@payment_attributes.merge(:delivery_address_type => '2', :shopper_type => '1'))
151
+ assert_equal "10000GBP2007-10-20Internet Order 123454aD37dJATestMerchant2007-10-11T11:00:00Z21", signature_string
125
152
  end
126
153
 
127
154
  def test_redirect_signature
@@ -150,10 +177,58 @@ class FormTest < Minitest::Test
150
177
  assert_raises(ArgumentError) { Adyen::Form.calculate_billing_address_signature(@payment_attributes) }
151
178
  end
152
179
 
153
- def test_billing_address_and_shopper_signature_in_redirect_url
180
+ def test_delivery_address_signature
181
+ signature_string = Adyen::Form.calculate_delivery_address_signature_string(@payment_attributes[:delivery_address])
182
+ assert_equal "Pecunialaan316Geldrop1234 ABNoneNetherlands", signature_string
183
+ assert_equal 'g8wPEWYrDPatkGXzuQbN1++JVbE=', Adyen::Form.calculate_delivery_address_signature(@payment_attributes)
184
+
185
+ @payment_attributes.delete(:shared_secret)
186
+ assert_raises(ArgumentError) { Adyen::Form.calculate_delivery_address_signature(@payment_attributes) }
187
+ end
188
+
189
+ def test_open_invoice_signature
190
+ merchant_sig = Adyen::Form.calculate_signature(@payment_attributes, @payment_attributes[:shared_secret])
191
+ signature_string = Adyen::Form.calculate_open_invoice_signature_string(merchant_sig, @payment_attributes[:openinvoicedata])
192
+ expected_string =
193
+ [
194
+ 'merchantSig',
195
+ 'openinvoicedata.line1.currencyCode',
196
+ 'openinvoicedata.line1.description',
197
+ 'openinvoicedata.line1.itemAmount',
198
+ 'openinvoicedata.line1.itemVatAmount',
199
+ 'openinvoicedata.line1.itemVatCategory',
200
+ 'openinvoicedata.line1.itemVatPercentage',
201
+ 'openinvoicedata.line1.numberOfItems',
202
+ 'openinvoicedata.numberOfLines',
203
+ 'openinvoicedata.refundDescription'
204
+ ].join(':') +
205
+ '|' +
206
+ [
207
+ merchant_sig,
208
+ 'GBP',
209
+ 'Product Awesome',
210
+ 4000,
211
+ 1000,
212
+ 'High',
213
+ 2500,
214
+ 2,
215
+ 1,
216
+ 'Refund for 12345'
217
+ ].join(':')
218
+
219
+ assert_equal expected_string, signature_string
220
+ assert_equal 'OI71VGB7G3vKBRrtE6Ibv+RWvYY=', Adyen::Form.calculate_open_invoice_signature(@payment_attributes)
221
+
222
+ @payment_attributes.delete(:shared_secret)
223
+ assert_raises(ArgumentError) { Adyen::Form.calculate_open_invoice_signature(@payment_attributes) }
224
+ end
225
+
226
+ def test_billing_signatures_in_redirect_url
154
227
  get_params = CGI.parse(URI(Adyen::Form.redirect_url(@payment_attributes)).query)
155
228
  assert_equal '5KQb7VJq4cz75cqp11JDajntCY4=', get_params['billingAddressSig'].first
229
+ assert_equal 'g8wPEWYrDPatkGXzuQbN1++JVbE=', get_params['deliveryAddressSig'].first
156
230
  assert_equal 'rb2GEs1kGKuLh255a3QRPBYXmsQ=', get_params['shopperSig'].first
231
+ assert_equal 'OI71VGB7G3vKBRrtE6Ibv+RWvYY=', get_params['openinvoicedata.sig'].first
157
232
  end
158
233
 
159
234
  def test_redirect_signature_check
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ class SignatureTest < Minitest::Test
4
+ def setup
5
+ # values from https://docs.adyen.com/pages/viewpage.action?pageId=5376964
6
+ @shared_secret = "4468D9782DEF54FCD706C9100C71EC43932B1EBC2ACF6BA0560C05AAA7550C48"
7
+
8
+ @expected_sig = 'GJ1asjR5VmkvihDJxCd8yE2DGYOKwWwJCBiV3R51NFg='
9
+
10
+ @raw_params = {
11
+ 'merchantAccount' => 'TestMerchant',
12
+ 'currencyCode' => 'EUR',
13
+ 'paymentAmount' => '199',
14
+ 'sessionValidity' => '2015-06-25T10:31:06Z',
15
+ 'shipBeforeDate' => '2015-07-01',
16
+ 'shopperLocale' => 'en_GB',
17
+ 'merchantReference' => 'SKINTEST-1435226439255',
18
+ 'skinCode' => 'X7hsNDWp',
19
+ }
20
+ end
21
+
22
+ def test_sign
23
+ signed_params = Adyen::HPP::Signature.sign(@raw_params, @shared_secret)
24
+ assert_equal @expected_sig, signed_params['merchantSig']
25
+ end
26
+
27
+ def test_verify_succeeds_with_same_secret
28
+ signed_params = @raw_params.merge('merchantSig' => @expected_sig)
29
+ assert_equal true, Adyen::HPP::Signature.verify(signed_params, @shared_secret)
30
+ end
31
+
32
+ def test_verification_fails_with_different_secret
33
+ signed_params = @raw_params.merge('merchantSig' => @expected_sig)
34
+ assert_equal false, Adyen::HPP::Signature.verify(signed_params, '12345')
35
+ end
36
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adyen
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2015-03-06 00:00:00.000000000 Z
14
+ date: 2016-05-17 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rake
@@ -165,6 +165,7 @@ files:
165
165
  - lib/adyen/api/xml_querier.rb
166
166
  - lib/adyen/configuration.rb
167
167
  - lib/adyen/form.rb
168
+ - lib/adyen/hpp/signature.rb
168
169
  - lib/adyen/matchers.rb
169
170
  - lib/adyen/notification_generator.rb
170
171
  - lib/adyen/railtie.rb
@@ -204,6 +205,7 @@ files:
204
205
  - test/helpers/views/index.erb
205
206
  - test/helpers/views/pay.erb
206
207
  - test/helpers/views/redirect_shopper.erb
208
+ - test/hpp/signature_test.rb
207
209
  - test/integration/hpp_integration_test.rb
208
210
  - test/integration/payment_using_3d_secure_integration_test.rb
209
211
  - test/integration/payment_with_client_side_encryption_integration_test.rb
@@ -234,13 +236,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
234
236
  version: 1.9.3
235
237
  required_rubygems_version: !ruby/object:Gem::Requirement
236
238
  requirements:
237
- - - '>'
239
+ - - '>='
238
240
  - !ruby/object:Gem::Version
239
- version: 1.3.1
241
+ version: '0'
240
242
  requirements:
241
243
  - Having Nokogiri installed will speed up XML handling when using the SOAP API.
242
244
  rubyforge_project:
243
- rubygems_version: 2.0.14
245
+ rubygems_version: 2.0.14.1
244
246
  signing_key:
245
247
  specification_version: 4
246
248
  summary: Integrate Adyen payment services in your Ruby on Rails application.
@@ -269,6 +271,7 @@ test_files:
269
271
  - test/helpers/views/index.erb
270
272
  - test/helpers/views/pay.erb
271
273
  - test/helpers/views/redirect_shopper.erb
274
+ - test/hpp/signature_test.rb
272
275
  - test/integration/hpp_integration_test.rb
273
276
  - test/integration/payment_using_3d_secure_integration_test.rb
274
277
  - test/integration/payment_with_client_side_encryption_integration_test.rb