sage_pay 0.1.0 → 0.2.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.
@@ -0,0 +1,31 @@
1
+ module SagePay
2
+ module Server
3
+ class << self
4
+ attr_accessor :default_registration_options
5
+ end
6
+ # NOTE: Expected to be something along the lines of:
7
+ #
8
+ # SagePay::Server.default_registration_options = {
9
+ # :mode => :live,
10
+ # :vendor => "rubaidh",
11
+ # :notification_url => "http://test.host/notification"
12
+ # }
13
+ #
14
+ # ... which you'll want to stick in config/initializers/sage_pay.rb or one
15
+ # of your environment files, if you're doing this with a Rails app.
16
+ self.default_registration_options = {}
17
+
18
+ def self.payment(attributes = {})
19
+ registration({ :tx_type => :payment }.merge(attributes))
20
+ end
21
+
22
+ def self.registration(attributes)
23
+ defaults = {
24
+ :vendor_tx_code => TransactionCode.random,
25
+ :delivery_address => attributes[:billing_address]
26
+ }.merge(default_registration_options)
27
+
28
+ SagePay::Server::TransactionRegistration.new(defaults.merge(attributes))
29
+ end
30
+ end
31
+ end
data/lib/sage_pay.rb CHANGED
@@ -1,3 +1,21 @@
1
- class SagePay
2
- VERSION = '0.1.0'
1
+ require 'validatable'
2
+ require 'bigdecimal'
3
+ require 'net/https'
4
+ require 'uri'
5
+ require 'md5'
6
+ require 'uuid'
7
+
8
+ module SagePay
9
+ VERSION = '0.2.0'
3
10
  end
11
+
12
+ require 'validatable-ext'
13
+
14
+ require 'sage_pay/server/address'
15
+ require 'sage_pay/server/transaction_code'
16
+ require 'sage_pay/server/signature_verification_details'
17
+ require 'sage_pay/server/transaction_registration'
18
+ require 'sage_pay/server/transaction_registration_response'
19
+ require 'sage_pay/server/transaction_notification'
20
+ require 'sage_pay/server/transaction_notification_response'
21
+ require 'sage_pay/server'
@@ -0,0 +1,28 @@
1
+ require 'validatable'
2
+ require 'validations/validates_inclusion_of'
3
+
4
+ module Validatable
5
+ module Macros
6
+ # call-seq: validates_inclusion_of(*args)
7
+ #
8
+ # Encapsulates the pattern of wanting to validate that an attribute is is in a list. Example:
9
+ #
10
+ # class Person
11
+ # include Validatable
12
+ # validates_inclusion_of :operating_system, :in => ["Mac OS X", "Linux", "Windows"]
13
+ # end
14
+ #
15
+ # Configuration options:
16
+ #
17
+ # * in - a list of acceptable answers
18
+ # * after_validate - A block that executes following the run of a validation
19
+ # * message - The message to add to the errors collection when the validation fails
20
+ # * times - The number of times the validation applies
21
+ # * level - The level at which the validation should occur
22
+ # * if - A block that when executed must return true of the validation will not occur
23
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
24
+ def validates_inclusion_of(*args)
25
+ add_validations(args, ValidatesInclusionOf)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ module Validatable
2
+ class ValidatesInclusionOf < ValidationBase #:nodoc:
3
+ option :allow_blank
4
+ required_option :in
5
+
6
+ def message(instance)
7
+ super || "is not in the list"
8
+ end
9
+
10
+ def valid?(instance)
11
+ valid = true
12
+ value = instance.send(self.attribute)
13
+
14
+ if value.nil? || (value.respond_to?(:empty?) && value.empty?)
15
+ return true if allow_blank
16
+ value = ""
17
+ end
18
+
19
+ @in.include?(value)
20
+ end
21
+ end
22
+ end
data/sage_pay.gemspec CHANGED
@@ -7,8 +7,8 @@ Gem::Specification.new do |s|
7
7
  ## If your rubyforge_project name is different, then edit it and comment out
8
8
  ## the sub! line in the Rakefile
9
9
  s.name = 'sage_pay'
10
- s.version = '0.1.0'
11
- s.date = '2010-04-07'
10
+ s.version = '0.2.0'
11
+ s.date = '2010-04-13'
12
12
  s.rubyforge_project = 'sage_pay'
13
13
 
14
14
  ## Make sure your summary is short. The description may be as long
@@ -37,11 +37,12 @@ gateway for accepting credit card payments through your web app.
37
37
 
38
38
  ## List your runtime dependencies here. Runtime dependencies are those
39
39
  ## that are needed for an end user to actually USE your code.
40
- # s.add_dependency('DEPNAME', [">= 1.1.0", "< 2.0.0"])
40
+ s.add_dependency('validatable', [">= 1.6.7"])
41
+ s.add_dependency('uuid', [">= 2.3.0"])
41
42
 
42
43
  ## List your development dependencies here. Development dependencies are
43
44
  ## those that are only needed during development
44
- # s.add_development_dependency('DEVDEPNAME', [">= 1.1.0", "< 2.0.0"])
45
+ s.add_development_dependency('rspec')
45
46
 
46
47
  ## Leave this section as-is. It will be automatically generated from the
47
48
  ## contents of your Git repository via the gemspec task. DO NOT REMOVE
@@ -52,11 +53,35 @@ gateway for accepting credit card payments through your web app.
52
53
  README.md
53
54
  Rakefile
54
55
  lib/sage_pay.rb
56
+ lib/sage_pay/server.rb
57
+ lib/sage_pay/server/address.rb
58
+ lib/sage_pay/server/signature_verification_details.rb
59
+ lib/sage_pay/server/transaction_code.rb
60
+ lib/sage_pay/server/transaction_notification.rb
61
+ lib/sage_pay/server/transaction_notification_response.rb
62
+ lib/sage_pay/server/transaction_registration.rb
63
+ lib/sage_pay/server/transaction_registration_response.rb
64
+ lib/validatable-ext.rb
65
+ lib/validations/validates_inclusion_of.rb
55
66
  sage_pay.gemspec
67
+ spec/integration/sage_pay/server_spec.rb
68
+ spec/sage_pay/server/address_spec.rb
69
+ spec/sage_pay/server/signature_verification_details_spec.rb
70
+ spec/sage_pay/server/transaction_code_spec.rb
71
+ spec/sage_pay/server/transaction_notification_response_spec.rb
72
+ spec/sage_pay/server/transaction_notification_spec.rb
73
+ spec/sage_pay/server/transaction_registration_response_spec.rb
74
+ spec/sage_pay/server/transaction_registration_spec.rb
75
+ spec/sage_pay/server_spec.rb
76
+ spec/sage_pay_spec.rb
77
+ spec/spec_helper.rb
78
+ spec/support/factories.rb
79
+ spec/support/integration.rb
80
+ spec/support/validation_matchers.rb
56
81
  ]
57
82
  # = MANIFEST =
58
83
 
59
84
  ## Test files will be grabbed from the file list. Make sure the path glob
60
85
  ## matches what you actually use.
61
- s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
86
+ s.test_files = s.files.select { |path| path =~ /^spec\/.*_spec\.rb/ }
62
87
  end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ if run_integration_specs?
4
+ describe SagePay::Server, "integration specs" do
5
+ describe ".payment" do
6
+ before(:each) do
7
+ SagePay::Server.default_registration_options = {
8
+ :mode => :simulator,
9
+ :vendor => "rubaidh",
10
+ :notification_url => "http://test.host/notification"
11
+ }
12
+
13
+ @payment = SagePay::Server.payment(
14
+ :description => "Demo payment",
15
+ :amount => 12.34,
16
+ :currency => "GBP",
17
+ :billing_address => address_factory
18
+ )
19
+ end
20
+
21
+ after(:each) do
22
+ SagePay::Server.default_registration_options = {}
23
+ end
24
+
25
+ it "should successfully register the payment with SagePay" do
26
+ @payment.register!.should_not be_nil
27
+ end
28
+
29
+ it "should be a valid registered payment" do
30
+ registration = @payment.register!
31
+ registration.should be_ok
32
+ end
33
+
34
+ it "should have a next URL" do
35
+ registration = @payment.register!
36
+ registration.next_url.should_not be_nil
37
+ end
38
+
39
+ it "should allow us to follow the next URL and the response should be successful" do
40
+ pending "URI#parse claims that the URL SagePay hands us isn't a valid URI."
41
+ registration = @payment.register!
42
+ uri = URI.parse(registration.next_url)
43
+ end
44
+
45
+ it "should allow us to retrieve signature verification details" do
46
+ @payment.register!
47
+ sig_details = @payment.signature_verification_details
48
+
49
+ sig_details.should_not be_nil
50
+ sig_details.vps_tx_id.should_not be_nil
51
+ sig_details.security_key.should_not be_nil
52
+ sig_details.vendor.should_not be_nil
53
+ sig_details.vendor_tx_code.should_not be_nil
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ describe SagePay::Server::Address do
4
+ it "should be valid straight from the factory" do
5
+ lambda {
6
+ address_factory.should be_valid
7
+ }.should_not raise_error
8
+ end
9
+
10
+ describe "validations" do
11
+ it { validates_the_presence_of(:address, :first_names) }
12
+ it { validates_the_presence_of(:address, :surname) }
13
+ it { validates_the_presence_of(:address, :address_1) }
14
+ it { validates_the_presence_of(:address, :city) }
15
+ it { validates_the_presence_of(:address, :post_code) }
16
+ it { validates_the_presence_of(:address, :country) }
17
+
18
+ it { does_not_require_the_presence_of(:address, :address_2) }
19
+ it { does_not_require_the_presence_of(:address, :state) }
20
+ it { does_not_require_the_presence_of(:address, :phone) }
21
+
22
+ it { validates_the_length_of(:address, :first_names, :max => 20) }
23
+ it { validates_the_length_of(:address, :surname, :max => 20) }
24
+ it { validates_the_length_of(:address, :address_1, :max => 100) }
25
+ it { validates_the_length_of(:address, :address_2, :max => 100) }
26
+ it { validates_the_length_of(:address, :city, :max => 40) }
27
+ it { validates_the_length_of(:address, :post_code, :max => 10) }
28
+ it { validates_the_length_of(:address, :phone, :max => 20) }
29
+
30
+ it "validates the format of first names" do
31
+ validates_the_format_of :address, :first_names,
32
+ :valid => [
33
+ "Bob", "Graham & Sarah", "John-Angus", "Graeme R.", "O'Brien", "Gr/\\eme"
34
+ ],
35
+ :invalid => [
36
+ "mathie1979", "B()b", "mathie@woss.name"
37
+ ]
38
+ end
39
+
40
+ it "validates the format of surname" do
41
+ validates_the_format_of :address, :surname,
42
+ :valid => [
43
+ "Bob", "Graham & Sarah", "John-Angus", "Graeme R.", "O'Brien", "Gr/\\eme"
44
+ ],
45
+ :invalid => [
46
+ "mathie1979", "B()b", "mathie@woss.name"
47
+ ]
48
+ end
49
+
50
+ it "validates the format of address 1" do
51
+ validates_the_format_of :address, :address_1,
52
+ :valid => [
53
+ "123 Any Street, Suburb", "123 Any Street\nSuburb",
54
+ "123+ Al'Agrathia", "Attn: Bob & Sarah (not John-Angus)."
55
+ ],
56
+ :invalid => [
57
+ "!", "_", ";", "@"
58
+ ]
59
+ end
60
+
61
+ it "validates the format of address 2" do
62
+ validates_the_format_of :address, :address_2,
63
+ :valid => [
64
+ "123 Any Street, Suburb", "123 Any Street\nSuburb",
65
+ "123+ Al'Agrathia", "Attn: Bob & Sarah (not John-Angus)."
66
+ ],
67
+ :invalid => [
68
+ "!", "_", ";", "@"
69
+ ]
70
+ end
71
+
72
+ it "validates the format of city" do
73
+ validates_the_format_of :address, :city,
74
+ :valid => [
75
+ "123 Any Street, Suburb", "123 Any Street\nSuburb",
76
+ "123+ Al'Agrathia", "Attn: Bob & Sarah (not John-Angus)."
77
+ ],
78
+ :invalid => [
79
+ "!", "_", ";", "@"
80
+ ]
81
+ end
82
+
83
+ it "validates the format of post code" do
84
+ validates_the_format_of :address, :post_code,
85
+ :valid => [
86
+ "AB12 3CD", "EH21-7PB"
87
+ ],
88
+ :invalid => [
89
+ "AB&^3FG"
90
+ ]
91
+ end
92
+
93
+ it "validates the format of phone" do
94
+ validates_the_format_of :address, :phone,
95
+ :valid => [
96
+ "+44 (0)131 273 5271", "01312735271", "0131 CALL-FOR-HELP"
97
+ ],
98
+ :invalid => [
99
+ "2735271 ext.3444"
100
+ ]
101
+ end
102
+
103
+ it "should validate the state against a list of US states" do
104
+ address = address_factory(:state => "WY")
105
+ address.should be_valid
106
+ address = address_factory(:state => "AA")
107
+ address.should_not be_valid
108
+ address.errors.on(:state).should == "is not a US state"
109
+ end
110
+
111
+ it "should validate the country against a list of ISO 3166-1 country codes" do
112
+ address = address_factory(:country => "GB")
113
+ address.should be_valid
114
+ address = address_factory(:country => "AA")
115
+ address.should_not be_valid
116
+ address.errors.on(:country).should == "is not an ISO3166-1 country code"
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ include SagePay::Server
4
+ describe SignatureVerificationDetails do
5
+ before(:each) do
6
+ transaction_registration = mock("Transaction registration", :vendor_tx_code => "vendor transaction id", :vendor => "vendor")
7
+ transaction_registration_response = mock("Transaction registration response", :vps_tx_id => "sage pay transaction id", :security_key => "security key")
8
+ @sig_details = SignatureVerificationDetails.new(transaction_registration, transaction_registration_response)
9
+ end
10
+
11
+ it "should take the vendor_tx_code from the transaction registration" do
12
+ @sig_details.vendor_tx_code.should == "vendor transaction id"
13
+ end
14
+
15
+ it "should take the vendor from the transaction registration" do
16
+ @sig_details.vendor.should == "vendor"
17
+ end
18
+
19
+ it "should take the vps_tx_id from the transaction registration response" do
20
+ @sig_details.vps_tx_id.should == "sage pay transaction id"
21
+ end
22
+
23
+ it "should take the security_key from the transaction registration response" do
24
+ @sig_details.security_key.should == "security key"
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ include SagePay::Server
4
+
5
+ describe TransactionCode do
6
+ it "should generate a transaction code" do
7
+ TransactionCode.random.should_not be_nil
8
+ end
9
+
10
+ it "should not deliver two the same in a row" do
11
+ first = TransactionCode.random
12
+ second = TransactionCode.random
13
+ first.should_not == second
14
+ end
15
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ include SagePay::Server
4
+
5
+ describe TransactionNotificationResponse do
6
+ it "should work straight from the factory" do
7
+ lambda {
8
+ transaction_notification_response_factory.should be_valid
9
+ }.should_not raise_error
10
+ end
11
+
12
+ describe "validations" do
13
+ it { validates_the_presence_of(:transaction_notification_response, :status) }
14
+ it { validates_the_presence_of(:transaction_notification_response, :redirect_url) }
15
+
16
+ it "validates the presence of the status_detail field only if the status is something other than OK" do
17
+ transaction_notification_response = transaction_notification_response_factory(:status => :ok, :status_detail => nil)
18
+ transaction_notification_response.should be_valid
19
+
20
+ transaction_notification_response = transaction_notification_response_factory(:status => :invalid, :status_detail => "Invalid request!")
21
+ transaction_notification_response.should be_valid
22
+
23
+ transaction_notification_response = transaction_notification_response_factory(:status => :invalid, :status_detail => "")
24
+ transaction_notification_response.should_not be_valid
25
+ transaction_notification_response.errors.on(:status_detail).should include("can't be empty")
26
+ end
27
+
28
+ it { validates_the_length_of(:transaction_notification_response, :redirect_url, :max => 255) }
29
+ it { validates_the_length_of(:transaction_notification_response, :status_detail, :max => 255) }
30
+
31
+ it "should allow the status to be one of :ok, :invalid or :error" do
32
+ transaction_notification_response = transaction_notification_response_factory(:status => :ok)
33
+ transaction_notification_response.should be_valid
34
+
35
+ transaction_notification_response = transaction_notification_response_factory(:status => :invalid)
36
+ transaction_notification_response.should be_valid
37
+
38
+ transaction_notification_response = transaction_notification_response_factory(:status => :error)
39
+ transaction_notification_response.should be_valid
40
+
41
+ transaction_notification_response = transaction_notification_response_factory(:status => :chickens)
42
+ transaction_notification_response.should_not be_valid
43
+ transaction_notification_response.errors.on(:status).should include("is not in the list")
44
+ end
45
+ end
46
+
47
+ describe "#response" do
48
+ it "should produce the expected response for an OK status" do
49
+ transaction_notification_response = transaction_notification_response_factory(
50
+ :status => :ok,
51
+ :redirect_url => "http://test.host/some/redirect",
52
+ :status_detail => nil
53
+ )
54
+ transaction_notification_response.response.should == <<-RESPONSE.chomp
55
+ Status=OK
56
+ RedirectURL=http://test.host/some/redirect
57
+ RESPONSE
58
+ end
59
+
60
+ it "should produce the expected response for an invalid status" do
61
+ transaction_notification_response = transaction_notification_response_factory(
62
+ :status => :invalid,
63
+ :redirect_url => "http://test.host/some/redirect",
64
+ :status_detail => "Totally didn't expect that notification, dude."
65
+ )
66
+ # FIXME: I'm asserting here that I don't have to URI-encode the body
67
+ # here. OK?
68
+ transaction_notification_response.response.should == <<-RESPONSE.chomp
69
+ Status=INVALID
70
+ RedirectURL=http://test.host/some/redirect
71
+ StatusDetail=Totally didn't expect that notification, dude.
72
+ RESPONSE
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,142 @@
1
+ require 'spec_helper'
2
+
3
+ include SagePay::Server
4
+
5
+ describe TransactionNotification do
6
+ it "should work straight from the factory" do
7
+ lambda {
8
+ transaction_notification_factory.should_not be_nil
9
+ }.should_not raise_error
10
+ end
11
+
12
+ describe ".from_params" do
13
+ context "with an OK status" do
14
+ before(:each) do
15
+ signature_verification_details = mock("Signature verification details",
16
+ :vps_tx_id => "{728A5721-B45F-4570-937E-90A16B0A5000}",
17
+ :vendor_tx_code => "unique-tx-code",
18
+ :vendor => "rubaidh",
19
+ :security_key => "17F13DCBD8"
20
+ )
21
+
22
+ @params = {
23
+ "VPSProtocol" => "2.23",
24
+ "TxType" => "PAYMENT",
25
+ "Status" => "OK",
26
+ "StatusDetail" => "2000 : Card processed successfully.", # FIXME: Make this match reality
27
+ "TxAuthNo" => "1234567890",
28
+ "AVSCV2" => "ALL MATCH",
29
+ "AddressResult" => "MATCHED",
30
+ "PostCodeResult" => "MATCHED",
31
+ "CV2Result" => "MATCHED",
32
+ "GiftAid" => "0",
33
+ "3DSecureStatus" => "OK",
34
+ "CAVV" => "Something?",
35
+ "AddressStatus" => "CONFIRMED",
36
+ "PayerStatus" => "VERIFIED",
37
+ "CardType" => "VISA",
38
+ "Last4Digits" => "1234",
39
+ # FIXME: Calculated manually using the information above. Should
40
+ # really get one from a sample transaction...
41
+ "VPSSignature" => "6AB7A7FFB5369AF953CD57A84D5C2979"
42
+ }
43
+
44
+ @notification = TransactionNotification.from_params(@params, signature_verification_details)
45
+ end
46
+
47
+ it "should successfully parse the params" do
48
+ lambda {
49
+ TransactionNotification.from_params(@params).should_not be_nil
50
+ }.should_not raise_error
51
+ end
52
+
53
+ it "should report the vps_protocol as '2.23'" do
54
+ @notification.vps_protocol.should == "2.23"
55
+ end
56
+
57
+ it "should report the status as :ok" do
58
+ @notification.status.should == :ok
59
+ end
60
+
61
+ it "should be ok" do
62
+ @notification.should be_ok
63
+ end
64
+
65
+ it "should report the status detail as the status detail message supplied" do
66
+ @notification.status_detail.should == "2000 : Card processed successfully."
67
+ end
68
+
69
+ it "should report the transaction authorisation number as a string (even if the spec says it's always an integer I'm betting leading zeros are important)" do
70
+ @notification.tx_auth_no.should == "1234567890"
71
+ end
72
+
73
+ it "should report the avs_cv2 as :all_match" do
74
+ @notification.avs_cv2.should == :all_match
75
+ end
76
+
77
+ it "should report the avs_cv2? as true" do
78
+ @notification.should be_avs_cv2_matched
79
+ end
80
+
81
+ it "should report the address result as :matched" do
82
+ @notification.address_result.should == :matched
83
+ end
84
+
85
+ it "should report the address as matched" do
86
+ @notification.should be_address_matched
87
+ end
88
+
89
+ it "should report the post code result as :matched" do
90
+ @notification.post_code_result.should == :matched
91
+ end
92
+
93
+ it "should report the post code as matched" do
94
+ @notification.should be_post_code_matched
95
+ end
96
+
97
+ it "should report the CV2 result as :matched" do
98
+ @notification.cv2_result.should == :matched
99
+ end
100
+
101
+ it "should report the cv2 as matched" do
102
+ @notification.should be_cv2_matched
103
+ end
104
+
105
+ it "should report gift aid as false" do
106
+ @notification.gift_aid.should be_false
107
+ end
108
+
109
+ it "should report the 3d secure status as :ok" do
110
+ @notification.threed_secure_status.should == :ok
111
+ end
112
+
113
+ it "should report the 3d secure status as ok" do
114
+ @notification.should be_threed_secure_status_ok
115
+ end
116
+
117
+ it "should report the value passed in for the CAVV result" do
118
+ @notification.cavv.should == "Something?"
119
+ end
120
+
121
+ it "should report the address status as confirmed" do
122
+ @notification.address_status.should == :confirmed
123
+ end
124
+
125
+ it "should report the payer status as verified" do
126
+ @notification.payer_status.should == :verified
127
+ end
128
+
129
+ it "should report the card type as visa" do
130
+ @notification.card_type.should == :visa
131
+ end
132
+
133
+ it "should report the last 4 digits as 1234" do
134
+ @notification.last_4_digits.should == "1234"
135
+ end
136
+
137
+ it "should report that the vps signature is valid" do
138
+ @notification.should be_valid_signature
139
+ end
140
+ end
141
+ end
142
+ end