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.
@@ -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