adyen_jpiqueras 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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