adyen 2.1.0 → 2.2.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 +4 -4
- data/CHANGELOG.md +16 -4
- data/lib/adyen.rb +3 -7
- data/lib/adyen/configuration.rb +8 -3
- data/lib/adyen/hpp.rb +27 -0
- data/lib/adyen/hpp/request.rb +192 -0
- data/lib/adyen/hpp/response.rb +52 -0
- data/lib/adyen/hpp/signature.rb +7 -6
- data/lib/adyen/matchers.rb +1 -1
- data/lib/adyen/signature.rb +1 -1
- data/lib/adyen/version.rb +1 -1
- data/test/form_test.rb +2 -2
- data/test/helpers/configure_adyen.rb +1 -0
- data/test/helpers/example_server.rb +26 -1
- data/test/helpers/views/hpp.erb +3 -2
- data/test/hpp_test.rb +250 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79d7e1124e75e3e161c514b3d4fe8eab7058bec4
|
4
|
+
data.tar.gz: 97a72df3756f9d5ab4385b17f9db517c0c67d762
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 870d40ed54714492339b6945c61acf0069d4d90a82e547e8b0c0fc69075e49b2c568b0ab25504e8efdddb7a61126fe4934c776e2003340279970167ad987f46b
|
7
|
+
data.tar.gz: 61da208663797528ecd1c8e2beaf1fd9c3bf895dead0a13833bf27b4114880a799ece1e1fb84dc77356e937fcf01a34817506c2f665099e8f32c88d2ad882ebf
|
data/CHANGELOG.md
CHANGED
@@ -4,12 +4,24 @@ The following changes have been made to the library over the years. Pleae add an
|
|
4
4
|
|
5
5
|
#### Unrelease changes
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
Nothing yet!
|
8
|
+
|
9
|
+
#### Version 2.2.0
|
10
|
+
|
11
|
+
- Add `Adyen::HPP` to integrate with Adyen's Hosted Payment Pages. `Adyen::HPP` supports the new HMAC-256 signing mechanism. `Adyen::Form` should be considered deprecated and will be removed from a future release.
|
12
|
+
|
13
|
+
#### Version 2.1.0
|
14
|
+
|
15
|
+
- Create syntax sugar for signature responses
|
16
|
+
- Various code cleanups.
|
17
|
+
|
18
|
+
#### Version 2.0.0
|
19
|
+
|
20
|
+
- Add `Adyen::REST` to intereact with Adyen's webservices. `Adyen::API` should be considered deprecated and will be removed from a future release.
|
9
21
|
- Make client-side encryption a first class citizen.
|
10
22
|
- Add integration test suite that uses a functional example app.
|
11
|
-
-
|
12
|
-
-
|
23
|
+
- Documentation updates and improvements.
|
24
|
+
- Drop support for Ruby 1.9
|
13
25
|
|
14
26
|
#### Version 1.6.0
|
15
27
|
|
data/lib/adyen.rb
CHANGED
@@ -3,18 +3,14 @@
|
|
3
3
|
# configuration methods.
|
4
4
|
#
|
5
5
|
# The most important submodules are:
|
6
|
-
# * {Adyen::
|
7
|
-
#
|
8
|
-
# * {Adyen::API} for communicating with the Adyen SOAP services for issuing
|
9
|
-
# (recurring) payments and recurring contract maintenance.
|
6
|
+
# * {Adyen::HPP} for interacting with Adyen's Hosted Payment Pages.
|
7
|
+
# * {Adyen::REST} for communicating with the Adyen REST webservices.
|
10
8
|
require 'adyen/base'
|
11
9
|
require 'adyen/version'
|
12
10
|
|
13
11
|
require 'adyen/form'
|
14
12
|
require 'adyen/api'
|
15
13
|
require 'adyen/rest'
|
16
|
-
|
17
|
-
# TODO: Move into main hpp file once it exists
|
18
|
-
require 'adyen/hpp/signature'
|
14
|
+
require 'adyen/hpp'
|
19
15
|
|
20
16
|
require 'adyen/railtie' if defined?(::Rails) && ::Rails::VERSION::MAJOR >= 3
|
data/lib/adyen/configuration.rb
CHANGED
@@ -86,14 +86,19 @@ class Adyen::Configuration
|
|
86
86
|
# @return [String]
|
87
87
|
attr_accessor :cse_public_key
|
88
88
|
|
89
|
-
# Default arguments that will be used
|
89
|
+
# Default arguments that will be used in every HTML form.
|
90
90
|
#
|
91
91
|
# @example
|
92
|
-
# Adyen.configuration.default_form_params[:
|
92
|
+
# Adyen.configuration.default_form_params[:merchant_account] = 'SuperShop'
|
93
93
|
#
|
94
94
|
# @return [Hash]
|
95
95
|
attr_accessor :default_form_params
|
96
96
|
|
97
|
+
# Name of the default skin for HPP requests.
|
98
|
+
#
|
99
|
+
# @return [String]
|
100
|
+
attr_accessor :default_skin
|
101
|
+
|
97
102
|
# Username that's set in Notification settings screen in Adyen PSP system and used by notification service to
|
98
103
|
# authenticate instant payment notification requests.
|
99
104
|
#
|
@@ -152,7 +157,7 @@ class Adyen::Configuration
|
|
152
157
|
@form_skins[skin_name.to_sym]
|
153
158
|
end
|
154
159
|
|
155
|
-
# Returns skin information by
|
160
|
+
# Returns skin information by skin code.
|
156
161
|
#
|
157
162
|
# @param [String] skin_code The code of the skin.
|
158
163
|
#
|
data/lib/adyen/hpp.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'adyen/hpp/signature'
|
2
|
+
require 'adyen/hpp/request'
|
3
|
+
require 'adyen/hpp/response'
|
4
|
+
|
5
|
+
module Adyen
|
6
|
+
module HPP
|
7
|
+
|
8
|
+
# The DOMAIN of the Adyen payment system that still requires the current
|
9
|
+
# Adyen enviroment.
|
10
|
+
HPP_DOMAIN = "%s.adyen.com"
|
11
|
+
|
12
|
+
# The URL of the Adyen payment system that still requires the current
|
13
|
+
# domain and payment flow to be filled.
|
14
|
+
HPP_URL = "https://%s/hpp/%s.shtml"
|
15
|
+
|
16
|
+
class Error < Adyen::Error
|
17
|
+
end
|
18
|
+
|
19
|
+
class ForgedResponse < Adyen::HPP::Error
|
20
|
+
end
|
21
|
+
|
22
|
+
class Notification
|
23
|
+
def initialize(request)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'adyen/hpp/signature'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module Adyen
|
5
|
+
module HPP
|
6
|
+
|
7
|
+
class Request
|
8
|
+
attr_accessor :parameters
|
9
|
+
attr_writer :skin, :environment, :shared_secret
|
10
|
+
|
11
|
+
# Initialize the HPP request
|
12
|
+
#
|
13
|
+
# @param [Hash] parameters The payment parameters
|
14
|
+
# You must not provide the +:merchant_sig+ parameter: it will be calculated automatically.
|
15
|
+
# @param [Hash|String] skin A skin hash in the same format that is returned by
|
16
|
+
# Adyen::Configuration.register_form_skin, or the name of a registered skin.
|
17
|
+
# When not set, the default skin specified in the configuration will be used.
|
18
|
+
# @param [String] environment The Adyen environment to use.
|
19
|
+
# When not set, the environment specified in the configuration will be used.
|
20
|
+
# @param [String] shared_secret The shared secret to use for signing the request.
|
21
|
+
# When not set, the shared secret of the skin will be used.
|
22
|
+
def initialize(parameters, skin: nil, environment: nil, shared_secret: nil)
|
23
|
+
@parameters, @skin, @environment, @shared_secret = parameters, skin, environment, shared_secret
|
24
|
+
@skin = Adyen.configuration.form_skin_by_name(@skin) unless skin.nil? || skin.is_a?(Hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the Adyen skin to use for the request, in the same format that is
|
28
|
+
# returned by Adyen::Configuration.register_form_skin
|
29
|
+
#
|
30
|
+
# @return [Hash] skin if set, configuration default otherwise
|
31
|
+
def skin
|
32
|
+
@skin || Adyen.configuration.form_skin_by_name(Adyen.configuration.default_skin) || {}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the Adyen environment the request will be directed to
|
36
|
+
#
|
37
|
+
# @return [String] environment if set, configuration default otherwise
|
38
|
+
def environment
|
39
|
+
@environment || Adyen.configuration.environment
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the shared secret to use for signing the request
|
43
|
+
#
|
44
|
+
# @return [String] shared secret if set, the skin's shared secret otherwise
|
45
|
+
def shared_secret
|
46
|
+
@shared_secret || skin[:shared_secret]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the DOMAIN of the Adyen payment system, adjusted for an Adyen environment.
|
50
|
+
#
|
51
|
+
# @return [String] The domain of the Adyen payment system that can be used
|
52
|
+
# for payment forms or redirects.
|
53
|
+
# @see Adyen::HPP::Request.redirect_url
|
54
|
+
def domain
|
55
|
+
(Adyen.configuration.payment_flow_domain || HPP_DOMAIN) % [environment.to_s]
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the URL of the Adyen payment system, adjusted for an Adyen environment.
|
59
|
+
#
|
60
|
+
# @param [String] payment_flow The Adyen payment type to use. This parameter can be
|
61
|
+
# left out, in which case the default payment type will be used.
|
62
|
+
# @return [String] The absolute URL of the Adyen payment system that can be used
|
63
|
+
# for payment forms or redirects.
|
64
|
+
# @see Adyen::HPP::Request.domain
|
65
|
+
# @see Adyen::HPP::Request.redirect_url
|
66
|
+
# @see Adyen::HPP::Request.payment_methods_url
|
67
|
+
def url(payment_flow = nil)
|
68
|
+
payment_flow ||= Adyen.configuration.payment_flow
|
69
|
+
HPP_URL % [domain, payment_flow.to_s]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Transforms the payment parameters hash to be in the correct format. It will also
|
73
|
+
# include the Adyen::Configuration#default_form_params hash and it will
|
74
|
+
# include the +:skin_code+ parameter and the default attributes of the skin
|
75
|
+
# Any default parameter value will be overrided if another value is provided in the request.
|
76
|
+
#
|
77
|
+
# @return [Hash] Completed and formatted payment parameters.
|
78
|
+
# @raise [ArgumentError] Thrown if some parameter health check fails.
|
79
|
+
def formatted_parameters
|
80
|
+
raise ArgumentError, "Cannot generate request: parameters should be a hash!" unless parameters.is_a?(Hash)
|
81
|
+
|
82
|
+
formatted_parameters = parameters
|
83
|
+
default_form_parameters = Adyen.configuration.default_form_params
|
84
|
+
unless skin.empty?
|
85
|
+
formatted_parameters[:skin_code] ||= skin[:skin_code]
|
86
|
+
default_form_parameters = default_form_parameters.merge(skin[:default_form_params] || {})
|
87
|
+
end
|
88
|
+
formatted_parameters = default_form_parameters.merge(formatted_parameters)
|
89
|
+
|
90
|
+
raise ArgumentError, "Cannot generate request: :currency code attribute not found!" unless formatted_parameters[:currency_code]
|
91
|
+
raise ArgumentError, "Cannot generate request: :payment_amount code attribute not found!" unless formatted_parameters[:payment_amount]
|
92
|
+
raise ArgumentError, "Cannot generate request: :merchant_account attribute not found!" unless formatted_parameters[:merchant_account]
|
93
|
+
raise ArgumentError, "Cannot generate request: :skin_code attribute not found!" unless formatted_parameters[:skin_code]
|
94
|
+
|
95
|
+
formatted_parameters[:recurring_contract] = 'RECURRING' if formatted_parameters.delete(:recurring) == true
|
96
|
+
formatted_parameters[:order_data] = Adyen::Util.gzip_base64(formatted_parameters.delete(:order_data_raw)) if formatted_parameters[:order_data_raw]
|
97
|
+
formatted_parameters[:ship_before_date] = Adyen::Util.format_date(formatted_parameters[:ship_before_date])
|
98
|
+
formatted_parameters[:session_validity] = Adyen::Util.format_timestamp(formatted_parameters[:session_validity])
|
99
|
+
formatted_parameters
|
100
|
+
end
|
101
|
+
|
102
|
+
# Transforms and flattens payment parameters to be in the correct format which is understood and accepted by adyen
|
103
|
+
#
|
104
|
+
# @return [Hash] The payment parameters, with camelized and prefixed key, stringified values and
|
105
|
+
# the +:merchant_signature+ parameter set.
|
106
|
+
def flat_payment_parameters
|
107
|
+
Adyen::HPP::Signature.sign(Adyen::Util.flatten(formatted_parameters), shared_secret)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns an absolute URL to the Adyen payment system, with the payment parameters included
|
111
|
+
# as GET parameters in the URL. The URL also depends on the Adyen enviroment
|
112
|
+
#
|
113
|
+
# Note that Internet Explorer has a maximum length for URLs it can handle (2083 characters).
|
114
|
+
# Make sure that the URL is not longer than this limit if you want your site to work in IE.
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
#
|
118
|
+
# def pay
|
119
|
+
# # Generate a URL to redirect to Adyen's payment system.
|
120
|
+
# payment_parameters = {
|
121
|
+
# :currency_code => 'USD',
|
122
|
+
# :payment_amount => 1000,
|
123
|
+
# :merchant_account => 'MyMerchant',
|
124
|
+
# ...
|
125
|
+
# }
|
126
|
+
# hpp_request = Adyen::HPP::Request.new(payment_parameters, skin: :my_skin, environment: :test)
|
127
|
+
#
|
128
|
+
# respond_to do |format|
|
129
|
+
# format.html { redirect_to(hpp_request.redirect_url) }
|
130
|
+
# end
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# @return [String] An absolute URL to redirect to the Adyen payment system.
|
134
|
+
def redirect_url
|
135
|
+
url + '?' + flat_payment_parameters.map { |(k, v)|
|
136
|
+
"#{CGI.escape(k)}=#{CGI.escape(v)}"
|
137
|
+
}.join('&')
|
138
|
+
end
|
139
|
+
|
140
|
+
# @see Adyen::HPP::Request.redirect_url
|
141
|
+
#
|
142
|
+
# Returns an absolute URL very similar to the one returned by Adyen::HPP::Request.redirect_url
|
143
|
+
# except that it uses the directory.shtml call which returns a list of all available
|
144
|
+
# payment methods
|
145
|
+
#
|
146
|
+
# @return [String] An absolute URL to redirect to the Adyen payment system.
|
147
|
+
def payment_methods_url
|
148
|
+
url(:directory) + '?' + flat_payment_parameters.map { |(k, v)|
|
149
|
+
"#{CGI.escape(k)}=#{CGI.escape(v)}"
|
150
|
+
}.join('&')
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns a HTML snippet of hidden INPUT tags with the provided payment parameters.
|
154
|
+
# The snippet can be included in a payment form that POSTs to the Adyen payment system.
|
155
|
+
#
|
156
|
+
# The payment parameters that are provided to this method will be merged with the
|
157
|
+
# {Adyen::Configuration#default_form_params} hash. The default parameter values will be
|
158
|
+
# overrided if another value is provided to this method.
|
159
|
+
#
|
160
|
+
# You do not have to provide the +:merchant_sig+ parameter: it will be calculated automatically.
|
161
|
+
#
|
162
|
+
# @example
|
163
|
+
#
|
164
|
+
# <%
|
165
|
+
# payment_parameters = {
|
166
|
+
# :currency_code => 'USD',
|
167
|
+
# :payment_amount => 1000,
|
168
|
+
# :merchant_account => 'MyMerchant',
|
169
|
+
# ...
|
170
|
+
# }
|
171
|
+
# hpp_request = Adyen::HPP::Request.new(payment_parameters, skin: :my_skin, environment: :test)
|
172
|
+
# %>
|
173
|
+
#
|
174
|
+
# <%= form_tag(hpp_request.url, authenticity_token: false, enforce_utf8: false) do %>
|
175
|
+
# <%= hpp_request.hidden_fields %>
|
176
|
+
# <%= submit_tag("Pay invoice")
|
177
|
+
# <% end %>
|
178
|
+
#
|
179
|
+
# @return [String] An HTML snippet that can be included in a form that POSTs to the
|
180
|
+
# Adyen payment system.
|
181
|
+
def hidden_fields
|
182
|
+
|
183
|
+
# Generate a hidden input tag per parameter, join them by newlines.
|
184
|
+
form_str = flat_payment_parameters.map { |key, value|
|
185
|
+
"<input type=\"hidden\" name=\"#{CGI.escapeHTML(key)}\" value=\"#{CGI.escapeHTML(value)}\" />"
|
186
|
+
}.join("\n")
|
187
|
+
|
188
|
+
form_str.respond_to?(:html_safe) ? form_str.html_safe : form_str
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Adyen
|
2
|
+
module HPP
|
3
|
+
|
4
|
+
class Response
|
5
|
+
attr_reader :params, :shared_secret
|
6
|
+
|
7
|
+
# Initialize the HPP response
|
8
|
+
#
|
9
|
+
# @param [Hash] params params A hash of HTTP GET parameters for the redirect request. This
|
10
|
+
# should include the +:merchantSig+ parameter, which contains the signature.
|
11
|
+
# @param [String] shared_secret Optional shared secret; if not provided, the shared secret
|
12
|
+
# of the skin determined by params['skinCode'] will be used
|
13
|
+
def initialize(params, shared_secret: nil)
|
14
|
+
raise ArgumentError, "params should be a Hash" unless params.is_a?(Hash)
|
15
|
+
raise ArgumentError, "params should contain :merchantSig" unless params.key?('merchantSig')
|
16
|
+
|
17
|
+
@params = params
|
18
|
+
skin = Adyen.configuration.form_skin_by_code(params['skinCode']) || {}
|
19
|
+
@shared_secret = shared_secret || skin[:shared_secret]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Checks the redirect signature for this request by calculating the signature from
|
23
|
+
# the provided parameters, and comparing it to the signature provided in the +merchantSig+
|
24
|
+
# parameter.
|
25
|
+
#
|
26
|
+
# If this method returns false, the request could be a forgery and should not be handled.
|
27
|
+
# Therefore, you should include this check in a +before_filter+, and raise an error of the
|
28
|
+
# signature check fails.
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# class PaymentsController < ApplicationController
|
32
|
+
# before_filter :check_signature, :only => [:return_from_adyen]
|
33
|
+
#
|
34
|
+
# def return_from_adyen
|
35
|
+
# @invoice = Invoice.find(params[:merchantReference])
|
36
|
+
# @invoice.set_paid! if params[:authResult] == 'AUTHORISED'
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# private
|
40
|
+
#
|
41
|
+
# def check_signature
|
42
|
+
# raise "Forgery!" unless Adyen::HPP::Response.new(params).has_valid_signature?
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# @return [true, false] Returns true only if the signature in the parameters is correct.
|
47
|
+
def has_valid_signature?
|
48
|
+
Adyen::HPP::Signature.verify(params, shared_secret)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/adyen/hpp/signature.rb
CHANGED
@@ -8,10 +8,11 @@ module Adyen
|
|
8
8
|
|
9
9
|
# Sign the parameters with the given shared secret
|
10
10
|
# @param [Hash] params The set of parameters to sign.
|
11
|
-
# @param [String] shared_secret The shared secret for signing
|
12
|
-
# params hash with the `sharedSecret` key.
|
11
|
+
# @param [String] shared_secret The shared secret for signing.
|
13
12
|
# @return [Hash] params The params that were passed in plus a new `merchantSig` param
|
14
|
-
def sign(params, shared_secret
|
13
|
+
def sign(params, shared_secret)
|
14
|
+
params = params.dup
|
15
|
+
params.delete('merchantSig')
|
15
16
|
params["sharedSecret"] ||= shared_secret
|
16
17
|
params.merge('merchantSig' => Adyen::Signature.sign(params))
|
17
18
|
end
|
@@ -19,10 +20,10 @@ module Adyen
|
|
19
20
|
# Verify the parameters with the given shared secret
|
20
21
|
# @param [Hash] params The set of parameters to verify. Must include a `merchantSig`
|
21
22
|
# param that will be compared to the signature we calculate.
|
22
|
-
# @param [String] shared_secret The shared secret for
|
23
|
-
# params hash with the `sharedSecret` key.
|
23
|
+
# @param [String] shared_secret The shared secret for verification.
|
24
24
|
# @return [Boolean] true if the `merchantSig` in the params matches our calculated signature
|
25
|
-
def verify(params, shared_secret
|
25
|
+
def verify(params, shared_secret)
|
26
|
+
params = params.dup
|
26
27
|
params["sharedSecret"] ||= shared_secret
|
27
28
|
their_sig = params.delete('merchantSig')
|
28
29
|
raise ArgumentError, "params must include 'merchantSig' for verification" if their_sig.empty?
|
data/lib/adyen/matchers.rb
CHANGED
@@ -7,7 +7,7 @@ module Adyen
|
|
7
7
|
|
8
8
|
def self.build_xpath_query(checks)
|
9
9
|
# Start by finding the check for the Adyen form tag
|
10
|
-
xpath_query = "//form[@
|
10
|
+
xpath_query = "//form[@id='adyen']"
|
11
11
|
|
12
12
|
# Add recurring/single check if specified
|
13
13
|
recurring = checks.delete(:recurring)
|
data/lib/adyen/signature.rb
CHANGED
@@ -13,7 +13,7 @@ module Adyen
|
|
13
13
|
# @return [String] The signature
|
14
14
|
def sign(params, type = :hpp)
|
15
15
|
shared_secret = params.delete('sharedSecret')
|
16
|
-
raise ArgumentError,
|
16
|
+
raise ArgumentError, "Cannot sign parameters without a shared secret" unless shared_secret
|
17
17
|
sig = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), Array(shared_secret).pack("H*"), string_to_sign(params, type))
|
18
18
|
Base64.encode64(sig).strip
|
19
19
|
end
|
data/lib/adyen/version.rb
CHANGED
data/test/form_test.rb
CHANGED
@@ -270,7 +270,7 @@ class FormTest < Minitest::Test
|
|
270
270
|
|
271
271
|
def test_hidden_payment_form_fields
|
272
272
|
payment_snippet = <<-HTML
|
273
|
-
<form action="#{CGI.escapeHTML(Adyen::Form.url)}" method="post">
|
273
|
+
<form id="adyen" action="#{CGI.escapeHTML(Adyen::Form.url)}" method="post">
|
274
274
|
#{Adyen::Form.hidden_fields(@payment_attributes)}
|
275
275
|
</form>
|
276
276
|
HTML
|
@@ -286,7 +286,7 @@ class FormTest < Minitest::Test
|
|
286
286
|
|
287
287
|
def test_hidden_recurring_payment_form_fields
|
288
288
|
recurring_snippet = <<-HTML
|
289
|
-
<form action="#{CGI.escapeHTML(Adyen::Form.url)}" method="post">
|
289
|
+
<form id="adyen" action="#{CGI.escapeHTML(Adyen::Form.url)}" method="post">
|
290
290
|
#{Adyen::Form.hidden_fields(@recurring_payment_attributes)}
|
291
291
|
</form>
|
292
292
|
HTML
|
@@ -3,3 +3,4 @@ Adyen.configuration.api_username = 'ws@Company.VanBergen'
|
|
3
3
|
Adyen.configuration.api_password = '7phtHzbfnzsp'
|
4
4
|
Adyen.configuration.cse_public_key = '10001|AC2CEF7D1BE904AF35B76467927A7044CCFC470E725B4D5327CC143BC5245983A56A4E5B0AC969F7F706F4B4A81B184DE2ECEA229CDBB943E6F7D6AD1623604F66640D1F2FAE1E4AE80EE1E5D4486AA8F553F6CE47BF57C8EFEF745E731AE27DD7B74F8895D41DA8339CC32677E4BD35288EC2FB9A18D46E7E3DFF5C7DD6D756F2223BED29427E5899A5877F0E9D1FAA50C17F8C96BA96DFA79BD04B1116366FFEE33F1BD18B8C3694BACBF8BFC7E2045CB9FFFEFA49AAB18EF1D9E9710A16B7DC67433ABD3A4FD3661CD9F5B1967753E4DC744DD3A1F8C8BED87827EED443CBED06A0D5C86C329406BE452B9A6EB997EE43A9FA7241A74E87AC3FC898F327CD'
|
5
5
|
Adyen.configuration.register_form_skin(:testing, 'tifSfXeX', 'testing123', :merchant_account => 'VanBergenORG')
|
6
|
+
Adyen.configuration.default_skin = :testing
|
@@ -12,6 +12,31 @@ class Adyen::ExampleServer < Sinatra::Base
|
|
12
12
|
end
|
13
13
|
|
14
14
|
get '/hpp' do
|
15
|
+
@payment = {
|
16
|
+
:currency_code => 'EUR',
|
17
|
+
:payment_amount => 4321,
|
18
|
+
:merchant_reference => params[:merchant_reference] || 'HPP test order',
|
19
|
+
:ship_before_date => (Date.today + 1).strftime('%F'),
|
20
|
+
:session_validity => (Time.now.utc + 30*60).strftime('%FT%TZ'),
|
21
|
+
:billing_address => {
|
22
|
+
:street => 'Alexanderplatz',
|
23
|
+
:house_number_or_name => '0815',
|
24
|
+
:city => 'Berlin',
|
25
|
+
:postal_code => '10119',
|
26
|
+
:state_or_province => 'Berlin',
|
27
|
+
:country => 'Germany',
|
28
|
+
},
|
29
|
+
:shopper => {
|
30
|
+
:telephone_number => '123-4512-345',
|
31
|
+
:first_name => 'John',
|
32
|
+
:last_name => 'Doe',
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
erb :hpp
|
37
|
+
end
|
38
|
+
|
39
|
+
get '/form' do
|
15
40
|
@payment = {
|
16
41
|
:skin => :testing,
|
17
42
|
:currency_code => 'EUR',
|
@@ -38,7 +63,7 @@ class Adyen::ExampleServer < Sinatra::Base
|
|
38
63
|
end
|
39
64
|
|
40
65
|
get '/hpp/result' do
|
41
|
-
raise "Forgery!" unless Adyen::
|
66
|
+
raise "Forgery!" unless Adyen::HPP::Response(params).has_valid_signature?
|
42
67
|
|
43
68
|
case params['authResult']
|
44
69
|
when 'AUTHORISED'
|
data/test/helpers/views/hpp.erb
CHANGED
@@ -3,10 +3,11 @@
|
|
3
3
|
<title> HPP Payment</title>
|
4
4
|
</head>
|
5
5
|
<body>
|
6
|
-
|
6
|
+
<% hpp_client = Adyen::HPP::Client.new(:test, :testing) %>
|
7
|
+
<form action="<%= hpp_client.url %>" method="post">
|
7
8
|
<p> Price: <strong>EUR 43.21</strong>. </p>
|
8
9
|
<p>
|
9
|
-
<%=
|
10
|
+
<%= hpp_client.new_request.hidden_fields(@payment) %>
|
10
11
|
<input type="submit" value="Pay" />
|
11
12
|
</p>
|
12
13
|
</form>
|
data/test/hpp_test.rb
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'adyen/hpp'
|
3
|
+
|
4
|
+
class HppTest < Minitest::Test
|
5
|
+
include Adyen::Matchers
|
6
|
+
include Adyen::Test::EachXMLBackend
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@skin_code1 = 'abcdefgh'
|
10
|
+
@skin_code2 = 'ijklmnop'
|
11
|
+
@shared_secret_skin1 = '4468D9782DEF54FCD706C9100C71EC43932B1EBC2ACF6BA0560C05AAA7550C48'
|
12
|
+
@shared_secret_skin2 = '21F58626031F08A30F6BD07BB8AC12C19B56F6C99C6E1DA991A52A1C64A4C010'
|
13
|
+
|
14
|
+
Adyen.configuration.default_form_params[:merchant_account] = 'TestMerchant'
|
15
|
+
Adyen.configuration.register_form_skin(:skin1, @skin_code1, @shared_secret_skin1)
|
16
|
+
Adyen.configuration.register_form_skin(:skin2, @skin_code2, @shared_secret_skin2, merchant_account: 'OtherMerchant')
|
17
|
+
|
18
|
+
# Use autodetection for the environment unless otherwise specified
|
19
|
+
Adyen.configuration.environment = nil
|
20
|
+
Adyen.configuration.payment_flow = :select
|
21
|
+
Adyen.configuration.payment_flow_domain = nil
|
22
|
+
Adyen.configuration.default_skin = :skin1
|
23
|
+
|
24
|
+
@payment_attributes = {
|
25
|
+
:currency_code => 'GBP',
|
26
|
+
:payment_amount => 10000,
|
27
|
+
:merchant_reference => 'Internet Order 12345',
|
28
|
+
:ship_before_date => '2007-10-20',
|
29
|
+
:session_validity => '2007-10-11T11:00:00Z',
|
30
|
+
:billing_address => {
|
31
|
+
:street => 'Alexanderplatz',
|
32
|
+
:house_number_or_name => '0815',
|
33
|
+
:city => 'Berlin',
|
34
|
+
:postal_code => '10119',
|
35
|
+
:state_or_province => 'Berlin',
|
36
|
+
:country => 'Germany',
|
37
|
+
},
|
38
|
+
:shopper => {
|
39
|
+
:telephone_number => '1234512345',
|
40
|
+
:first_name => 'John',
|
41
|
+
:last_name => 'Doe',
|
42
|
+
:social_security_number => '123-45-1234'
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
@recurring_payment_attributes = @payment_attributes.merge(
|
47
|
+
:recurring_contract => 'DEFAULT',
|
48
|
+
:shopper_reference => 'grasshopper52',
|
49
|
+
:shopper_email => 'gras.shopper@somewhere.org'
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_autodetected_redirect_url
|
54
|
+
request = Adyen::HPP::Request.new(@payment_attributes)
|
55
|
+
assert_equal 'https://test.adyen.com/hpp/select.shtml', request.url
|
56
|
+
|
57
|
+
Adyen.configuration.stubs(:autodetect_environment).returns('live')
|
58
|
+
assert_equal 'https://live.adyen.com/hpp/select.shtml', request.url
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_explicit_redirect_url
|
62
|
+
assert_equal 'https://test.adyen.com/hpp/select.shtml',
|
63
|
+
Adyen::HPP::Request.new(@payment_attributes, skin: :skin1, environment: :test).url
|
64
|
+
assert_equal 'https://live.adyen.com/hpp/select.shtml',
|
65
|
+
Adyen::HPP::Request.new(@payment_attributes, skin: :skin1, environment: :live).url
|
66
|
+
assert_equal 'https://test.adyen.com/hpp/select.shtml',
|
67
|
+
Adyen::HPP::Request.new(@payment_attributes, skin: :skin2, environment: :test).url
|
68
|
+
assert_equal 'https://live.adyen.com/hpp/select.shtml',
|
69
|
+
Adyen::HPP::Request.new(@payment_attributes, skin: :skin2, environment: :live).url
|
70
|
+
assert_equal 'https://test.adyen.com/hpp/select.shtml',
|
71
|
+
Adyen::HPP::Request.new(@payment_attributes, environment: :test).url
|
72
|
+
assert_equal 'https://live.adyen.com/hpp/select.shtml',
|
73
|
+
Adyen::HPP::Request.new(@payment_attributes, environment: :live).url
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_redirect_url_for_different_payment_flows
|
77
|
+
request = Adyen::HPP::Request.new(@payment_attributes, environment: :test)
|
78
|
+
|
79
|
+
Adyen.configuration.payment_flow = :select
|
80
|
+
assert_equal 'https://test.adyen.com/hpp/select.shtml', request.url
|
81
|
+
|
82
|
+
Adyen.configuration.payment_flow = :pay
|
83
|
+
assert_equal 'https://test.adyen.com/hpp/pay.shtml', request.url
|
84
|
+
|
85
|
+
Adyen.configuration.payment_flow = :details
|
86
|
+
assert_equal 'https://test.adyen.com/hpp/details.shtml', request.url
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_redirect_url_for_custom_domain
|
90
|
+
request = Adyen::HPP::Request.new(@payment_attributes, environment: :test)
|
91
|
+
|
92
|
+
Adyen.configuration.payment_flow_domain = "checkout.mydomain.com"
|
93
|
+
assert_equal 'https://checkout.mydomain.com/hpp/select.shtml', request.url
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_redirect_url_generation
|
97
|
+
attributes = {
|
98
|
+
:currency_code => 'GBP', :payment_amount => 10000, :ship_before_date => Date.parse('2015-10-26'),
|
99
|
+
:merchant_reference => 'Internet Order 12345', :session_validity => Time.parse('2015-10-26 10:30')
|
100
|
+
}
|
101
|
+
|
102
|
+
request = Adyen::HPP::Request.new(attributes)
|
103
|
+
|
104
|
+
processed_attributes = {
|
105
|
+
'currencyCode' => 'GBP', 'paymentAmount' => '10000', 'shipBeforeDate' => '2015-10-26',
|
106
|
+
'merchantReference' => 'Internet Order 12345', 'sessionValidity' => '2015-10-26T10:30:00Z',
|
107
|
+
'merchantAccount' => 'TestMerchant', 'skinCode' => @skin_code1, 'merchantSig' => 'ewDgqa+m3rMO6MOZfQ0ugWdwsu+otvRVBVujqGfgvb8='
|
108
|
+
}
|
109
|
+
|
110
|
+
redirect_uri = URI(request.redirect_url)
|
111
|
+
assert_match %r[^#{request.url}], redirect_uri.to_s
|
112
|
+
|
113
|
+
params = CGI.parse(redirect_uri.query)
|
114
|
+
processed_attributes.each do |key, value|
|
115
|
+
assert_equal value, params[key].first
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_redirect_url_generation_explicit_skin_code_and_shared_secret
|
120
|
+
attributes = {
|
121
|
+
:currency_code => 'GBP', :payment_amount => 10000, :ship_before_date => Date.parse('2015-10-26'),
|
122
|
+
:merchant_reference => 'Internet Order 12345', :session_validity => Time.parse('2015-10-26 10:30'),
|
123
|
+
:skin_code => @skin_code1
|
124
|
+
}
|
125
|
+
|
126
|
+
request = Adyen::HPP::Request.new(attributes, shared_secret: @shared_secret_skin1)
|
127
|
+
|
128
|
+
processed_attributes = {
|
129
|
+
'currencyCode' => 'GBP', 'paymentAmount' => '10000', 'shipBeforeDate' => '2015-10-26',
|
130
|
+
'merchantReference' => 'Internet Order 12345', 'sessionValidity' => '2015-10-26T10:30:00Z',
|
131
|
+
'merchantAccount' => 'TestMerchant', 'skinCode' => @skin_code1, 'merchantSig' => 'ewDgqa+m3rMO6MOZfQ0ugWdwsu+otvRVBVujqGfgvb8='
|
132
|
+
}
|
133
|
+
|
134
|
+
redirect_uri = URI(request.redirect_url)
|
135
|
+
assert_match %r[^#{request.url}], redirect_uri.to_s
|
136
|
+
|
137
|
+
params = CGI.parse(redirect_uri.query)
|
138
|
+
processed_attributes.each do |key, value|
|
139
|
+
assert_equal value, params[key].first
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_redirect_url_generation_with_direct_skin_details
|
144
|
+
attributes = {
|
145
|
+
:currency_code => 'GBP', :payment_amount => 10000, :ship_before_date => Date.parse('2015-10-26'),
|
146
|
+
:merchant_reference => 'Internet Order 12345', :session_validity => Time.parse('2015-10-26 10:30'),
|
147
|
+
:merchant_account => 'OtherMerchant'
|
148
|
+
}
|
149
|
+
|
150
|
+
request = Adyen::HPP::Request.new(attributes, skin: :skin2)
|
151
|
+
|
152
|
+
processed_attributes = {
|
153
|
+
'currencyCode' => 'GBP', 'paymentAmount' => '10000', 'shipBeforeDate' => '2015-10-26',
|
154
|
+
'merchantReference' => 'Internet Order 12345', 'sessionValidity' => '2015-10-26T10:30:00Z',
|
155
|
+
'merchantAccount' => 'OtherMerchant', 'skinCode' => @skin_code2, 'merchantSig' => 'uS/tdqapxD8rQKBRzKQ9wOIiOFRmcOR3HsC8CO15Zto='
|
156
|
+
}
|
157
|
+
|
158
|
+
redirect_uri = URI(request.redirect_url)
|
159
|
+
assert_match %r[^#{request.url}], redirect_uri.to_s
|
160
|
+
|
161
|
+
params = CGI.parse(redirect_uri.query)
|
162
|
+
processed_attributes.each do |key, value|
|
163
|
+
assert_equal value, params[key].first
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_payment_methods_url_generation
|
168
|
+
attributes = {
|
169
|
+
:currency_code => 'GBP', :payment_amount => 10000, :ship_before_date => Date.parse('2015-10-26'),
|
170
|
+
:merchant_reference => 'Internet Order 12345', :session_validity => Time.parse('2015-10-26 10:30')
|
171
|
+
}
|
172
|
+
|
173
|
+
request = Adyen::HPP::Request.new(attributes)
|
174
|
+
|
175
|
+
processed_attributes = {
|
176
|
+
'currencyCode' => 'GBP', 'paymentAmount' => '10000', 'shipBeforeDate' => '2015-10-26',
|
177
|
+
'merchantReference' => 'Internet Order 12345', 'sessionValidity' => '2015-10-26T10:30:00Z',
|
178
|
+
'merchantAccount' => 'TestMerchant', 'skinCode' => @skin_code1, 'merchantSig' => 'ewDgqa+m3rMO6MOZfQ0ugWdwsu+otvRVBVujqGfgvb8='
|
179
|
+
}
|
180
|
+
|
181
|
+
payment_methods_uri = URI(request.payment_methods_url)
|
182
|
+
assert_match %r[^#{request.url(:directory)}], payment_methods_uri.to_s
|
183
|
+
|
184
|
+
params = CGI.parse(payment_methods_uri.query)
|
185
|
+
processed_attributes.each do |key, value|
|
186
|
+
assert_equal value, params[key].first
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_has_valid_signature
|
191
|
+
params = {
|
192
|
+
'authResult' => 'AUTHORISED', 'pspReference' => '1211992213193029',
|
193
|
+
'merchantReference' => 'Internet Order 12345', 'skinCode' => @skin_code1,
|
194
|
+
'merchantSig' => 'owrLGxBP/l5xej5VZn8FKS1exn0qOgk0P9kmRdBBw9Q='
|
195
|
+
}
|
196
|
+
|
197
|
+
correct_secret = @shared_secret_skin1
|
198
|
+
incorrect_secret = @shared_secret_skin2
|
199
|
+
|
200
|
+
assert Adyen::HPP::Response.new(params).has_valid_signature?
|
201
|
+
assert Adyen::HPP::Response.new(params, shared_secret: correct_secret).has_valid_signature?
|
202
|
+
|
203
|
+
refute Adyen::HPP::Response.new(params.merge('skinCode' => @skin_code2)).has_valid_signature?
|
204
|
+
refute Adyen::HPP::Response.new(params, shared_secret: incorrect_secret).has_valid_signature?
|
205
|
+
|
206
|
+
refute Adyen::HPP::Response.new(params.merge('pspReference' => 'tampered')).has_valid_signature?
|
207
|
+
refute Adyen::HPP::Response.new(params.merge('merchantSig' => 'tampered')).has_valid_signature?
|
208
|
+
|
209
|
+
assert_raises(ArgumentError) { Adyen::HPP::Response.new(nil).has_valid_signature? }
|
210
|
+
assert_raises(ArgumentError) { Adyen::HPP::Response.new({}).has_valid_signature? }
|
211
|
+
assert_raises(ArgumentError) { Adyen::HPP::Response.new(params.delete(:skinCode)).has_valid_signature? }
|
212
|
+
end
|
213
|
+
|
214
|
+
def test_hidden_payment_form_fields
|
215
|
+
request = Adyen::HPP::Request.new(@payment_attributes, skin: :skin1, environment: :test)
|
216
|
+
|
217
|
+
payment_snippet = <<-HTML
|
218
|
+
<form id="adyen" action="#{CGI.escapeHTML(request.url)}" method="post">
|
219
|
+
#{request.hidden_fields}
|
220
|
+
</form>
|
221
|
+
HTML
|
222
|
+
|
223
|
+
for_each_xml_backend do
|
224
|
+
assert_adyen_single_payment_form payment_snippet,
|
225
|
+
merchantAccount: 'TestMerchant',
|
226
|
+
currencyCode: 'GBP',
|
227
|
+
paymentAmount: '10000',
|
228
|
+
skinCode: @skin_code1
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def test_hidden_recurring_payment_form_fields
|
233
|
+
request = Adyen::HPP::Request.new(@recurring_payment_attributes, skin: :skin2, environment: :live)
|
234
|
+
|
235
|
+
recurring_snippet = <<-HTML
|
236
|
+
<form id="adyen" action="#{CGI.escapeHTML(request.url)}" method="post">
|
237
|
+
#{request.hidden_fields}
|
238
|
+
</form>
|
239
|
+
HTML
|
240
|
+
|
241
|
+
for_each_xml_backend do
|
242
|
+
assert_adyen_recurring_payment_form recurring_snippet,
|
243
|
+
merchantAccount: 'OtherMerchant',
|
244
|
+
currencyCode: 'GBP',
|
245
|
+
paymentAmount: '10000',
|
246
|
+
recurringContract: 'DEFAULT',
|
247
|
+
skinCode: @skin_code2
|
248
|
+
end
|
249
|
+
end
|
250
|
+
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.
|
4
|
+
version: 2.2.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: 2016-06-
|
14
|
+
date: 2016-06-23 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rake
|
@@ -166,6 +166,9 @@ files:
|
|
166
166
|
- lib/adyen/base.rb
|
167
167
|
- lib/adyen/configuration.rb
|
168
168
|
- lib/adyen/form.rb
|
169
|
+
- lib/adyen/hpp.rb
|
170
|
+
- lib/adyen/hpp/request.rb
|
171
|
+
- lib/adyen/hpp/response.rb
|
169
172
|
- lib/adyen/hpp/signature.rb
|
170
173
|
- lib/adyen/matchers.rb
|
171
174
|
- lib/adyen/notification_generator.rb
|
@@ -210,6 +213,7 @@ files:
|
|
210
213
|
- test/helpers/views/pay.erb
|
211
214
|
- test/helpers/views/redirect_shopper.erb
|
212
215
|
- test/hpp/signature_test.rb
|
216
|
+
- test/hpp_test.rb
|
213
217
|
- test/integration/hpp_integration_test.rb
|
214
218
|
- test/integration/payment_using_3d_secure_integration_test.rb
|
215
219
|
- test/integration/payment_with_client_side_encryption_integration_test.rb
|
@@ -279,6 +283,7 @@ test_files:
|
|
279
283
|
- test/helpers/views/pay.erb
|
280
284
|
- test/helpers/views/redirect_shopper.erb
|
281
285
|
- test/hpp/signature_test.rb
|
286
|
+
- test/hpp_test.rb
|
282
287
|
- test/integration/hpp_integration_test.rb
|
283
288
|
- test/integration/payment_using_3d_secure_integration_test.rb
|
284
289
|
- test/integration/payment_with_client_side_encryption_integration_test.rb
|