sage_pay 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +4 -0
- data/lib/sage_pay.rb +9 -5
- data/lib/sage_pay/server.rb +22 -1
- data/lib/sage_pay/server/command.rb +98 -0
- data/lib/sage_pay/server/{transaction_notification.rb → notification.rb} +3 -3
- data/lib/sage_pay/server/{transaction_notification_response.rb → notification_response.rb} +1 -1
- data/lib/sage_pay/server/{transaction_registration.rb → registration.rb} +22 -79
- data/lib/sage_pay/server/registration_response.rb +35 -0
- data/lib/sage_pay/server/release.rb +48 -0
- data/lib/sage_pay/server/{transaction_registration_response.rb → response.rb} +18 -42
- data/sage_pay.gemspec +12 -9
- data/spec/integration/sage_pay/server_spec.rb +62 -15
- data/spec/sage_pay/server/notification_response_spec.rb +75 -0
- data/spec/sage_pay/server/{transaction_notification_spec.rb → notification_spec.rb} +66 -28
- data/spec/sage_pay/server/{transaction_registration_response_spec.rb → registration_response_spec.rb} +10 -10
- data/spec/sage_pay/server/registration_spec.rb +609 -0
- data/spec/sage_pay_spec.rb +2 -2
- data/spec/support/factories.rb +8 -8
- metadata +17 -14
- data/spec/sage_pay/server/transaction_notification_response_spec.rb +0 -75
- data/spec/sage_pay/server/transaction_registration_spec.rb +0 -609
data/CHANGELOG.md
CHANGED
data/lib/sage_pay.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'active_support'
|
1
2
|
require 'validatable'
|
2
3
|
require 'bigdecimal'
|
3
4
|
require 'net/https'
|
@@ -6,7 +7,7 @@ require 'md5'
|
|
6
7
|
require 'uuid'
|
7
8
|
|
8
9
|
module SagePay
|
9
|
-
VERSION = '0.2.
|
10
|
+
VERSION = '0.2.6'
|
10
11
|
end
|
11
12
|
|
12
13
|
require 'validatable-ext'
|
@@ -15,8 +16,11 @@ require 'sage_pay/uri_fixups'
|
|
15
16
|
require 'sage_pay/server/address'
|
16
17
|
require 'sage_pay/server/transaction_code'
|
17
18
|
require 'sage_pay/server/signature_verification_details'
|
18
|
-
require 'sage_pay/server/
|
19
|
-
require 'sage_pay/server/
|
20
|
-
require 'sage_pay/server/
|
21
|
-
require 'sage_pay/server/
|
19
|
+
require 'sage_pay/server/command'
|
20
|
+
require 'sage_pay/server/response'
|
21
|
+
require 'sage_pay/server/registration'
|
22
|
+
require 'sage_pay/server/registration_response'
|
23
|
+
require 'sage_pay/server/notification'
|
24
|
+
require 'sage_pay/server/notification_response'
|
25
|
+
require 'sage_pay/server/release'
|
22
26
|
require 'sage_pay/server'
|
data/lib/sage_pay/server.rb
CHANGED
@@ -15,17 +15,38 @@ module SagePay
|
|
15
15
|
# of your environment files, if you're doing this with a Rails app.
|
16
16
|
self.default_registration_options = {}
|
17
17
|
|
18
|
+
# The notification URL is only relevant to registration options, but the
|
19
|
+
# rest are relevant to all requests.
|
20
|
+
def self.default_options
|
21
|
+
@default_options ||= default_registration_options.except(:notification_url)
|
22
|
+
end
|
23
|
+
|
18
24
|
def self.payment(attributes = {})
|
19
25
|
registration({ :tx_type => :payment }.merge(attributes))
|
20
26
|
end
|
21
27
|
|
28
|
+
def self.deferred(attributes = {})
|
29
|
+
registration({ :tx_type => :deferred }.merge(attributes))
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.authenticate(attributes = {})
|
33
|
+
registration({ :tx_type => :authenticate }.merge(attributes))
|
34
|
+
end
|
35
|
+
|
22
36
|
def self.registration(attributes)
|
23
37
|
defaults = {
|
24
38
|
:vendor_tx_code => TransactionCode.random,
|
25
39
|
:delivery_address => attributes[:billing_address]
|
26
40
|
}.merge(default_registration_options)
|
27
41
|
|
28
|
-
SagePay::Server::
|
42
|
+
SagePay::Server::Registration.new(defaults.merge(attributes))
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.release(attributes = {})
|
46
|
+
defaults = {
|
47
|
+
}.merge(default_options)
|
48
|
+
|
49
|
+
SagePay::Server::Release.new(defaults.merge(attributes))
|
29
50
|
end
|
30
51
|
end
|
31
52
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module SagePay
|
2
|
+
module Server
|
3
|
+
class Command
|
4
|
+
include Validatable
|
5
|
+
|
6
|
+
attr_reader :vps_protocol
|
7
|
+
attr_accessor :mode, :tx_type, :vendor, :vendor_tx_code
|
8
|
+
|
9
|
+
validates_presence_of :vps_protocol, :mode, :tx_type, :vendor,
|
10
|
+
:vendor_tx_code
|
11
|
+
|
12
|
+
validates_length_of :vps_protocol, :is => 4
|
13
|
+
validates_length_of :vendor, :maximum => 15
|
14
|
+
validates_length_of :vendor_tx_code, :maximum => 40
|
15
|
+
|
16
|
+
validates_inclusion_of :mode, :allow_blank => true, :in => [ :simulator, :test, :live ]
|
17
|
+
|
18
|
+
def initialize(attributes = {})
|
19
|
+
@vps_protocol = "2.23"
|
20
|
+
|
21
|
+
attributes.each do |k, v|
|
22
|
+
send("#{k}=", v)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def run!
|
27
|
+
@response ||= handle_response(post)
|
28
|
+
end
|
29
|
+
|
30
|
+
def live_service
|
31
|
+
raise NotImplementedError, "Subclass of command implement live_service with tail of the URL used for that command in the test & live systems."
|
32
|
+
end
|
33
|
+
|
34
|
+
def simulator_service
|
35
|
+
raise NotImplementedError, "Subclass of command implement simulator_service with tail of the URL used for that command in the simulator."
|
36
|
+
end
|
37
|
+
|
38
|
+
def url
|
39
|
+
case mode
|
40
|
+
when :simulator
|
41
|
+
"https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=#{simulator_service}"
|
42
|
+
when :test
|
43
|
+
"https://test.sagepay.com/gateway/service/#{live_service}.vsp"
|
44
|
+
when :live
|
45
|
+
"https://live.sagepay.com/gateway/service/#{live_service}.vsp"
|
46
|
+
else
|
47
|
+
raise ArgumentError, "Invalid transaction mode"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def post_params
|
52
|
+
raise ArgumentError, "Invalid transaction registration options (see errors hash for details)" unless valid?
|
53
|
+
|
54
|
+
{
|
55
|
+
"VPSProtocol" => vps_protocol,
|
56
|
+
"TxType" => tx_type.to_s.upcase,
|
57
|
+
"Vendor" => vendor,
|
58
|
+
"VendorTxCode" => vendor_tx_code,
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
def response_from_response_body(response_body)
|
63
|
+
Response.from_response_body(response_body)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def post
|
68
|
+
parsed_uri = URI.parse(url)
|
69
|
+
request = Net::HTTP::Post.new(parsed_uri.request_uri)
|
70
|
+
request.form_data = post_params
|
71
|
+
|
72
|
+
http = Net::HTTP.new(parsed_uri.host, parsed_uri.port)
|
73
|
+
http.use_ssl = true if parsed_uri.scheme == "https"
|
74
|
+
http.start { |http|
|
75
|
+
http.request(request)
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def handle_response(response)
|
80
|
+
case response.code.to_i
|
81
|
+
when 200
|
82
|
+
response_from_response_body(response.body)
|
83
|
+
else
|
84
|
+
# FIXME: custom error response would be nice.
|
85
|
+
raise RuntimeError, "I guess SagePay doesn't like us today."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def present?(value)
|
90
|
+
!blank?(value)
|
91
|
+
end
|
92
|
+
|
93
|
+
def blank?(value)
|
94
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module SagePay
|
2
2
|
module Server
|
3
|
-
class
|
3
|
+
class Notification
|
4
4
|
attr_reader :vps_protocol, :tx_type, :vendor_tx_code, :vps_tx_id,
|
5
5
|
:status, :status_detail, :tx_auth_no, :avs_cv2, :address_result,
|
6
6
|
:post_code_result, :cv2_result, :gift_aid, :threed_secure_status,
|
@@ -180,9 +180,9 @@ module SagePay
|
|
180
180
|
|
181
181
|
def response(redirect_url)
|
182
182
|
if valid_signature?
|
183
|
-
SagePay::Server::
|
183
|
+
SagePay::Server::NotificationResponse.new(:status => :ok, :redirect_url => redirect_url)
|
184
184
|
else
|
185
|
-
SagePay::Server::
|
185
|
+
SagePay::Server::NotificationResponse.new(:status => :invalid, :redirect_url => redirect_url, :status_detail => "Signature did not match our expectations")
|
186
186
|
end.response
|
187
187
|
end
|
188
188
|
end
|
@@ -1,29 +1,20 @@
|
|
1
1
|
module SagePay
|
2
2
|
module Server
|
3
|
-
class
|
4
|
-
|
3
|
+
class Registration < Command
|
4
|
+
attr_accessor :amount, :currency, :description, :notification_url,
|
5
|
+
:billing_address, :delivery_address, :customer_email, :basket,
|
6
|
+
:allow_gift_aid, :apply_avs_cv2, :apply_3d_secure, :profile,
|
7
|
+
:billing_agreement, :account_type
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
:amount, :currency, :description, :notification_url, :billing_address,
|
9
|
-
:delivery_address, :customer_email, :basket, :allow_gift_aid,
|
10
|
-
:apply_avs_cv2, :apply_3d_secure, :profile, :billing_agreement,
|
11
|
-
:account_type
|
9
|
+
validates_presence_of :amount, :currency, :description,
|
10
|
+
:notification_url, :billing_address, :delivery_address
|
12
11
|
|
13
|
-
validates_presence_of :mode, :vps_protocol, :tx_type, :vendor,
|
14
|
-
:vendor_tx_code, :amount, :currency, :description, :notification_url,
|
15
|
-
:billing_address, :delivery_address
|
16
|
-
|
17
|
-
validates_length_of :vps_protocol, :is => 4
|
18
|
-
validates_length_of :vendor, :maximum => 15
|
19
|
-
validates_length_of :vendor_tx_code, :maximum => 40
|
20
12
|
validates_length_of :currency, :is => 3
|
21
13
|
validates_length_of :description, :maximum => 100
|
22
14
|
validates_length_of :notification_url, :maximum => 255
|
23
15
|
validates_length_of :customer_email, :maximum => 255
|
24
16
|
validates_length_of :basket, :maximum => 7_500
|
25
17
|
|
26
|
-
validates_inclusion_of :mode, :allow_blank => true, :in => [ :simulator, :test, :live ]
|
27
18
|
validates_inclusion_of :tx_type, :allow_blank => true, :in => [ :payment, :deferred, :authenticate ]
|
28
19
|
validates_inclusion_of :allow_gift_aid, :allow_blank => true, :in => [ true, false ]
|
29
20
|
validates_inclusion_of :apply_avs_cv2, :allow_blank => true, :in => (0..3).to_a
|
@@ -35,16 +26,7 @@ module SagePay
|
|
35
26
|
validates_true_for :amount, :key => :amount_minimum_value, :logic => lambda { amount.nil? || amount >= BigDecimal.new("0.01") }, :message => "is less than the minimum value (0.01)"
|
36
27
|
validates_true_for :amount, :key => :amount_maximum_value, :logic => lambda { amount.nil? || amount <= BigDecimal.new("100000") }, :message => "is greater than the maximum value (100,000.00)"
|
37
28
|
|
38
|
-
def
|
39
|
-
# Effectively hard coded for now
|
40
|
-
@vps_protocol = "2.23"
|
41
|
-
|
42
|
-
attributes.each do |k, v|
|
43
|
-
send("#{k}=", v)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def register!
|
29
|
+
def run!
|
48
30
|
if @response.nil? || (@vendor_tx_code_sent != vendor_tx_code)
|
49
31
|
@vendor_tx_code_sent = vendor_tx_code
|
50
32
|
@response = handle_response(post)
|
@@ -55,36 +37,24 @@ module SagePay
|
|
55
37
|
|
56
38
|
def signature_verification_details
|
57
39
|
if @response.nil?
|
58
|
-
raise RuntimeError, "
|
40
|
+
raise RuntimeError, "Not yet registered"
|
59
41
|
elsif @response.failed?
|
60
|
-
raise RuntimeError, "
|
42
|
+
raise RuntimeError, "Registration failed"
|
61
43
|
else
|
62
44
|
@signature_verification_details ||= SignatureVerificationDetails.new(vendor, @response.security_key)
|
63
45
|
end
|
64
46
|
end
|
65
47
|
|
66
|
-
def
|
67
|
-
|
68
|
-
when :simulator
|
69
|
-
"https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=VendorRegisterTx"
|
70
|
-
when :test
|
71
|
-
"https://test.sagepay.com/gateway/service/vspserver-register.vsp"
|
72
|
-
when :live
|
73
|
-
"https://live.sagepay.com/gateway/service/vspserver-register.vsp"
|
74
|
-
else
|
75
|
-
raise ArgumentError, "Invalid transaction mode"
|
76
|
-
end
|
48
|
+
def live_service
|
49
|
+
"vspserver-register"
|
77
50
|
end
|
78
51
|
|
79
|
-
def
|
80
|
-
|
52
|
+
def simulator_service
|
53
|
+
"VendorRegisterTx"
|
54
|
+
end
|
81
55
|
|
82
|
-
|
83
|
-
params = {
|
84
|
-
"VPSProtocol" => vps_protocol,
|
85
|
-
"TxType" => tx_type.to_s.upcase,
|
86
|
-
"Vendor" => vendor,
|
87
|
-
"VendorTxCode" => vendor_tx_code,
|
56
|
+
def post_params
|
57
|
+
params = super.merge({
|
88
58
|
"Amount" => ("%.2f" % amount),
|
89
59
|
"Currency" => currency.upcase,
|
90
60
|
"Description" => description,
|
@@ -101,7 +71,7 @@ module SagePay
|
|
101
71
|
"DeliveryCity" => delivery_address.city,
|
102
72
|
"DeliveryPostCode" => delivery_address.post_code,
|
103
73
|
"DeliveryCountry" => delivery_address.country,
|
104
|
-
}
|
74
|
+
})
|
105
75
|
|
106
76
|
# Optional parameters that are only inserted if they are present
|
107
77
|
params['BillingAddress2'] = billing_address.address_2 if present?(billing_address.address_2)
|
@@ -123,34 +93,15 @@ module SagePay
|
|
123
93
|
params
|
124
94
|
end
|
125
95
|
|
96
|
+
def response_from_response_body(response_body)
|
97
|
+
RegistrationResponse.from_response_body(response_body)
|
98
|
+
end
|
99
|
+
|
126
100
|
def amount=(value)
|
127
101
|
@amount = blank?(value) ? nil : BigDecimal.new(value.to_s)
|
128
102
|
end
|
129
103
|
|
130
104
|
private
|
131
|
-
|
132
|
-
def post
|
133
|
-
parsed_uri = URI.parse(url)
|
134
|
-
request = Net::HTTP::Post.new(parsed_uri.request_uri)
|
135
|
-
request.form_data = post_params
|
136
|
-
|
137
|
-
http = Net::HTTP.new(parsed_uri.host, parsed_uri.port)
|
138
|
-
http.use_ssl = true if parsed_uri.scheme == "https"
|
139
|
-
http.start { |http|
|
140
|
-
http.request(request)
|
141
|
-
}
|
142
|
-
end
|
143
|
-
|
144
|
-
def handle_response(response)
|
145
|
-
case response.code.to_i
|
146
|
-
when 200
|
147
|
-
TransactionRegistrationResponse.from_response_body(response.body)
|
148
|
-
else
|
149
|
-
# FIXME: custom error response would be nice.
|
150
|
-
raise RuntimeError, "I guess SagePay doesn't like us today."
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
105
|
def account_type_param
|
155
106
|
case account_type
|
156
107
|
when :ecommerce
|
@@ -163,14 +114,6 @@ module SagePay
|
|
163
114
|
raise ArgumentError, "account type is an invalid value: #{account_type}"
|
164
115
|
end
|
165
116
|
end
|
166
|
-
|
167
|
-
def present?(value)
|
168
|
-
!blank?(value)
|
169
|
-
end
|
170
|
-
|
171
|
-
def blank?(value)
|
172
|
-
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
173
|
-
end
|
174
117
|
end
|
175
118
|
end
|
176
119
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SagePay
|
2
|
+
module Server
|
3
|
+
class RegistrationResponse < Response
|
4
|
+
self.key_converter = key_converter.merge({
|
5
|
+
"VPSTxId" => :vps_tx_id,
|
6
|
+
"SecurityKey" => :security_key,
|
7
|
+
"NextURL" => :next_url
|
8
|
+
})
|
9
|
+
|
10
|
+
def vps_tx_id
|
11
|
+
if ok?
|
12
|
+
@vps_tx_id
|
13
|
+
else
|
14
|
+
raise RuntimeError, "Unable to retrieve the transaction id as the status was not OK."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def security_key
|
19
|
+
if ok?
|
20
|
+
@security_key
|
21
|
+
else
|
22
|
+
raise RuntimeError, "Unable to retrieve the security key as the status was not OK."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def next_url
|
27
|
+
if ok?
|
28
|
+
@next_url
|
29
|
+
else
|
30
|
+
raise RuntimeError, "Unable to retrieve the next URL as the status was not OK."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module SagePay
|
2
|
+
module Server
|
3
|
+
class Release < Command
|
4
|
+
attr_accessor :vps_tx_id, :security_key, :tx_auth_no, :release_amount
|
5
|
+
|
6
|
+
validates_presence_of :vps_tx_id, :security_key, :tx_auth_no,
|
7
|
+
:release_amount
|
8
|
+
|
9
|
+
validates_length_of :vps_tx_id, :is => 38
|
10
|
+
validates_length_of :security_key, :is => 10
|
11
|
+
|
12
|
+
validates_inclusion_of :tx_type, :allow_blank => true, :in => [ :release ]
|
13
|
+
|
14
|
+
validates_true_for :release_amount, :key => :release_amount_minimum_value, :logic => lambda { release_amount.nil? || release_amount >= BigDecimal.new("0.01") }, :message => "is less than the minimum value (0.01)"
|
15
|
+
validates_true_for :release_amount, :key => :release_amount_maximum_value, :logic => lambda { release_amount.nil? || release_amount <= BigDecimal.new("100000") }, :message => "is greater than the maximum value (100,000.00)"
|
16
|
+
|
17
|
+
def initialize(attributes = {})
|
18
|
+
@tx_type = :release
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def post_params
|
23
|
+
super.merge({
|
24
|
+
"VPSTxId" => vps_tx_id,
|
25
|
+
"SecurityKey" => security_key,
|
26
|
+
"TxAuthNo" => tx_auth_no,
|
27
|
+
"ReleaseAmount" => ("%.2f" % amount)
|
28
|
+
})
|
29
|
+
end
|
30
|
+
|
31
|
+
def release_amount=(value)
|
32
|
+
@release_amount = blank?(value) ? nil : BigDecimal.new(value.to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
def live_service
|
36
|
+
"release"
|
37
|
+
end
|
38
|
+
|
39
|
+
def simulator_service
|
40
|
+
"VendorReleaseTx"
|
41
|
+
end
|
42
|
+
|
43
|
+
def response_from_response_body(response_body)
|
44
|
+
SagePay::Server::ReleaseResponse.from_response_body(response_body)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|