adyen_jpiqueras 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +30 -0
- data/CHANGELOG.md +128 -0
- data/CONTRIBUTING.md +85 -0
- data/Gemfile +11 -0
- data/LICENSE +21 -0
- data/README.md +31 -0
- data/Rakefile +54 -0
- data/adyen_jpiqueras.gemspec +44 -0
- data/config.ru +5 -0
- data/lib/adyen.rb +16 -0
- data/lib/adyen/api.rb +424 -0
- data/lib/adyen/api/cacert.pem +3894 -0
- data/lib/adyen/api/payment_service.rb +374 -0
- data/lib/adyen/api/recurring_service.rb +188 -0
- data/lib/adyen/api/response.rb +61 -0
- data/lib/adyen/api/simple_soap_client.rb +134 -0
- data/lib/adyen/api/templates/payment_service.rb +159 -0
- data/lib/adyen/api/templates/recurring_service.rb +71 -0
- data/lib/adyen/api/test_helpers.rb +133 -0
- data/lib/adyen/api/xml_querier.rb +137 -0
- data/lib/adyen/base.rb +17 -0
- data/lib/adyen/configuration.rb +179 -0
- data/lib/adyen/form.rb +419 -0
- 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 +34 -0
- data/lib/adyen/matchers.rb +92 -0
- data/lib/adyen/notification_generator.rb +30 -0
- data/lib/adyen/railtie.rb +13 -0
- data/lib/adyen/rest.rb +67 -0
- data/lib/adyen/rest/authorise_payment.rb +234 -0
- data/lib/adyen/rest/authorise_recurring_payment.rb +46 -0
- data/lib/adyen/rest/client.rb +127 -0
- data/lib/adyen/rest/errors.rb +33 -0
- data/lib/adyen/rest/modify_payment.rb +89 -0
- data/lib/adyen/rest/payout.rb +89 -0
- data/lib/adyen/rest/request.rb +104 -0
- data/lib/adyen/rest/response.rb +80 -0
- data/lib/adyen/rest/signature.rb +27 -0
- data/lib/adyen/signature.rb +76 -0
- data/lib/adyen/templates/notification_migration.rb +29 -0
- data/lib/adyen/templates/notification_model.rb +69 -0
- data/lib/adyen/util.rb +147 -0
- data/lib/adyen/version.rb +5 -0
- data/spec/api/api_spec.rb +231 -0
- data/spec/api/payment_service_spec.rb +505 -0
- data/spec/api/recurring_service_spec.rb +236 -0
- data/spec/api/response_spec.rb +59 -0
- data/spec/api/simple_soap_client_spec.rb +133 -0
- data/spec/api/spec_helper.rb +463 -0
- data/spec/api/test_helpers_spec.rb +84 -0
- data/spec/functional/api_spec.rb +117 -0
- data/spec/functional/initializer.rb.ci +3 -0
- data/spec/functional/initializer.rb.sample +3 -0
- data/spec/spec_helper.rb +8 -0
- data/test/form_test.rb +303 -0
- data/test/functional/payment_authorisation_api_test.rb +107 -0
- data/test/functional/payment_modification_api_test.rb +58 -0
- data/test/functional/payout_api_test.rb +93 -0
- data/test/helpers/capybara.rb +12 -0
- data/test/helpers/configure_adyen.rb +6 -0
- data/test/helpers/example_server.rb +136 -0
- data/test/helpers/public/adyen.encrypt.js +679 -0
- data/test/helpers/public/adyen.encrypt.min.js +14 -0
- data/test/helpers/test_cards.rb +20 -0
- data/test/helpers/views/authorized.erb +7 -0
- data/test/helpers/views/hpp.erb +20 -0
- data/test/helpers/views/index.erb +6 -0
- data/test/helpers/views/pay.erb +36 -0
- data/test/helpers/views/redirect_shopper.erb +18 -0
- data/test/hpp/signature_test.rb +37 -0
- data/test/hpp_test.rb +250 -0
- data/test/integration/hpp_integration_test.rb +52 -0
- data/test/integration/payment_using_3d_secure_integration_test.rb +41 -0
- data/test/integration/payment_with_client_side_encryption_integration_test.rb +26 -0
- data/test/rest/signature_test.rb +36 -0
- data/test/rest_list_recurring_details_response_test.rb +22 -0
- data/test/rest_request_test.rb +43 -0
- data/test/rest_response_test.rb +19 -0
- data/test/signature_test.rb +76 -0
- data/test/test_helper.rb +45 -0
- data/test/util_test.rb +78 -0
- data/yard_extensions.rb +16 -0
- metadata +308 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
module Adyen
|
2
|
+
module API
|
3
|
+
# A simple wrapper around the raw response body returned by Adyen. It abstracts away the
|
4
|
+
# differences between REXML and Nokogiri, ensuring that this library will always work.
|
5
|
+
#
|
6
|
+
# At load time, it will first check if Nokogiri is available, otherwise REXML is used. This
|
7
|
+
# means that if you want to use Nokogiri and have it installed as a gem, you will have to make
|
8
|
+
# sure rubygems is loaded and the gem has been activated. Or assign the backend to use.
|
9
|
+
class XMLQuerier
|
10
|
+
# The namespaces used by Adyen.
|
11
|
+
NS = {
|
12
|
+
'soap' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
13
|
+
'payment' => 'http://payment.services.adyen.com',
|
14
|
+
'recurring' => 'http://recurring.services.adyen.com',
|
15
|
+
'common' => 'http://common.services.adyen.com'
|
16
|
+
}
|
17
|
+
|
18
|
+
class NokogiriBackend
|
19
|
+
def initialize
|
20
|
+
require 'nokogiri'
|
21
|
+
end
|
22
|
+
|
23
|
+
def document_for_html(html)
|
24
|
+
Nokogiri::HTML::Document.parse(html, nil, 'UTF-8')
|
25
|
+
end
|
26
|
+
|
27
|
+
def document_for_xml(xml)
|
28
|
+
Nokogiri::XML::Document.parse(xml)
|
29
|
+
end
|
30
|
+
|
31
|
+
def perform_xpath(query, root_node)
|
32
|
+
root_node.xpath(query, NS)
|
33
|
+
end
|
34
|
+
|
35
|
+
def stringify_nodeset(nodeset)
|
36
|
+
nodeset.to_xml(encoding: 'UTF-8')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class REXMLBackend
|
41
|
+
def initialize
|
42
|
+
require 'rexml/document'
|
43
|
+
end
|
44
|
+
|
45
|
+
def document_for_html(html)
|
46
|
+
REXML::Document.new(html)
|
47
|
+
end
|
48
|
+
|
49
|
+
def document_for_xml(xml)
|
50
|
+
REXML::Document.new(xml)
|
51
|
+
end
|
52
|
+
|
53
|
+
def perform_xpath(query, root_node)
|
54
|
+
REXML::XPath.match(root_node, query, NS)
|
55
|
+
end
|
56
|
+
|
57
|
+
def stringify_nodeset(nodeset)
|
58
|
+
nodeset.map { |n| n.to_s }.join("")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return A backend to handle XML parsing.
|
63
|
+
def self.default_backend
|
64
|
+
@default_backend ||= begin
|
65
|
+
NokogiriBackend.new
|
66
|
+
rescue LoadError
|
67
|
+
REXMLBackend.new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Creates an XML querier for an XML document
|
72
|
+
def self.xml(data, backend = nil)
|
73
|
+
backend ||= default_backend
|
74
|
+
self.new(backend.document_for_xml(string_from(data)), backend)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Creates an XML querier for an HTML document
|
78
|
+
def self.html(data, backend = nil)
|
79
|
+
backend ||= default_backend
|
80
|
+
self.new(backend.document_for_html(string_from(data)), backend)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.string_from(data)
|
84
|
+
if data.is_a?(String)
|
85
|
+
data
|
86
|
+
elsif data.responds_to?(:body)
|
87
|
+
data.body.to_s
|
88
|
+
else
|
89
|
+
data.to_s
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
attr_reader :backend
|
94
|
+
|
95
|
+
# @param [Nokogiri::XML::NodeSet] data The XML data to wrap.
|
96
|
+
def initialize(node, backend)
|
97
|
+
@node, @backend = node, backend
|
98
|
+
end
|
99
|
+
|
100
|
+
# @param [String] query The xpath query to perform.
|
101
|
+
# @yield [XMLQuerier] A new XMLQuerier scoped to the given +query+.
|
102
|
+
# @return [XMLQuerier] A new XMLQuerier scoped to the given +query+. Or, if a block is given,
|
103
|
+
# the result of calling the block.
|
104
|
+
def xpath(query)
|
105
|
+
result = self.class.new(backend.perform_xpath(query, @node), backend)
|
106
|
+
block_given? ? yield(result) : result
|
107
|
+
end
|
108
|
+
|
109
|
+
# @param [String] query The xpath query to perform.
|
110
|
+
# @return [String] The contents of the text node indicated by the given +query+.
|
111
|
+
def text(query)
|
112
|
+
xpath("#{query}/text()").to_s.strip
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [Array, Nokogiri::XML::NodeSet] The children of this node.
|
116
|
+
def children
|
117
|
+
@node.first.children
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return [Boolean] Returns whether or not this node is empty.
|
121
|
+
def empty?
|
122
|
+
@node.empty?
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [String] A string representation of this node.
|
126
|
+
def to_s
|
127
|
+
backend.stringify_nodeset(@node)
|
128
|
+
end
|
129
|
+
|
130
|
+
# @yield [XMLQuerier] A member of this node set, ready to be queried.
|
131
|
+
# @return [Array] The list of nodes wrapped in XMLQuerier instances.
|
132
|
+
def map(&block)
|
133
|
+
@node.map { |n| self.class.new(n, backend) }.map(&block)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/adyen/base.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Adyen
|
2
|
+
|
3
|
+
# Basic exception class for Adyen
|
4
|
+
class Error < ::StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
# @return [Configuration] The configuration singleton.
|
8
|
+
def self.configuration
|
9
|
+
@configuration ||= Adyen::Configuration.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.configuration=(configuration)
|
13
|
+
@configuration = configuration
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'adyen/configuration'
|
@@ -0,0 +1,179 @@
|
|
1
|
+
class Adyen::Configuration
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@default_api_params = {}
|
5
|
+
@default_form_params = {}
|
6
|
+
@form_skins = {}
|
7
|
+
@payment_flow = :select
|
8
|
+
@environment = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
# The Rails environment for which to use to Adyen "live" environment.
|
12
|
+
LIVE_RAILS_ENVIRONMENTS = ['production']
|
13
|
+
|
14
|
+
# Setter voor the current Adyen environment.
|
15
|
+
# @param ['test', 'live'] env The Adyen environment to use
|
16
|
+
def environment=(env)
|
17
|
+
@environment = env
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the current Adyen environment, either test or live.
|
21
|
+
#
|
22
|
+
# It will return the +override+ value if set, it will return the value set
|
23
|
+
# using Adyen.configuration.environment= otherwise. If this value also isn't set, the
|
24
|
+
# environment is determined with autodetect_environment.
|
25
|
+
#
|
26
|
+
# @param ['test', 'live'] override An environment to override the default with.
|
27
|
+
# @return ['test', 'live'] The Adyen environment that is currently being used.
|
28
|
+
def environment(override = nil)
|
29
|
+
override || @environment || autodetect_environment
|
30
|
+
end
|
31
|
+
|
32
|
+
# Autodetects the Adyen environment based on the RAILS_ENV constant.
|
33
|
+
# @return ['test', 'live'] The Adyen environment that corresponds to the Rails environment
|
34
|
+
def autodetect_environment
|
35
|
+
rails_env = if defined?(::Rails) && ::Rails.respond_to?(:env)
|
36
|
+
::Rails.env.to_s
|
37
|
+
elsif defined?(::RAILS_ENV)
|
38
|
+
::RAILS_ENV.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
LIVE_RAILS_ENVIRONMENTS.include?(rails_env) ? 'live' : 'test'
|
42
|
+
end
|
43
|
+
|
44
|
+
# The payment flow page type that’s used to choose the payment process
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# Adyen.configuration.payment_flow = :select
|
48
|
+
# Adyen.configuration.payment_flow = :pay
|
49
|
+
# Adyen.configuration.payment_flow = :details
|
50
|
+
#
|
51
|
+
# @return [String]
|
52
|
+
attr_accessor :payment_flow
|
53
|
+
|
54
|
+
# The payment flow domain that’s used to choose the payment process
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
# Adyen.configuration.payment_flow_domain = checkout.mydomain.com
|
58
|
+
#
|
59
|
+
# @return [String]
|
60
|
+
attr_accessor :payment_flow_domain
|
61
|
+
|
62
|
+
# The username that’s used to authenticate for the Adyen SOAP services. It should look
|
63
|
+
# something like ‘+ws@AndyInc.SuperShop+’
|
64
|
+
#
|
65
|
+
# @return [String]
|
66
|
+
attr_accessor :api_username
|
67
|
+
|
68
|
+
# The password that’s used to authenticate for the Adyen SOAP services. You can configure it
|
69
|
+
# in the user management tool of the merchant area.
|
70
|
+
#
|
71
|
+
# @return [String]
|
72
|
+
attr_accessor :api_password
|
73
|
+
|
74
|
+
# Default arguments that will be used for every API call. You can override these default
|
75
|
+
# values by passing a diffferent value to the service class’s constructor.
|
76
|
+
#
|
77
|
+
# @example
|
78
|
+
# Adyen.configuration.default_api_params[:merchant_account] = 'SuperShop'
|
79
|
+
#
|
80
|
+
# @return [Hash]
|
81
|
+
attr_accessor :default_api_params
|
82
|
+
|
83
|
+
# The client-side encryption public key that is used to encrypt payment forms.
|
84
|
+
# You can find the key on the webservice user page in the Adyen settings.
|
85
|
+
#
|
86
|
+
# @return [String]
|
87
|
+
attr_accessor :cse_public_key
|
88
|
+
|
89
|
+
# Default arguments that will be used in every HTML form.
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# Adyen.configuration.default_form_params[:merchant_account] = 'SuperShop'
|
93
|
+
#
|
94
|
+
# @return [Hash]
|
95
|
+
attr_accessor :default_form_params
|
96
|
+
|
97
|
+
# Name of the default skin for HPP requests.
|
98
|
+
#
|
99
|
+
# @return [String]
|
100
|
+
attr_accessor :default_skin
|
101
|
+
|
102
|
+
# Username that's set in Notification settings screen in Adyen PSP system and used by notification service to
|
103
|
+
# authenticate instant payment notification requests.
|
104
|
+
#
|
105
|
+
# @return [String]
|
106
|
+
attr_accessor :ipn_username
|
107
|
+
|
108
|
+
# Password used to authenticate notification requests together with '+ipn_username+' configuration attribute.
|
109
|
+
#
|
110
|
+
# @return [String]
|
111
|
+
attr_accessor :ipn_password
|
112
|
+
|
113
|
+
######################################################
|
114
|
+
# SKINS
|
115
|
+
######################################################
|
116
|
+
|
117
|
+
# Returns all registered skins and their accompanying skin code and shared secret.
|
118
|
+
#
|
119
|
+
# @return [Hash] The hash of registered skins.
|
120
|
+
attr_reader :form_skins
|
121
|
+
|
122
|
+
# Sets the registered skins.
|
123
|
+
#
|
124
|
+
# @param [Hash<Symbol, Hash>] hash A hash with the skin name as key and the skin parameter hash
|
125
|
+
# (which should include +:skin_code+ and +:shared_secret+) as value.
|
126
|
+
#
|
127
|
+
# @see Adyen::Configuration.register_form_skin
|
128
|
+
def form_skins=(hash)
|
129
|
+
@form_skins = hash.inject({}) do |skins, (name, skin)|
|
130
|
+
skins[name.to_sym] = skin.merge(:name => name.to_sym)
|
131
|
+
skins
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Registers a skin for later use.
|
136
|
+
#
|
137
|
+
# You can store a skin using a self defined symbol. Once the skin is registered,
|
138
|
+
# you can refer to it using this symbol instead of the hard-to-remember skin code.
|
139
|
+
# Moreover, the skin's shared_secret will be looked up automatically for calculting
|
140
|
+
# signatures.
|
141
|
+
#
|
142
|
+
# @example
|
143
|
+
# Adyen::Configuration.register_form_skin(:my_skin, 'dsfH67PO', 'Dfs*7uUln9')
|
144
|
+
#
|
145
|
+
# @param [Symbol] name The name of the skin.
|
146
|
+
# @param [String] skin_code The skin code for this skin, as defined by Adyen.
|
147
|
+
# @param [String] shared_secret The shared secret used for signature calculation.
|
148
|
+
def register_form_skin(name, skin_code, shared_secret, default_form_params = {})
|
149
|
+
@form_skins[name.to_sym] = { :name => name.to_sym, :skin_code => skin_code, :shared_secret => shared_secret, :default_form_params => default_form_params}
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns a skin information by name.
|
153
|
+
#
|
154
|
+
# @param [Symbol] skin_name The name of the skin
|
155
|
+
# @return [Hash, nil] A hash with the skin information, or nil if not found.
|
156
|
+
def form_skin_by_name(skin_name)
|
157
|
+
@form_skins[skin_name.to_sym]
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns skin information by skin code.
|
161
|
+
#
|
162
|
+
# @param [String] skin_code The code of the skin.
|
163
|
+
#
|
164
|
+
# @return [Hash, nil] A hash with the skin information, or nil if not found.
|
165
|
+
def form_skin_by_code(skin_code)
|
166
|
+
@form_skins.values.find { |skin| skin[:skin_code] == skin_code }
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns the shared secret belonging to a skin.
|
170
|
+
#
|
171
|
+
# @param [String] skin_code The skin code of the skin
|
172
|
+
#
|
173
|
+
# @return [String, nil] The shared secret for the skin, or nil if not found.
|
174
|
+
def form_skin_shared_secret_by_code(skin_code)
|
175
|
+
if skin = form_skin_by_code(skin_code)
|
176
|
+
skin[:shared_secret]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/lib/adyen/form.rb
ADDED
@@ -0,0 +1,419 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'adyen/base'
|
3
|
+
require 'adyen/util'
|
4
|
+
|
5
|
+
module Adyen
|
6
|
+
|
7
|
+
# The Adyen::Form module contains all functionality that is used to send payment requests
|
8
|
+
# to the Adyen payment system, using either a HTML form (see {Adyen::Form.hidden_fields})
|
9
|
+
# or a HTTP redirect (see {Adyen::Form.redirect_url}).
|
10
|
+
#
|
11
|
+
# Moreover, this module contains the method {Adyen::Form.redirect_signature_check} to
|
12
|
+
# check the request, that is made to your website after the visitor has made his payment
|
13
|
+
# on the Adyen system, for genuinity.
|
14
|
+
#
|
15
|
+
# You can use different skins in Adyen to define different payment environments. You can
|
16
|
+
# register these skins under a custom name in the module. The other methods will automatically
|
17
|
+
# use this information (i.e. the skin code and the shared secret) if it is available.
|
18
|
+
# Otherwise, you have to provide it yourself for every method call you make. See
|
19
|
+
# {Adyen::Configuration#register_form_skin} for more information.
|
20
|
+
#
|
21
|
+
# @see Adyen::Configuration#register_form_skin
|
22
|
+
# @see Adyen::Form.hidden_fields
|
23
|
+
# @see Adyen::Form.redirect_url
|
24
|
+
# @see Adyen::Form.redirect_signature_check
|
25
|
+
module Form
|
26
|
+
extend self
|
27
|
+
|
28
|
+
######################################################
|
29
|
+
# ADYEN FORM URL
|
30
|
+
######################################################
|
31
|
+
|
32
|
+
# The DOMAIN of the Adyen payment system that still requires the current
|
33
|
+
# Adyen enviroment.
|
34
|
+
ACTION_DOMAIN = "%s.adyen.com"
|
35
|
+
|
36
|
+
# The URL of the Adyen payment system that still requires the current
|
37
|
+
# domain and payment flow to be filled.
|
38
|
+
ACTION_URL = "https://%s/hpp/%s.shtml"
|
39
|
+
|
40
|
+
# Returns the DOMAIN of the Adyen payment system, adjusted for an Adyen environment.
|
41
|
+
#
|
42
|
+
# @param [String] environment The Adyen environment to use. This parameter can be
|
43
|
+
# left out, in which case the 'current' environment will be used.
|
44
|
+
# @return [String] The domain of the Adyen payment system that can be used
|
45
|
+
# for payment forms or redirects.
|
46
|
+
# @see Adyen::Form.environment
|
47
|
+
# @see Adyen::Form.redirect_url
|
48
|
+
def domain(environment = nil)
|
49
|
+
environment ||= Adyen.configuration.environment
|
50
|
+
(Adyen.configuration.payment_flow_domain || ACTION_DOMAIN) % [environment.to_s]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the URL of the Adyen payment system, adjusted for an Adyen environment.
|
54
|
+
#
|
55
|
+
# @param [String] environment The Adyen environment to use. This parameter can be
|
56
|
+
# left out, in which case the 'current' environment will be used.
|
57
|
+
# @param [String] payment_flow The Adyen payment type to use. This parameter can be
|
58
|
+
# left out, in which case the default payment type will be used.
|
59
|
+
# @return [String] The absolute URL of the Adyen payment system that can be used
|
60
|
+
# for payment forms or redirects.
|
61
|
+
# @see Adyen::Form.environment
|
62
|
+
# @see Adyen::Form.domain
|
63
|
+
# @see Adyen::Form.redirect_url
|
64
|
+
def url(environment = nil, payment_flow = nil)
|
65
|
+
payment_flow ||= Adyen.configuration.payment_flow
|
66
|
+
Adyen::Form::ACTION_URL % [domain(environment), payment_flow.to_s]
|
67
|
+
end
|
68
|
+
|
69
|
+
######################################################
|
70
|
+
# POSTING/REDIRECTING TO ADYEN
|
71
|
+
######################################################
|
72
|
+
|
73
|
+
# Transforms the payment parameters hash to be in the correct format. It will also
|
74
|
+
# include the Adyen::Configuration#default_form_params hash. Finally, switches the
|
75
|
+
# +:skin+ parameter out for the +:skin_code+ and +:shared_secret+ parameter using
|
76
|
+
# the list of registered skins.
|
77
|
+
#
|
78
|
+
# @private
|
79
|
+
# @param [Hash] parameters The payment parameters hash to transform
|
80
|
+
def do_parameter_transformations!(parameters = {})
|
81
|
+
parameters.replace(Adyen.configuration.default_form_params.merge(parameters))
|
82
|
+
|
83
|
+
if parameters[:skin]
|
84
|
+
skin = Adyen.configuration.form_skin_by_name(parameters.delete(:skin))
|
85
|
+
parameters[:skin_code] ||= skin[:skin_code]
|
86
|
+
parameters[:shared_secret] ||= skin[:shared_secret]
|
87
|
+
parameters.merge!(skin[:default_form_params])
|
88
|
+
end
|
89
|
+
|
90
|
+
parameters[:recurring_contract] = 'RECURRING' if parameters.delete(:recurring) == true
|
91
|
+
parameters[:order_data] = Adyen::Util.gzip_base64(parameters.delete(:order_data_raw)) if parameters[:order_data_raw]
|
92
|
+
parameters[:ship_before_date] = Adyen::Util.format_date(parameters[:ship_before_date])
|
93
|
+
parameters[:session_validity] = Adyen::Util.format_timestamp(parameters[:session_validity])
|
94
|
+
end
|
95
|
+
|
96
|
+
# Transforms the payment parameters to be in the correct format and calculates the merchant
|
97
|
+
# signature parameter. It also does some basic health checks on the parameters hash.
|
98
|
+
#
|
99
|
+
# @param [Hash] parameters The payment parameters. The parameters set in the
|
100
|
+
# {Adyen::Configuration#default_form_params} hash will be included automatically.
|
101
|
+
# @param [String] shared_secret The shared secret that should be used to calculate
|
102
|
+
# the payment request signature. This parameter can be left if the skin that is
|
103
|
+
# used is registered (see {Adyen::Configuration#register_form_skin}), or if the
|
104
|
+
# shared secret is provided as the +:shared_secret+ parameter.
|
105
|
+
# @return [Hash] The payment parameters with the +:merchant_signature+ parameter set.
|
106
|
+
# @raise [ArgumentError] Thrown if some parameter health check fails.
|
107
|
+
def payment_parameters(parameters = {}, shared_secret = nil)
|
108
|
+
raise ArgumentError, "Cannot generate form: parameters should be a hash!" unless parameters.is_a?(Hash)
|
109
|
+
do_parameter_transformations!(parameters)
|
110
|
+
|
111
|
+
raise ArgumentError, "Cannot generate form: :currency code attribute not found!" unless parameters[:currency_code]
|
112
|
+
raise ArgumentError, "Cannot generate form: :payment_amount code attribute not found!" unless parameters[:payment_amount]
|
113
|
+
raise ArgumentError, "Cannot generate form: :merchant_account attribute not found!" unless parameters[:merchant_account]
|
114
|
+
raise ArgumentError, "Cannot generate form: :skin_code attribute not found!" unless parameters[:skin_code]
|
115
|
+
|
116
|
+
# Calculate the merchant signature using the shared secret.
|
117
|
+
shared_secret ||= parameters.delete(:shared_secret)
|
118
|
+
raise ArgumentError, "Cannot calculate payment request signature without shared secret!" unless shared_secret
|
119
|
+
parameters[:merchant_sig] = calculate_signature(parameters, shared_secret)
|
120
|
+
|
121
|
+
if parameters[:billing_address]
|
122
|
+
parameters[:billing_address_sig] = calculate_billing_address_signature(parameters, shared_secret)
|
123
|
+
end
|
124
|
+
|
125
|
+
if parameters[:delivery_address]
|
126
|
+
parameters[:delivery_address_sig] = calculate_delivery_address_signature(parameters, shared_secret)
|
127
|
+
end
|
128
|
+
|
129
|
+
if parameters[:shopper]
|
130
|
+
parameters[:shopper_sig] = calculate_shopper_signature(parameters, shared_secret)
|
131
|
+
end
|
132
|
+
|
133
|
+
if parameters[:openinvoicedata]
|
134
|
+
parameters[:openinvoicedata][:sig] = calculate_open_invoice_signature(parameters, shared_secret)
|
135
|
+
end
|
136
|
+
|
137
|
+
return parameters
|
138
|
+
end
|
139
|
+
|
140
|
+
# Transforms and flattens payment parameters to be in the correct format which is understood and accepted by adyen
|
141
|
+
#
|
142
|
+
# @param [Hash] parameters The payment parameters. The parameters set in the
|
143
|
+
# {Adyen::Configuration#default_form_params} hash will be included automatically.
|
144
|
+
# @return [Hash] The payment parameters flatten, with camelized and prefixed key, stringified value
|
145
|
+
def flat_payment_parameters(parameters = {})
|
146
|
+
Adyen::Util.flatten(payment_parameters(parameters))
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns an absolute URL to the Adyen payment system, with the payment parameters included
|
150
|
+
# as GET parameters in the URL. The URL also depends on the current Adyen enviroment.
|
151
|
+
#
|
152
|
+
# The payment parameters that are provided to this method will be merged with the
|
153
|
+
# {Adyen::Configuration#default_form_params} hash. The default parameter values will be
|
154
|
+
# overrided if another value is provided to this method.
|
155
|
+
#
|
156
|
+
# You do not have to provide the +:merchant_sig+ parameter: it will be calculated automatically
|
157
|
+
# if you provide either a registered skin name as the +:skin+ parameter or provide both the
|
158
|
+
# +:skin_code+ and +:shared_secret+ parameters.
|
159
|
+
#
|
160
|
+
# Note that Internet Explorer has a maximum length for URLs it can handle (2083 characters).
|
161
|
+
# Make sure that the URL is not longer than this limit if you want your site to work in IE.
|
162
|
+
#
|
163
|
+
# @example
|
164
|
+
#
|
165
|
+
# def pay
|
166
|
+
# # Genarate a URL to redirect to Adyen's payment system.
|
167
|
+
# adyen_url = Adyen::Form.redirect_url(:skin => :my_skin, :currency_code => 'USD',
|
168
|
+
# :payment_amount => 1000, merchant_account => 'MyMerchant', ... )
|
169
|
+
#
|
170
|
+
# respond_to do |format|
|
171
|
+
# format.html { redirect_to(adyen_url) }
|
172
|
+
# end
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# @param [Hash] parameters The payment parameters to include in the payment request.
|
176
|
+
# @return [String] An absolute URL to redirect to the Adyen payment system.
|
177
|
+
def redirect_url(parameters = {})
|
178
|
+
url + '?' + flat_payment_parameters(parameters).map { |(k, v)|
|
179
|
+
"#{k}=#{CGI.escape(v)}"
|
180
|
+
}.join('&')
|
181
|
+
end
|
182
|
+
|
183
|
+
# @see Adyen::Form.redirect_url
|
184
|
+
#
|
185
|
+
# Returns an absolute URL very similar to the one returned by Adyen::Form.redirect_url
|
186
|
+
# except that it uses the directory.shtml call which returns a list of all available
|
187
|
+
# payment methods
|
188
|
+
#
|
189
|
+
# @param [Hash] parameters The payment parameters to include in the payment request.
|
190
|
+
# @return [String] An absolute URL to redirect to the Adyen payment system.
|
191
|
+
def payment_methods_url(parameters = {})
|
192
|
+
url(nil, :directory) + '?' + flat_payment_parameters(parameters).map { |(k, v)|
|
193
|
+
"#{k}=#{CGI.escape(v)}"
|
194
|
+
}.join('&')
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns a HTML snippet of hidden INPUT tags with the provided payment parameters.
|
198
|
+
# The snippet can be included in a payment form that POSTs to the Adyen payment system.
|
199
|
+
#
|
200
|
+
# The payment parameters that are provided to this method will be merged with the
|
201
|
+
# {Adyen::Configuration#default_form_params} hash. The default parameter values will be
|
202
|
+
# overrided if another value is provided to this method.
|
203
|
+
#
|
204
|
+
# You do not have to provide the +:merchant_sig+ parameter: it will be calculated automatically
|
205
|
+
# if you provide either a registered skin name as the +:skin+ parameter or provide both the
|
206
|
+
# +:skin_code+ and +:shared_secret+ parameters.
|
207
|
+
#
|
208
|
+
# @example
|
209
|
+
# <% form_tag(Adyen::Form.url) do %>
|
210
|
+
# <%= Adyen::Form.hidden_fields(:skin => :my_skin, :currency_code => 'USD',
|
211
|
+
# :payment_amount => 1000, ...) %>
|
212
|
+
# <%= submit_tag("Pay invoice")
|
213
|
+
# <% end %>
|
214
|
+
#
|
215
|
+
# @param [Hash] parameters The payment parameters to include in the payment request.
|
216
|
+
# @return [String] An HTML snippet that can be included in a form that POSTs to the
|
217
|
+
# Adyen payment system.
|
218
|
+
def hidden_fields(parameters = {})
|
219
|
+
|
220
|
+
# Generate a hidden input tag per parameter, join them by newlines.
|
221
|
+
form_str = flat_payment_parameters(parameters).map { |key, value|
|
222
|
+
"<input type=\"hidden\" name=\"#{CGI.escapeHTML(key)}\" value=\"#{CGI.escapeHTML(value)}\" />"
|
223
|
+
}.join("\n")
|
224
|
+
|
225
|
+
form_str.respond_to?(:html_safe) ? form_str.html_safe : form_str
|
226
|
+
end
|
227
|
+
|
228
|
+
######################################################
|
229
|
+
# MERCHANT SIGNATURE CALCULATION
|
230
|
+
######################################################
|
231
|
+
|
232
|
+
# Generates the string that is used to calculate the request signature. This signature
|
233
|
+
# is used by Adyen to check whether the request is genuinely originating from you.
|
234
|
+
# @param [Hash] parameters The parameters that will be included in the payment request.
|
235
|
+
# @return [String] The string for which the siganture is calculated.
|
236
|
+
def calculate_signature_string(parameters)
|
237
|
+
merchant_sig_string = ""
|
238
|
+
merchant_sig_string << parameters[:payment_amount].to_s << parameters[:currency_code].to_s <<
|
239
|
+
parameters[:ship_before_date].to_s << parameters[:merchant_reference].to_s <<
|
240
|
+
parameters[:skin_code].to_s << parameters[:merchant_account].to_s <<
|
241
|
+
parameters[:session_validity].to_s << parameters[:shopper_email].to_s <<
|
242
|
+
parameters[:shopper_reference].to_s << parameters[:recurring_contract].to_s <<
|
243
|
+
parameters[:allowed_methods].to_s << parameters[:blocked_methods].to_s <<
|
244
|
+
parameters[:shopper_statement].to_s << parameters[:merchant_return_data].to_s <<
|
245
|
+
parameters[:billing_address_type].to_s << parameters[:delivery_address_type].to_s <<
|
246
|
+
parameters[:shopper_type].to_s << parameters[:offset].to_s
|
247
|
+
end
|
248
|
+
|
249
|
+
# Calculates the payment request signature for the given payment 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 payment request parameters as the +merchantSig+
|
254
|
+
# parameter; the shared secret should of course not be included.
|
255
|
+
#
|
256
|
+
# @param [Hash] parameters The payment parameters for which to calculate
|
257
|
+
# the payment 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 payment request
|
262
|
+
# @raise [ArgumentError] Thrown if shared_secret is empty
|
263
|
+
def calculate_signature(parameters, shared_secret = nil)
|
264
|
+
shared_secret ||= parameters.delete(:shared_secret)
|
265
|
+
raise ArgumentError, "Cannot calculate payment request signature with empty shared_secret" if shared_secret.to_s.empty?
|
266
|
+
Adyen::Util.hmac_base64(shared_secret, calculate_signature_string(parameters))
|
267
|
+
end
|
268
|
+
|
269
|
+
# Generates the string that is used to calculate the request signature. This signature
|
270
|
+
# is used by Adyen to check whether the request is genuinely originating from you.
|
271
|
+
# @param [Hash] parameters The parameters that will be included in the billing address request.
|
272
|
+
# @return [String] The string for which the siganture is calculated.
|
273
|
+
def calculate_billing_address_signature_string(parameters)
|
274
|
+
%w(street house_number_or_name city postal_code state_or_province country).map do |key|
|
275
|
+
parameters[key.to_sym]
|
276
|
+
end.join
|
277
|
+
end
|
278
|
+
|
279
|
+
# Generates the string that is used to calculate the request signature. This signature
|
280
|
+
# is used by Adyen to check whether the request is genuinely originating from you.
|
281
|
+
# @param [Hash] parameters The parameters that will be included in the delivery address request.
|
282
|
+
# @return [String] The string for which the siganture is calculated.
|
283
|
+
def calculate_delivery_address_signature_string(parameters)
|
284
|
+
%w(street house_number_or_name city postal_code state_or_province country).map do |key|
|
285
|
+
parameters[key.to_sym]
|
286
|
+
end.join
|
287
|
+
end
|
288
|
+
|
289
|
+
# Calculates the billing address request signature for the given billing address parameters.
|
290
|
+
#
|
291
|
+
# This signature is used by Adyen to check whether the request is
|
292
|
+
# genuinely originating from you. The resulting signature should be
|
293
|
+
# included in the billing address request parameters as the +billingAddressSig+
|
294
|
+
# parameter; the shared secret should of course not be included.
|
295
|
+
#
|
296
|
+
# @param [Hash] parameters The billing address parameters for which to calculate
|
297
|
+
# the billing address request signature.
|
298
|
+
# @param [String] shared_secret The shared secret to use for this signature.
|
299
|
+
# It should correspond with the skin_code parameter. This parameter can be
|
300
|
+
# left out if the shared_secret is included as key in the parameters.
|
301
|
+
# @return [String] The signature of the billing address request
|
302
|
+
# @raise [ArgumentError] Thrown if shared_secret is empty
|
303
|
+
def calculate_billing_address_signature(parameters, shared_secret = nil)
|
304
|
+
shared_secret ||= parameters.delete(:shared_secret)
|
305
|
+
raise ArgumentError, "Cannot calculate billing address request signature with empty shared_secret" if shared_secret.to_s.empty?
|
306
|
+
Adyen::Util.hmac_base64(shared_secret, calculate_billing_address_signature_string(parameters[:billing_address]))
|
307
|
+
end
|
308
|
+
|
309
|
+
# Calculates the delivery address request signature for the given delivery address parameters.
|
310
|
+
#
|
311
|
+
# This signature is used by Adyen to check whether the request is
|
312
|
+
# genuinely originating from you. The resulting signature should be
|
313
|
+
# included in the delivery address request parameters as the +deliveryAddressSig+
|
314
|
+
# parameter; the shared secret should of course not be included.
|
315
|
+
#
|
316
|
+
# @param [Hash] parameters The delivery address parameters for which to calculate
|
317
|
+
# the delivery address request signature.
|
318
|
+
# @param [String] shared_secret The shared secret to use for this signature.
|
319
|
+
# It should correspond with the skin_code parameter. This parameter can be
|
320
|
+
# left out if the shared_secret is included as key in the parameters.
|
321
|
+
# @return [String] The signature of the delivery address request
|
322
|
+
# @raise [ArgumentError] Thrown if shared_secret is empty
|
323
|
+
def calculate_delivery_address_signature(parameters, shared_secret = nil)
|
324
|
+
shared_secret ||= parameters.delete(:shared_secret)
|
325
|
+
raise ArgumentError, "Cannot calculate delivery address request signature with empty shared_secret" if shared_secret.to_s.empty?
|
326
|
+
Adyen::Util.hmac_base64(shared_secret, calculate_delivery_address_signature_string(parameters[:delivery_address]))
|
327
|
+
end
|
328
|
+
|
329
|
+
# shopperSig: shopper.firstName + shopper.infix + shopper.lastName + shopper.gender + shopper.dateOfBirthDayOfMonth + shopper.dateOfBirthMonth + shopper.dateOfBirthYear + shopper.telephoneNumber
|
330
|
+
# (Note that you can send only shopper.firstName and shopper.lastName if you like. Do NOT include shopperSocialSecurityNumber in the shopperSig!)
|
331
|
+
def calculate_shopper_signature_string(parameters)
|
332
|
+
%w(first_name infix last_name gender date_of_birth_day_of_month date_of_birth_month date_of_birth_year telephone_number).map do |key|
|
333
|
+
parameters[key.to_sym]
|
334
|
+
end.join
|
335
|
+
end
|
336
|
+
|
337
|
+
def calculate_shopper_signature(parameters, shared_secret = nil)
|
338
|
+
shared_secret ||= parameters.delete(:shared_secret)
|
339
|
+
raise ArgumentError, "Cannot calculate shopper request signature with empty shared_secret" if shared_secret.to_s.empty?
|
340
|
+
Adyen::Util.hmac_base64(shared_secret, calculate_shopper_signature_string(parameters[:shopper]))
|
341
|
+
end
|
342
|
+
|
343
|
+
def calculate_open_invoice_signature_string(merchant_sig, parameters)
|
344
|
+
flattened = Adyen::Util.flatten(:merchant_sig => merchant_sig, :openinvoicedata => parameters)
|
345
|
+
pairs = flattened.to_a.sort
|
346
|
+
pairs.transpose.map { |it| it.join(':') }.join('|')
|
347
|
+
end
|
348
|
+
|
349
|
+
def calculate_open_invoice_signature(parameters, shared_secret = nil)
|
350
|
+
shared_secret ||= parameters.delete(:shared_secret)
|
351
|
+
raise ArgumentError, "Cannot calculate open invoice request signature with empty shared_secret" if shared_secret.to_s.empty?
|
352
|
+
merchant_sig = calculate_signature(parameters, shared_secret)
|
353
|
+
Adyen::Util.hmac_base64(shared_secret, calculate_open_invoice_signature_string(merchant_sig, parameters[:openinvoicedata]))
|
354
|
+
end
|
355
|
+
|
356
|
+
######################################################
|
357
|
+
# REDIRECT SIGNATURE CHECKING
|
358
|
+
######################################################
|
359
|
+
|
360
|
+
# Generates the string for which the redirect signature is calculated, using the request paramaters.
|
361
|
+
# @param [Hash] params A hash of HTTP GET parameters for the redirect request.
|
362
|
+
# @return [String] The signature string.
|
363
|
+
def redirect_signature_string(params)
|
364
|
+
params['authResult'].to_s + params['pspReference'].to_s + params['merchantReference'].to_s +
|
365
|
+
params['skinCode'].to_s + params['merchantReturnData'].to_s
|
366
|
+
end
|
367
|
+
|
368
|
+
# Computes the redirect signature using the request parameters, so that the
|
369
|
+
# redirect can be checked for forgery.
|
370
|
+
#
|
371
|
+
# @param [Hash] params A hash of HTTP GET parameters for the redirect request.
|
372
|
+
# @param [String] shared_secret The shared secret for the Adyen skin that was used for
|
373
|
+
# the original payment form. You can leave this out of the skin is registered
|
374
|
+
# using the {Adyen::Form.register_skin} method.
|
375
|
+
# @return [String] The redirect signature
|
376
|
+
# @raise [ArgumentError] Thrown if shared_secret is empty
|
377
|
+
def redirect_signature(params, shared_secret = nil)
|
378
|
+
shared_secret ||= Adyen.configuration.form_skin_shared_secret_by_code(params['skinCode'])
|
379
|
+
raise ArgumentError, "Cannot compute redirect signature with empty shared_secret" if shared_secret.to_s.empty?
|
380
|
+
Adyen::Util.hmac_base64(shared_secret, redirect_signature_string(params))
|
381
|
+
end
|
382
|
+
|
383
|
+
# Checks the redirect signature for this request by calcultating the signature from
|
384
|
+
# the provided parameters, and comparing it to the signature provided in the +merchantSig+
|
385
|
+
# parameter.
|
386
|
+
#
|
387
|
+
# If this method returns false, the request could be a forgery and should not be handled.
|
388
|
+
# Therefore, you should include this check in a +before_filter+, and raise an error of the
|
389
|
+
# signature check fails.
|
390
|
+
#
|
391
|
+
# @example
|
392
|
+
# class PaymentsController < ApplicationController
|
393
|
+
# before_filter :check_signature, :only => [:return_from_adyen]
|
394
|
+
#
|
395
|
+
# def return_from_adyen
|
396
|
+
# @invoice = Invoice.find(params[:merchantReference])
|
397
|
+
# @invoice.set_paid! if params[:authResult] == 'AUTHORISED'
|
398
|
+
# end
|
399
|
+
#
|
400
|
+
# private
|
401
|
+
#
|
402
|
+
# def check_signature
|
403
|
+
# raise "Forgery!" unless Adyen::Form.redirect_signature_check(params)
|
404
|
+
# end
|
405
|
+
# end
|
406
|
+
#
|
407
|
+
# @param [Hash] params params A hash of HTTP GET parameters for the redirect request. This
|
408
|
+
# should include the +:merchantSig+ parameter, which contains the signature.
|
409
|
+
# @param [String] shared_secret The shared secret for the Adyen skin that was used for
|
410
|
+
# the original payment form. You can leave this out of the skin is registered
|
411
|
+
# using the {Adyen::Configuration#register_form_skin} method.
|
412
|
+
# @return [true, false] Returns true only if the signature in the parameters is correct.
|
413
|
+
def redirect_signature_check(params, shared_secret = nil)
|
414
|
+
raise ArgumentError, "params should be a Hash" unless params.is_a?(Hash)
|
415
|
+
raise ArgumentError, "params should contain :merchantSig" unless params.key?('merchantSig')
|
416
|
+
params['merchantSig'] == redirect_signature(params, shared_secret)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|