sage_pay 0.2.5 → 0.2.6
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/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
|