sage_pay 0.2.13 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ 1.9.3-p194
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format n
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0
4
+
5
+ * Ruby 1.9 compatibility. Note that Ruby 1.8.x is no longer supported due to
6
+ the different way in which `URI` needs to be patched to support `{` and `}`
7
+ in the URLs. Thanks to @jairodiaz, @moomerman and @gato-omega for
8
+ contributing!
9
+
10
+ * The validation regular expressions have been pulled out into a module so that
11
+ you can also use them in your higher level models. Thanks, @lenary!
12
+
3
13
  ## 0.2.13
4
14
 
5
15
  * If the country for an address is the US, then the US state is required.
data/Gemfile CHANGED
@@ -1,11 +1,6 @@
1
1
  # Source gems from the default public repository
2
2
  source :rubygems
3
3
 
4
- gem 'activesupport', '>= 2.3.8'
5
- gem 'validatable', '>= 1.6.7'
6
- gem 'uuid', '>= 2.3.1'
4
+ gem 'simplecov'
7
5
 
8
- group :development do
9
- gem 'rake'
10
- gem 'rspec'
11
- end
6
+ gemspec
data/README.md CHANGED
@@ -1,7 +1,9 @@
1
- SagePay
2
- =======
1
+ # SagePay
3
2
 
4
- # Description
3
+ [![Build Status](https://secure.travis-ci.org/mathie/sage_pay.png?branch=master)](http://travis-ci.org/mathie/sage_pay)
4
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/mathie/sage_pay)
5
+
6
+ ## Description
5
7
 
6
8
  This is a Ruby library for integrating with SagePay. SagePay is a payment
7
9
  gateway for accepting credit card payments through your web app. It offers
@@ -23,17 +25,34 @@ The current client app I'm writing is using SagePay Server, so that's where
23
25
  the current implementation will be focused. Direct will follow when I (or
24
26
  somebody else) has the impetus to do so.
25
27
 
26
- # Installation
28
+ ## Installation
27
29
 
28
- You should be able to install the gem directly from Gemcutter, the newly
29
- default Rubygems repository. Simply do:
30
+ You should be able to install the gem directly from Rubygems. Simply do:
30
31
 
31
32
  (sudo) gem install sage_pay
32
33
 
33
- and you're good to go.
34
+ and you're good to go. If you're adding it as a dependency to an existing
35
+ project and you're using bundler, simply add:
36
+
37
+ gem 'sage_pay'
34
38
 
35
- # Assumptions
39
+ to your `Gemfile` and you're also good to go.
40
+
41
+ ## Assumptions
36
42
 
37
43
  This gem currently implements SagePay protocol version 2.23. The client app
38
44
  I'm writing is in Rails, so there are probably some assumptions around that,
39
45
  too.
46
+
47
+ ## Test Configuration
48
+
49
+ For running the integration tests, you'll need a valid SagePay account on the
50
+ simulator. If you want to run the integration tests, pass in your vendor name
51
+ with the environment variable `VENDOR_NAME`. If you don't supply a vendor name,
52
+ the integration tests will be skipped. For example:
53
+
54
+ VENDOR_NAME=rubaidh bundle exec rake spec
55
+
56
+ Please note that you need to include your current IP address in the Sage Pay
57
+ Simulator portal. The Simulator only accepts calls from white listed IP
58
+ addresses.
data/Rakefile CHANGED
@@ -1,129 +1,20 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
- require 'spec/rake/spectask'
4
- require 'date'
5
-
6
- #############################################################################
7
- #
8
- # Helper functions
9
- #
10
- #############################################################################
11
-
12
- def name
13
- @name ||= Dir['*.gemspec'].first.split('.').first
14
- end
15
-
16
- def version
17
- line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
18
- line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
19
- end
20
-
21
- def date
22
- Date.today.to_s
23
- end
24
-
25
- def rubyforge_project
26
- name
27
- end
28
-
29
- def gemspec_file
30
- "#{name}.gemspec"
31
- end
32
-
33
- def gem_file
34
- "#{name}-#{version}.gem"
35
- end
36
-
37
- def replace_header(head, header_name)
38
- head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
39
- end
40
-
41
- #############################################################################
42
- #
43
- # Standard tasks
44
- #
45
- #############################################################################
3
+ require 'rdoc/task'
4
+ require 'rspec/core/rake_task'
5
+ require 'bundler/gem_tasks'
46
6
 
47
7
  task :default => :spec
48
8
 
49
- Spec::Rake::SpecTask.new
9
+ RSpec::Core::RakeTask.new
50
10
 
51
- Spec::Rake::SpecTask.new(:coverage) do |coverage|
52
- coverage.rcov = true
53
- coverage.rcov_opts << '--exclude' << '\.rvm,spec'
54
- end
55
-
56
- require 'rake/rdoctask'
57
11
  Rake::RDocTask.new do |rdoc|
58
12
  rdoc.rdoc_dir = 'rdoc'
59
- rdoc.title = "#{name} #{version}"
60
13
  rdoc.rdoc_files.include('README*')
61
14
  rdoc.rdoc_files.include('lib/**/*.rb')
62
15
  end
63
16
 
64
17
  desc "Open an irb session preloaded with this library"
65
18
  task :console do
66
- sh "irb -rubygems -Ilib -r ./lib/#{name}.rb -r spec/support/factories.rb"
67
- end
68
-
69
- #############################################################################
70
- #
71
- # Custom tasks (add your own tasks here)
72
- #
73
- #############################################################################
74
-
75
-
76
-
77
- #############################################################################
78
- #
79
- # Packaging tasks
80
- #
81
- #############################################################################
82
-
83
- desc "Release the new version of the gem into the wild"
84
- task :release => :build do
85
- unless `git branch` =~ /^\* master$/
86
- puts "You must be on the master branch to release!"
87
- exit!
88
- end
89
- sh "git commit --allow-empty -a -m 'Release #{version}'"
90
- sh "git tag v#{version}"
91
- sh "git push origin master"
92
- sh "git push origin v#{version}"
93
- sh "gem push pkg/#{name}-#{version}.gem"
94
- end
95
-
96
- desc "Build the gem"
97
- task :build => [:spec, :gemspec] do
98
- sh "mkdir -p pkg"
99
- sh "gem build #{gemspec_file}"
100
- sh "mv #{gem_file} pkg"
101
- end
102
-
103
- task :gemspec do
104
- # read spec file and split out manifest section
105
- spec = File.read(gemspec_file)
106
- head, manifest, tail = spec.split(" # = MANIFEST =\n")
107
-
108
- # replace name version and date
109
- replace_header(head, :name)
110
- replace_header(head, :version)
111
- replace_header(head, :date)
112
- #comment this out if your rubyforge_project has a different name
113
- replace_header(head, :rubyforge_project)
114
-
115
- # determine file list from git ls-files
116
- files = `git ls-files`.
117
- split("\n").
118
- sort.
119
- reject { |file| file =~ /^\./ }.
120
- reject { |file| file =~ /^(rdoc|pkg)/ }.
121
- map { |file| " #{file}" }.
122
- join("\n")
123
-
124
- # piece file back together and write
125
- manifest = " s.files = %w[\n#{files}\n ]\n"
126
- spec = [head, manifest, tail].join(" # = MANIFEST =\n")
127
- File.open(gemspec_file, 'w') { |io| io.write(spec) }
128
- puts "Updated #{gemspec_file}"
19
+ ruby "-S irb -rubygems -Ilib -r ./lib/sage_pay.rb -r ./spec/support/factories.rb"
129
20
  end
@@ -0,0 +1,8 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ blank: "can't be empty"
5
+ inclusion: 'is not in the list'
6
+ invalid: 'is invalid'
7
+ too_long: 'is invalid'
8
+ wrong_length: 'is invalid'
@@ -1,18 +1,22 @@
1
- require 'active_support'
2
- require 'validatable'
1
+ require 'active_support/core_ext/class'
3
2
  require 'bigdecimal'
4
3
  require 'net/https'
5
4
  require 'uri'
6
- require 'md5'
5
+ require 'digest/md5'
7
6
  require 'uuid'
8
7
 
8
+ require 'active_model/naming'
9
+ require 'active_model/validator'
10
+ require 'active_model/validations'
11
+ require 'active_model/translation'
12
+
9
13
  module SagePay
10
- VERSION = '0.2.13'
11
14
  end
12
15
 
13
- require 'validatable-ext'
14
-
16
+ require 'sage_pay/version'
15
17
  require 'sage_pay/uri_fixups'
18
+ require 'sage_pay/locale_initializer'
19
+ require 'sage_pay/validators'
16
20
  require 'sage_pay/server/address'
17
21
  require 'sage_pay/server/transaction_code'
18
22
  require 'sage_pay/server/signature_verification_details'
@@ -0,0 +1,3 @@
1
+ # Loads the i18n translations file for validation errors
2
+ translation_dir = [File.expand_path("../../../config/locales/en.yml", __FILE__)]
3
+ I18n.load_path += translation_dir
@@ -1,13 +1,8 @@
1
1
  module SagePay
2
2
  module Server
3
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"]
4
+ include ActiveModel::Validations
5
+ include SagePay::Validators
11
6
 
12
7
  attr_accessor :first_names, :surname, :address_1, :address_2, :city,
13
8
  :post_code, :country, :state, :phone
@@ -16,10 +11,10 @@ module SagePay
16
11
 
17
12
  # FIXME: This regexp isn't correctly matching accented characters. I
18
13
  # 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:] \+\(\)-]*$/
14
+ validates_format_of :first_names, :surname, :with => NAME_FORMAT
15
+ validates_format_of :address_1, :address_2, :city, :with => ADDRESS_FORMAT
16
+ validates_format_of :post_code, :with => POST_CODE_FORMAT
17
+ validates_format_of :phone, :with => PHONE_FORMAT
23
18
 
24
19
  validates_length_of :first_names, :surname, :maximum => 20
25
20
  validates_length_of :address_1, :address_2, :maximum => 100
@@ -32,13 +27,14 @@ module SagePay
32
27
  # contains two-character strings, so this validation has no real win.
33
28
  # validates_length_of :country, :state, :maximum => 2
34
29
 
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"
30
+ validates_inclusion_of :state, :in => US_STATE_OPTIONS, :allow_blank => true, :message => "is not a US state"
31
+ validates_inclusion_of :country, :in => COUNTRY_OPTIONS, :allow_blank => true, :message => "is not an ISO3166-1 country code"
37
32
 
38
33
  # The state's presence is required if the country is the US, and
39
34
  # verboten otherwise.
40
- validates_true_for :state, :key => :state_required_in_us, :logic => lambda { in_us? ? state.present? : true }, :message => "is required if the country is US"
41
- validates_true_for :state, :key => :verboten_outside_us, :logic => lambda { in_us? ? true : !state.present? }, :message => "is present but the country is not US"
35
+
36
+ validates :state, :presence => {:message => "is required if the country is US"}, :if => :in_us?
37
+ validates_inclusion_of :state, :in => ['', nil], :message => "is present but the country is not US", :unless => :in_us?
42
38
 
43
39
  def initialize(attributes = {})
44
40
  attributes.each do |k, v|
@@ -12,8 +12,8 @@ module SagePay
12
12
 
13
13
  validates_inclusion_of :apply_avs_cv2, :allow_blank => true, :in => (0..3).to_a
14
14
 
15
- 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)"
16
- 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)"
15
+ validates :amount, :numericality => {:message => "is less than the minimum value (0.01)", :greater_than_or_equal_to => BigDecimal.new("0.01")}
16
+ validates :amount, :numericality => {:message => "is greater than the maximum value (100,000.00)", :less_than_or_equal_toi => BigDecimal.new("100000")}
17
17
 
18
18
  def post_params
19
19
  params = super.merge({
@@ -1,9 +1,10 @@
1
1
  module SagePay
2
2
  module Server
3
3
  class Command
4
- include Validatable
4
+ include ActiveModel::Validations
5
+
6
+ class_attribute :tx_type, :vps_protocol
5
7
 
6
- class_inheritable_accessor :tx_type, :vps_protocol
7
8
  self.vps_protocol = "2.23"
8
9
 
9
10
  attr_accessor :mode, :vendor, :vendor_tx_code
@@ -15,7 +16,7 @@ module SagePay
15
16
  validates_length_of :vendor, :maximum => 15
16
17
  validates_length_of :vendor_tx_code, :maximum => 40
17
18
 
18
- validates_inclusion_of :mode, :allow_blank => true, :in => [ :simulator, :test, :live ]
19
+ validates_inclusion_of :mode, :allow_blank => true, :in => [ :showpost, :simulator, :test, :live ]
19
20
 
20
21
  def self.decimal_accessor(*attrs)
21
22
  attrs.each do |attr|
@@ -46,6 +47,8 @@ module SagePay
46
47
 
47
48
  def url
48
49
  case mode
50
+ when :showpost
51
+ "https://test.sagepay.com/showpost/showpost.asp?Service=#{simulator_service}"
49
52
  when :simulator
50
53
  "https://test.sagepay.com/simulator/VSPServerGateway.asp?Service=#{simulator_service}"
51
54
  when :test
@@ -91,7 +94,7 @@ module SagePay
91
94
  response_from_response_body(response.body)
92
95
  else
93
96
  # FIXME: custom error response would be nice.
94
- raise RuntimeError, "I guess SagePay doesn't like us today."
97
+ raise RuntimeError, "I guess SagePay doesn't like us today: #{response.inspect}"
95
98
  end
96
99
  end
97
100
  end
@@ -136,7 +136,7 @@ module SagePay
136
136
  params["CardType"],
137
137
  params["Last4Digits"]
138
138
  ]
139
- attributes[:calculated_hash] = MD5.md5(fields_used_in_signature.join).to_s.upcase
139
+ attributes[:calculated_hash] = Digest::MD5.hexdigest(fields_used_in_signature.join).upcase
140
140
  end
141
141
 
142
142
  new(attributes)
@@ -1,7 +1,7 @@
1
1
  module SagePay
2
2
  module Server
3
3
  class NotificationResponse
4
- include Validatable
4
+ include ActiveModel::Validations
5
5
 
6
6
  attr_accessor :status, :status_detail, :redirect_url
7
7
 
@@ -11,9 +11,8 @@ module SagePay
11
11
  validates_length_of :currency, :is => 3
12
12
  validates_length_of :description, :maximum => 100
13
13
 
14
-
15
- 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)"
16
- 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)"
14
+ validates :amount, :numericality => {:message => "is less than the minimum value (0.01)", :greater_than_or_equal_to => BigDecimal.new("0.01")}
15
+ validates :amount, :numericality => {:message => "is greater than the maximum value (100,000.00)", :less_than_or_equal_to => BigDecimal.new("100000")}
17
16
 
18
17
  def post_params
19
18
  super.merge({
@@ -24,8 +24,8 @@ module SagePay
24
24
  validates_inclusion_of :billing_agreement, :allow_blank => true, :in => [true, false]
25
25
  validates_inclusion_of :account_type, :allow_blank => true, :in => [:ecommerce, :continuous_authority, :mail_order]
26
26
 
27
- 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)"
28
- 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)"
27
+ validates :amount, :numericality => {:message => "is less than the minimum value (0.01)", :greater_than_or_equal_to => BigDecimal.new("0.01")}
28
+ validates :amount, :numericality => {:message => "is greater than the maximum value (100,000.00)", :less_than_or_equal_to => BigDecimal.new("100000")}
29
29
 
30
30
  def run!
31
31
  if @response.nil? || (@vendor_tx_code_sent != vendor_tx_code)
@@ -1,7 +1,7 @@
1
1
  module SagePay
2
2
  module Server
3
3
  class RelatedTransaction
4
- include Validatable
4
+ include ActiveModel::Validations
5
5
 
6
6
  attr_accessor :vps_tx_id, :vendor_tx_code, :security_key, :tx_auth_no
7
7
 
@@ -12,9 +12,8 @@ module SagePay
12
12
  validates_length_of :vps_tx_id, :is => 38
13
13
  validates_length_of :security_key, :is => 10
14
14
 
15
-
16
- 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)"
17
- 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)"
15
+ validates :amount, :numericality => {:message => "is less than the minimum value (0.01)", :greater_than_or_equal_to => BigDecimal.new("0.01")}
16
+ validates :amount, :numericality => {:message => "is greater than the maximum value (100,000.00)", :less_than_or_equal_to => BigDecimal.new("100000")}
18
17
 
19
18
  def post_params
20
19
  super.merge({
@@ -11,8 +11,8 @@ module SagePay
11
11
  validates_length_of :currency, :is => 3
12
12
  validates_length_of :description, :maximum => 100
13
13
 
14
- 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)"
15
- 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)"
14
+ validates :amount, :numericality => {:message => "is less than the minimum value (0.01)", :greater_than_or_equal_to => BigDecimal.new("0.01")}
15
+ validates :amount, :numericality => {:message => "is greater than the maximum value (100,000.00)", :less_than_or_equal_toi => BigDecimal.new("100000")}
16
16
 
17
17
  def post_params
18
18
  params = super.merge({