wvanbergen-adyen 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,100 +1,32 @@
1
1
  = Adyen
2
2
 
3
3
  Package to simplify including the Adyen payments services into a Ruby on Rails application.
4
- Currently, the package contains functions to easily generate the required hidden fields and
5
- matchers to easily check your views using rspec.
6
4
 
7
- * For more information about Adyen, see http://www.adyen.com
8
- * For more information about integrating Adyen, see their manuals at
9
- http://support.adyen.com/links/documentation
10
-
11
- == Skins
12
-
13
- Adyen using the notion of "skins" to determine what payment methods should be available
14
- and what the payment environment should look like. At least one skin is required, which
15
- can be created in the merchant area of Adyen. For every key, a shared secret is generated
16
- that is required to sign payment forms. You will need to provide this secret as the
17
- :shared_secret field to the hidden_fields method (see below).
18
-
19
- == Building payment forms
20
-
21
- <% form_tag(:url => Adyen::Form.url) do %>
22
- <%= Adyen::Form.hidden_fields(:merchant_account => 'myaccount', ... ,
23
- :skin_code => 'myperfectskin', :shared_secret => 'youllneverguess')
24
- ...
25
- <%= submit_tag('pay') %>
26
- <% end %>
27
-
28
- Please refer to the Adyen integration manual for all the
29
-
30
- <tt>Adyen::Form.url</tt> will return the URL to the live environment of Adyen in production
31
- mode, otherwise it will return the testing environment. To override this behavior, use:
32
-
33
- <% form_tag(:url => Adyen::Form.url('live')) do %>
34
- ...
35
- <% end %>
36
-
37
- <tt>Adyen::Form.hidden_fields</tt> will generate the hidden fields for the key/value pairs
38
- you provide to the function. The keys will be camelized automatically. Some notes:
5
+ Adyen integration relies on three modes of communication between Adyen, your server and your client/customer:
39
6
 
40
- * <tt>:recurring => true</tt> will be translated to <tt>:recurringContract => 'DEFAULT'</tt>.
41
- * <tt>:order_data</tt> will be encoded using gzip/base64.
42
- * <tt>:shared_secret</tt> must be provided to calculate the merchant signature.
43
- * <tt>:merchant_sig</tt> will be computed automatically using this secret.
7
+ * Client-to-Adyen communication using forms and redirects.
8
+ * Adyen-to-server communications using notifications.
9
+ * Server-to-Adyen communication using SOAP services.
44
10
 
45
- == Testing payment forms using rspec matchers
11
+ This library aims to ease the implementation of all these modes into your application. Moreover, it provides matchers, assertions and mocks to make it easier to implement an automated test suite to assert the integration is working correctly.
46
12
 
47
- First, make sure that the Adyen matchers are available in your view specs:
13
+ == Installation
48
14
 
49
- Spec::Runner.configure do |config|
50
- ...
51
- config.include Adyen::Matchers, :type => :views
52
- ...
53
- end
15
+ This plugin can either be installed as gem or Rails plugin:
54
16
 
55
- To check the response in a view spec, use the <tt>have_adyen_payment_form</tt>,
56
- <tt>have_adyen_recurrent_payment_form</tt> and <tt>have_adyen_single_payment_form matchers</tt>.
57
- By passing a hash, you can check the values of the hidden fields. By passing :anything
58
- as value, the matcher will simply check if the hidden field exists and ignore its value.
17
+ gem install wvanbergen-adyen --source http://gems.github.com # as gem
18
+ script/plugin install git://github.com/wvanbergen/adyen.git # as plugin
59
19
 
60
- Some example specs:
20
+ == Usage
61
21
 
62
- before(:each) do
63
- render 'payments/new.html.erb'
64
- end
22
+ See the project wiki on http://wiki.github.com/wvanbergen/adyen to get started.
65
23
 
66
- it "should contain an Adyen payment form" do
67
- # either single or recurring
68
- response.should have_adyen_payment_form(:currency_code => 'EUR', :payment_amount => 1000)
69
- end
70
-
71
- it "should contain an Adyen recurrent payment form" do
72
- response.should have_adyen_recurrent_payment_form
73
- end
74
-
75
- it "should contain an Adyen recurrent payment form" do
76
- response.should have_adyen_single_payment_form(:merchant_reference => :anything)
77
- end
78
-
79
- == Testing payment forms using assertions
80
-
81
- To use the assertions in unit tests, first include the matchers module in your test class:
82
-
83
- class PaymentControllerTest < Test::Unit
84
- include Adyen::Matchers
85
- ...
86
-
87
- Use the assertion methods <tt>assert_adyen_payment_form</tt>, <tt>assert_adyen_single_payment_form</tt>
88
- and <tt>assert_adyen_recurring_payment_form</tt>. They work similarly to the RSpec matcher methods
89
- described above. An example:
90
-
91
- def test_payment_form
92
- get new_payment_path
93
- assert_adyen_payment_form(@response, :currency_code => 'EUR', :payment_amount => 1000)
94
- end
24
+ * For more information about Adyen, see http://www.adyen.com
25
+ * For more information about integrating Adyen, see their manuals at
26
+ http://support.adyen.com/links/documentation
95
27
 
96
28
  == About
97
29
 
98
30
  This package is written by Michel Barbosa and Willem van Bergen for Floorplanner.com,
99
31
  and made public under the MIT license (see LICENSE). It comes without warranty of any kind,
100
- so use at your own risk.
32
+ so use at your own risk.
@@ -0,0 +1,21 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require 'stringio'
4
+ require 'zlib'
5
+
6
+ module Adyen
7
+ module Encoding
8
+ def self.hmac_base64(hmac_key, message)
9
+ digest = OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), hmac_key, message)
10
+ Base64.encode64(digest).strip
11
+ end
12
+
13
+ def self.gzip_base64(message)
14
+ sio = StringIO.new
15
+ gz = Zlib::GzipWriter.new(sio)
16
+ gz.write(message)
17
+ gz.close
18
+ Base64.encode64(sio.string).gsub("\n", "")
19
+ end
20
+ end
21
+ end
data/lib/adyen/form.rb CHANGED
@@ -36,6 +36,7 @@ module Adyen
36
36
  attributes[:session_validity] = Adyen::Formatter::DateTime.fmt_time(attributes[:session_validity])
37
37
  end
38
38
 
39
+
39
40
  def self.hidden_fields(attributes = {})
40
41
  do_attribute_transformations!(attributes)
41
42
 
@@ -54,5 +55,17 @@ module Adyen
54
55
  }.join("\n")
55
56
  end
56
57
 
58
+ def self.redirect_signature_string(params)
59
+ params[:authResult].to_s + params[:pspReference].to_s + params[:merchantReference].to_s + params[:skinCode].to_s
60
+ end
61
+
62
+ def self.redirect_signature(params, shared_secret)
63
+ Adyen::Encoding.hmac_base64(shared_secret, redirect_signature_string(params))
64
+ end
65
+
66
+ def self.redirect_signature_check(params, shared_secret)
67
+ params[:merchantSig] == redirect_signature(params, shared_secret)
68
+ end
69
+
57
70
  end
58
71
  end
@@ -0,0 +1,37 @@
1
+ module Adyen
2
+ module Formatter
3
+ module DateTime
4
+ # Returns a valid Adyen string representation for a date
5
+ def self.fmt_date(date)
6
+ case date
7
+ when Date, DateTime, Time
8
+ date.strftime('%Y-%m-%d')
9
+ else
10
+ raise "Invalid date notation: #{date.inspect}!" unless /^\d{4}-\d{2}-\d{2}$/ =~ date
11
+ date
12
+ end
13
+ end
14
+
15
+ # Returns a valid Adyen string representation for a timestamp
16
+ def self.fmt_time(time)
17
+ case time
18
+ when Date, DateTime, Time
19
+ time.strftime('%Y-%m-%dT%H:%M:%SZ')
20
+ else
21
+ raise "Invalid timestamp notation: #{time.inspect}!" unless /^\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}Z$/ =~ time
22
+ time
23
+ end
24
+ end
25
+ end
26
+
27
+ module Price
28
+ def self.in_cents(price)
29
+ ((price * 100).round).to_i
30
+ end
31
+
32
+ def self.from_cents(price)
33
+ BigDecimal.new(price.to_s) / 100
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,101 @@
1
+ require 'activerecord'
2
+
3
+ module Adyen
4
+ class Notification < ActiveRecord::Base
5
+
6
+ DEFAULT_TABLE_NAME = :adyen_notifications
7
+ set_table_name(DEFAULT_TABLE_NAME)
8
+
9
+ validates_presence_of :event_code
10
+ validates_presence_of :psp_reference
11
+ validates_uniqueness_of :success, :scope => [:psp_reference, :event_code]
12
+
13
+ # Make sure we don't end up with an original_reference with an empty string
14
+ before_validation { |notification| notification.original_reference = nil if notification.original_reference.blank? }
15
+
16
+ def self.log(params)
17
+ converted_params = {}
18
+ # Convert each attribute from CamelCase notation to under_score notation
19
+ # For example, merchantReference will be converted to merchant_reference
20
+ params.each do |key, value|
21
+ field_name = key.to_s.underscore
22
+ converted_params[field_name] = value if self.column_names.include?(field_name)
23
+ end
24
+ self.create!(converted_params)
25
+ end
26
+
27
+ def authorisation?
28
+ event_code == 'AUTHORISATION'
29
+ end
30
+
31
+ alias :authorization? :authorisation?
32
+
33
+ def successful_authorisation?
34
+ event_code == 'AUTHORISATION' && success?
35
+ end
36
+
37
+ def collect_payment_for_recurring_contract!(options)
38
+ # Make sure we convert the value to cents
39
+ options[:value] = Adyen::Formatter::Price.in_cents(options[:value])
40
+ raise "This is not a recurring contract!" unless event_code == 'RECURRING_CONTRACT'
41
+ Adyen::SOAP::RecurringService.submit(options.merge(:recurring_reference => self.psp_reference))
42
+ end
43
+
44
+ def deactivate_recurring_contract!(options)
45
+ raise "This is not a recurring contract!" unless event_code == 'RECURRING_CONTRACT'
46
+ Adyen::SOAP::RecurringService.deactivate(options.merge(:recurring_reference => self.psp_reference))
47
+ end
48
+
49
+ alias :successful_authorization? :successful_authorisation?
50
+
51
+ class HttpPost < Notification
52
+
53
+ def self.log(request)
54
+ super(request.params)
55
+ end
56
+
57
+ def live=(value)
58
+ self.write_attribute(:live, [true, 1, '1', 'true'].include?(value))
59
+ end
60
+
61
+ def success=(value)
62
+ self.write_attribute(:success, [true, 1, '1', 'true'].include?(value))
63
+ end
64
+
65
+ def value=(value)
66
+ self.write_attribute(:value, Adyen::Formatter::Price.from_cents(value)) unless value.blank?
67
+ end
68
+ end
69
+
70
+ class Migration < ActiveRecord::Migration
71
+
72
+ def self.up(table_name = Adyen::Notification::DEFAULT_TABLE_NAME)
73
+ create_table(table_name) do |t|
74
+ t.boolean :live, :null => false, :default => false
75
+ t.string :event_code, :null => false
76
+ t.string :psp_reference, :null => false
77
+ t.string :original_reference, :null => true
78
+ t.string :merchant_reference, :null => false
79
+ t.string :merchant_account_code, :null => false
80
+ t.datetime :event_date, :null => false
81
+ t.boolean :success, :null => false, :default => false
82
+ t.string :payment_method, :null => true
83
+ t.string :operations, :null => true
84
+ t.text :reason
85
+ t.string :currency, :null => false, :limit => 3
86
+ t.decimal :value, :null => true, :precision => 9, :scale => 2
87
+ t.boolean :processed, :null => false, :default => false
88
+ t.timestamps
89
+ end
90
+ add_index table_name, [:psp_reference, :event_code, :success], :unique => true
91
+ end
92
+
93
+ def self.down(table_name = Adyen::Notification::DEFAULT_TABLE_NAME)
94
+ remove_index(table_name, [:psp_reference, :event_code, :success])
95
+ drop_table(table_name)
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+ end
data/lib/adyen/soap.rb ADDED
@@ -0,0 +1,149 @@
1
+ require "handsoap"
2
+
3
+ module Adyen
4
+
5
+ # The SOAP module contains classes that interact with the Adyen
6
+ # SOAP services. The clients are based on the Handsoap library.
7
+ # Shared functionality for all services is implemented in the
8
+ # Adyen::SOAP::Base class.
9
+ #
10
+ # Note that you'll need an Adyen notification PSP reference for
11
+ # most SOAP calls. Because of this, store all notifications that
12
+ # Adyen sends to you. (e.g. using the Adyen::Notification ActiveRecord
13
+ # class). Moreover, most SOAP calls do not respond that they were
14
+ # successful immediately, but a notifications to indicate that will
15
+ # be sent later on.
16
+ #
17
+ # You'll need to provide a username and password to interact
18
+ # with the Adyen SOAP services:
19
+ #
20
+ # Adyen::SOAP.username = 'ws@Company.MyAccount'
21
+ # Adyen::SOAP.password = 'very$ecret'
22
+ #
23
+ # You can setup default values for every SOAP call that needs them:
24
+ #
25
+ # Adyen::SOAP.default_arguments[:merchent_account] = 'MyMerchant'
26
+ #
27
+ # For now, only the recurring payment service client is implemented
28
+ # (Adyen::SOAP::RecurringService).
29
+ module SOAP
30
+
31
+ class << self
32
+ # Set up accessors for HTTP Basic Authentication and
33
+ # for adding default arguments to SOAP calls.
34
+ attr_accessor :username, :password, :default_arguments
35
+ end
36
+
37
+ # Use no default arguments by default
38
+ self.default_arguments = {}
39
+
40
+ # The base class sets up XML namespaces and HTTP authentication
41
+ # for all the Adyen SOAP services
42
+ class Base < Handsoap::Service
43
+
44
+ def self.inherited(klass)
45
+ # The version must be set to construct the request envelopes,
46
+ # the URI wil be set later using the correct Adyen.environment.
47
+ klass.endpoint :version => 1, :uri => 'bogus'
48
+ end
49
+
50
+ # Setup basic auth headers in the HTTP client
51
+ def on_after_create_http_client(http_client)
52
+ debug { |logger| logger.puts "Authorization: #{Adyen::SOAP.username}:#{Adyen::SOAP.password}..." }
53
+ # Handsoap BUG: Setting headers does not work, using a Curb specific method for now.
54
+ # auth = Base64.encode64("#{Adyen::SOAP.username}:#{Adyen::SOAP.password}").chomp
55
+ # http_client.headers['Authorization'] = "Basic #{auth}"
56
+ http_client.userpwd = "#{Adyen::SOAP.username}:#{Adyen::SOAP.password}"
57
+ end
58
+
59
+ # Setup XML namespaces for SOAP request body
60
+ def on_create_document(doc)
61
+ doc.alias 'payment', 'http://payment.services.adyen.com'
62
+ doc.alias 'recurring', 'http://recurring.services.adyen.com'
63
+ doc.alias 'common', 'http://common.services.adyen.com'
64
+ end
65
+
66
+ # Setup XML namespaces for SOAP response
67
+ def on_response_document(doc)
68
+ doc.add_namespace 'payment', 'http://payment.services.adyen.com'
69
+ doc.add_namespace 'recurring', 'http://recurring.services.adyen.com'
70
+ doc.add_namespace 'common', 'http://common.services.adyen.com'
71
+ end
72
+
73
+ # Set endpoint URI before dispatch, so that changes in environment
74
+ # are reflected correctly.
75
+ def on_before_dispatch
76
+ self.class.endpoint(:uri => self.class::ENDPOINT_URI % Adyen.environment.to_s, :version => 1)
77
+ end
78
+ end
79
+
80
+ # SOAP client to interact with the payment modification service of Adyen.
81
+ # At this moment, none of the calls are implemented.
82
+ class PaymentService < Base
83
+
84
+ ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Payment'
85
+
86
+ end
87
+
88
+ # SOAP client to interact with the recurring payment service of Adyen.
89
+ # This client implements the submitRecurring call to submit payments
90
+ # for a recurring contract. Moreover, it implements the deactiveRecurring
91
+ # call to cancel a recurring contract.
92
+ #
93
+ # See the Adyen Recurring manual for more information about this SOAP service
94
+ class RecurringService < Base
95
+
96
+ ENDPOINT_URI = 'https://pal-%s.adyen.com/pal/servlet/soap/Recurring'
97
+
98
+ # Submits a recurring payment. Requires the following arguments as hash:
99
+ #
100
+ # * <tt>:currency</tt> The currency code (EUR, GBP, USD, etc)
101
+ # * <tt>:value</tt> The value of the payments in cents
102
+ # * <tt>:merchent_account</tt> The merchant account under which to place
103
+ # this payment.
104
+ # * <tt>:recurring_reference</tt> The psp_reference of the RECURRING_CONTRACT
105
+ # notification that was sent after the initial payment.
106
+ # * <tt>:reference</tt> The (merchant) reference for this payment.
107
+ # * <tt>:shopper_email</tt> The email address of the shopper.
108
+ # * <tt>:shopper_reference</tt> The refrence of the shopper. This should be
109
+ # the same as the reference that was used to create the recurring contract.
110
+ def submit(args = {})
111
+ invoke_args = Adyen::SOAP.default_arguments.merge(args)
112
+ response = invoke('recurring:submitRecurring') do |message|
113
+ message.add('recurring:recurringRequest') do |req|
114
+ req.add('recurring:amount') do |amount|
115
+ amount.add('common:currency', invoke_args[:currency])
116
+ amount.add('common:value', invoke_args[:value])
117
+ end
118
+ req.add('recurring:merchantAccount', invoke_args[:merchant_account])
119
+ req.add('recurring:recurringReference', invoke_args[:recurring_reference])
120
+ req.add('recurring:reference', invoke_args[:reference])
121
+ req.add('recurring:shopperEmail', invoke_args[:shopper_email])
122
+ req.add('recurring:shopperReference', invoke_args[:shopper_reference])
123
+ end
124
+ end
125
+ end
126
+
127
+ # Deactivates a recurring payment contract. Requires the following arguments:
128
+ #
129
+ # * <tt>:merchent_account</tt> The merchant account under which to place
130
+ # this payment.
131
+ # * <tt>:recurring_reference</tt> The psp_reference of the RECURRING_CONTRACT
132
+ # notification that was sent after the initial payment.
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.
136
+ def deactivate(args = {})
137
+ invoke_args = Adyen::SOAP.default_arguments.merge(args)
138
+ response = invoke('recurring:deactivateRecurring') do |message|
139
+ message.add('recurring:recurringRequest') do |req|
140
+ req.add('recurring:merchantAccount', invoke_args[:merchant_account])
141
+ req.add('recurring:recurringReference', invoke_args[:recurring_reference])
142
+ req.add('recurring:reference', invoke_args[:reference])
143
+ req.add('recurring:shopperReference', invoke_args[:shopper_reference])
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,53 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper.rb"
2
+
3
+ describe Adyen do
4
+ describe Adyen::Encoding do
5
+ it "should a hmac_base64 correcly" do
6
+ encoded_str = Adyen::Encoding.hmac_base64('bla', 'bla')
7
+ encoded_str.should_not be_blank
8
+ encoded_str.size.should == 28
9
+ end
10
+
11
+ it "should gzip_base64 correcly" do
12
+ encoded_str = Adyen::Encoding.gzip_base64('bla')
13
+ encoded_str.should_not be_blank
14
+ encoded_str.size.should == 32
15
+ end
16
+ end
17
+
18
+ describe Adyen::Formatter::DateTime do
19
+ it "should accept dates" do
20
+ Adyen::Formatter::DateTime.fmt_date(Date.today).should match(/^\d{4}-\d{2}-\d{2}$/)
21
+ end
22
+
23
+ it "should accept times" do
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
+ end
26
+
27
+ it "should accept valid time strings" do
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
32
+ Adyen::Formatter::DateTime.fmt_date('2009-01-01').should match(/^\d{4}-\d{2}-\d{2}$/)
33
+ end
34
+
35
+ it "should raise on an invalid time string" do
36
+ lambda { Adyen::Formatter::DateTime.fmt_time('2009-01-01 11:11:11') }.should raise_error
37
+ end
38
+
39
+ it "should raise on an invalid date string" do
40
+ lambda { Adyen::Formatter::DateTime.fmt_date('2009-1-1') }.should raise_error
41
+ end
42
+ end
43
+
44
+ describe Adyen::Formatter::Price do
45
+ it "should return a Fixnum with digits only when converting to cents" do
46
+ Adyen::Formatter::Price.in_cents(33.76).should be_kind_of(Fixnum)
47
+ end
48
+
49
+ it "should return a BigDecimal when converting from cents" do
50
+ Adyen::Formatter::Price.from_cents(1234).should be_kind_of(BigDecimal)
51
+ end
52
+ end
53
+ end
data/spec/form_spec.rb CHANGED
@@ -28,6 +28,41 @@ describe Adyen::Form do
28
28
  end
29
29
  end
30
30
 
31
+ describe 'redirect signature check' do
32
+ before(:each) do
33
+ # Example taken from integration manual
34
+
35
+ # Shared secret between you and Adyen, only valid for this skinCode!
36
+ @shared_secret = 'Kah942*$7sdp0)'
37
+
38
+ # Example get params sent back with redirect
39
+ @params = { :authResult => 'AUTHORISED', :pspReference => '1211992213193029',
40
+ :merchantReference => 'Internet Order 12345', :skinCode => '4aD37dJA',
41
+ :merchantSig => 'ytt3QxWoEhAskUzUne0P5VA9lPw='}
42
+ end
43
+
44
+ it "should calculate the signature string correctly" do
45
+ Adyen::Form.redirect_signature_string(@params).should eql('AUTHORISED1211992213193029Internet Order 123454aD37dJA')
46
+ end
47
+
48
+ it "should calculate the signature correctly" do
49
+ Adyen::Form.redirect_signature(@params, @shared_secret).should eql(@params[:merchantSig])
50
+ end
51
+
52
+ it "should check the signature correctly" do
53
+ Adyen::Form.redirect_signature_check(@params, @shared_secret).should be_true
54
+ end
55
+
56
+ it "should detect a tampered field" do
57
+ Adyen::Form.redirect_signature_check(@params.merge(:pspReference => 'tampered'), @shared_secret).should be_false
58
+ end
59
+
60
+ it "should detect a tampered signature" do
61
+ Adyen::Form.redirect_signature_check(@params.merge(:merchantSig => 'tampered'), @shared_secret).should be_false
62
+ end
63
+
64
+ end
65
+
31
66
  describe 'hidden fields generation' do
32
67
 
33
68
  include ActionView::Helpers::TagHelper
@@ -0,0 +1,101 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper.rb"
2
+
3
+ require 'action_controller'
4
+ require 'action_controller/test_process'
5
+
6
+ describe Adyen::Notification do
7
+
8
+ before(:all) do
9
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
10
+
11
+ ActiveRecord::Migration.verbose = false
12
+ Adyen::Notification::Migration.up
13
+ end
14
+
15
+ after(:all) do
16
+ Adyen::Notification::Migration.down
17
+ end
18
+
19
+ describe Adyen::Notification::HttpPost do
20
+
21
+ describe 'receiving payment authorization notification' do
22
+
23
+ before(:each) do
24
+ @request = mock('request')
25
+ @request.stub!(:params).and_return({
26
+ "merchantAccountCode"=>"FloorPlannerNL", "eventCode"=>"AUTHORISATION",
27
+ "paymentMethod"=>"mc", "eventDate"=>"2009-08-10T09:00:08.04Z",
28
+ "operations"=>"CANCEL,CAPTURE,REFUND", "merchantReference"=>"4",
29
+ "action"=>"process_adyen", "live"=>"false", "controller"=>"payment_notifications",
30
+ "value"=>"2500", "success"=>"false", "reason"=>"10676:1111:12/2012",
31
+ "originalReference"=>"", "pspReference"=>"8712498948081194", "currency"=>"USD"})
32
+
33
+ @notification = Adyen::Notification::HttpPost.log(@request)
34
+ end
35
+
36
+ after(:each) { @notification.destroy }
37
+
38
+ it "should have saved the notification record" do
39
+ @notification.should_not be_new_record
40
+ end
41
+
42
+ it "should be an authorization" do
43
+ @notification.should be_authorisation
44
+ end
45
+
46
+ it "should convert the amount to a bigdecimal" do
47
+ @notification.value.should eql(BigDecimal.new('25.00'))
48
+ end
49
+
50
+ it "should convert live to a boolean" do
51
+ @notification.should_not be_live
52
+ end
53
+
54
+ it "should convert success to a boolean" do
55
+ @notification.should_not be_success
56
+ end
57
+
58
+ it "should not be a successfull authorization" do
59
+ @notification.should_not be_successful_authorization
60
+ end
61
+
62
+ it "should convert the eventDate" do
63
+ @notification.event_date.should be_kind_of(Time)
64
+ end
65
+
66
+ it "should convert the empty original reference to NULL" do
67
+ @notification.original_reference.should be_nil
68
+ end
69
+ end
70
+
71
+ context 'duplicate detection' do
72
+ before(:each) do
73
+
74
+ @fields = { "merchantAccountCode"=>"FloorPlannerNL", "eventCode"=>"AUTHORISATION",
75
+ "paymentMethod"=>"mc", "eventDate"=>"2009-08-10T09:00:08.04Z",
76
+ "operations"=>"CANCEL,CAPTURE,REFUND", "merchantReference"=>"4",
77
+ "action"=>"process_adyen", "live"=>"false", "controller"=>"payment_notifications",
78
+ "value"=>"2500", "success"=>"false", "reason"=>"10676:1111:12/2012",
79
+ "originalReference"=>"", "pspReference"=>"8712498948081194", "currency"=>"USD"}
80
+
81
+ @request = mock('request')
82
+ @request.stub!(:params).and_return(@fields)
83
+ @notification = Adyen::Notification::HttpPost.log(@request)
84
+ end
85
+
86
+ after(:each) { @notification.destroy }
87
+
88
+ it "should raise an error on a duplicate notification" do
89
+ lambda { Adyen::Notification::HttpPost.log(@request) }.should raise_error(ActiveRecord::RecordInvalid)
90
+ end
91
+
92
+ it "should not raise an error on a when success is set to true" do
93
+ second_request = mock('request')
94
+ second_request.stub!(:params).and_return(@fields.merge('success' => 'true'))
95
+ lambda { Adyen::Notification::HttpPost.log(second_request) }.should_not raise_error
96
+ end
97
+
98
+ end
99
+ end
100
+
101
+ end
data/spec/soap_spec.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper.rb"
2
+
3
+ describe Adyen::SOAP do
4
+
5
+ end
@@ -26,27 +26,37 @@ module Rake
26
26
  desc "Updates the file lists for this gem"
27
27
  task(:manifest) { manifest_task }
28
28
 
29
- desc "Releases a new version of #{@name}"
29
+ desc "Builds a gem package for #{@name}"
30
30
  task(:build => [:manifest]) { build_task }
31
31
 
32
32
 
33
33
  release_dependencies = [:check_clean_master_branch, :version, :build, :create_tag]
34
- release_dependencies.push 'doc:publish' if generate_rdoc?
34
+ release_dependencies.push 'doc:publish' if has_rdoc?
35
35
  release_dependencies.unshift 'test' if has_tests?
36
36
  release_dependencies.unshift 'spec' if has_specs?
37
37
 
38
38
  desc "Releases a new version of #{@name}"
39
39
  task(:release => release_dependencies) { release_task }
40
40
 
41
+ namespace(:release) do
42
+ release_checks = [:check_clean_master_branch, :check_version, :build]
43
+ release_checks.push 'doc:compile' if has_rdoc?
44
+ release_checks.unshift 'test' if has_tests?
45
+ release_checks.unshift 'spec' if has_specs?
46
+
47
+ desc "Test release conditions"
48
+ task(:check => release_checks) { release_check_task }
49
+ end
50
+
41
51
  # helper task for releasing
42
- task(:check_clean_master_branch) { verify_clean_status('master') }
52
+ task(:check_clean_master_branch) { verify_fast_forward('master', 'origin', 'master'); verify_clean_status('master') }
43
53
  task(:check_version) { verify_version(ENV['VERSION'] || @specification.version) }
44
54
  task(:version => [:check_version]) { set_gem_version! }
45
55
  task(:create_tag) { create_version_tag! }
46
56
  end
47
57
 
48
58
  # Register RDoc tasks
49
- if generate_rdoc?
59
+ if has_rdoc?
50
60
  require 'rake/rdoctask'
51
61
 
52
62
  namespace(:doc) do
@@ -77,7 +87,7 @@ module Rake
77
87
 
78
88
  desc "Run all specs for #{@name}"
79
89
  Spec::Rake::SpecTask.new(:spec) do |t|
80
- t.spec_files = FileList[File.dirname(__FILE__) + '/../spec/**/*_spec.rb']
90
+ t.spec_files = FileList['spec/**/*_spec.rb']
81
91
  end
82
92
  end
83
93
 
@@ -87,7 +97,7 @@ module Rake
87
97
 
88
98
  desc "Run all unit tests for #{@name}"
89
99
  Rake::TestTask.new(:test) do |t|
90
- t.pattern = File.dirname(__FILE__) + '/../test/**/*_test.rb'
100
+ t.pattern = 'test/**/*_test.rb'
91
101
  t.verbose = true
92
102
  t.libs << 'test'
93
103
  end
@@ -96,16 +106,16 @@ module Rake
96
106
 
97
107
  protected
98
108
 
99
- def generate_rdoc?
100
- git_branch_exists?('gh-pages')
109
+ def has_rdoc?
110
+ run_command('git branch').any? { |line| /gh-pages\s*$/ =~ line}
101
111
  end
102
112
 
103
113
  def has_specs?
104
- Dir[File.dirname(__FILE__) + '/../spec/**/*_spec.rb'].any?
114
+ Dir['spec/**/*_spec.rb'].any?
105
115
  end
106
116
 
107
117
  def has_tests?
108
- Dir[File.dirname(__FILE__) + '/../test/**/*_test.rb'].any?
118
+ Dir['test/**/*_test.rb'].any?
109
119
  end
110
120
 
111
121
  def reload_gemspec!
@@ -161,23 +171,22 @@ module Rake
161
171
  end
162
172
 
163
173
  def gemspec_file
164
- @gemspec_file ||= Dir[File.dirname(__FILE__) + '/../*.gemspec'].first
165
- end
166
-
167
- def git_branch_exists?(branch_name)
168
- branches = run_command('git branch').map { |line| /^\*?\s+(\w+)/ =~ line; $1 }
169
- branches.include?(branch_name.to_s)
174
+ @gemspec_file ||= Dir['*.gemspec'].first
170
175
  end
171
176
 
172
177
  def verify_current_branch(branch)
173
178
  run_command('git branch').detect { |line| /^\* (.+)/ =~ line }
174
- raise "You are currently not working in the master branch!" unless branch == $1
179
+ raise "You are currently not working in the #{branch} branch!" unless branch == $1
175
180
  end
176
181
 
177
- def verify_clean_status(on_branch = nil)
178
- sh "git fetch"
182
+ def verify_fast_forward(local_branch = 'master', remote = 'origin', remote_branch = 'master')
183
+ sh "git fetch #{remote}"
184
+ lines = run_command("git rev-list #{local_branch}..remotes/#{remote}/#{remote_branch}")
185
+ raise "Remote branch #{remote}/#{remote_branch} has commits that are not yet incorporated in local #{local_branch} branch" unless lines.length == 0
186
+ end
187
+
188
+ def verify_clean_status(on_branch = nil)
179
189
  lines = run_command('git status')
180
- raise "You don't have the most recent version available. Run git pull first." if /^\# Your branch is behind/ =~ lines[1]
181
190
  raise "You are currently not working in the #{on_branch} branch!" unless on_branch.nil? || (/^\# On branch (.+)/ =~ lines.first && $1 == on_branch)
182
191
  raise "Your master branch contains modifications!" unless /^nothing to commit \(working directory clean\)/ =~ lines.last
183
192
  end
@@ -249,7 +258,15 @@ module Rake
249
258
  puts '------------------------------------------------------------'
250
259
  puts "Released #{@name} - version #{@specification.version}"
251
260
  end
261
+
262
+ def release_check_task
263
+ puts
264
+ puts '------------------------------------------------------------'
265
+ puts "Checked all conditions for a release of version #{ENV['VERSION'] || @specification.version}!"
266
+ puts 'You should be safe to do a release now.'
267
+ puts '------------------------------------------------------------'
268
+ end
252
269
  end
253
270
  end
254
271
 
255
- Rake::GithubGem.define_tasks!
272
+ Rake::GithubGem.define_tasks!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wvanbergen-adyen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-08-06 00:00:00 -07:00
13
+ date: 2009-08-20 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -41,10 +41,17 @@ files:
41
41
  - lib
42
42
  - lib/adyen
43
43
  - lib/adyen.rb
44
+ - lib/adyen/encoding.rb
44
45
  - lib/adyen/form.rb
46
+ - lib/adyen/formatter.rb
45
47
  - lib/adyen/matchers.rb
48
+ - lib/adyen/notification.rb
49
+ - lib/adyen/soap.rb
46
50
  - spec
51
+ - spec/adyen_spec.rb
47
52
  - spec/form_spec.rb
53
+ - spec/notification_spec.rb
54
+ - spec/soap_spec.rb
48
55
  - spec/spec_helper.rb
49
56
  - tasks
50
57
  - tasks/github-gem.rake
@@ -81,4 +88,7 @@ signing_key:
81
88
  specification_version: 2
82
89
  summary: Integrate Adyen payment services in you Ruby on Rails application
83
90
  test_files:
91
+ - spec/adyen_spec.rb
84
92
  - spec/form_spec.rb
93
+ - spec/notification_spec.rb
94
+ - spec/soap_spec.rb