sage_party 0.0.1

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.
data/lib/sage_party.rb ADDED
@@ -0,0 +1,119 @@
1
+ require 'digest'
2
+ require 'active_support'
3
+ require 'party_resource'
4
+
5
+ PartyResource::Connector.add(:sage_party, {})
6
+
7
+ module SageParty
8
+ class Transaction
9
+ include PartyResource
10
+
11
+ party_connector :sage_party
12
+ URLS = {:simulator => 'https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=VendorRegisterTx',
13
+ :test => 'https://test.sagepay.com/gateway/service/vspserver-register.vsp',
14
+ :live => 'https://live.sagepay.com/gateway/service/vspserver-register.vsp'}
15
+ ::SAGE_PAY_SERVER = :simulator unless Object.const_defined?('SAGE_PAY_SERVER')
16
+
17
+ connect :raw_register, :post => URLS[::SAGE_PAY_SERVER.to_sym], :as => :raw
18
+
19
+ %w{VPSProtocol StatusDetail VPSTxId SecurityKey NextURL
20
+ VPSTxId VendorTxCode Status TxAuthNo VendorName AVSCV2 SecurityKey
21
+ AddressResult PostCodeResult CV2Result GiftAid CAVV AddressStatus
22
+ PayerStatus CardType Last4Digits VPSSignature}.each do |name|
23
+ property name.underscore, :from => name
24
+ end
25
+ property :three_d_secure_status, :from => '3DSecureStatus'
26
+ property :id, :vendor_name
27
+
28
+
29
+ class << self
30
+ # Register a new transaction with SagePay
31
+ # @return [Transaction]
32
+ def register_tx(data)
33
+ response = raw_register(data)
34
+ hash = {}
35
+ response.split("\r\n").each do |line|
36
+ line = line.split("=", 2)
37
+ hash[line.first] = line.last
38
+ end
39
+ self.new(hash.merge({:id => data[:VendorTxCode], :vendor_name => data[:Vendor]}))
40
+ end
41
+
42
+ # Find a stored transaction
43
+ # @return [Transaction]
44
+ def find(vendor_id, sage_id)
45
+ transaction = get(vendor_id)
46
+ return missing_transaction if transaction.nil? || transaction.vps_tx_id != sage_id
47
+ transaction
48
+ end
49
+
50
+ protected
51
+ def get(vendor_id)
52
+ raise 'self.get method get needs to be defined'
53
+ end
54
+
55
+ private
56
+ def missing_transaction
57
+ self.new(:not_found => true)
58
+ end
59
+ end
60
+
61
+ # Return HTTP response to return to SagePay server
62
+ # @return [String]
63
+ def response
64
+ return format_response(:invalid, 'Transaction not found') unless exists?
65
+ return format_response(:invalid, 'Security check failed') unless signature_ok?
66
+ return format_response(:error, 'Sage Pay reported an error') if status == 'ERROR'
67
+ return format_response(:invalid, 'Unexpected status') if %w{AUTHENTICATED REGISTERED}.include?(status)
68
+ return format_response(:invalid, "Invalid status: #{status}") unless %w{OK NOTAUTHED ABORT REJECTED}.include?(status)
69
+ format_response(:ok)
70
+ end
71
+
72
+ # Test transaction equality
73
+ # @return [Boolean]
74
+ def ==(other)
75
+ properties_equal?(other) && self.exists? == other.exists?
76
+ end
77
+
78
+ # Detrmine if this transaction exists
79
+ def exists?
80
+ !@not_found
81
+ end
82
+
83
+ # Merge in transaction stage two data
84
+ # @return [Transaction] self
85
+ def merge!(data)
86
+ data = data.with_indifferent_access
87
+ data.delete(:SecurityKey)
88
+ populate_properties(data)
89
+ self
90
+ end
91
+
92
+ # Check if transaction data matches its signature
93
+ def signature_ok?
94
+ generate_md5 == vps_signature
95
+ end
96
+
97
+ protected
98
+ def initialize(params)
99
+ populate_properties(params)
100
+ @not_found = params[:not_found]
101
+ end
102
+
103
+ def notification_url
104
+ raise 'notification_url method needs to be defined'
105
+ end
106
+
107
+ private
108
+ def generate_md5
109
+ Digest::MD5.hexdigest("#{vps_tx_id}#{vendor_tx_code}#{status}#{tx_auth_no}#{vendor_name}#{avscv2}#{security_key}#{address_result}#{post_code_result}#{cv2_result}#{gift_aid}#{three_d_secure_status}#{cavv}#{address_status}#{payer_status}#{card_type}#{last4_digits}").upcase
110
+ end
111
+
112
+ def format_response(status, details=nil)
113
+ str = "Status=#{status.to_s.upcase}\r\nRedirectURL=#{notification_url}"
114
+ str += "\r\nStatusDetail=#{details}" unless details.nil?
115
+ str
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,183 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ def m(name)
4
+ v = mock(name)
5
+ instance_variable_set("@#{name}", v)
6
+ end
7
+
8
+ describe SageParty::Transaction do
9
+ describe 'registering a transaction' do
10
+ before do
11
+ SageParty::Transaction.stub!(:raw_register => '')
12
+ SageParty::Transaction.stub!(:tx_id => 'my_tx_id')
13
+ @basket = mock(:basket, :id => mock, :null_object => true)
14
+ @customer = mock(:customer, :null_object => true)
15
+ @data = {:VendorTxCode => m(:tx_code), :Vendor => m(:vendor)}
16
+ end
17
+
18
+ it 'registers with the data' do
19
+ SageParty::Transaction.should_receive(:raw_register).with(@data)
20
+ SageParty::Transaction.register_tx(@data)
21
+ end
22
+
23
+ it 'parses the response' do
24
+ SageParty::Transaction.stub!(:raw_register => "VPSProtocol=2.23\r\nStatus=OK\r\nStatusDetail=Server transaction registered successfully.\r\nVPSTxId={F2A9E367-AC15-4F5F-AB4C-D74B5A0EE8CF}\r\nSecurityKey=YK4N4LO9PT\r\nNextURL=https://test.sagepay.com/Simulator/VSPServerPaymentPage.asp?SageTransactionID={F2A9E367-AC15-4F5F-AB4C-D74B5A0EE8CF}")
25
+ result = SageParty::Transaction.new({:vps_protocol => '2.23', :status => 'OK', :status_detail => 'Server transaction registered successfully.', :vps_tx_id => '{F2A9E367-AC15-4F5F-AB4C-D74B5A0EE8CF}', :security_key => 'YK4N4LO9PT', :next_url => 'https://test.sagepay.com/Simulator/VSPServerPaymentPage.asp?SageTransactionID={F2A9E367-AC15-4F5F-AB4C-D74B5A0EE8CF}', :id => @tx_code, :vendor_name => @vendor, :basket_id => @basket.id})
26
+ SageParty::Transaction.register_tx(@data).should == result
27
+ end
28
+ end
29
+
30
+ describe 'find' do
31
+ it 'looks for the transaction' do
32
+ SageParty::Transaction.should_receive(:get).with('foo')
33
+ SageParty::Transaction.find('foo', 'bar')
34
+ end
35
+
36
+ context 'transaction can be found' do
37
+ it 'returns the transaction' do
38
+ transaction = mock(:transaction, :vps_tx_id => 'bar')
39
+ SageParty::Transaction.stub!(:get => transaction)
40
+ SageParty::Transaction.find('foo', 'bar').should == transaction
41
+ end
42
+ end
43
+
44
+ context 'transaction cannot be found' do
45
+ it 'returns a "null" transaction' do
46
+ SageParty::Transaction.stub!(:get => nil)
47
+ SageParty::Transaction.find('foo', 'bar').should_not be_exists
48
+ end
49
+ end
50
+
51
+ context 'transaction VPSTxId mismatch' do
52
+ it 'returns a "null" transaction' do
53
+ SageParty::Transaction.stub!(:get => mock(:transaction, :vps_tx_id => mock))
54
+ SageParty::Transaction.find('foo', 'bar').should_not be_exists
55
+ end
56
+ end
57
+ end
58
+
59
+ describe 'merge!' do
60
+ before do
61
+ @transaction = SageParty::Transaction.new('CardType' => m(:original_card_type), 'Status' => m(:status), 'SecurityKey' => m(:original_security_key))
62
+ @transaction.merge!('GiftAid' => m(:gift_aid), 'CardType' => m(:card_type), 'SecurityKey' => m(:security_key))
63
+ end
64
+
65
+ it 'repopulates existing data' do
66
+ @transaction.card_type.should == @card_type
67
+ end
68
+
69
+ it 'populates new data' do
70
+ @transaction.gift_aid.should == @gift_aid
71
+ end
72
+
73
+ it 'leaves unchanged data unchaged' do
74
+ @transaction.status.should == @status
75
+ end
76
+
77
+ it 'DOES NOT repopulate the security key' do
78
+ @transaction.security_key.should == @original_security_key
79
+ end
80
+ end
81
+
82
+ describe 'response' do
83
+ before do
84
+ @transaction = SageParty::Transaction.new({:id => mock(:id)})
85
+ @transaction.stub!(:notification_url => 'notify_url')
86
+ @transaction.stub!(:signature_ok? => true)
87
+ end
88
+
89
+ def set_status(status)
90
+ @transaction.merge!('Status' => status)
91
+ end
92
+
93
+ def check_response(*args)
94
+ @transaction.response.should == @transaction.send(:format_response, *args)
95
+ end
96
+
97
+ context 'transaction not found' do
98
+ it 'returns invalid' do
99
+ @transaction.stub!(:exists? => false)
100
+ check_response(:invalid, 'Transaction not found')
101
+ end
102
+ end
103
+
104
+ context 'with incorrect signature' do
105
+ before do
106
+ @transaction.stub!(:signature_ok? => false)
107
+ end
108
+
109
+ it 'returns invalid' do
110
+ check_response(:invalid, 'Security check failed')
111
+ end
112
+ end
113
+
114
+ context 'when status == ERROR' do
115
+ before do
116
+ set_status('ERROR')
117
+ end
118
+
119
+ it 'returns error' do
120
+ check_response(:error, 'Sage Pay reported an error')
121
+ end
122
+ end
123
+
124
+ context 'status is an unexpected value' do
125
+ it 'returns invalid when status is incorrect value' do
126
+ set_status('CUSTARD')
127
+ check_response(:invalid, 'Invalid status: CUSTARD')
128
+ end
129
+
130
+ it 'returns invalid when status is blank' do
131
+ set_status('')
132
+ check_response(:invalid, 'Invalid status: ')
133
+ end
134
+
135
+ it 'returns invalid when status is nil' do
136
+ set_status(nil)
137
+ check_response(:invalid, 'Invalid status: ')
138
+ end
139
+
140
+ it 'returns invalid/unexpected when status is AUTHENTICATED' do
141
+ set_status('AUTHENTICATED')
142
+ check_response(:invalid, 'Unexpected status')
143
+ end
144
+
145
+ it 'returns invalid/unexpected when status is REGISTERED' do
146
+ set_status('REGISTERED')
147
+ check_response(:invalid, 'Unexpected status')
148
+ end
149
+ end
150
+
151
+ context 'status is an expected value' do
152
+ %w{OK NOTAUTHED ABORT REJECTED}.each do |status|
153
+ it "returns ok when status is #{status}" do
154
+ set_status(status)
155
+ check_response(:ok)
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ describe 'signature_ok?' do
162
+ before do
163
+ @transaction = SageParty::Transaction.new({:vps_protocol => '2.23', :status => 'OK', :status_detail => 'Server transaction registered successfully.', :vps_tx_id => '{F2A9E367-AC15-4F5F-AB4C-D74B5A0EE8CF}', :security_key => 'YK4N4LO9PT', :next_url => 'https://test.sagepay.com/Simulator/VSPServerPaymentPage.asp?SageTransactionID={F2A9E367-AC15-4F5F-AB4C-D74B5A0EE8CF}', :id => 'tx_code', :vendor_name => 'sage_key'})
164
+ end
165
+
166
+ it 'passes if the signature matches' do
167
+ @transaction.merge!(:vps_signature => 'DBCB54EB1128738F0C9E48600CBCF4DA')
168
+ @transaction.signature_ok?.should be_true
169
+ end
170
+
171
+ it 'fails if the signature does not match' do
172
+ @transaction.merge!(:vps_signature => 'CBCB54EB1128738F0C9E48600CBCF4DA')
173
+ @transaction.signature_ok?.should be_false
174
+ end
175
+
176
+ it 'fails if the signature is unset' do
177
+ @transaction.signature_ok?.should be_false
178
+ end
179
+ end
180
+
181
+ end
182
+
183
+
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'sage_party'
5
+ require 'spec'
6
+ require 'spec/autorun'
7
+ require 'webmock/rspec'
8
+
9
+ include WebMock
10
+
11
+ module LetMock
12
+ def let_mock(name, options = {})
13
+ let(name) { mock(name, options) }
14
+ end
15
+ end
16
+
17
+ Spec::Runner.configure do |config|
18
+ config.extend(LetMock)
19
+ end
20
+
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sage_party
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Tristan Harris
13
+ - Steve Tooke
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-05-28 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: party_resource
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 0
31
+ - 2
32
+ version: 0.0.2
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: activesupport
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 2
44
+ - 3
45
+ - 5
46
+ version: 2.3.5
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 1
58
+ - 2
59
+ - 9
60
+ version: 1.2.9
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: yard
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ type: :development
74
+ version_requirements: *id004
75
+ - !ruby/object:Gem::Dependency
76
+ name: webmock
77
+ prerelease: false
78
+ requirement: &id005 !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ type: :development
86
+ version_requirements: *id005
87
+ description: sage_party is a simple interface to SagePay's Server service built on top of party_resource
88
+ email: dev+sage_party@edendevelopment.co.uk
89
+ executables: []
90
+
91
+ extensions: []
92
+
93
+ extra_rdoc_files: []
94
+
95
+ files:
96
+ - lib/sage_party.rb
97
+ has_rdoc: true
98
+ homepage: http://github.com/edendevelopment/sage_party.git
99
+ licenses: []
100
+
101
+ post_install_message:
102
+ rdoc_options:
103
+ - --charset=UTF-8
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ segments:
111
+ - 0
112
+ version: "0"
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ requirements: []
121
+
122
+ rubyforge_project:
123
+ rubygems_version: 1.3.6
124
+ signing_key:
125
+ specification_version: 3
126
+ summary: Simple interface to the SagePay Server service.
127
+ test_files:
128
+ - spec/sage_transaction_spec.rb
129
+ - spec/spec_helper.rb