adyen 0.3.8 → 1.0.0

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