adyen_jpiqueras 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +30 -0
  4. data/CHANGELOG.md +128 -0
  5. data/CONTRIBUTING.md +85 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE +21 -0
  8. data/README.md +31 -0
  9. data/Rakefile +54 -0
  10. data/adyen_jpiqueras.gemspec +44 -0
  11. data/config.ru +5 -0
  12. data/lib/adyen.rb +16 -0
  13. data/lib/adyen/api.rb +424 -0
  14. data/lib/adyen/api/cacert.pem +3894 -0
  15. data/lib/adyen/api/payment_service.rb +374 -0
  16. data/lib/adyen/api/recurring_service.rb +188 -0
  17. data/lib/adyen/api/response.rb +61 -0
  18. data/lib/adyen/api/simple_soap_client.rb +134 -0
  19. data/lib/adyen/api/templates/payment_service.rb +159 -0
  20. data/lib/adyen/api/templates/recurring_service.rb +71 -0
  21. data/lib/adyen/api/test_helpers.rb +133 -0
  22. data/lib/adyen/api/xml_querier.rb +137 -0
  23. data/lib/adyen/base.rb +17 -0
  24. data/lib/adyen/configuration.rb +179 -0
  25. data/lib/adyen/form.rb +419 -0
  26. data/lib/adyen/hpp.rb +27 -0
  27. data/lib/adyen/hpp/request.rb +192 -0
  28. data/lib/adyen/hpp/response.rb +52 -0
  29. data/lib/adyen/hpp/signature.rb +34 -0
  30. data/lib/adyen/matchers.rb +92 -0
  31. data/lib/adyen/notification_generator.rb +30 -0
  32. data/lib/adyen/railtie.rb +13 -0
  33. data/lib/adyen/rest.rb +67 -0
  34. data/lib/adyen/rest/authorise_payment.rb +234 -0
  35. data/lib/adyen/rest/authorise_recurring_payment.rb +46 -0
  36. data/lib/adyen/rest/client.rb +127 -0
  37. data/lib/adyen/rest/errors.rb +33 -0
  38. data/lib/adyen/rest/modify_payment.rb +89 -0
  39. data/lib/adyen/rest/payout.rb +89 -0
  40. data/lib/adyen/rest/request.rb +104 -0
  41. data/lib/adyen/rest/response.rb +80 -0
  42. data/lib/adyen/rest/signature.rb +27 -0
  43. data/lib/adyen/signature.rb +76 -0
  44. data/lib/adyen/templates/notification_migration.rb +29 -0
  45. data/lib/adyen/templates/notification_model.rb +69 -0
  46. data/lib/adyen/util.rb +147 -0
  47. data/lib/adyen/version.rb +5 -0
  48. data/spec/api/api_spec.rb +231 -0
  49. data/spec/api/payment_service_spec.rb +505 -0
  50. data/spec/api/recurring_service_spec.rb +236 -0
  51. data/spec/api/response_spec.rb +59 -0
  52. data/spec/api/simple_soap_client_spec.rb +133 -0
  53. data/spec/api/spec_helper.rb +463 -0
  54. data/spec/api/test_helpers_spec.rb +84 -0
  55. data/spec/functional/api_spec.rb +117 -0
  56. data/spec/functional/initializer.rb.ci +3 -0
  57. data/spec/functional/initializer.rb.sample +3 -0
  58. data/spec/spec_helper.rb +8 -0
  59. data/test/form_test.rb +303 -0
  60. data/test/functional/payment_authorisation_api_test.rb +107 -0
  61. data/test/functional/payment_modification_api_test.rb +58 -0
  62. data/test/functional/payout_api_test.rb +93 -0
  63. data/test/helpers/capybara.rb +12 -0
  64. data/test/helpers/configure_adyen.rb +6 -0
  65. data/test/helpers/example_server.rb +136 -0
  66. data/test/helpers/public/adyen.encrypt.js +679 -0
  67. data/test/helpers/public/adyen.encrypt.min.js +14 -0
  68. data/test/helpers/test_cards.rb +20 -0
  69. data/test/helpers/views/authorized.erb +7 -0
  70. data/test/helpers/views/hpp.erb +20 -0
  71. data/test/helpers/views/index.erb +6 -0
  72. data/test/helpers/views/pay.erb +36 -0
  73. data/test/helpers/views/redirect_shopper.erb +18 -0
  74. data/test/hpp/signature_test.rb +37 -0
  75. data/test/hpp_test.rb +250 -0
  76. data/test/integration/hpp_integration_test.rb +52 -0
  77. data/test/integration/payment_using_3d_secure_integration_test.rb +41 -0
  78. data/test/integration/payment_with_client_side_encryption_integration_test.rb +26 -0
  79. data/test/rest/signature_test.rb +36 -0
  80. data/test/rest_list_recurring_details_response_test.rb +22 -0
  81. data/test/rest_request_test.rb +43 -0
  82. data/test/rest_response_test.rb +19 -0
  83. data/test/signature_test.rb +76 -0
  84. data/test/test_helper.rb +45 -0
  85. data/test/util_test.rb +78 -0
  86. data/yard_extensions.rb +16 -0
  87. metadata +308 -0
@@ -0,0 +1,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