adyen 0.3.8 → 1.0.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 (46) hide show
  1. data/.gitignore +4 -0
  2. data/.kick +35 -0
  3. data/LICENSE +3 -2
  4. data/README.rdoc +8 -4
  5. data/Rakefile +10 -0
  6. data/TODO +14 -4
  7. data/adyen.gemspec +9 -15
  8. data/lib/adyen.rb +10 -59
  9. data/lib/adyen/api.rb +281 -0
  10. data/lib/adyen/api/cacert.pem +3509 -0
  11. data/lib/adyen/api/payment_service.rb +258 -0
  12. data/lib/adyen/api/recurring_service.rb +126 -0
  13. data/lib/adyen/api/response.rb +54 -0
  14. data/lib/adyen/api/simple_soap_client.rb +118 -0
  15. data/lib/adyen/api/templates/payment_service.rb +103 -0
  16. data/lib/adyen/api/templates/recurring_service.rb +34 -0
  17. data/lib/adyen/api/test_helpers.rb +133 -0
  18. data/lib/adyen/api/xml_querier.rb +94 -0
  19. data/lib/adyen/configuration.rb +139 -0
  20. data/lib/adyen/form.rb +37 -109
  21. data/lib/adyen/formatter.rb +0 -10
  22. data/lib/adyen/matchers.rb +1 -1
  23. data/lib/adyen/notification_generator.rb +30 -0
  24. data/lib/adyen/railtie.rb +13 -0
  25. data/lib/adyen/templates/notification_migration.rb +29 -0
  26. data/lib/adyen/templates/notification_model.rb +70 -0
  27. data/spec/adyen_spec.rb +3 -45
  28. data/spec/api/api_spec.rb +139 -0
  29. data/spec/api/payment_service_spec.rb +439 -0
  30. data/spec/api/recurring_service_spec.rb +105 -0
  31. data/spec/api/response_spec.rb +35 -0
  32. data/spec/api/simple_soap_client_spec.rb +91 -0
  33. data/spec/api/spec_helper.rb +417 -0
  34. data/spec/api/test_helpers_spec.rb +83 -0
  35. data/spec/form_spec.rb +27 -23
  36. data/spec/functional/api_spec.rb +90 -0
  37. data/spec/functional/initializer.rb.sample +3 -0
  38. data/spec/spec_helper.rb +5 -5
  39. data/tasks/github-gem.rake +49 -55
  40. data/yard_extensions.rb +16 -0
  41. metadata +63 -82
  42. data/init.rb +0 -1
  43. data/lib/adyen/notification.rb +0 -151
  44. data/lib/adyen/soap.rb +0 -649
  45. data/spec/notification_spec.rb +0 -97
  46. data/spec/soap_spec.rb +0 -340
@@ -0,0 +1,103 @@
1
+ module Adyen
2
+ module API
3
+ class PaymentService < SimpleSOAPClient
4
+ class << self
5
+ private
6
+
7
+ def modification_request(method, body = nil)
8
+ return <<EOS
9
+ <payment:#{method} xmlns:payment="http://payment.services.adyen.com" xmlns:recurring="http://recurring.services.adyen.com" xmlns:common="http://common.services.adyen.com">
10
+ <payment:modificationRequest>
11
+ <payment:merchantAccount>%s</payment:merchantAccount>
12
+ <payment:originalReference>%s</payment:originalReference>
13
+ #{body}
14
+ </payment:modificationRequest>
15
+ </payment:#{method}>
16
+ EOS
17
+ end
18
+
19
+ def modification_request_with_amount(method)
20
+ modification_request(method, <<EOS)
21
+ <payment:modificationAmount>
22
+ <common:currency>%s</common:currency>
23
+ <common:value>%s</common:value>
24
+ </payment:modificationAmount>
25
+ EOS
26
+ end
27
+ end
28
+
29
+ # @private
30
+ CAPTURE_LAYOUT = modification_request_with_amount(:capture)
31
+ # @private
32
+ REFUND_LAYOUT = modification_request_with_amount(:refund)
33
+ # @private
34
+ CANCEL_LAYOUT = modification_request(:cancel)
35
+ # @private
36
+ CANCEL_OR_REFUND_LAYOUT = modification_request(:cancelOrRefund)
37
+
38
+ # @private
39
+ LAYOUT = <<EOS
40
+ <payment:authorise xmlns:payment="http://payment.services.adyen.com" xmlns:recurring="http://recurring.services.adyen.com" xmlns:common="http://common.services.adyen.com">
41
+ <payment:paymentRequest>
42
+ <payment:merchantAccount>%s</payment:merchantAccount>
43
+ <payment:reference>%s</payment:reference>
44
+ %s
45
+ </payment:paymentRequest>
46
+ </payment:authorise>
47
+ EOS
48
+
49
+ # @private
50
+ AMOUNT_PARTIAL = <<EOS
51
+ <payment:amount>
52
+ <common:currency>%s</common:currency>
53
+ <common:value>%s</common:value>
54
+ </payment:amount>
55
+ EOS
56
+
57
+ # @private
58
+ CARD_PARTIAL = <<EOS
59
+ <payment:card>
60
+ <payment:holderName>%s</payment:holderName>
61
+ <payment:number>%s</payment:number>
62
+ <payment:cvc>%s</payment:cvc>
63
+ <payment:expiryYear>%s</payment:expiryYear>
64
+ <payment:expiryMonth>%02d</payment:expiryMonth>
65
+ </payment:card>
66
+ EOS
67
+
68
+ # @private
69
+ ENABLE_RECURRING_CONTRACTS_PARTIAL = <<EOS
70
+ <payment:recurring>
71
+ <payment:contract>RECURRING,ONECLICK</payment:contract>
72
+ </payment:recurring>
73
+ EOS
74
+
75
+ # @private
76
+ RECURRING_PAYMENT_BODY_PARTIAL = <<EOS
77
+ <payment:recurring>
78
+ <payment:contract>RECURRING</payment:contract>
79
+ </payment:recurring>
80
+ <payment:selectedRecurringDetailReference>%s</payment:selectedRecurringDetailReference>
81
+ <payment:shopperInteraction>ContAuth</payment:shopperInteraction>
82
+ EOS
83
+
84
+ # @private
85
+ ONE_CLICK_PAYMENT_BODY_PARTIAL = <<EOS
86
+ <payment:recurring>
87
+ <payment:contract>ONECLICK</payment:contract>
88
+ </payment:recurring>
89
+ <payment:selectedRecurringDetailReference>%s</payment:selectedRecurringDetailReference>
90
+ <payment:card>
91
+ <payment:cvc>%s</payment:cvc>
92
+ </payment:card>
93
+ EOS
94
+
95
+ # @private
96
+ SHOPPER_PARTIALS = {
97
+ :reference => ' <payment:shopperReference>%s</payment:shopperReference>',
98
+ :email => ' <payment:shopperEmail>%s</payment:shopperEmail>',
99
+ :ip => ' <payment:shopperIP>%s</payment:shopperIP>',
100
+ }
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,34 @@
1
+ module Adyen
2
+ module API
3
+ class RecurringService < SimpleSOAPClient
4
+ # @private
5
+ LIST_LAYOUT = <<EOS
6
+ <recurring:listRecurringDetails xmlns:recurring="http://recurring.services.adyen.com">
7
+ <recurring:request>
8
+ <recurring:recurring>
9
+ <recurring:contract>RECURRING</recurring:contract>
10
+ </recurring:recurring>
11
+ <recurring:merchantAccount>%s</recurring:merchantAccount>
12
+ <recurring:shopperReference>%s</recurring:shopperReference>
13
+ </recurring:request>
14
+ </recurring:listRecurringDetails>
15
+ EOS
16
+
17
+ # @private
18
+ DISABLE_LAYOUT = <<EOS
19
+ <recurring:disable xmlns:recurring="http://recurring.services.adyen.com">
20
+ <recurring:request>
21
+ <recurring:merchantAccount>%s</recurring:merchantAccount>
22
+ <recurring:shopperReference>%s</recurring:shopperReference>
23
+ %s
24
+ </recurring:request>
25
+ </recurring:disable>
26
+ EOS
27
+
28
+ # @private
29
+ RECURRING_DETAIL_PARTIAL = <<EOS
30
+ <recurring:recurringDetailReference>%s</recurring:recurringDetailReference>
31
+ EOS
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,133 @@
1
+ require 'adyen/api/payment_service'
2
+
3
+ module Adyen
4
+ module API
5
+ class PaymentService < SimpleSOAPClient
6
+ # A collection of test helpers that create and assign stubbed response instances for a
7
+ # subsequent remote call.
8
+ #
9
+ # This module extends the {PaymentService} class and thus these methods are callable on it.
10
+ module TestHelpers
11
+ AUTHORISE_RESPONSE = SimpleSOAPClient::ENVELOPE % <<EOS
12
+ <ns1:authoriseResponse xmlns:ns1="http://payment.services.adyen.com">
13
+ <ns1:paymentResult>
14
+ <additionalData xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
15
+ <authCode xmlns="http://payment.services.adyen.com">1234</authCode>
16
+ <dccAmount xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
17
+ <dccSignature xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
18
+ <fraudResult xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
19
+ <issuerUrl xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
20
+ <md xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
21
+ <paRequest xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
22
+ <pspReference xmlns="http://payment.services.adyen.com">9876543210987654</pspReference>
23
+ <refusalReason xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
24
+ <resultCode xmlns="http://payment.services.adyen.com">Authorised</resultCode>
25
+ </ns1:paymentResult>
26
+ </ns1:authoriseResponse>
27
+ EOS
28
+
29
+ AUTHORISATION_REFUSED_RESPONSE = SimpleSOAPClient::ENVELOPE % <<EOS
30
+ <ns1:authoriseResponse xmlns:ns1="http://payment.services.adyen.com">
31
+ <ns1:paymentResult>
32
+ <additionalData xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
33
+ <authCode xmlns="http://payment.services.adyen.com">1234</authCode>
34
+ <dccAmount xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
35
+ <dccSignature xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
36
+ <fraudResult xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
37
+ <issuerUrl xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
38
+ <md xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
39
+ <paRequest xmlns="http://payment.services.adyen.com" xsi:nil="true"/>
40
+ <pspReference xmlns="http://payment.services.adyen.com">9876543210987654</pspReference>
41
+ <refusalReason xmlns="http://payment.services.adyen.com">You need to actually own money.</refusalReason>
42
+ <resultCode xmlns="http://payment.services.adyen.com">Refused</resultCode>
43
+ </ns1:paymentResult>
44
+ </ns1:authoriseResponse>
45
+ EOS
46
+
47
+ AUTHORISATION_REQUEST_INVALID_RESPONSE = SimpleSOAPClient::ENVELOPE % <<EOS
48
+ <soap:Fault>
49
+ <faultcode>soap:Server</faultcode>
50
+ <faultstring>validation 101 Invalid card number</faultstring>
51
+ </soap:Fault>
52
+ EOS
53
+
54
+ # @return [AuthorisationResponse] A authorisation succeeded response instance.
55
+ def success_stub
56
+ http_response = Net::HTTPOK.new('1.1', '200', 'OK')
57
+ def http_response.body; AUTHORISE_RESPONSE; end
58
+ PaymentService::AuthorisationResponse.new(http_response)
59
+ end
60
+
61
+ # @return [AuthorisationResponse] An authorisation refused response instance.
62
+ def refused_stub
63
+ http_response = Net::HTTPOK.new('1.1', '200', 'OK')
64
+ def http_response.body; AUTHORISATION_REFUSED_RESPONSE; end
65
+ PaymentService::AuthorisationResponse.new(http_response)
66
+ end
67
+
68
+ # @return [AuthorisationResponse] An ‘invalid request’ response instance.
69
+ def invalid_stub
70
+ http_response = Net::HTTPOK.new('1.1', '200', 'OK')
71
+ def http_response.body; AUTHORISATION_REQUEST_INVALID_RESPONSE; end
72
+ PaymentService::AuthorisationResponse.new(http_response)
73
+ end
74
+
75
+ # Assigns a {#success_stub}, meaning the subsequent authoristaion request will be authorised.
76
+ #
77
+ # @return [AuthorisationResponse] A authorisation succeeded response instance.
78
+ def stub_success!
79
+ @stubbed_response = success_stub
80
+ end
81
+
82
+ # Assigns a {#refused_stub}, meaning the subsequent authoristaion request will be refused.
83
+ #
84
+ # @return [AuthorisationResponse] An authorisation refused response instance.
85
+ def stub_refused!
86
+ @stubbed_response = refused_stub
87
+ end
88
+
89
+ # Assigns a {#invalid_stub}, meaning the subsequent authoristaion request will be refused,
90
+ # because the request was invalid.
91
+ #
92
+ # @return [AuthorisationResponse] An ‘invalid request’ response instance.
93
+ def stub_invalid!
94
+ @stubbed_response = invalid_stub
95
+ end
96
+ end
97
+
98
+ extend TestHelpers
99
+ end
100
+
101
+ class RecurringService < SimpleSOAPClient
102
+ # A collection of test helpers that create and assign stubbed response instances for a
103
+ # subsequent remote call.
104
+ #
105
+ # This module extends the {RecurringService} class and thus these methods are callable on it.
106
+ module TestHelpers
107
+ DISABLE_RESPONSE = SimpleSOAPClient::ENVELOPE % <<EOS
108
+ <ns1:disableResponse xmlns:ns1="http://recurring.services.adyen.com">
109
+ <ns1:result>
110
+ <response xmlns="http://recurring.services.adyen.com">
111
+ %s
112
+ </response>
113
+ </ns1:result>
114
+ </ns1:disableResponse>
115
+ EOS
116
+
117
+ # @return [DisableResponse] A ‘disable succeeded’ response instance.
118
+ def disabled_stub
119
+ http_response = Net::HTTPOK.new('1.1', '200', 'OK')
120
+ def http_response.body; DISABLE_RESPONSE % DisableResponse::DISABLED_RESPONSES.first; end
121
+ RecurringService::DisableResponse.new(http_response)
122
+ end
123
+
124
+ # Assigns a {#disabled_stub}, meaning the subsequent disable request will be successful.
125
+ def stub_disabled!
126
+ @stubbed_response = disabled_stub
127
+ end
128
+ end
129
+
130
+ extend TestHelpers
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,94 @@
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 << self
19
+ # @return [:rexml, :nokogiri] The XML backend to use.
20
+ attr_reader :backend
21
+ def backend=(backend)
22
+ @backend = backend
23
+ class_eval do
24
+ private
25
+ if backend == :nokogiri
26
+ def document_for_xml(xml)
27
+ Nokogiri::XML::Document.parse(xml)
28
+ end
29
+ def perform_xpath(query)
30
+ @node.xpath(query, NS)
31
+ end
32
+ else
33
+ def document_for_xml(xml)
34
+ REXML::Document.new(xml)
35
+ end
36
+ def perform_xpath(query)
37
+ REXML::XPath.match(@node, query, NS)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ begin
45
+ require 'nokogiri'
46
+ self.backend = :nokogiri
47
+ rescue LoadError
48
+ require 'rexml/document'
49
+ self.backend = :rexml
50
+ end
51
+
52
+ # @param [String, Array, Nokogiri::XML::NodeSet] data The XML data to wrap.
53
+ def initialize(data)
54
+ @node = data.is_a?(String) ? document_for_xml(data) : data
55
+ end
56
+
57
+ # @param [String] query The xpath query to perform.
58
+ # @yield [XMLQuerier] A new XMLQuerier scoped to the given +query+.
59
+ # @return [XMLQuerier] A new XMLQuerier scoped to the given +query+. Or, if a block is given,
60
+ # the result of calling the block.
61
+ def xpath(query)
62
+ result = self.class.new(perform_xpath(query))
63
+ block_given? ? yield(result) : result
64
+ end
65
+
66
+ # @param [String] query The xpath query to perform.
67
+ # @return [String] The contents of the text node indicated by the given +query+.
68
+ def text(query)
69
+ xpath("#{query}/text()").to_s.strip
70
+ end
71
+
72
+ # @return [Array, Nokogiri::XML::NodeSet] The children of this node.
73
+ def children
74
+ @node.first.children
75
+ end
76
+
77
+ # @return [Boolean] Returns whether or not this node is empty.
78
+ def empty?
79
+ @node.empty?
80
+ end
81
+
82
+ # @return [String] A string representation of this node.
83
+ def to_s
84
+ Array === @node ? @node.join("") : @node.to_s
85
+ end
86
+
87
+ # @yield [XMLQuerier] A member of this node set, ready to be queried.
88
+ # @return [Array] The list of nodes wrapped in XMLQuerier instances.
89
+ def map(&block)
90
+ @node.map { |n| self.class.new(n) }.map(&block)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,139 @@
1
+ class Adyen::Configuration
2
+
3
+ def initialize
4
+ @default_api_params = {}
5
+ @default_form_params = {}
6
+ @form_skins = {}
7
+ end
8
+
9
+ # The Rails environment for which to use to Adyen "live" environment.
10
+ LIVE_RAILS_ENVIRONMENTS = ['production']
11
+
12
+ # Setter voor the current Adyen environment.
13
+ # @param ['test', 'live'] env The Adyen environment to use
14
+ def environment=(env)
15
+ @environment = env
16
+ end
17
+
18
+ # Returns the current Adyen environment, either test or live.
19
+ #
20
+ # It will return the +override+ value if set, it will return the value set
21
+ # using Adyen.configuration.environment= otherwise. If this value also isn't set, the
22
+ # environment is determined with autodetect_environment.
23
+ #
24
+ # @param ['test', 'live'] override An environment to override the default with.
25
+ # @return ['test', 'live'] The Adyen environment that is currently being used.
26
+ def environment(override = nil)
27
+ override || @environment || autodetect_environment
28
+ end
29
+
30
+ # Autodetects the Adyen environment based on the RAILS_ENV constant.
31
+ # @return ['test', 'live'] The Adyen environment that corresponds to the Rails environment
32
+ def autodetect_environment
33
+ rails_env = if defined?(::Rails) && ::Rails.respond_to?(:env)
34
+ ::Rails.env.to_s
35
+ elsif defined?(::RAILS_ENV)
36
+ ::RAILS_ENV.to_s
37
+ end
38
+
39
+ LIVE_RAILS_ENVIRONMENTS.include?(rails_env) ? 'live' : 'test'
40
+ end
41
+
42
+ # The username that’s used to authenticate for the Adyen SOAP services. It should look
43
+ # something like ‘+ws@AndyInc.SuperShop+’
44
+ #
45
+ # @return [String]
46
+ attr_accessor :api_username
47
+
48
+ # The password that’s used to authenticate for the Adyen SOAP services. You can configure it
49
+ # in the user management tool of the merchant area.
50
+ #
51
+ # @return [String]
52
+ attr_accessor :api_password
53
+
54
+ # Default arguments that will be used for every API call. You can override these default
55
+ # values by passing a diffferent value to the service class’s constructor.
56
+ #
57
+ # @example
58
+ # Adyen.configuration.default_api_params[:merchant_account] = 'SuperShop'
59
+ #
60
+ # @return [Hash]
61
+ attr_accessor :default_api_params
62
+
63
+ # Default arguments that will be used for in every HTML form.
64
+ #
65
+ # @example
66
+ # Adyen.configuration.default_form_params[:shared_secret] = 'secret'
67
+ #
68
+ # @return [Hash]
69
+ attr_accessor :default_form_params
70
+
71
+ ######################################################
72
+ # SKINS
73
+ ######################################################
74
+
75
+ # Returns all registered skins and their accompanying skin code and shared secret.
76
+ #
77
+ # @return [Hash] The hash of registered skins.
78
+ attr_reader :form_skins
79
+
80
+ # Sets the registered skins.
81
+ #
82
+ # @param [Hash<Symbol, Hash>] hash A hash with the skin name as key and the skin parameter hash
83
+ # (which should include +:skin_code+ and +:shared_secret+) as value.
84
+ #
85
+ # @see Adyen::Configuration.register_form_skin
86
+ def form_skins=(hash)
87
+ @form_skins = hash.inject({}) do |skins, (name, skin)|
88
+ skins[name.to_sym] = skin.merge(:name => name.to_sym)
89
+ skins
90
+ end
91
+ end
92
+
93
+ # Registers a skin for later use.
94
+ #
95
+ # You can store a skin using a self defined symbol. Once the skin is registered,
96
+ # you can refer to it using this symbol instead of the hard-to-remember skin code.
97
+ # Moreover, the skin's shared_secret will be looked up automatically for calculting
98
+ # signatures.
99
+ #
100
+ # @example
101
+ # Adyen::Configuration.register_form_skin(:my_skin, 'dsfH67PO', 'Dfs*7uUln9')
102
+ #
103
+ # @param [Symbol] name The name of the skin.
104
+ # @param [String] skin_code The skin code for this skin, as defined by Adyen.
105
+ # @param [String] shared_secret The shared secret used for signature calculation.
106
+ def register_form_skin(name, skin_code, shared_secret)
107
+ @form_skins[name.to_sym] = { :name => name.to_sym, :skin_code => skin_code, :shared_secret => shared_secret }
108
+ end
109
+
110
+ # Returns a skin information by name.
111
+ #
112
+ # @param [Symbol] skin_name The name of the skin
113
+ # @return [Hash, nil] A hash with the skin information, or nil if not found.
114
+ def form_skin_by_name(skin_name)
115
+ @form_skins[skin_name.to_sym]
116
+ end
117
+
118
+ # Returns skin information by code code.
119
+ #
120
+ # @param [String] skin_code The code of the skin.
121
+ #
122
+ # @return [Hash, nil] A hash with the skin information, or nil if not found.
123
+ def form_skin_by_code(skin_code)
124
+ if skin = @form_skins.detect { |(name, skin)| skin[:skin_code] == skin_code }
125
+ skin.last
126
+ end
127
+ end
128
+
129
+ # Returns the shared secret belonging to a skin.
130
+ #
131
+ # @param [String] skin_code The skin code of the skin
132
+ #
133
+ # @return [String, nil] The shared secret for the skin, or nil if not found.
134
+ def form_skin_shared_secret_by_code(skin_code)
135
+ if skin = form_skin_by_code(skin_code)
136
+ skin[:shared_secret]
137
+ end
138
+ end
139
+ end