sage_pay 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
+ require 'spec/rake/spectask'
3
4
  require 'date'
4
5
 
5
6
  #############################################################################
@@ -43,21 +44,13 @@ end
43
44
  #
44
45
  #############################################################################
45
46
 
46
- task :default => :test
47
+ task :default => :spec
47
48
 
48
- require 'rake/testtask'
49
- Rake::TestTask.new(:test) do |test|
50
- test.libs << 'lib' << 'test'
51
- test.pattern = 'test/**/test_*.rb'
52
- test.verbose = true
53
- end
49
+ Spec::Rake::SpecTask.new
54
50
 
55
- desc "Generate RCov test coverage and open in your browser"
56
- task :coverage do
57
- require 'rcov'
58
- sh "rm -fr coverage"
59
- sh "rcov test/test_*.rb"
60
- sh "open coverage/index.html"
51
+ Spec::Rake::SpecTask.new(:coverage) do |coverage|
52
+ coverage.rcov = true
53
+ coverage.rcov_opts << '--exclude' << '\.rvm,spec'
61
54
  end
62
55
 
63
56
  require 'rake/rdoctask'
@@ -70,7 +63,7 @@ end
70
63
 
71
64
  desc "Open an irb session preloaded with this library"
72
65
  task :console do
73
- sh "irb -rubygems -r ./lib/#{name}.rb"
66
+ sh "irb -rubygems -Ilib -r ./lib/#{name}.rb -r spec/support/factories.rb"
74
67
  end
75
68
 
76
69
  #############################################################################
@@ -87,6 +80,7 @@ end
87
80
  #
88
81
  #############################################################################
89
82
 
83
+ desc "Release the new version of the gem into the wild"
90
84
  task :release => :build do
91
85
  unless `git branch` =~ /^\* master$/
92
86
  puts "You must be on the master branch to release!"
@@ -99,13 +93,14 @@ task :release => :build do
99
93
  sh "gem push pkg/#{name}-#{version}.gem"
100
94
  end
101
95
 
96
+ desc "Build the gem"
102
97
  task :build => :gemspec do
103
98
  sh "mkdir -p pkg"
104
99
  sh "gem build #{gemspec_file}"
105
100
  sh "mv #{gem_file} pkg"
106
101
  end
107
102
 
108
- task :gemspec => :validate do
103
+ task :gemspec do
109
104
  # read spec file and split out manifest section
110
105
  spec = File.read(gemspec_file)
111
106
  head, manifest, tail = spec.split(" # = MANIFEST =\n")
@@ -132,15 +127,3 @@ task :gemspec => :validate do
132
127
  File.open(gemspec_file, 'w') { |io| io.write(spec) }
133
128
  puts "Updated #{gemspec_file}"
134
129
  end
135
-
136
- task :validate do
137
- libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
138
- unless libfiles.empty?
139
- puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
140
- exit!
141
- end
142
- unless Dir['VERSION*'].empty?
143
- puts "A `VERSION` file at root level violates Gem best practices."
144
- exit!
145
- end
146
- end
@@ -0,0 +1,45 @@
1
+ module SagePay
2
+ module Server
3
+ class Address
4
+ include Validatable
5
+
6
+ class << self
7
+ attr_accessor :us_states, :iso_3166_country_codes
8
+ end
9
+ self.us_states = ["AK", "AL", "AR", "AS", "AZ", "CA", "CO", "CT", "DC", "DE", "FL", "FM", "GA", "GU", "HI", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MA", "MD", "ME", "MH", "MI", "MN", "MO", "MS", "MT", "NC", "ND", "NE", "NH", "NJ", "NM", "NV", "NY", "OH", "OK", "OR", "PA", "PR", "PW", "RI", "SC", "SD", "TN", "TX", "UT", "VA", "VI", "VT", "WA", "WI", "WV", "WY"]
10
+ self.iso_3166_country_codes = ["AF", "AX", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", "CR", "CI", "HR", "CU", "CY", "CZ", "DK", "DJ", "DM", "DO", "EC", "EG", "SV", "GQ", "ER", "EE", "ET", "FK", "FO", "FJ", "FI", "FR", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GG", "GN", "GW", "GY", "HT", "HM", "VA", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IM", "IL", "IT", "JM", "JP", "JE", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MK", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "ME", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "AN", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", "OM", "PK", "PW", "PS", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "BL", "SH", "KN", "LC", "MF", "PM", "VC", "WS", "SM", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SK", "SI", "SB", "SO", "ZA", "GS", "ES", "LK", "SD", "SR", "SJ", "SZ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TL", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "ZM", "ZW"]
11
+
12
+ attr_accessor :first_names, :surname, :address_1, :address_2, :city,
13
+ :post_code, :country, :state, :phone
14
+
15
+ validates_presence_of :first_names, :surname, :address_1, :city, :post_code, :country
16
+
17
+ # FIXME: This regexp isn't correctly matching accented characters. I
18
+ # think it's a Ruby version issue so I'm punting for the moment.
19
+ validates_format_of :first_names, :surname, :with => /^[[:alpha:] \\\/&'\.\-]*$/
20
+ validates_format_of :address_1, :address_2, :city, :with => /^[[:alnum:][:space:]\+\\\/&'\.:,\(\)\-]*$/
21
+ validates_format_of :post_code, :with => /^[[:alnum:] -]*$/
22
+ validates_format_of :phone, :with => /^[[:alnum:] \+\(\)-]*$/
23
+
24
+ validates_length_of :first_names, :surname, :maximum => 20
25
+ validates_length_of :address_1, :address_2, :maximum => 100
26
+ validates_length_of :city, :maximum => 40
27
+ validates_length_of :post_code, :maximum => 10
28
+ validates_length_of :phone, :maximum => 20
29
+
30
+ # While the spec specifies the lengths of these columns, we're
31
+ # validating that they're included in our list, and our list only
32
+ # contains two-character strings, so this validation has no real win.
33
+ # validates_length_of :country, :state, :maximum => 2
34
+
35
+ validates_inclusion_of :state, :in => us_states, :allow_blank => true, :message => "is not a US state"
36
+ validates_inclusion_of :country, :in => iso_3166_country_codes, :allow_blank => true, :message => "is not an ISO3166-1 country code"
37
+
38
+ def initialize(attributes = {})
39
+ attributes.each do |k, v|
40
+ send("#{k}=", v)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,14 @@
1
+ module SagePay
2
+ module Server
3
+ class SignatureVerificationDetails
4
+ attr_reader :vps_tx_id, :vendor_tx_code, :vendor, :security_key
5
+
6
+ def initialize(transaction_registration, transaction_registration_response)
7
+ @vendor_tx_code = transaction_registration.vendor_tx_code
8
+ @vendor = transaction_registration.vendor
9
+ @vps_tx_id = transaction_registration_response.vps_tx_id
10
+ @security_key = transaction_registration_response.security_key
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module SagePay
2
+ module Server
3
+ class TransactionCode
4
+ def self.random
5
+ new.random
6
+ end
7
+
8
+ def random
9
+ uuid.generate
10
+ end
11
+
12
+ protected
13
+ def uuid
14
+ @uuid ||= UUID.new
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,169 @@
1
+ module SagePay
2
+ module Server
3
+ class TransactionNotification
4
+ attr_reader :vps_protocol, :status, :status_detail, :tx_auth_no,
5
+ :avs_cv2, :address_result, :post_code_result, :cv2_result, :gift_aid,
6
+ :threed_secure_status, :cavv, :address_status, :payer_status,
7
+ :card_type, :last_4_digits, :vps_signature
8
+
9
+ def self.from_params(params, signature_verification_details = nil)
10
+ key_converter = {
11
+ "VPSProtocol" => :vps_protocol,
12
+ "Status" => :status,
13
+ "StatusDetail" => :status_detail,
14
+ "TxAuthNo" => :tx_auth_no,
15
+ "AVSCV2" => :avs_cv2,
16
+ "AddressResult" => :address_result,
17
+ "PostCodeResult" => :post_code_result,
18
+ "CV2Result" => :cv2_result,
19
+ "GiftAid" => :gift_aid,
20
+ "3DSecureStatus" => :threed_secure_status,
21
+ "CAVV" => :cavv,
22
+ "AddressStatus" => :address_status,
23
+ "PayerStatus" => :payer_status,
24
+ "CardType" => :card_type,
25
+ "Last4Digits" => :last_4_digits,
26
+ "VPSSignature" => :vps_signature
27
+ }
28
+
29
+ match_converter = {
30
+ "NOTPROVIDED" => :not_provided,
31
+ "NOTCHECKED" => :not_checked,
32
+ "MATCHED" => :matched,
33
+ "NOTMATCHED" => :not_matched
34
+ }
35
+
36
+ true_false_converter = {
37
+ "0" => false,
38
+ "1" => true
39
+ }
40
+
41
+ value_converter = {
42
+ :status => {
43
+ "OK" => :ok,
44
+ "NOTAUTHED" => :not_authed,
45
+ "ABORT" => :abort,
46
+ "REJECTED" => :rejected,
47
+ "AUTHENTICATED" => :authenticated,
48
+ "REGISTERED" => :registered,
49
+ "ERROR" => :error
50
+ },
51
+ :avs_cv2 => {
52
+ "ALL MATCH" => :all_match,
53
+ "SECURITY CODE MATCH ONLY" => :security_code_match_only,
54
+ "ADDRESS MATCH ONLY" => :address_match_only,
55
+ "NO DATA MATCHES" => :no_data_matches,
56
+ "DATA NOT CHECKED" => :data_not_checked
57
+ },
58
+ :address_result => match_converter,
59
+ :post_code_result => match_converter,
60
+ :cv2_result => match_converter,
61
+ :gift_aid => true_false_converter,
62
+ :threed_secure_status => {
63
+ "OK" => :ok,
64
+ "NOTCHECKED" => :not_checked,
65
+ "NOTAVAILABLE" => :not_available,
66
+ "NOTAUTHED" => :not_authed,
67
+ "INCOMPLETE" => :incomplete,
68
+ "ERROR" => :error
69
+ },
70
+ :address_status => {
71
+ "NONE" => :none,
72
+ "CONFIRMED" => :confirmed,
73
+ "UNCONFIRMED" => :unconfirmed
74
+ },
75
+ :payer_status => {
76
+ "VERIFIED" => :verified,
77
+ "UNVERIFIED" => :unverified
78
+ },
79
+ :card_type => {
80
+ "VISA" => :visa,
81
+ "MC" => :mastercard,
82
+ "DELTA" => :visa_delta,
83
+ "SOLO" => :solo,
84
+ "MAESTRO" => :maestro,
85
+ "UKE" => :visa_electron,
86
+ "AMEX" => :american_express,
87
+ "DC" => :diners,
88
+ "JCB" => :jcb,
89
+ "LASER" => :laser,
90
+ "PAYPAL" => :paypal
91
+ }
92
+ }
93
+
94
+ attributes = {}
95
+ params.each do |key, value|
96
+ unless value.nil?
97
+ converted_key = key_converter[key]
98
+ converted_value = value_converter[converted_key].nil? ? value : value_converter[converted_key][value]
99
+
100
+ attributes[converted_key] = converted_value
101
+ end
102
+ end
103
+
104
+ unless signature_verification_details.nil?
105
+ # We need to calculate the VPS signature from the values passed in as
106
+ # additional params from the original registration and notification.
107
+ fields_used_in_signature = [
108
+ signature_verification_details.vps_tx_id,
109
+ signature_verification_details.vendor_tx_code,
110
+ params["Status"],
111
+ params["TxAuthNo"],
112
+ signature_verification_details.vendor,
113
+ params["AVSCV2"],
114
+ signature_verification_details.security_key,
115
+ params["AddressResult"],
116
+ params["PostCodeResult"],
117
+ params["CV2Result"],
118
+ params["GiftAid"],
119
+ params["3DSecureStatus"],
120
+ params["CAVV"],
121
+ params["AddressStatus"],
122
+ params["PayerStatus"],
123
+ params["CardType"],
124
+ params["Last4Digits"]
125
+ ]
126
+ attributes[:calculated_hash] = MD5.md5(fields_used_in_signature.join).to_s.upcase
127
+ end
128
+
129
+ new(attributes)
130
+ end
131
+
132
+ def initialize(attributes = {})
133
+ attributes.each do |k, v|
134
+ # We're only providing readers, not writers, so we have to directly
135
+ # set the instance variable.
136
+ instance_variable_set("@#{k}", v)
137
+ end
138
+ end
139
+
140
+ def ok?
141
+ status == :ok
142
+ end
143
+
144
+ def avs_cv2_matched?
145
+ avs_cv2 == :all_match
146
+ end
147
+
148
+ def address_matched?
149
+ address_result == :matched
150
+ end
151
+
152
+ def post_code_matched?
153
+ post_code_result == :matched
154
+ end
155
+
156
+ def cv2_matched?
157
+ cv2_result == :matched
158
+ end
159
+
160
+ def threed_secure_status_ok?
161
+ threed_secure_status == :ok
162
+ end
163
+
164
+ def valid_signature?
165
+ @calculated_hash == vps_signature
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,62 @@
1
+ module SagePay
2
+ module Server
3
+ class TransactionNotificationResponse
4
+ include Validatable
5
+
6
+ attr_accessor :status, :status_detail, :redirect_url
7
+
8
+ validates_presence_of :status, :redirect_url
9
+ validates_presence_of :status_detail, :if => lambda { |response| !response.ok? }
10
+
11
+ validates_length_of :redirect_url, :maximum => 255
12
+ validates_length_of :status_detail, :maximum => 255
13
+
14
+ validates_inclusion_of :status, :allow_blank => true, :in => [ :ok, :invalid, :error ]
15
+
16
+ def initialize(attributes = {})
17
+ attributes.each do |k, v|
18
+ send("#{k}=", v)
19
+ end
20
+ end
21
+
22
+ def ok?
23
+ status == :ok
24
+ end
25
+
26
+ def response
27
+ response_params.map do |tuple|
28
+ key, value = tuple
29
+ "#{key}=#{value}"
30
+ end.join("\n")
31
+ end
32
+
33
+
34
+ def response_params
35
+ raise ArgumentError, "Invalid transaction registration options (see errors hash for details)" unless valid?
36
+
37
+ # Mandatory parameters that we've already validated are present. Note
38
+ # that the order of parameters is important (Status must be first!) so
39
+ # we're using a list of lists this time around...
40
+ params = [
41
+ ["Status", status.to_s.upcase],
42
+ ["RedirectURL", redirect_url]
43
+ ]
44
+
45
+ # Optional parameters that are only inserted if they are present
46
+ params << ['StatusDetail', status_detail] if present?(status_detail)
47
+
48
+ # And return the completed hash
49
+ params
50
+ end
51
+
52
+ private
53
+ def present?(value)
54
+ !blank?(value)
55
+ end
56
+
57
+ def blank?(value)
58
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,176 @@
1
+ module SagePay
2
+ module Server
3
+ class TransactionRegistration
4
+ include Validatable
5
+
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
12
+
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
+ validates_length_of :currency, :is => 3
21
+ validates_length_of :description, :maximum => 100
22
+ validates_length_of :notification_url, :maximum => 255
23
+ validates_length_of :customer_email, :maximum => 255
24
+ validates_length_of :basket, :maximum => 7_500
25
+
26
+ validates_inclusion_of :mode, :allow_blank => true, :in => [ :simulator, :test, :live ]
27
+ validates_inclusion_of :tx_type, :allow_blank => true, :in => [ :payment, :deferred, :authenticate ]
28
+ validates_inclusion_of :allow_gift_aid, :allow_blank => true, :in => [ true, false ]
29
+ validates_inclusion_of :apply_avs_cv2, :allow_blank => true, :in => (0..3).to_a
30
+ validates_inclusion_of :apply_3d_secure, :allow_blank => true, :in => (0..3).to_a
31
+ validates_inclusion_of :profile, :allow_blank => true, :in => [:normal, :low]
32
+ validates_inclusion_of :billing_agreement, :allow_blank => true, :in => [true, false]
33
+ validates_inclusion_of :account_type, :allow_blank => true, :in => [:ecommerce, :continuous_authority, :mail_order]
34
+
35
+ 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
+ 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
+
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!
48
+ if @response.nil? || (@vendor_tx_code_sent != vendor_tx_code)
49
+ @vendor_tx_code_sent = vendor_tx_code
50
+ @response = handle_response(post)
51
+ else
52
+ raise RuntimeError, "This vendor transaction code has already been registered"
53
+ end
54
+ end
55
+
56
+ def signature_verification_details
57
+ if @response.nil?
58
+ raise RuntimeError, "Transaction not yet registered"
59
+ elsif @response.failed?
60
+ raise RuntimeError, "Transaction registration failed"
61
+ else
62
+ SignatureVerificationDetails.new(self, @response)
63
+ end
64
+ end
65
+
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
77
+ end
78
+
79
+ def post_params
80
+ raise ArgumentError, "Invalid transaction registration options (see errors hash for details)" unless valid?
81
+
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,
88
+ "Amount" => ("%.2f" % amount),
89
+ "Currency" => currency.upcase,
90
+ "Description" => description,
91
+ "NotificationURL" => notification_url,
92
+ "BillingSurname" => billing_address.surname,
93
+ "BillingFirstnames" => billing_address.first_names,
94
+ "BillingAddress1" => billing_address.address_1,
95
+ "BillingCity" => billing_address.city,
96
+ "BillingPostCode" => billing_address.post_code,
97
+ "BillingCountry" => billing_address.country,
98
+ "DeliverySurname" => delivery_address.surname,
99
+ "DeliveryFirstnames" => delivery_address.first_names,
100
+ "DeliveryAddress1" => delivery_address.address_1,
101
+ "DeliveryCity" => delivery_address.city,
102
+ "DeliveryPostCode" => delivery_address.post_code,
103
+ "DeliveryCountry" => delivery_address.country,
104
+ }
105
+
106
+ # Optional parameters that are only inserted if they are present
107
+ params['BillingAddress2'] = billing_address.address_2 if present?(billing_address.address_2)
108
+ params['BillingState'] = billing_address.state if present?(billing_address.state)
109
+ params['BillingPhone'] = billing_address.phone if present?(billing_address.phone)
110
+ params['DeliveryAddress2'] = delivery_address.address_2 if present?(delivery_address.address_2)
111
+ params['DeliveryState'] = delivery_address.state if present?(delivery_address.state)
112
+ params['DeliveryPhone'] = delivery_address.phone if present?(delivery_address.phone)
113
+ params['CustomerEmail'] = customer_email if present?(customer_email)
114
+ params['Basket'] = basket if present?(basket)
115
+ params['AllowGiftAid'] = allow_gift_aid ? "1" : "0" if present?(allow_gift_aid)
116
+ params['ApplyAVSCV2'] = apply_avs_cv2.to_s if present?(apply_avs_cv2)
117
+ params['Apply3DSecure'] = apply_3d_secure.to_s if present?(apply_3d_secure)
118
+ params['Profile'] = profile.to_s.upcase if present?(profile)
119
+ params['BillingAgreement'] = billing_agreement ? "1" : "0" if present?(billing_agreement)
120
+ params['AccountType'] = account_type_param if present?(account_type)
121
+
122
+ # And return the completed hash
123
+ params
124
+ end
125
+
126
+ def amount=(value)
127
+ @amount = blank?(value) ? nil : BigDecimal.new(value.to_s)
128
+ end
129
+
130
+ 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
+ def account_type_param
155
+ case account_type
156
+ when :ecommerce
157
+ 'E'
158
+ when :continuous_authority
159
+ 'C'
160
+ when :mail_order
161
+ 'M'
162
+ else
163
+ raise ArgumentError, "account type is an invalid value: #{account_type}"
164
+ end
165
+ 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
+ end
175
+ end
176
+ end
@@ -0,0 +1,94 @@
1
+ module SagePay
2
+ module Server
3
+ class TransactionRegistrationResponse
4
+ attr_reader :vps_protocol, :status, :status_detail
5
+
6
+ def self.from_response_body(response_body)
7
+ key_converter = {
8
+ "VPSProtocol" => :vps_protocol,
9
+ "Status" => :status,
10
+ "StatusDetail" => :status_detail,
11
+ "VPSTxId" => :vps_tx_id,
12
+ "SecurityKey" => :security_key,
13
+ "NextURL" => :next_url
14
+ }
15
+
16
+ value_converter = {
17
+ :status => {
18
+ "OK" => :ok,
19
+ "MALFORMED" => :malformed,
20
+ "INVALID" => :invalid,
21
+ "ERROR" => :error
22
+ }
23
+ }
24
+
25
+ attributes = {}
26
+ response_body.each_line do |line|
27
+ key, value = line.split('=', 2)
28
+ unless key.nil? || value.nil?
29
+ value = value.chomp
30
+
31
+ converted_key = key_converter[key]
32
+ converted_value = value_converter[converted_key].nil? ? value : value_converter[converted_key][value]
33
+
34
+ attributes[converted_key] = converted_value
35
+ end
36
+ end
37
+
38
+ new(attributes)
39
+ end
40
+
41
+ def initialize(attributes = {})
42
+ attributes.each do |k, v|
43
+ # We're only providing readers, not writers, so we have to directly
44
+ # set the instance variable.
45
+ instance_variable_set("@#{k}", v)
46
+ end
47
+ end
48
+
49
+ def ok?
50
+ status == :ok
51
+ end
52
+
53
+ def failed?
54
+ !ok?
55
+ end
56
+
57
+ def invalid?
58
+ status == :invalid
59
+ end
60
+
61
+ def malformed?
62
+ status == :malformed
63
+ end
64
+
65
+ def error?
66
+ status == :error
67
+ end
68
+
69
+ def vps_tx_id
70
+ if ok?
71
+ @vps_tx_id
72
+ else
73
+ raise RuntimeError, "Unable to retrieve the transaction id as the status was not OK."
74
+ end
75
+ end
76
+
77
+ def security_key
78
+ if ok?
79
+ @security_key
80
+ else
81
+ raise RuntimeError, "Unable to retrieve the security key as the status was not OK."
82
+ end
83
+ end
84
+
85
+ def next_url
86
+ if ok?
87
+ @next_url
88
+ else
89
+ raise RuntimeError, "Unable to retrieve the next URL as the status was not OK."
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end