wvanbergen-adyen 0.1.1 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/adyen/soap.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require "handsoap"
2
2
 
3
3
  module Adyen
4
-
4
+
5
5
  # The SOAP module contains classes that interact with the Adyen
6
6
  # SOAP services. The clients are based on the Handsoap library.
7
7
  # Shared functionality for all services is implemented in the
@@ -9,11 +9,11 @@ module Adyen
9
9
  #
10
10
  # Note that you'll need an Adyen notification PSP reference for
11
11
  # most SOAP calls. Because of this, store all notifications that
12
- # Adyen sends to you. (e.g. using the Adyen::Notification ActiveRecord
12
+ # Adyen sends to you. (e.g. using the Adyen::Notification ActiveRecord
13
13
  # class). Moreover, most SOAP calls do not respond that they were
14
14
  # successful immediately, but a notifications to indicate that will
15
15
  # be sent later on.
16
- #
16
+ #
17
17
  # You'll need to provide a username and password to interact
18
18
  # with the Adyen SOAP services:
19
19
  #
@@ -24,11 +24,11 @@ module Adyen
24
24
  #
25
25
  # Adyen::SOAP.default_arguments[:merchent_account] = 'MyMerchant'
26
26
  #
27
- # For now, only the recurring payment service client is implemented
28
- # (Adyen::SOAP::RecurringService).
27
+ # For now, only the recurring payment service client is implemented
28
+ # (Adyen::SOAP::RecurringService).
29
29
  module SOAP
30
30
 
31
- class << self
31
+ class << self
32
32
  # Set up accessors for HTTP Basic Authentication and
33
33
  # for adding default arguments to SOAP calls.
34
34
  attr_accessor :username, :password, :default_arguments
@@ -36,7 +36,7 @@ module Adyen
36
36
 
37
37
  # Use no default arguments by default
38
38
  self.default_arguments = {}
39
-
39
+
40
40
  # The base class sets up XML namespaces and HTTP authentication
41
41
  # for all the Adyen SOAP services
42
42
  class Base < Handsoap::Service
@@ -48,43 +48,43 @@ module Adyen
48
48
  end
49
49
 
50
50
  # Setup basic auth headers in the HTTP client
51
- def on_after_create_http_client(http_client)
51
+ def on_after_create_http_client(http_client)
52
52
  debug { |logger| logger.puts "Authorization: #{Adyen::SOAP.username}:#{Adyen::SOAP.password}..." }
53
53
  # Handsoap BUG: Setting headers does not work, using a Curb specific method for now.
54
54
  # auth = Base64.encode64("#{Adyen::SOAP.username}:#{Adyen::SOAP.password}").chomp
55
55
  # http_client.headers['Authorization'] = "Basic #{auth}"
56
56
  http_client.userpwd = "#{Adyen::SOAP.username}:#{Adyen::SOAP.password}"
57
57
  end
58
-
58
+
59
59
  # Setup XML namespaces for SOAP request body
60
- def on_create_document(doc)
60
+ def on_create_document(doc)
61
61
  doc.alias 'payment', 'http://payment.services.adyen.com'
62
62
  doc.alias 'recurring', 'http://recurring.services.adyen.com'
63
63
  doc.alias 'common', 'http://common.services.adyen.com'
64
64
  end
65
-
65
+
66
66
  # Setup XML namespaces for SOAP response
67
67
  def on_response_document(doc)
68
68
  doc.add_namespace 'payment', 'http://payment.services.adyen.com'
69
69
  doc.add_namespace 'recurring', 'http://recurring.services.adyen.com'
70
- doc.add_namespace 'common', 'http://common.services.adyen.com'
70
+ doc.add_namespace 'common', 'http://common.services.adyen.com'
71
71
  end
72
-
72
+
73
73
  # Set endpoint URI before dispatch, so that changes in environment
74
74
  # are reflected correctly.
75
75
  def on_before_dispatch
76
76
  self.class.endpoint(:uri => self.class::ENDPOINT_URI % Adyen.environment.to_s, :version => 1)
77
77
  end
78
78
  end
79
-
79
+
80
80
  # SOAP client to interact with the payment modification service of Adyen.
81
81
  # At this moment, none of the calls are implemented.
82
82
  class PaymentService < Base
83
-
83
+
84
84
  ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Payment'
85
-
85
+
86
86
  end
87
-
87
+
88
88
  # SOAP client to interact with the recurring payment service of Adyen.
89
89
  # This client implements the submitRecurring call to submit payments
90
90
  # for a recurring contract. Moreover, it implements the deactiveRecurring
@@ -92,20 +92,20 @@ module Adyen
92
92
  #
93
93
  # See the Adyen Recurring manual for more information about this SOAP service
94
94
  class RecurringService < Base
95
-
95
+
96
96
  ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Recurring'
97
-
97
+
98
98
  # Submits a recurring payment. Requires the following arguments as hash:
99
99
  #
100
100
  # * <tt>:currency</tt> The currency code (EUR, GBP, USD, etc)
101
101
  # * <tt>:value</tt> The value of the payments in cents
102
102
  # * <tt>:merchent_account</tt> The merchant account under which to place
103
103
  # this payment.
104
- # * <tt>:recurring_reference</tt> The psp_reference of the RECURRING_CONTRACT
104
+ # * <tt>:recurring_reference</tt> The psp_reference of the RECURRING_CONTRACT
105
105
  # notification that was sent after the initial payment.
106
106
  # * <tt>:reference</tt> The (merchant) reference for this payment.
107
107
  # * <tt>:shopper_email</tt> The email address of the shopper.
108
- # * <tt>:shopper_reference</tt> The refrence of the shopper. This should be
108
+ # * <tt>:shopper_reference</tt> The refrence of the shopper. This should be
109
109
  # the same as the reference that was used to create the recurring contract.
110
110
  def submit(args = {})
111
111
  invoke_args = Adyen::SOAP.default_arguments.merge(args)
@@ -113,37 +113,37 @@ module Adyen
113
113
  message.add('recurring:recurringRequest') do |req|
114
114
  req.add('recurring:amount') do |amount|
115
115
  amount.add('common:currency', invoke_args[:currency])
116
- amount.add('common:value', invoke_args[:value])
116
+ amount.add('common:value', invoke_args[:value])
117
117
  end
118
- req.add('recurring:merchantAccount', invoke_args[:merchant_account])
118
+ req.add('recurring:merchantAccount', invoke_args[:merchant_account])
119
119
  req.add('recurring:recurringReference', invoke_args[:recurring_reference])
120
120
  req.add('recurring:reference', invoke_args[:reference])
121
121
  req.add('recurring:shopperEmail', invoke_args[:shopper_email])
122
- req.add('recurring:shopperReference', invoke_args[:shopper_reference])
122
+ req.add('recurring:shopperReference', invoke_args[:shopper_reference])
123
123
  end
124
124
  end
125
125
  end
126
-
126
+
127
127
  # Deactivates a recurring payment contract. Requires the following arguments:
128
128
  #
129
129
  # * <tt>:merchent_account</tt> The merchant account under which to place
130
130
  # this payment.
131
- # * <tt>:recurring_reference</tt> The psp_reference of the RECURRING_CONTRACT
131
+ # * <tt>:recurring_reference</tt> The psp_reference of the RECURRING_CONTRACT
132
132
  # notification that was sent after the initial payment.
133
133
  # * <tt>:reference</tt> The (merchant) reference for this deactivation.
134
- # * <tt>:shopper_reference</tt> The refrence of the shopper. This should be
135
- # the same as the reference that was used to create the recurring contract.
134
+ # * <tt>:shopper_reference</tt> The refrence of the shopper. This should be
135
+ # the same as the reference that was used to create the recurring contract.
136
136
  def deactivate(args = {})
137
- invoke_args = Adyen::SOAP.default_arguments.merge(args)
137
+ invoke_args = Adyen::SOAP.default_arguments.merge(args)
138
138
  response = invoke('recurring:deactivateRecurring') do |message|
139
- message.add('recurring:recurringRequest') do |req|
140
- req.add('recurring:merchantAccount', invoke_args[:merchant_account])
139
+ message.add('recurring:recurringRequest') do |req|
140
+ req.add('recurring:merchantAccount', invoke_args[:merchant_account])
141
141
  req.add('recurring:recurringReference', invoke_args[:recurring_reference])
142
142
  req.add('recurring:reference', invoke_args[:reference])
143
- req.add('recurring:shopperReference', invoke_args[:shopper_reference])
143
+ req.add('recurring:shopperReference', invoke_args[:shopper_reference])
144
144
  end
145
145
  end
146
146
  end
147
147
  end
148
- end
148
+ end
149
149
  end
data/spec/adyen_spec.rb CHANGED
@@ -6,8 +6,8 @@ describe Adyen do
6
6
  encoded_str = Adyen::Encoding.hmac_base64('bla', 'bla')
7
7
  encoded_str.should_not be_blank
8
8
  encoded_str.size.should == 28
9
- end
10
-
9
+ end
10
+
11
11
  it "should gzip_base64 correcly" do
12
12
  encoded_str = Adyen::Encoding.gzip_base64('bla')
13
13
  encoded_str.should_not be_blank
@@ -19,26 +19,26 @@ describe Adyen do
19
19
  it "should accept dates" do
20
20
  Adyen::Formatter::DateTime.fmt_date(Date.today).should match(/^\d{4}-\d{2}-\d{2}$/)
21
21
  end
22
-
22
+
23
23
  it "should accept times" do
24
24
  Adyen::Formatter::DateTime.fmt_time(Time.now).should match(/^\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}Z$/)
25
25
  end
26
-
26
+
27
27
  it "should accept valid time strings" do
28
28
  Adyen::Formatter::DateTime.fmt_time('2009-01-01T11:11:11Z').should match(/^\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}Z$/)
29
- end
30
-
31
- it "should accept valid time strings" do
29
+ end
30
+
31
+ it "should accept valid time strings" do
32
32
  Adyen::Formatter::DateTime.fmt_date('2009-01-01').should match(/^\d{4}-\d{2}-\d{2}$/)
33
33
  end
34
34
 
35
35
  it "should raise on an invalid time string" do
36
36
  lambda { Adyen::Formatter::DateTime.fmt_time('2009-01-01 11:11:11') }.should raise_error
37
- end
37
+ end
38
38
 
39
39
  it "should raise on an invalid date string" do
40
40
  lambda { Adyen::Formatter::DateTime.fmt_date('2009-1-1') }.should raise_error
41
- end
41
+ end
42
42
  end
43
43
 
44
44
  describe Adyen::Formatter::Price do
data/spec/form_spec.rb CHANGED
@@ -1,14 +1,14 @@
1
1
  require "#{File.dirname(__FILE__)}/spec_helper.rb"
2
2
 
3
3
  describe Adyen::Form do
4
-
4
+
5
5
  describe 'Action URLs' do
6
-
6
+
7
7
  before(:each) do
8
8
  # Use autodetection for the environment unless otherwise specified
9
9
  Adyen.environment = nil
10
10
  end
11
-
11
+
12
12
  it "should generate correct the testing url" do
13
13
  Adyen::Form.url.should eql('https://test.adyen.com/hpp/select.shtml')
14
14
  end
@@ -17,7 +17,7 @@ describe Adyen::Form do
17
17
  Adyen.environment = :live
18
18
  Adyen::Form.url.should eql('https://live.adyen.com/hpp/select.shtml')
19
19
  end
20
-
20
+
21
21
  it "should generate correct live url in a production environment" do
22
22
  Adyen.stub!(:autodetect_environment).and_return('live')
23
23
  Adyen::Form.url.should eql('https://live.adyen.com/hpp/select.shtml')
@@ -26,98 +26,122 @@ describe Adyen::Form do
26
26
  it "should generate correct live url if explicitely asked for" do
27
27
  Adyen::Form.url(:live).should eql('https://live.adyen.com/hpp/select.shtml')
28
28
  end
29
- end
30
-
29
+ end
30
+
31
31
  describe 'redirect signature check' do
32
32
  before(:each) do
33
33
  # Example taken from integration manual
34
-
34
+
35
35
  # Shared secret between you and Adyen, only valid for this skinCode!
36
- @shared_secret = 'Kah942*$7sdp0)'
37
-
36
+ @shared_secret = 'Kah942*$7sdp0)'
37
+
38
38
  # Example get params sent back with redirect
39
39
  @params = { :authResult => 'AUTHORISED', :pspReference => '1211992213193029',
40
40
  :merchantReference => 'Internet Order 12345', :skinCode => '4aD37dJA',
41
41
  :merchantSig => 'ytt3QxWoEhAskUzUne0P5VA9lPw='}
42
42
  end
43
-
43
+
44
44
  it "should calculate the signature string correctly" do
45
45
  Adyen::Form.redirect_signature_string(@params).should eql('AUTHORISED1211992213193029Internet Order 123454aD37dJA')
46
46
  end
47
-
47
+
48
48
  it "should calculate the signature correctly" do
49
49
  Adyen::Form.redirect_signature(@params, @shared_secret).should eql(@params[:merchantSig])
50
50
  end
51
-
51
+
52
52
  it "should check the signature correctly" do
53
53
  Adyen::Form.redirect_signature_check(@params, @shared_secret).should be_true
54
- end
55
-
54
+ end
55
+
56
56
  it "should detect a tampered field" do
57
57
  Adyen::Form.redirect_signature_check(@params.merge(:pspReference => 'tampered'), @shared_secret).should be_false
58
- end
58
+ end
59
59
 
60
60
  it "should detect a tampered signature" do
61
61
  Adyen::Form.redirect_signature_check(@params.merge(:merchantSig => 'tampered'), @shared_secret).should be_false
62
- end
62
+ end
63
63
 
64
64
  end
65
-
66
- describe 'hidden fields generation' do
65
+
66
+ describe 'redirect URL generation' do
67
+ before(:each) do
68
+ @attributes = { :currency_code => 'GBP', :payment_amount => 10000, :ship_before_date => Date.today,
69
+ :merchant_reference => 'Internet Order 12345', :skin_code => '4aD37dJA',
70
+ :merchant_account => 'TestMerchant', :session_validity => 1.hour.from_now }
71
+
72
+ @redirect_url = Adyen::Form.redirect_url(@attributes.merge(:shared_secret => 'secret'))
73
+ end
67
74
 
75
+ it "should return an URL pointing to the adyen server" do
76
+ @redirect_url.should =~ %r[^#{Adyen::Form.url}]
77
+ end
78
+
79
+ it "should include all provided attributes" do
80
+ params = @redirect_url.split('?', 2).last.split('&').map { |param| param.split('=', 2).first }
81
+ params.should include(*(@attributes.keys.map { |k| k.to_s.camelize(:lower) }))
82
+ end
83
+
84
+ it "should include the merchant signature" do
85
+ params = @redirect_url.split('?', 2).last.split('&').map { |param| param.split('=', 2).first }
86
+ params.should include('merchantSig')
87
+ end
88
+ end
89
+
90
+ describe 'hidden fields generation' do
91
+
68
92
  include ActionView::Helpers::TagHelper
69
-
93
+
70
94
  before(:each) do
71
- @attributes = { :currency_code => 'GBP', :payment_amount => 10000, :ship_before_date => Date.today,
72
- :merchant_reference => 'Internet Order 12345', :skin_code => '4aD37dJA',
73
- :merchant_account => 'TestMerchant', :session_validity => 1.hour.from_now }
95
+ @attributes = { :currency_code => 'GBP', :payment_amount => 10000, :ship_before_date => Date.today,
96
+ :merchant_reference => 'Internet Order 12345', :skin_code => '4aD37dJA',
97
+ :merchant_account => 'TestMerchant', :session_validity => 1.hour.from_now }
74
98
  end
75
-
99
+
76
100
  it "should generate a valid payment form" do
77
101
  content_tag(:form, Adyen::Form.hidden_fields(@attributes.merge(:shared_secret => 'secret')),
78
102
  :action => Adyen::Form.url, :method => :post).should have_adyen_payment_form
79
103
  end
80
104
  end
81
-
105
+
82
106
  describe 'signature calculation' do
83
107
 
84
- # This example is taken from the Adyen integration manual
108
+ # This example is taken from the Adyen integration manual
85
109
 
86
- before(:each) do
110
+ before(:each) do
87
111
  @attributes = { :currency_code => 'GBP', :payment_amount => 10000,
88
112
  :ship_before_date => '2007-10-20', :merchant_reference => 'Internet Order 12345',
89
- :skin_code => '4aD37dJA', :merchant_account => 'TestMerchant',
90
- :session_validity => '2007-10-11T11:00:00Z' }
91
-
92
- Adyen::Form.do_attribute_transformations!(@attributes)
113
+ :skin_code => '4aD37dJA', :merchant_account => 'TestMerchant',
114
+ :session_validity => '2007-10-11T11:00:00Z' }
115
+
116
+ Adyen::Form.do_attribute_transformations!(@attributes)
93
117
  end
94
-
118
+
95
119
  it "should construct the signature string correctly" do
96
120
  signature_string = Adyen::Form.calculate_signature_string(@attributes)
97
121
  signature_string.should eql("10000GBP2007-10-20Internet Order 123454aD37dJATestMerchant2007-10-11T11:00:00Z")
98
122
  end
99
-
123
+
100
124
  it "should calculate the signature correctly" do
101
125
  signature = Adyen::Form.calculate_signature(@attributes.merge(:shared_secret => 'Kah942*$7sdp0)'))
102
- signature.should eql('x58ZcRVL1H6y+XSeBGrySJ9ACVo=')
126
+ signature.should eql('x58ZcRVL1H6y+XSeBGrySJ9ACVo=')
103
127
  end
104
128
 
105
129
  it "should calculate the signature correctly for a recurring payment" do
106
- # Add the required recurrent payment attributes
130
+ # Add the required recurrent payment attributes
107
131
  @attributes.merge!(:recurring_contract => 'DEFAULT', :shopper_reference => 'grasshopper52', :shopper_email => 'gras.shopper@somewhere.org')
108
-
132
+
109
133
  signature_string = Adyen::Form.calculate_signature_string(@attributes)
110
134
  signature_string.should eql("10000GBP2007-10-20Internet Order 123454aD37dJATestMerchant2007-10-11T11:00:00Zgras.shopper@somewhere.orggrasshopper52DEFAULT")
111
135
  end
112
-
136
+
113
137
  it "should calculate the signature correctly for a recurring payment" do
114
138
  # Add the required recurrent payment attributes
115
139
  @attributes.merge!(:recurring_contract => 'DEFAULT', :shopper_reference => 'grasshopper52', :shopper_email => 'gras.shopper@somewhere.org')
116
-
140
+
117
141
  signature = Adyen::Form.calculate_signature(@attributes.merge(:shared_secret => 'Kah942*$7sdp0)'))
118
- signature.should eql('F2BQEYbE+EUhiRGuPtcD16Gm7JY=')
142
+ signature.should eql('F2BQEYbE+EUhiRGuPtcD16Gm7JY=')
119
143
  end
120
-
144
+
121
145
  end
122
-
146
+
123
147
  end
@@ -6,80 +6,80 @@ describe Adyen::Notification do
6
6
  ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
7
7
 
8
8
  ActiveRecord::Migration.verbose = false
9
- Adyen::Notification::Migration.up
9
+ Adyen::Notification::Migration.up
10
10
  end
11
-
11
+
12
12
  after(:all) do
13
13
  Adyen::Notification::Migration.down
14
14
  end
15
-
15
+
16
16
  describe Adyen::Notification::HttpPost do
17
17
 
18
18
  describe 'receiving payment authorization notification' do
19
-
19
+
20
20
  before(:each) do
21
21
  @request = mock('request')
22
22
  @request.stub!(:params).and_return({
23
- "merchantAccountCode"=>"FloorPlannerNL", "eventCode"=>"AUTHORISATION",
24
- "paymentMethod"=>"mc", "eventDate"=>"2009-08-10T09:00:08.04Z",
25
- "operations"=>"CANCEL,CAPTURE,REFUND", "merchantReference"=>"4",
26
- "action"=>"process_adyen", "live"=>"false", "controller"=>"payment_notifications",
27
- "value"=>"2500", "success"=>"false", "reason"=>"10676:1111:12/2012",
23
+ "merchantAccountCode"=>"FloorPlannerNL", "eventCode"=>"AUTHORISATION",
24
+ "paymentMethod"=>"mc", "eventDate"=>"2009-08-10T09:00:08.04Z",
25
+ "operations"=>"CANCEL,CAPTURE,REFUND", "merchantReference"=>"4",
26
+ "action"=>"process_adyen", "live"=>"false", "controller"=>"payment_notifications",
27
+ "value"=>"2500", "success"=>"false", "reason"=>"10676:1111:12/2012",
28
28
  "originalReference"=>"", "pspReference"=>"8712498948081194", "currency"=>"USD"})
29
29
 
30
30
  @notification = Adyen::Notification::HttpPost.log(@request)
31
31
  end
32
-
32
+
33
33
  after(:each) { @notification.destroy }
34
-
34
+
35
35
  it "should have saved the notification record" do
36
36
  @notification.should_not be_new_record
37
37
  end
38
-
38
+
39
39
  it "should be an authorization" do
40
40
  @notification.should be_authorisation
41
41
  end
42
-
42
+
43
43
  it "should convert the amount to a bigdecimal" do
44
44
  @notification.value.should eql(BigDecimal.new('25.00'))
45
45
  end
46
-
46
+
47
47
  it "should convert live to a boolean" do
48
48
  @notification.should_not be_live
49
49
  end
50
-
50
+
51
51
  it "should convert success to a boolean" do
52
52
  @notification.should_not be_success
53
53
  end
54
-
54
+
55
55
  it "should not be a successfull authorization" do
56
56
  @notification.should_not be_successful_authorization
57
57
  end
58
-
58
+
59
59
  it "should convert the eventDate" do
60
60
  @notification.event_date.should be_kind_of(Time)
61
61
  end
62
-
62
+
63
63
  it "should convert the empty original reference to NULL" do
64
64
  @notification.original_reference.should be_nil
65
- end
65
+ end
66
66
  end
67
-
67
+
68
68
  context 'duplicate detection' do
69
69
  before(:each) do
70
70
 
71
- @fields = { "merchantAccountCode"=>"FloorPlannerNL", "eventCode"=>"AUTHORISATION",
72
- "paymentMethod"=>"mc", "eventDate"=>"2009-08-10T09:00:08.04Z",
73
- "operations"=>"CANCEL,CAPTURE,REFUND", "merchantReference"=>"4",
74
- "action"=>"process_adyen", "live"=>"false", "controller"=>"payment_notifications",
75
- "value"=>"2500", "success"=>"false", "reason"=>"10676:1111:12/2012",
71
+ @fields = { "merchantAccountCode"=>"FloorPlannerNL", "eventCode"=>"AUTHORISATION",
72
+ "paymentMethod"=>"mc", "eventDate"=>"2009-08-10T09:00:08.04Z",
73
+ "operations"=>"CANCEL,CAPTURE,REFUND", "merchantReference"=>"4",
74
+ "action"=>"process_adyen", "live"=>"false", "controller"=>"payment_notifications",
75
+ "value"=>"2500", "success"=>"false", "reason"=>"10676:1111:12/2012",
76
76
  "originalReference"=>"", "pspReference"=>"8712498948081194", "currency"=>"USD"}
77
77
 
78
78
  @request = mock('request')
79
79
  @request.stub!(:params).and_return(@fields)
80
80
  @notification = Adyen::Notification::HttpPost.log(@request)
81
81
  end
82
-
82
+
83
83
  after(:each) { @notification.destroy }
84
84
 
85
85
  it "should raise an error on a duplicate notification" do
@@ -92,6 +92,6 @@ describe Adyen::Notification do
92
92
  lambda { Adyen::Notification::HttpPost.log(second_request) }.should_not raise_error
93
93
  end
94
94
 
95
- end
95
+ end
96
96
  end
97
97
  end