solon 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +101 -93
- data/solon.gemspec +1 -1
- data/spec/solon_gateway_spec.rb +82 -67
- data/spec/spec_helper.rb +14 -3
- metadata +4 -4
data/README.textile
CHANGED
@@ -2,142 +2,150 @@ h1. Solon - Sagepay VSP Server
|
|
2
2
|
|
3
3
|
h2. Introduction
|
4
4
|
|
5
|
-
Solon is
|
5
|
+
Solon is another library for interacting with the sagepay VSP server. It is largely the codebase from the seemingly defunct Peeves library with some modifications.
|
6
6
|
|
7
|
-
|
7
|
+
This library is not the cleanest out there and you may want to look at the other sagepay offerings before using this library.
|
8
8
|
|
9
|
-
Sagepay is an UK payment gateway. You can find more information on their VPS Server service "here":http://techsupport.Sagepay.com/vspservercustom.asp.
|
10
9
|
|
11
|
-
h2.
|
10
|
+
h2. Installation (Rails 3)
|
12
11
|
|
13
|
-
|
12
|
+
Add the following to your Gemfile
|
14
13
|
|
15
|
-
|
14
|
+
<pre>
|
15
|
+
gem 'solon'
|
16
|
+
</pre>
|
16
17
|
|
17
|
-
|
18
|
+
<pre>
|
19
|
+
bundle
|
20
|
+
</pre>
|
18
21
|
|
19
|
-
|
22
|
+
In each of your environments you can define the sagepay mode of operation accordingly.
|
20
23
|
|
21
|
-
|
24
|
+
In config/environments/(development|test|production|[staging]).rb
|
22
25
|
|
23
26
|
<pre>
|
24
|
-
|
27
|
+
Solon::Config.vendor = [vendor_name]
|
28
|
+
Solon::Config.gateway_mode = :simulator | :test | :live
|
25
29
|
</pre>
|
26
30
|
|
27
|
-
|
31
|
+
h2. Integration Guide
|
28
32
|
|
29
|
-
|
33
|
+
Best plan is to generate yourself an controller for dealing with the integration.
|
30
34
|
|
31
|
-
h2. Usage
|
32
35
|
|
33
|
-
|
36
|
+
rails g controller sages
|
34
37
|
|
35
|
-
|
38
|
+
in routes.rb
|
36
39
|
|
37
|
-
|
38
|
-
PEEVES_VENDOR = "woobius"
|
39
|
-
PEEVES_GATEWAY_MODE = :simulator
|
40
|
-
</pre>
|
40
|
+
resources :sages
|
41
41
|
|
42
|
-
The gateway mode can be set to <code>:simulator</code>, <code>:test</code>, or <code>:live</code>.
|
43
42
|
|
44
|
-
|
43
|
+
Assumes an order has an identifier in order.identifier
|
45
44
|
|
46
45
|
<pre>
|
47
|
-
|
48
|
-
Solon::Config.gateway_mode = PEEVES_GATEWAY_MODE
|
49
|
-
</pre>
|
46
|
+
require 'digest/sha1'
|
50
47
|
|
51
|
-
|
48
|
+
before_create :set_identifier
|
52
49
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
transaction_reference = Solon::UniqueId.generate("TEST")
|
57
|
-
customer_data = Solon::CustomerData.new(:surname => 'blah',
|
58
|
-
:firstnames => 'blah',
|
59
|
-
:address1 => 'blah',
|
60
|
-
:address2 => 'blah',
|
61
|
-
:city => 'blah',
|
62
|
-
:post_code => 'blah',
|
63
|
-
:country => 'gb',
|
64
|
-
:email => 'customer@email.com'
|
65
|
-
)
|
66
|
-
|
67
|
-
# Payment registration
|
68
|
-
payment_response = p.payment(Solon::Money.new(1000, "GBP"),
|
69
|
-
{
|
70
|
-
:transaction_reference => transaction_reference,
|
71
|
-
:description => "Test Transaction",
|
72
|
-
:notification_url => "http://callback.example.com/process_stuff",
|
73
|
-
:customer_data => { :billing => customer_data,
|
74
|
-
:delivery => customer_data,
|
75
|
-
:email => customer_data.email }
|
76
|
-
})
|
50
|
+
def set_identifier
|
51
|
+
self.identifier = Digest::SHA1.hexdigest(rand(1000).to_s + Time.now.to_s.split(//).sort_by {rand}.join)
|
52
|
+
end
|
77
53
|
</pre>
|
78
54
|
|
79
|
-
|
55
|
+
It is a good plan to create an order_transaction model to store order transactions locally for debugging.
|
80
56
|
|
81
|
-
|
57
|
+
The first step is registering the payment.
|
82
58
|
|
83
59
|
<pre>
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
60
|
+
def create
|
61
|
+
@order.update_attribute(:transaction_reference, Solon::UniqueId.generate(@order.id.to_s))
|
62
|
+
|
63
|
+
p = SolonGateway.new
|
64
|
+
@response = p.payment Solon::Money.new(@order.amount.to_f, "GBP"),
|
65
|
+
{
|
66
|
+
:transaction_reference => @order.transaction_reference,
|
67
|
+
:description => "Description of payment",
|
68
|
+
:notification_url => callback_sage_url(@order.identifier),
|
69
|
+
:customer_data => @order.sage_customer_data
|
70
|
+
}
|
71
|
+
if @response.next_url
|
72
|
+
@order.transactions.create(:params => @response, :success => true, :message => "Purchase Transaction",
|
73
|
+
:amount => @order.amount.to_f, :reference => @order.transaction_reference)
|
74
|
+
@order.update_attributes(:security_key => @response.security_key, :user_location => 'away')
|
75
|
+
@order.send_pending_email
|
76
|
+
redirect_to @response.next_url
|
77
|
+
else
|
78
|
+
@order.transactions.create(:params => @response, :success => false, :message => "Purchase Transaction")
|
79
|
+
flash[:warning] = "Unable to make payment, please contact support."
|
80
|
+
redirect_to root_url
|
95
81
|
end
|
82
|
+
end
|
96
83
|
</pre>
|
97
84
|
|
98
|
-
|
85
|
+
The next step is handling to callbacks from sage page.
|
99
86
|
|
100
87
|
<pre>
|
101
|
-
|
102
|
-
|
88
|
+
def callback
|
89
|
+
# Parse the callback
|
90
|
+
callback = SolonGateway.parse_callback(request.raw_post)
|
91
|
+
|
92
|
+
|
93
|
+
# Get the order from identifier
|
94
|
+
begin
|
95
|
+
@order = Order.find_by_identifier!(params[:id])
|
96
|
+
@order.sage_callback = callback
|
97
|
+
rescue ActiveRecord::RecordNotFound => msg
|
98
|
+
# Error if cannot find.
|
99
|
+
render :text => SolonGateway.response('INVALID', no_order_sage_url, 'Transaction complete'), :layout => false
|
100
|
+
return
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
# If the order has already been paid, render an OK response.
|
105
|
+
if @order.paid?
|
106
|
+
render_response(@order)
|
107
|
+
return
|
108
|
+
end
|
103
109
|
|
104
|
-
|
110
|
+
if @order.valid_callback?
|
111
|
+
# all is well
|
112
|
+
else
|
113
|
+
# error
|
114
|
+
end
|
105
115
|
|
106
|
-
|
116
|
+
render_response(@order)
|
117
|
+
end
|
118
|
+
</pre>
|
107
119
|
|
108
|
-
|
120
|
+
Various responses tell the app different things about the order and you need to render a response in the right manner.
|
109
121
|
|
110
122
|
<pre>
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
:
|
123
|
-
|
124
|
-
:
|
125
|
-
|
126
|
-
|
127
|
-
if response.approved?
|
128
|
-
self.paid!
|
123
|
+
private
|
124
|
+
def render_response(order)
|
125
|
+
if order.valid_callback?
|
126
|
+
render :text => SolonGateway.response('OK', sage_url(order), 'Transaction complete'), :layout => false
|
127
|
+
elsif order.invalid_callback?
|
128
|
+
render :text => SolonGateway.response('INVALID', invalid_order_sage_url(order), 'Transaction complete'), :layout => false
|
129
|
+
elsif order.sage_callback.st(:NOTAUTHED)
|
130
|
+
render :text => SolonGateway.response('OK', notauthed_sage_url(order), 'Transaction complete'), :layout => false
|
131
|
+
elsif order.sage_callback.st(:ABORT)
|
132
|
+
render :text => SolonGateway.response('OK', abort_sage_url(order), 'Transaction complete'), :layout => false
|
133
|
+
elsif order.sage_callback.st(:REJECTED)
|
134
|
+
render :text => SolonGateway.response('OK', rejected_sage_url(order), 'Transaction complete'), :layout => false
|
135
|
+
elsif order.sage_callback.error?
|
136
|
+
render :text => SolonGateway.response('INVALID', invalid_sage_url(order), 'Transaction complete'), :layout => false
|
137
|
+
else
|
138
|
+
render :text => SolonGateway.response('ERROR', error_sage_url(order), 'Transaction complete'), :layout => false
|
129
139
|
end
|
130
|
-
|
131
|
-
response
|
132
140
|
end
|
133
141
|
</pre>
|
134
142
|
|
135
|
-
h3. Keeping abreast of changes
|
136
143
|
|
137
|
-
|
144
|
+
Valid and invalid callbacks not only check that the callback is approved (callback.approved?) but also check that the value of the order locally has not changed since the transaction began. This is a danger with offsite payment gateways.
|
145
|
+
|
146
|
+
The checking of whether a callback is valid is left to the app developer.
|
147
|
+
|
148
|
+
|
138
149
|
|
139
|
-
h2. Contributing back
|
140
150
|
|
141
|
-
Please use the fork functionality on github to make a fork and then push back your changes to the fork queue. I will probably accept most useful changes, but it might take me a few days before I get around to it!
|
142
151
|
|
143
|
-
Thanks!
|
data/solon.gemspec
CHANGED
data/spec/solon_gateway_spec.rb
CHANGED
@@ -6,31 +6,31 @@ describe SolonGateway do
|
|
6
6
|
@p = SolonGateway.new(:simulator)
|
7
7
|
@p.debug = false
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
describe "sending a payment request" do
|
11
11
|
before(:each) do
|
12
12
|
customer_data = Solon::CustomerData.new(:surname => 'blah',
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
:firstnames => 'blah',
|
14
|
+
:address1 => 'blah',
|
15
|
+
:address2 => 'blah',
|
16
|
+
:city => 'blah',
|
17
|
+
:post_code => 'blah',
|
18
|
+
:country => 'gb'
|
19
|
+
)
|
20
20
|
|
21
21
|
@response = @p.payment(Solon::Money.new(1000, "GBP"),
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
{
|
23
|
+
:transaction_reference => Solon::UniqueId.generate("TEST"),
|
24
|
+
:description => "Test Transaction",
|
25
|
+
:notification_url => "http://test.example.com",
|
26
|
+
:customer_data => {:billing => customer_data, :delivery =>customer_data} ,
|
27
|
+
})
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
it "should return a SagepayResponse" do
|
31
31
|
@response.is_a?(Solon::SagepayResponse).should be_true
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
it "should have a vps_transaction_id" do
|
35
35
|
@response.vps_transaction_id.should_not be_nil
|
36
36
|
end
|
@@ -38,7 +38,7 @@ describe SolonGateway do
|
|
38
38
|
it "should have a security_key" do
|
39
39
|
@response.security_key.should_not be_nil
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
it "should not have a transaction_authorisation_number" do
|
43
43
|
@response.transaction_authorisation_number.should be_nil
|
44
44
|
end
|
@@ -46,60 +46,75 @@ describe SolonGateway do
|
|
46
46
|
|
47
47
|
# commented out
|
48
48
|
# AUTHORISE can only be used after AUTHENTICATE, not PAYMENT
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
49
|
+
describe "sending an authenticate request" do
|
50
|
+
before(:each) do
|
51
|
+
# pending
|
52
|
+
@transaction_reference = Solon::UniqueId.generate("TEST")
|
53
|
+
customer_data = Solon::CustomerData.new(:surname => 'blah',
|
54
|
+
:firstnames => 'blah',
|
55
|
+
:address1 => 'blah',
|
56
|
+
:address2 => 'blah',
|
57
|
+
:city => 'blah',
|
58
|
+
:post_code => 'blah',
|
59
|
+
:country => 'gb'
|
60
|
+
)
|
61
|
+
@response = @p.authenticate Solon::Money.new(1000, "GBP"),
|
62
|
+
{
|
63
|
+
:transaction_reference => @transaction_reference,
|
64
|
+
:description => "Test Transaction",
|
65
|
+
:notification_url => "http://test.example.com",
|
66
|
+
:customer_data => {:billing => customer_data, :delivery =>customer_data}
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should return a SagepayResponse" do
|
71
|
+
@response.is_a?(Solon::SagepayResponse).should be_true
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should have a vps_transaction_id" do
|
75
|
+
@response.vps_transaction_id.should_not be_nil
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should have a security_key" do
|
79
|
+
@response.security_key.should_not be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should not have a transaction_authorisation_number" do
|
83
|
+
@response.transaction_authorisation_number.should be_nil
|
84
|
+
end
|
85
|
+
|
86
|
+
# it "should be cancellable" do
|
87
|
+
# pending
|
88
|
+
# @response2 = @p.cancel({
|
89
|
+
# :transaction_reference => @transaction_reference,
|
90
|
+
# :vps_transaction_id => @response.vps_transaction_id,
|
91
|
+
# :security_key => @response.security_key,
|
92
|
+
# :related_vps_transaction_id => '12345'
|
93
|
+
# })
|
94
|
+
# puts @response2.inspect
|
95
|
+
# puts @response.transaction_authorisation_number
|
96
|
+
# puts @response.vps_transaction_id
|
97
|
+
# puts @response.inspect
|
98
|
+
# @response2.status.should == SolonGateway::APPROVED
|
99
|
+
# end
|
100
|
+
end
|
86
101
|
|
87
102
|
describe "receiving a notification" do
|
88
103
|
before(:each) do
|
89
104
|
params = {
|
90
|
-
"Status"=>"OK",
|
91
|
-
"TxType"=>"PAYMENT",
|
92
|
-
"VPSTxId"=>"{861A2DB0-E734-4DEB-8F8B-12C47B9ADF3E}",
|
93
|
-
"VendorTxCode"=>"W-TEST-1227524828.86576-59414",
|
94
|
-
"GiftAid"=>"0",
|
95
|
-
"AVSCV2"=>"ALL MATCH",
|
96
|
-
"TxAuthNo"=>"8661",
|
97
|
-
"VPSProtocol"=>"2.22",
|
98
|
-
"CAVV"=>"MNL2CYF4URE47IQNBI6DAH",
|
99
|
-
"3DSecureStatus"=>"OK",
|
100
|
-
"VPSSignature"=>"49A6FA9FE0631919D9B1E72ACE57584D",
|
101
|
-
"CV2Result"=>"MATCHED",
|
102
|
-
"PostCodeResult"=>"MATCHED",
|
105
|
+
"Status"=>"OK",
|
106
|
+
"TxType"=>"PAYMENT",
|
107
|
+
"VPSTxId"=>"{861A2DB0-E734-4DEB-8F8B-12C47B9ADF3E}",
|
108
|
+
"VendorTxCode"=>"W-TEST-1227524828.86576-59414",
|
109
|
+
"GiftAid"=>"0",
|
110
|
+
"AVSCV2"=>"ALL MATCH",
|
111
|
+
"TxAuthNo"=>"8661",
|
112
|
+
"VPSProtocol"=>"2.22",
|
113
|
+
"CAVV"=>"MNL2CYF4URE47IQNBI6DAH",
|
114
|
+
"3DSecureStatus"=>"OK",
|
115
|
+
"VPSSignature"=>"49A6FA9FE0631919D9B1E72ACE57584D",
|
116
|
+
"CV2Result"=>"MATCHED",
|
117
|
+
"PostCodeResult"=>"MATCHED",
|
103
118
|
"AddressResult"=>"MATCHED"
|
104
119
|
}
|
105
120
|
@result = SolonGateway.parse_notification(params)
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require '
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/core_ext'
|
4
|
+
|
5
|
+
require File.dirname(__FILE__) + "/../lib/solon"
|
6
|
+
|
7
|
+
|
8
|
+
class Rails
|
9
|
+
def self.logger
|
10
|
+
Logger.new('/tmp/log.txt')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Solon::Config.vendor = '[add vendorname here for simulator]'
|
15
|
+
Solon::Config.gateway_mode = :simulator
|
3
16
|
|
4
|
-
$LOAD_PATH << File.dirname(__FILE__) + "/../lib/"
|
5
17
|
|
6
|
-
ActiveSupport::Dependencies.load_paths = $LOAD_PATH
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.5
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Alastair Brunton
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-09-02 00:00:00 +02:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|