adyen 1.6.0 → 2.0.0.pre1

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +116 -0
  4. data/CONTRIBUTING.md +84 -0
  5. data/Gemfile +1 -1
  6. data/README.md +33 -0
  7. data/Rakefile +19 -4
  8. data/adyen.gemspec +7 -5
  9. data/config.ru +5 -0
  10. data/lib/adyen.rb +2 -2
  11. data/lib/adyen/configuration.rb +6 -0
  12. data/lib/adyen/form.rb +13 -48
  13. data/lib/adyen/matchers.rb +1 -1
  14. data/lib/adyen/rest.rb +53 -0
  15. data/lib/adyen/rest/authorise_payment.rb +148 -0
  16. data/lib/adyen/rest/client.rb +110 -0
  17. data/lib/adyen/rest/errors.rb +33 -0
  18. data/lib/adyen/rest/modify_payment.rb +97 -0
  19. data/lib/adyen/rest/request.rb +107 -0
  20. data/lib/adyen/rest/response.rb +59 -0
  21. data/lib/adyen/util.rb +147 -0
  22. data/lib/adyen/version.rb +1 -1
  23. data/test/form_test.rb +17 -10
  24. data/test/functional/payment_authorisation_api_test.rb +54 -0
  25. data/test/functional/payment_modification_api_test.rb +57 -0
  26. data/test/helpers/configure_adyen.rb +5 -0
  27. data/test/helpers/example_server.rb +105 -0
  28. data/test/helpers/public/adyen.encrypt.js +679 -0
  29. data/test/helpers/public/adyen.encrypt.min.js +14 -0
  30. data/test/helpers/test_cards.rb +20 -0
  31. data/test/helpers/views/authorized.erb +7 -0
  32. data/test/helpers/views/hpp.erb +19 -0
  33. data/test/helpers/views/index.erb +6 -0
  34. data/test/helpers/views/pay.erb +36 -0
  35. data/test/helpers/views/redirect_shopper.erb +18 -0
  36. data/test/integration/hpp_integration_test.rb +52 -0
  37. data/test/integration/payment_using_3d_secure_integration_test.rb +40 -0
  38. data/test/integration/payment_with_client_side_encryption_integration_test.rb +26 -0
  39. data/test/rest_request_test.rb +50 -0
  40. data/test/rest_response_test.rb +18 -0
  41. data/test/test_helper.rb +32 -0
  42. data/test/util_test.rb +77 -0
  43. metadata +89 -16
  44. data/README.rdoc +0 -50
  45. data/lib/adyen/encoding.rb +0 -21
  46. data/lib/adyen/formatter.rb +0 -33
  47. data/test/adyen_test.rb +0 -31
@@ -21,7 +21,7 @@ module Adyen
21
21
 
22
22
  # Add a check for all the other fields specified
23
23
  checks.each do |key, value|
24
- condition = "\n descendant::input[@type='hidden'][@name='#{Adyen::Form.camelize(key)}']"
24
+ condition = "\n descendant::input[@type='hidden'][@name='#{Adyen::Util.camelize(key)}']"
25
25
  condition << "[@value='#{value}']" unless value == :anything
26
26
  xpath_query << "[#{condition}]"
27
27
  end
@@ -0,0 +1,53 @@
1
+ require 'adyen'
2
+ require 'adyen/rest/client'
3
+
4
+ module Adyen
5
+
6
+ # The Adyen::REST module allows you to interact with Adyen's REST API.
7
+ #
8
+ # The primary method here is {Adyen::REST.session}, which will yield a
9
+ # {Adyen::REST::Client} which you can use to send API requests.
10
+ #
11
+ # @example
12
+ #
13
+ # Adyen::REST.session do |client|
14
+ # client.http.read_timeout = 5
15
+ # response = client.api_request(...)
16
+ # # ...
17
+ # end
18
+ #
19
+ # @see Adyen::REST.session Use Adyen::REST.session to run code against the API.
20
+ # @see Adyen::REST::Client Adyen::REST::Client implements the actual API calls.
21
+ module REST
22
+
23
+ # Provides a REST API client this is configured using the values in <tt>Adyen.configuration</tt>.
24
+ # @param options [Hash] (see Adyen::REST::Client#initialize)
25
+ # @return [Adyen::REST::Client] A configured client instance
26
+ # @see .session
27
+ def self.client
28
+ Adyen::REST::Client.new(
29
+ Adyen.configuration.environment,
30
+ Adyen.configuration.api_username,
31
+ Adyen.configuration.api_password
32
+ )
33
+ end
34
+
35
+ # Exectutes a session against the Adyen REST API.
36
+ #
37
+ # It will use a standard client from {Adyen::REST.client}, or it uses a provided client.
38
+ # The client will be yielded to the block, and will be closed after the block is finisged
39
+ #
40
+ # @param client [Adyen::REST::Client] A custom API client if a default one won't do.
41
+ # @yield The provided block will be called in which you can interact with the API using
42
+ # the provided client. The client will be closed after the block returns.
43
+ # @yieldparam client [Adyen::REST::Client] The REST client to use for the session.
44
+ # @return [void]
45
+ # @see Adyen::REST::Client
46
+ def self.session(client = nil)
47
+ client ||= self.client
48
+ yield(client)
49
+ ensure
50
+ client.close
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,148 @@
1
+ module Adyen
2
+ module REST
3
+
4
+ # This module implements the <b>Payment.authorise</b>, and <b>Payment.authorise3d</b>
5
+ # API calls, and includes a custom response class to make handling the response easier.
6
+ module AuthorisePayment
7
+
8
+ class Request < Adyen::REST::Request
9
+
10
+ def set_amount(currency, value)
11
+ self['amount'] = { currency: currency, value: value }
12
+ end
13
+
14
+ def set_encrypted_card_data(source)
15
+ encrypted_json = if source.respond_to?(:params)
16
+ source.params['adyen-encrypted-data']
17
+ elsif source.respond_to?(:[]) && source.key?('adyen-encrypted-data')
18
+ source['adyen-encrypted-data']
19
+ else
20
+ source
21
+ end
22
+
23
+ self['additional_data.card.encrypted.json'] = encrypted_json
24
+ end
25
+
26
+ def set_browser_info(request)
27
+ self['shopper_ip'] = request.ip
28
+ self['browser_info.accept_header'] = request['Accept'] || "text/html;q=0.9,*/*",
29
+ self['browser_info.user_agent'] = request.user_agent
30
+ end
31
+
32
+ def set_3d_secure_parameters(request)
33
+ set_browser_info(request)
34
+ self['pa_response'] = request.params['PaRes']
35
+ self['md'] = request.params['MD']
36
+ end
37
+ end
38
+
39
+ # The Response class implements some extensions for the authorise payment call.
40
+ # @see Adyen::REST::Response
41
+ class Response < Adyen::REST::Response
42
+
43
+ # Checks whether the authorisation was successful.
44
+ # @return [Boolean] <tt>true</tt> iff the authorisation was successful, and the
45
+ # authorised amount can be captured.
46
+ def authorised?
47
+ result_code == AUTHORISED
48
+ end
49
+
50
+ alias_method :authorized?, :authorised?
51
+
52
+ # Check whether the payment was refused.
53
+ # @return [Boolean] <tt>true</tt> iff the authorisation was not successful.
54
+ def refused?
55
+ result_code == REFUSED
56
+ end
57
+
58
+ # Checks whether the result of the authorization call was RedirectShopper,
59
+ # which means that the customer has to be redirected away from your site to
60
+ # complete the 3Dsecure transaction.
61
+ # @return [Boolean] <tt>true</tt> iff the shopper has to be redirected,
62
+ # <tt>false</tt> in any other case.
63
+ def redirect_shopper?
64
+ result_code == REDIRECT_SHOPPER
65
+ end
66
+
67
+ # Returns the result code from the transaction.
68
+ # @return [String] The result code.
69
+ # @see #authorised?
70
+ # @see #refused?
71
+ # @see #redirect_shopper?
72
+ def result_code
73
+ self[:result_code]
74
+ end
75
+
76
+ private
77
+
78
+ AUTHORISED = 'Authorised'.freeze
79
+ REFUSED = 'Refused'.freeze
80
+ REDIRECT_SHOPPER = 'RedirectShopper'.freeze
81
+ private_constant :AUTHORISED, :REFUSED, :REDIRECT_SHOPPER
82
+ end
83
+
84
+ # Generates <tt>Payment.authorise</tt> request for Adyen's webservice.
85
+ # @param (see #authorise_payment)
86
+ # @return [Adyen::REST::Request] The request to send
87
+ # @see #authorise_payment
88
+ def authorise_payment_request(attributes = {})
89
+ Adyen::REST::AuthorisePayment::Request.new('Payment.authorise', attributes,
90
+ prefix: 'payment_request',
91
+ response_class: Adyen::REST::AuthorisePayment::Response,
92
+ response_options: { prefix: 'payment_result' })
93
+ end
94
+
95
+ # Sends an authorise payment request to Adyen's webservice.
96
+ # @param attributes [Hash] The attributes to include in the request.
97
+ # @return [Adyen::REST::AuthorisePayment::Response] The response from Adyen.
98
+ # The response responds to <tt>.authorised?</tt> to check whether the
99
+ # authorization was successful.
100
+ # @see Adyen::REST::AuthorisePayment::Response#authorised?
101
+ def authorise_payment(attributes)
102
+ request = authorise_payment_request(attributes)
103
+ execute_request(request)
104
+ end
105
+
106
+ # Generates a <tt>Payment.authorise3d</tt> request to Adyen's webservice.
107
+ #
108
+ # The response differs based on the credit card uses in the transaction.
109
+ # For some credit cards, an additional offsite step may be required to complete
110
+ # the transaction. Check <tt>.redirect_shopper?</tt> to see if this is the case.
111
+ # Other cards are not 3DSecure-enabled, and may immediately authorise the
112
+ # transaction. Check <tt>.authorised?</tt> to see if this is the case.
113
+ #
114
+ # @param attributes [Hash] The attributes to include in the request.
115
+ # @return [Adyen::REST::AuthorisePayment::Response] The response from Adyen.
116
+ # @see Adyen::REST::AuthorisePayment::Response#redirect_shopper?
117
+ # @see Adyen::REST::AuthorisePayment::Response#authorised?
118
+ def authorise_payment_3dsecure_request(attributes = {})
119
+ Adyen::REST::AuthorisePayment::Request.new('Payment.authorise3d', attributes,
120
+ prefix: 'payment_request_3d',
121
+ response_class: Adyen::REST::AuthorisePayment::Response,
122
+ response_options: { prefix: 'payment_result' })
123
+ end
124
+
125
+ # Sends a 3Dsecure-enabled authorise payment request to Adyen's webservice.
126
+ #
127
+ # The response differs based on the credit card uses in the transaction.
128
+ # For some credit cards, an additional offsite step may be required to complete
129
+ # the transaction. Check <tt>.redirect_shopper?</tt> to see if this is the case.
130
+ # Other cards are not 3DSecure-enabled, and may immediately authorise the
131
+ # transaction. Check <tt>.authorised?</tt> to see if this is the case.
132
+ #
133
+ # @param attributes [Hash] The attributes to include in the request.
134
+ # @return [Adyen::REST::AuthorisePayment::Response] The response from Adyen.
135
+ # @see Adyen::REST::AuthorisePayment::Response#redirect_shopper?
136
+ # @see Adyen::REST::AuthorisePayment::Response#authorised?
137
+ def authorise_payment_3dsecure(attributes)
138
+ request = authorise_payment_3dsecure_request(attributes)
139
+ execute_request(request)
140
+ end
141
+
142
+ alias_method :authorize_payment_request, :authorise_payment_request
143
+ alias_method :authorize_payment, :authorise_payment
144
+ alias_method :authorize_payment_3dsecure_request, :authorise_payment_3dsecure_request
145
+ alias_method :authorize_payment_3dsecure, :authorise_payment_3dsecure
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,110 @@
1
+ require 'cgi'
2
+ require 'net/http'
3
+
4
+ require 'adyen/rest/errors'
5
+ require 'adyen/rest/request'
6
+ require 'adyen/rest/response'
7
+ require 'adyen/rest/authorise_payment'
8
+ require 'adyen/rest/modify_payment'
9
+
10
+ module Adyen
11
+ module REST
12
+
13
+ # The Client class acts as a client to Adyen's REST webservice.
14
+ #
15
+ # @!attribute environment [r]
16
+ # The adyen environment to interact with. Either <tt>'live'</tt> or <tt>'test'</tt>.
17
+ # @return [String]
18
+ class Client
19
+ include AuthorisePayment
20
+ include ModifyPayment
21
+
22
+ attr_reader :environment
23
+
24
+ # @param environment [String] The Adyen environment to interface with. Either
25
+ # <tt>'live'</tt> or <tt>'test'</tt>.
26
+ # @param username [String] The webservice username, e.g. <tt>ws@Company.Account</tt>
27
+ # @param password [String] The password associated with the username
28
+ def initialize(environment, username, password)
29
+ @environment, @username, @password = environment, username, password
30
+ end
31
+
32
+ # Closes the client.
33
+ #
34
+ # - This will terminate the HTTP connection.
35
+ # - After calling this method, the behavior of any further method calls against
36
+ # this client instance is undefined.
37
+ #
38
+ # @return [void]
39
+ def close
40
+ @http.finish if @http && @http.started?
41
+ @http = nil
42
+ end
43
+
44
+ # The underlying <tt>Net::HTTP</tt> instance that is used to execute HTTP
45
+ # request against the API.
46
+ #
47
+ # You can use this to set options on the Net::HTTP instance, like <tt>read_timeout</tt>.
48
+ # Many of these options will only work if you set them before the HTTP connection is
49
+ # opened, i.e. before doing the first API call.
50
+ #
51
+ # @return [Net::HTTP] The underlying Net::HTTP instance the client uses to perform HTTP request.
52
+ def http
53
+ @http ||= Net::HTTP.new(endpoint.host, endpoint.port).tap do |http|
54
+ http.use_ssl = endpoint.scheme == 'https'
55
+ end
56
+ end
57
+
58
+ # Executes an API request, and returns a reponse of the given type.
59
+ #
60
+ # @param request [Adyen::REST::Request] The API request to execute.
61
+ # <tt>validate!</tt> will be called on the this object before the
62
+ # request is made.
63
+ # @param response_type [Class] The response type to use. Use either
64
+ # <tt>Adyen::REST::Response</tt> or a subclass.
65
+ # @return [Adyen::REST::Response] A response instance of the provided type
66
+ # @see execute_http_request The <tt>execute_http_request</tt> takes care
67
+ # of executing the underlying HTTP request.
68
+ def execute_request(request)
69
+ request.validate!
70
+ http_response = execute_http_request(request)
71
+ request.build_response(http_response)
72
+ end
73
+
74
+ protected
75
+
76
+ # Executes a HTTP request against Adyen's REST webservice.
77
+ # @param request [Adyen::REST::Request] The request to execute.
78
+ # @return [Net::HTTPResponse] The response from the server.
79
+ # @raise [Adyen::REST::Error] if the HTTP response code was not 200.
80
+ # @see #http Use the <tt>http</tt> method to set options on the underlying
81
+ # <tt>Net::HTTP</tt> object, like timeouts.
82
+ def execute_http_request(request)
83
+ http_request = Net::HTTP::Post.new(endpoint.path)
84
+ http_request.basic_auth(@username, @password)
85
+ http_request.set_form_data(request.form_data)
86
+
87
+ case response = http.request(http_request)
88
+ when Net::HTTPOK
89
+ return response
90
+ when Net::HTTPInternalServerError
91
+ raise Adyen::REST::ResponseError.new(response.body)
92
+ when Net::HTTPUnauthorized
93
+ raise Adyen::REST::Error.new("Webservice credentials are incorrect")
94
+ else
95
+ raise Adyen::REST::Error.new("Unexpected HTTP response: #{response.code}")
96
+ end
97
+ end
98
+
99
+ # The endpoint URI for this client.
100
+ # @return [URI] The endpoint to use for the environment.
101
+ def endpoint
102
+ @endpoint ||= URI(ENDPOINT % [environment])
103
+ end
104
+
105
+ # @see Adyen::REST::Client#endpoint
106
+ ENDPOINT = 'https://pal-%s.adyen.com/pal/adapter/httppost'
107
+ private_constant :ENDPOINT
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,33 @@
1
+ module Adyen
2
+ module REST
3
+
4
+ # The main exception class for error reporting when using the REST API Client.
5
+ class Error < ::StandardError
6
+ end
7
+
8
+ # Exception class for errors on requests
9
+ class RequestValidationFailed < Error
10
+ end
11
+
12
+ # Exception class for error responses from the Adyen API.
13
+ #
14
+ # @!attribute category
15
+ # @return [String, nil]
16
+ # @!attribute code
17
+ # @return [Integer, nil]
18
+ # @!attribute description
19
+ # @return [String, nil]
20
+ class ResponseError < Error
21
+ attr_accessor :category, :code, :description
22
+
23
+ def initialize(response_body)
24
+ if match = /\A(\w+)\s(\d+)\s(.*)\z/.match(response_body)
25
+ @category, @code, @description = match[1], match[2].to_i, match[3]
26
+ super("API request error: #{description} (code: #{code}/#{category})")
27
+ else
28
+ super("API request error: #{response_body}")
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,97 @@
1
+ module Adyen
2
+ module REST
3
+
4
+ # This module implements the <b>Payment.capture</b> API to capture
5
+ # previously authorised payments.
6
+ module ModifyPayment
7
+ class Request < Adyen::REST::Request
8
+ def set_modification_amount(currency, value)
9
+ self['modification_amount'] = { currency: currency, value: value }
10
+ end
11
+
12
+ alias_method :set_amount, :set_modification_amount
13
+ end
14
+
15
+ class Response < Adyen::REST::Response
16
+ attr_reader :expected_response
17
+
18
+ def initialize(http_response, options = {})
19
+ super
20
+ @expected_response = options[:expects]
21
+ end
22
+
23
+ def received?
24
+ self[:response] == expected_response
25
+ end
26
+ end
27
+
28
+ # Constructs and issues a Payment.capture API call.
29
+ def capture_payment(attributes = {})
30
+ request = capture_payment_request(attributes)
31
+ execute_request(request)
32
+ end
33
+
34
+ def capture_payment_request(attributes = {})
35
+ Adyen::REST::ModifyPayment::Request.new('Payment.capture', attributes,
36
+ prefix: 'modification_request',
37
+ response_class: Adyen::REST::ModifyPayment::Response,
38
+ response_options: {
39
+ prefix: 'modification_result',
40
+ expects: '[capture-received]'
41
+ }
42
+ )
43
+ end
44
+
45
+ # Constructs and issues a Payment.cancel API call.
46
+ def cancel_payment(attributes = {})
47
+ request = cancel_payment_request(attributes)
48
+ execute_request(request)
49
+ end
50
+
51
+ def cancel_payment_request(attributes = {})
52
+ Adyen::REST::ModifyPayment::Request.new('Payment.cancel', attributes,
53
+ prefix: 'modification_request',
54
+ response_class: Adyen::REST::ModifyPayment::Response,
55
+ response_options: {
56
+ prefix: 'modification_result',
57
+ expects: '[cancel-received]'
58
+ }
59
+ )
60
+ end
61
+
62
+ # Constructs and issues a Payment.cancel API call.
63
+ def refund_payment(attributes = {})
64
+ request = refund_payment_request(attributes)
65
+ execute_request(request)
66
+ end
67
+
68
+ def refund_payment_request(attributes = {})
69
+ Adyen::REST::ModifyPayment::Request.new('Payment.refund', attributes,
70
+ prefix: 'modification_request',
71
+ response_class: Adyen::REST::ModifyPayment::Response,
72
+ response_options: {
73
+ prefix: 'modification_result',
74
+ expects: '[refund-received]'
75
+ }
76
+ )
77
+ end
78
+
79
+ # Constructs and issues a Payment.cancel API call.
80
+ def cancel_or_refund_payment(attributes = {})
81
+ request = cancel_or_refund_payment_request(attributes)
82
+ execute_request(request)
83
+ end
84
+
85
+ def cancel_or_refund_payment_request(attributes = {})
86
+ Adyen::REST::ModifyPayment::Request.new('Payment.cancelOrRefund', attributes,
87
+ prefix: 'modification_request',
88
+ response_class: Adyen::REST::ModifyPayment::Response,
89
+ response_options: {
90
+ prefix: 'modification_result',
91
+ expects: '[cancelOrRefund-received]'
92
+ }
93
+ )
94
+ end
95
+ end
96
+ end
97
+ end