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.
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.6
4
+
5
+ * Is release implemented?
6
+
3
7
  ## 0.2.5
4
8
 
5
9
  * Carriage return and line feed. These will all be compressed into a 0.3
@@ -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.5'
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/transaction_registration'
19
- require 'sage_pay/server/transaction_registration_response'
20
- require 'sage_pay/server/transaction_notification'
21
- require 'sage_pay/server/transaction_notification_response'
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'
@@ -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::TransactionRegistration.new(defaults.merge(attributes))
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 TransactionNotification
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::TransactionNotificationResponse.new(:status => :ok, :redirect_url => redirect_url)
183
+ SagePay::Server::NotificationResponse.new(:status => :ok, :redirect_url => redirect_url)
184
184
  else
185
- SagePay::Server::TransactionNotificationResponse.new(:status => :invalid, :redirect_url => redirect_url, :status_detail => "Signature did not match our expectations")
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,6 +1,6 @@
1
1
  module SagePay
2
2
  module Server
3
- class TransactionNotificationResponse
3
+ class NotificationResponse
4
4
  include Validatable
5
5
 
6
6
  attr_accessor :status, :status_detail, :redirect_url
@@ -1,29 +1,20 @@
1
1
  module SagePay
2
2
  module Server
3
- class TransactionRegistration
4
- include Validatable
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
- attr_reader :vps_protocol
7
- attr_accessor :mode, :tx_type, :vendor, :vendor_tx_code,
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 initialize(attributes = {})
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, "Transaction not yet registered"
40
+ raise RuntimeError, "Not yet registered"
59
41
  elsif @response.failed?
60
- raise RuntimeError, "Transaction registration failed"
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 url
67
- case mode
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 post_params
80
- raise ArgumentError, "Invalid transaction registration options (see errors hash for details)" unless valid?
52
+ def simulator_service
53
+ "VendorRegisterTx"
54
+ end
81
55
 
82
- # Mandatory parameters that we've already validated are present
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