adyen_jpiqueras 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +30 -0
  4. data/CHANGELOG.md +128 -0
  5. data/CONTRIBUTING.md +85 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE +21 -0
  8. data/README.md +31 -0
  9. data/Rakefile +54 -0
  10. data/adyen_jpiqueras.gemspec +44 -0
  11. data/config.ru +5 -0
  12. data/lib/adyen.rb +16 -0
  13. data/lib/adyen/api.rb +424 -0
  14. data/lib/adyen/api/cacert.pem +3894 -0
  15. data/lib/adyen/api/payment_service.rb +374 -0
  16. data/lib/adyen/api/recurring_service.rb +188 -0
  17. data/lib/adyen/api/response.rb +61 -0
  18. data/lib/adyen/api/simple_soap_client.rb +134 -0
  19. data/lib/adyen/api/templates/payment_service.rb +159 -0
  20. data/lib/adyen/api/templates/recurring_service.rb +71 -0
  21. data/lib/adyen/api/test_helpers.rb +133 -0
  22. data/lib/adyen/api/xml_querier.rb +137 -0
  23. data/lib/adyen/base.rb +17 -0
  24. data/lib/adyen/configuration.rb +179 -0
  25. data/lib/adyen/form.rb +419 -0
  26. data/lib/adyen/hpp.rb +27 -0
  27. data/lib/adyen/hpp/request.rb +192 -0
  28. data/lib/adyen/hpp/response.rb +52 -0
  29. data/lib/adyen/hpp/signature.rb +34 -0
  30. data/lib/adyen/matchers.rb +92 -0
  31. data/lib/adyen/notification_generator.rb +30 -0
  32. data/lib/adyen/railtie.rb +13 -0
  33. data/lib/adyen/rest.rb +67 -0
  34. data/lib/adyen/rest/authorise_payment.rb +234 -0
  35. data/lib/adyen/rest/authorise_recurring_payment.rb +46 -0
  36. data/lib/adyen/rest/client.rb +127 -0
  37. data/lib/adyen/rest/errors.rb +33 -0
  38. data/lib/adyen/rest/modify_payment.rb +89 -0
  39. data/lib/adyen/rest/payout.rb +89 -0
  40. data/lib/adyen/rest/request.rb +104 -0
  41. data/lib/adyen/rest/response.rb +80 -0
  42. data/lib/adyen/rest/signature.rb +27 -0
  43. data/lib/adyen/signature.rb +76 -0
  44. data/lib/adyen/templates/notification_migration.rb +29 -0
  45. data/lib/adyen/templates/notification_model.rb +69 -0
  46. data/lib/adyen/util.rb +147 -0
  47. data/lib/adyen/version.rb +5 -0
  48. data/spec/api/api_spec.rb +231 -0
  49. data/spec/api/payment_service_spec.rb +505 -0
  50. data/spec/api/recurring_service_spec.rb +236 -0
  51. data/spec/api/response_spec.rb +59 -0
  52. data/spec/api/simple_soap_client_spec.rb +133 -0
  53. data/spec/api/spec_helper.rb +463 -0
  54. data/spec/api/test_helpers_spec.rb +84 -0
  55. data/spec/functional/api_spec.rb +117 -0
  56. data/spec/functional/initializer.rb.ci +3 -0
  57. data/spec/functional/initializer.rb.sample +3 -0
  58. data/spec/spec_helper.rb +8 -0
  59. data/test/form_test.rb +303 -0
  60. data/test/functional/payment_authorisation_api_test.rb +107 -0
  61. data/test/functional/payment_modification_api_test.rb +58 -0
  62. data/test/functional/payout_api_test.rb +93 -0
  63. data/test/helpers/capybara.rb +12 -0
  64. data/test/helpers/configure_adyen.rb +6 -0
  65. data/test/helpers/example_server.rb +136 -0
  66. data/test/helpers/public/adyen.encrypt.js +679 -0
  67. data/test/helpers/public/adyen.encrypt.min.js +14 -0
  68. data/test/helpers/test_cards.rb +20 -0
  69. data/test/helpers/views/authorized.erb +7 -0
  70. data/test/helpers/views/hpp.erb +20 -0
  71. data/test/helpers/views/index.erb +6 -0
  72. data/test/helpers/views/pay.erb +36 -0
  73. data/test/helpers/views/redirect_shopper.erb +18 -0
  74. data/test/hpp/signature_test.rb +37 -0
  75. data/test/hpp_test.rb +250 -0
  76. data/test/integration/hpp_integration_test.rb +52 -0
  77. data/test/integration/payment_using_3d_secure_integration_test.rb +41 -0
  78. data/test/integration/payment_with_client_side_encryption_integration_test.rb +26 -0
  79. data/test/rest/signature_test.rb +36 -0
  80. data/test/rest_list_recurring_details_response_test.rb +22 -0
  81. data/test/rest_request_test.rb +43 -0
  82. data/test/rest_response_test.rb +19 -0
  83. data/test/signature_test.rb +76 -0
  84. data/test/test_helper.rb +45 -0
  85. data/test/util_test.rb +78 -0
  86. data/yard_extensions.rb +16 -0
  87. metadata +308 -0
@@ -0,0 +1,29 @@
1
+ # @private
2
+ class CreateAdyenNotifications < ActiveRecord::Migration
3
+
4
+ def self.up
5
+ create_table :adyen_notifications do |t|
6
+ t.boolean :live, :null => false, :default => false
7
+ t.string :event_code, :null => false, :limit => 40
8
+ t.string :psp_reference, :null => false, :limit => 50
9
+ t.string :original_reference, :null => true
10
+ t.string :merchant_reference, :null => false
11
+ t.string :merchant_account_code, :null => false
12
+ t.datetime :event_date, :null => false
13
+ t.boolean :success, :null => false, :default => false
14
+ t.string :payment_method, :null => true
15
+ t.string :operations, :null => true
16
+ t.text :reason, :null => true
17
+ t.string :currency, :null => true, :limit => 3
18
+ t.integer :value, :null => true
19
+ t.boolean :processed, :null => false, :default => false
20
+ t.timestamps
21
+ end
22
+
23
+ add_index :adyen_notifications, [:merchant_account_code, :psp_reference, :event_code, :success], :unique => true, :name => 'adyen_notification_uniqueness'
24
+ end
25
+
26
+ def self.down
27
+ drop_table :adyen_notifications
28
+ end
29
+ end
@@ -0,0 +1,69 @@
1
+ # The +AdyenNotification+ class handles notifications sent by Adyen to your servers.
2
+ #
3
+ # Because notifications contain important payment status information, you should store
4
+ # these notifications in your database. For this reason, +AdyenNotification+ inherits
5
+ # from +ActiveRecord::Base+, and a migration is included to simply create a suitable table
6
+ # to store the notifications in.
7
+ #
8
+ # Adyen can either send notifications to you via HTTP POST requests, or SOAP requests.
9
+ # Because SOAP is not really well supported in Rails and setting up a SOAP server is
10
+ # not trivial, only handling HTTP POST notifications is currently supported.
11
+ #
12
+ # @example
13
+ # @notification = AdyenNotification.log(request)
14
+ # if @notification.successful_authorisation?
15
+ # @invoice = Invoice.find(@notification.merchant_reference)
16
+ # @invoice.set_paid!
17
+ # end
18
+ class AdyenNotification < ActiveRecord::Base
19
+ # A notification should always include an event_code
20
+ validates_presence_of :event_code
21
+
22
+ # A notification should always include a psp_reference
23
+ validates_presence_of :psp_reference
24
+
25
+ # A notification should be unique using the composed key of
26
+ # [:merchant_account_code, :psp_reference, :event_code, :success]
27
+ validates_uniqueness_of :success, :scope => [:merchant_account_code, :psp_reference, :event_code]
28
+
29
+ # Make sure we don't end up with an original_reference with an empty string
30
+ before_validation { |notification| notification.original_reference = nil if notification.original_reference.blank? }
31
+
32
+ # Logs an incoming notification into the database.
33
+ #
34
+ # @param [Hash] params The notification parameters that should be stored in the database.
35
+ # @return [Adyen::Notification] The initiated and persisted notification instance.
36
+ # @raise This method will raise an exception if the notification cannot be stored.
37
+ # @see Adyen::Notification::HttpPost.log
38
+ def self.log(params)
39
+ # Assign explicit each attribute from CamelCase notation to notification
40
+ # For example, merchantReference will be converted to merchant_reference
41
+ self.new.tap do |notification|
42
+ params.each do |key, value|
43
+ setter = "#{key.to_s.underscore}="
44
+ notification.send(setter, value) if notification.respond_to?(setter)
45
+ end
46
+
47
+ notification.save!
48
+ end
49
+ end
50
+
51
+ # Returns true if this notification is an AUTHORISATION notification
52
+ # @return [true, false] true iff event_code == 'AUTHORISATION'
53
+ # @see Adyen.notification#successful_authorisation?
54
+ def authorisation?
55
+ event_code == 'AUTHORISATION'
56
+ end
57
+
58
+ alias_method :authorization?, :authorisation?
59
+
60
+ # Returns true if this notification is an AUTHORISATION notification and
61
+ # the success status indicates that the authorization was successfull.
62
+ # @return [true, false] true iff the notification is an authorization
63
+ # and the authorization was successful according to the success field.
64
+ def successful_authorisation?
65
+ authorisation? && success?
66
+ end
67
+
68
+ alias_method :successful_authorization?, :successful_authorisation?
69
+ end
data/lib/adyen/util.rb ADDED
@@ -0,0 +1,147 @@
1
+ require 'date'
2
+ require 'openssl'
3
+ require 'base64'
4
+
5
+ module Adyen
6
+ module Util
7
+ extend self
8
+
9
+ # Returns a valid Adyen string representation for a date
10
+ def format_date(date)
11
+ case date
12
+ when Date, DateTime, Time
13
+ date.strftime('%Y-%m-%d')
14
+ when String
15
+ raise ArgumentError, "Invalid date notation: #{date.inspect}!" unless /^\d{4}-\d{2}-\d{2}$/ =~ date
16
+ date
17
+ else
18
+ raise ArgumentError, "Cannot convert #{date.inspect} to date!"
19
+ end
20
+ end
21
+
22
+ # Returns a valid Adyen string representation for a timestamp
23
+ def format_timestamp(time)
24
+ case time
25
+ when Date, DateTime, Time
26
+ time.strftime('%Y-%m-%dT%H:%M:%SZ')
27
+ when String
28
+ raise ArgumentError, "Invalid timestamp notation: #{time.inspect}!" unless /^\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}Z$/ =~ time
29
+ time
30
+ else
31
+ raise ArgumentError, "Cannot convert #{time.inspect} to timestamp!"
32
+ end
33
+ end
34
+
35
+ # Returns a base64-encoded signature for a message
36
+ # @param [String] hmac_key The secret key to use for the HMAC signature.
37
+ # @param [String] message The message to sign.
38
+ # @return [String] The signature, base64-encoded.
39
+ def hmac_base64(hmac_key, message)
40
+ digest = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), hmac_key, message)
41
+ Base64.strict_encode64(digest).strip
42
+ end
43
+
44
+ # Retuns a message gzip-compressed and base64-encoded.
45
+ # @param [String] message The message to compress and encode.
46
+ # @return [String] The compressed and encoded version of the message
47
+ def gzip_base64(message)
48
+ sio = StringIO.new
49
+ gz = Zlib::GzipWriter.new(sio)
50
+ gz.write(message)
51
+ gz.close
52
+ Base64.strict_encode64(sio.string)
53
+ end
54
+
55
+ # Returns the camelized version of a string.
56
+ # @param [:to_s] identifier The identifier to turn to camelcase.
57
+ # @return [String] The camelcase version of the identifier provided.
58
+ def camelize(identifier)
59
+ CAMELCASE_EXCEPTIONS[identifier.to_s] || identifier.to_s.gsub(/_+(.)/) { $1.upcase }
60
+ end
61
+
62
+ # Returns the underscore version of a string.
63
+ # @param [:to_s] identifier The identifier to turn to underscore notation.
64
+ # @return [String] The underscore version of the identifier provided.
65
+ def underscore(identifier)
66
+ UNDERSCORE_EXCEPTIONS[identifier.to_s] || identifier.to_s
67
+ .gsub(/([A-Z]{2,})([A-Z])/) { "#{$1.downcase}#{$2}" }
68
+ .gsub(/(?!\A)([A-Z][a-z]*)/, '_\1')
69
+ .downcase
70
+ end
71
+
72
+ # Transforms the nested parameters Hash into a 'flat' Hash which is understood by adyen. This is:
73
+ # * all keys are camelized
74
+ # * all keys are stringified
75
+ # * nested hash is flattened, keys are prefixed with root key
76
+ #
77
+ # @example
78
+ # flatten {:billing_address => { :street => 'My Street'}}
79
+ #
80
+ # # resolves in:
81
+ # {'billingAddress.street' => 'My Street'}
82
+ #
83
+ # @param [Hash] nested_hash The nested hash to transform
84
+ # @param [String] prefix The prefix to add to the key
85
+ # @param [Hash] return_hash The new hash which is retruned (needed for recursive calls)
86
+ # @return [Hash] The return hash filled with camelized and prefixed key, stringified value
87
+ def flatten(nested_hash, prefix = "", return_hash = {})
88
+ nested_hash ||= {}
89
+ nested_hash.inject(return_hash) do |hash, (key, value)|
90
+ key = "#{prefix}#{camelize(key)}"
91
+ if value.is_a?(Hash)
92
+ flatten(value, "#{key}.", return_hash)
93
+ else
94
+ hash[key] = value.to_s
95
+ end
96
+ hash
97
+ end
98
+ end
99
+
100
+ # Transforms a flat hash into a nested hash structure.
101
+ # * all keys are underscored
102
+ # * all keys are stringified
103
+ # * flattened hash is deflattened, using . as namespace separator
104
+ #
105
+ # @example
106
+ # deflatten {'billingAddress.street' => 'My Street'}
107
+ #
108
+ # # resolves in:
109
+ # {'billing_address' => { 'street' => 'My Street'}}
110
+ #
111
+ # @param [Hash] flattened_hash The flat hash to transform
112
+ # @param [Hash] return_hash The new hash which will be returned (needed for recursive calls)
113
+ # @return [Hash] A nested hash structure, using strings as key.
114
+ def deflatten(flattened_hash, return_hash = {})
115
+ return return_hash if flattened_hash.nil?
116
+ flattened_hash.each do |key, value|
117
+ deflatten_pair(key, value, return_hash)
118
+ end
119
+ return_hash
120
+ end
121
+
122
+ private
123
+
124
+ def deflatten_pair(key, value, return_hash)
125
+ head, rest = key.split('.', 2)
126
+ key = underscore(head)
127
+ if rest.nil?
128
+ raise ArgumentError, "Duplicate key in flattened hash." if return_hash.key?(key)
129
+ return_hash[key] = value
130
+ else
131
+ return_hash[key] ||= {}
132
+ raise ArgumentError, "Key nesting conflict in flattened hash." unless return_hash[key].is_a?(Hash)
133
+ deflatten_pair(rest, value, return_hash[key])
134
+ end
135
+ end
136
+
137
+ # This hash contains exceptions to the standard underscore to camelcase conversion rules.
138
+ CAMELCASE_EXCEPTIONS = {
139
+ 'shopper_ip' => 'shopperIP'
140
+ }
141
+
142
+ # This hash contains exceptions to the standard camelcase to underscore conversion rules.
143
+ UNDERSCORE_EXCEPTIONS = CAMELCASE_EXCEPTIONS.invert
144
+
145
+ private_constant :CAMELCASE_EXCEPTIONS, :UNDERSCORE_EXCEPTIONS
146
+ end
147
+ end
@@ -0,0 +1,5 @@
1
+ module Adyen
2
+ # Version constant for the Adyen plugin.
3
+ # Set it & commit the change before running rake release.
4
+ VERSION = "2.3.0"
5
+ end
@@ -0,0 +1,231 @@
1
+ # encoding: UTF-8
2
+ require 'api/spec_helper'
3
+
4
+ describe Adyen::API do
5
+ include APISpecHelper
6
+
7
+ describe "shortcut methods" do
8
+ describe "for the PaymentService" do
9
+ before do
10
+ @payment = double('PaymentService')
11
+ end
12
+
13
+ def should_map_shortcut_to(method, params)
14
+ Adyen::API::PaymentService.should_receive(:new).with(params).and_return(@payment)
15
+ @payment.should_receive(method)
16
+ end
17
+
18
+ it "performs a `authorise payment' request without enabling :recurring" do
19
+ should_map_shortcut_to(:authorise_payment,
20
+ :reference => 'order-id',
21
+ :amount => { :currency => 'EUR', :value => 1234 },
22
+ :shopper => { :reference => 'user-id', :email => 's.hopper@example.com' },
23
+ :card => { :expiry_month => 12, :expiry_year => 2012, :holder_name => "Simon Hopper", :number => '4444333322221111', :cvc => '737' },
24
+ :recurring => false,
25
+ :fraud_offset => nil,
26
+ :instant_capture => false
27
+ )
28
+ Adyen::API.authorise_payment('order-id',
29
+ { :currency => 'EUR', :value => 1234 },
30
+ { :reference => 'user-id', :email => 's.hopper@example.com' },
31
+ { :expiry_month => 12, :expiry_year => 2012, :holder_name => "Simon Hopper", :number => '4444333322221111', :cvc => '737' }
32
+ )
33
+ end
34
+
35
+ it "performs a `authorise payment' request with additional :fraud_offset" do
36
+ should_map_shortcut_to(:authorise_payment,
37
+ :reference => 'order-id',
38
+ :amount => { :currency => 'EUR', :value => 1234 },
39
+ :shopper => { :reference => 'user-id', :email => 's.hopper@example.com' },
40
+ :card => { :expiry_month => 12, :expiry_year => 2012, :holder_name => "Simon Hopper", :number => '4444333322221111', :cvc => '737' },
41
+ :recurring => false,
42
+ :fraud_offset => -100,
43
+ :instant_capture => false
44
+ )
45
+ Adyen::API.authorise_payment('order-id',
46
+ { :currency => 'EUR', :value => 1234 },
47
+ { :reference => 'user-id', :email => 's.hopper@example.com' },
48
+ { :expiry_month => 12, :expiry_year => 2012, :holder_name => "Simon Hopper", :number => '4444333322221111', :cvc => '737' },
49
+ false,
50
+ -100,
51
+ false
52
+ )
53
+ end
54
+
55
+ it "performs a `authorise payment' request with enabling :recurring" do
56
+ should_map_shortcut_to(:authorise_payment,
57
+ :reference => 'order-id',
58
+ :amount => { :currency => 'EUR', :value => 1234 },
59
+ :shopper => { :reference => 'user-id', :email => 's.hopper@example.com' },
60
+ :card => { :expiry_month => 12, :expiry_year => 2012, :holder_name => "Simon Hopper", :number => '4444333322221111', :cvc => '737' },
61
+ :recurring => true,
62
+ :fraud_offset => nil,
63
+ :instant_capture => false,
64
+ )
65
+ Adyen::API.authorise_payment('order-id',
66
+ { :currency => 'EUR', :value => 1234 },
67
+ { :reference => 'user-id', :email => 's.hopper@example.com' },
68
+ { :expiry_month => 12, :expiry_year => 2012, :holder_name => "Simon Hopper", :number => '4444333322221111', :cvc => '737' },
69
+ true,
70
+ nil,
71
+ false
72
+ )
73
+ end
74
+
75
+ it "performs a `authorise recurring payment' request without specific detail" do
76
+ should_map_shortcut_to(:authorise_recurring_payment,
77
+ :reference => 'order-id',
78
+ :amount => { :currency => 'EUR', :value => 1234 },
79
+ :shopper => { :reference => 'user-id', :email => 's.hopper@example.com' },
80
+ :recurring_detail_reference => 'LATEST',
81
+ :instant_capture => false,
82
+ :fraud_offset => nil
83
+ )
84
+ Adyen::API.authorise_recurring_payment('order-id',
85
+ { :currency => 'EUR', :value => 1234 },
86
+ { :reference => 'user-id', :email => 's.hopper@example.com' }
87
+ )
88
+ end
89
+
90
+ it "performs a `authorise recurring payment' request with specific detail" do
91
+ should_map_shortcut_to(:authorise_recurring_payment,
92
+ :reference => 'order-id',
93
+ :amount => { :currency => 'EUR', :value => 1234 },
94
+ :shopper => { :reference => 'user-id', :email => 's.hopper@example.com' },
95
+ :recurring_detail_reference => 'recurring-detail-reference',
96
+ :instant_capture => false,
97
+ :fraud_offset => nil
98
+ )
99
+ Adyen::API.authorise_recurring_payment('order-id',
100
+ { :currency => 'EUR', :value => 1234 },
101
+ { :reference => 'user-id', :email => 's.hopper@example.com' },
102
+ 'recurring-detail-reference'
103
+ )
104
+ end
105
+
106
+ it "performs a `authorise recurring payment' request with specific detail and fraud offset" do
107
+ should_map_shortcut_to(:authorise_recurring_payment,
108
+ :reference => 'order-id',
109
+ :amount => { :currency => 'EUR', :value => 1234 },
110
+ :shopper => { :reference => 'user-id', :email => 's.hopper@example.com' },
111
+ :recurring_detail_reference => 'recurring-detail-reference',
112
+ :fraud_offset => 50,
113
+ :instant_capture => false
114
+ )
115
+ Adyen::API.authorise_recurring_payment('order-id',
116
+ { :currency => 'EUR', :value => 1234 },
117
+ { :reference => 'user-id', :email => 's.hopper@example.com' },
118
+ 'recurring-detail-reference',
119
+ 50,
120
+ false
121
+ )
122
+ end
123
+
124
+ it "performs a `authorise one-click payment' request with specific detail" do
125
+ should_map_shortcut_to(:authorise_one_click_payment,
126
+ :reference => 'order-id',
127
+ :amount => { :currency => 'EUR', :value => 1234 },
128
+ :shopper => { :reference => 'user-id', :email => 's.hopper@example.com' },
129
+ :card => { :cvc => '737' },
130
+ :recurring_detail_reference => 'recurring-detail-reference',
131
+ :fraud_offset => nil,
132
+ :instant_capture => false
133
+ )
134
+ Adyen::API.authorise_one_click_payment('order-id',
135
+ { :currency => 'EUR', :value => 1234 },
136
+ { :reference => 'user-id', :email => 's.hopper@example.com' },
137
+ { :cvc => '737' },
138
+ 'recurring-detail-reference'
139
+ )
140
+ end
141
+
142
+ it "performs a `authorise one-click payment' request with specific detail and fraud offset" do
143
+ should_map_shortcut_to(:authorise_one_click_payment,
144
+ :reference => 'order-id',
145
+ :amount => { :currency => 'EUR', :value => 1234 },
146
+ :shopper => { :reference => 'user-id', :email => 's.hopper@example.com' },
147
+ :card => { :cvc => '737' },
148
+ :recurring_detail_reference => 'recurring-detail-reference',
149
+ :fraud_offset => -10,
150
+ :instant_capture => false,
151
+ )
152
+ Adyen::API.authorise_one_click_payment('order-id',
153
+ { :currency => 'EUR', :value => 1234 },
154
+ { :reference => 'user-id', :email => 's.hopper@example.com' },
155
+ { :cvc => '737' },
156
+ 'recurring-detail-reference',
157
+ -10,
158
+ false
159
+ )
160
+ end
161
+
162
+ it "performs a `capture' request" do
163
+ should_map_shortcut_to(:capture, :psp_reference => 'original-psp-reference', :amount => { :currency => 'EUR', :value => '1234' })
164
+ Adyen::API.capture_payment('original-psp-reference', { :currency => 'EUR', :value => '1234' })
165
+ end
166
+
167
+ it "performs a `refund payment' request" do
168
+ should_map_shortcut_to(:refund, :psp_reference => 'original-psp-reference', :amount => { :currency => 'EUR', :value => '1234' })
169
+ Adyen::API.refund_payment('original-psp-reference', { :currency => 'EUR', :value => '1234' })
170
+ end
171
+
172
+ it "performs a `cancel or refund payment' request" do
173
+ should_map_shortcut_to(:cancel_or_refund, :psp_reference => 'original-psp-reference')
174
+ Adyen::API.cancel_or_refund_payment('original-psp-reference')
175
+ end
176
+
177
+ it "performs a `cancel payment' request" do
178
+ should_map_shortcut_to(:cancel, :psp_reference => 'original-psp-reference')
179
+ Adyen::API.cancel_payment('original-psp-reference')
180
+ end
181
+ end
182
+
183
+ describe "for the RecurringService" do
184
+ before do
185
+ @recurring = double('RecurringService')
186
+ end
187
+
188
+ def should_map_shortcut_to(method, params)
189
+ Adyen::API::RecurringService.should_receive(:new).with(params).and_return(@recurring)
190
+ @recurring.should_receive(method)
191
+ end
192
+
193
+ it "performs a `tokenize creditcard details' request" do
194
+ should_map_shortcut_to(:store_token,
195
+ :shopper => { :reference => 'user-id', :email => 's.hopper@example.com' },
196
+ :card => { :expiry_month => 12, :expiry_year => 2012, :holder_name => "Simon Hopper", :number => '4444333322221111' }
197
+ )
198
+ Adyen::API.store_recurring_token(
199
+ { :reference => 'user-id', :email => 's.hopper@example.com' },
200
+ { :expiry_month => 12, :expiry_year => 2012, :holder_name => "Simon Hopper", :number => '4444333322221111' }
201
+ )
202
+ end
203
+
204
+ it "performs a `tokenize ELV details' request" do
205
+ should_map_shortcut_to(:store_token,
206
+ :shopper => { :reference => 'user-id', :email => 's.hopper@example.com' },
207
+ :elv => { :bank_location => "Berlin", :bank_name => "TestBank", :bank_location_id => "12345678", :holder_name => "Simon Hopper", :number => "1234567890" }
208
+ )
209
+ Adyen::API.store_recurring_token(
210
+ { :reference => 'user-id', :email => 's.hopper@example.com' },
211
+ { :bank_location => "Berlin", :bank_name => "TestBank", :bank_location_id => "12345678", :holder_name => "Simon Hopper", :number => "1234567890" }
212
+ )
213
+ end
214
+
215
+ it "preforms a `list recurring details' request" do
216
+ should_map_shortcut_to(:list, :shopper => { :reference => 'user-id' })
217
+ Adyen::API.list_recurring_details('user-id')
218
+ end
219
+
220
+ it "performs a `disable recurring contract' request for all details" do
221
+ should_map_shortcut_to(:disable, :shopper => { :reference => 'user-id' }, :recurring_detail_reference => nil)
222
+ Adyen::API.disable_recurring_contract('user-id')
223
+ end
224
+
225
+ it "performs a `disable recurring contract' request for a specific detail" do
226
+ should_map_shortcut_to(:disable, :shopper => { :reference => 'user-id' }, :recurring_detail_reference => 'detail-id')
227
+ Adyen::API.disable_recurring_contract('user-id', 'detail-id')
228
+ end
229
+ end
230
+ end
231
+ end