maxmind 0.1.2 → 0.4.2

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/.gitignore CHANGED
@@ -3,3 +3,6 @@
3
3
  coverage
4
4
  rdoc
5
5
  pkg
6
+ Gemfile.lock
7
+ .rvmrc
8
+ *.sublime*
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard 'rspec', :version => 2, :cli => '--color --format doc' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('lib/eventricle.rb') { "spec/eventricle.rb" }
5
+ watch('spec/spec_helper.rb') { "spec" }
6
+ end
data/README.md ADDED
@@ -0,0 +1,134 @@
1
+ maxmind
2
+ ==========
3
+
4
+ A wrapper around MaxMind's minFraud anti-fraud service.
5
+
6
+
7
+ Installation
8
+ ------------
9
+
10
+ In your Gemfile;
11
+
12
+ gem 'maxmind'
13
+
14
+ Tests
15
+ ------------
16
+
17
+ bundle install
18
+ guard
19
+
20
+ Dependencies
21
+ ------------
22
+
23
+ bundle install
24
+
25
+ Running Tests
26
+ -------------
27
+
28
+ Run `bundle install` to make sure you have all the dependencies. Once that's done, run:
29
+
30
+ rake test
31
+
32
+ Usage
33
+ -----
34
+
35
+ ### Minimum Required ###
36
+ These are the only required fields to acquire a response from MaxMind.
37
+
38
+ require 'maxmind'
39
+ Maxmind.license_key = 'LICENSE_KEY'
40
+ request = Maxmind::Request.new(
41
+ :client_ip => '24.24.24.24',
42
+ :city => 'New York',
43
+ :region => 'NY',
44
+ :postal => '11434',
45
+ :country => 'US'
46
+ )
47
+
48
+ response = request.process!
49
+
50
+
51
+ ### Recommended ###
52
+ For increased accuracy, these are the recommended fields to submit to MaxMind. The additional
53
+ fields here are optional and can be all or none.
54
+
55
+ require 'maxmind'
56
+ Maxmind.license_key = 'LICENSE_KEY'
57
+ request = Maxmind::Request.new(
58
+ :client_ip => '24.24.24.24',
59
+ :city => 'New York',
60
+ :region => 'NY',
61
+ :postal => '11434',
62
+ :country => 'US',
63
+ :domain => 'yahoo.com',
64
+ :bin => '549099',
65
+ :forwarded_ip => '24.24.24.25',
66
+ :email => 'test@test.com',
67
+ :username => 'test_carder_username',
68
+ :password => 'test_carder_password'
69
+ )
70
+
71
+ response = request.process!
72
+
73
+ ### Thorough ###
74
+ This is every field available.
75
+
76
+ require 'maxmind'
77
+ Maxmind.license_key = 'LICENSE_KEY'
78
+ request = Maxmind::Request.new(
79
+ :client_ip => '24.24.24.24',
80
+ :city => 'New York',
81
+ :region => 'NY',
82
+ :postal => '11434',
83
+ :country => 'US',
84
+ :domain => 'yahoo.com',
85
+ :bin => '549099',
86
+ :forwarded_ip => '24.24.24.25',
87
+ :email => 'test@test.com',
88
+ :username => 'test_carder_username',
89
+ :password => 'test_carder_password'
90
+ :bin_name => 'MBNA America Bank',
91
+ :bin_phone => '800-421-2110',
92
+ :cust_phone => '212-242',
93
+ :request_type => 'premium',
94
+ :shipping_address => '145-50 157th Street',
95
+ :shipping_city => 'Jamaica',
96
+ :shipping_region => 'NY',
97
+ :shipping_postal => '11434',
98
+ :shipping_country => 'US',
99
+ :transaction_id => '1234',
100
+ :session_id => 'abcd9876',
101
+ :user_agent => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_5; en-us) AppleWebKit/525.18 (KHTML, like Gecko) Version/3.1.2 Safari/525.20.1',
102
+ :accept_language => 'en-us'
103
+ )
104
+
105
+ response = request.process!
106
+
107
+
108
+ Also see examples/example.rb
109
+
110
+ TODO
111
+ ----
112
+ * Improve specs (eg, test server failover)
113
+
114
+ Reference
115
+ ---------
116
+ [minFraud API Reference](http://www.maxmind.com/app/ccv)
117
+
118
+ Contributors
119
+ ------------
120
+ Sam Oliver <sam@samoliver.com>
121
+ Nick Wilson <nick@di.fm>
122
+ Wolfram Arnold <wolfram@rubyfocus.biz>
123
+ Jonathan Lim <snowblink@gmail.com>
124
+ Tom Blomfield
125
+ Thomas Morgan
126
+ Tinu Cleatus <tinu.cleatus@me.com>
127
+
128
+ Thanks to all :)
129
+
130
+ Copyright
131
+ ---------
132
+ Copyright (c) 2009 Adam Daniels <adam@mediadrive.ca>.
133
+
134
+ See LICENSE for details.
data/Rakefile CHANGED
@@ -1,57 +1,20 @@
1
- require 'rubygems'
2
- require 'rake'
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
3
 
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "maxmind"
8
- gem.summary = %Q{TODO}
9
- gem.email = "adam@mediadrive.ca"
10
- gem.homepage = "http://github.com/adam12/maxmind"
11
- gem.authors = ["Adam Daniels"]
12
- gem.add_dependency 'httparty'
13
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
- end
15
-
16
- rescue LoadError
17
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
- end
19
-
20
- require 'rake/testtask'
21
- Rake::TestTask.new(:test) do |test|
22
- test.libs << 'lib' << 'test'
23
- test.pattern = 'test/**/*_test.rb'
24
- test.verbose = true
4
+ desc "Run the test suite"
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.pattern = FileList['spec/**/*_spec.rb']
7
+ t.rspec_opts = %w(--color --format doc)
25
8
  end
26
9
 
27
- begin
28
- require 'rcov/rcovtask'
29
- Rcov::RcovTask.new do |test|
30
- test.libs << 'test'
31
- test.pattern = 'test/**/*_test.rb'
32
- test.verbose = true
33
- end
34
- rescue LoadError
35
- task :rcov do
36
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
- end
38
- end
10
+ task :default => :spec
39
11
 
40
-
41
- task :default => :test
42
-
43
- require 'rake/rdoctask'
12
+ require 'rdoc/task'
44
13
  Rake::RDocTask.new do |rdoc|
45
- if File.exist?('VERSION.yml')
46
- config = YAML.load(File.read('VERSION.yml'))
47
- version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
48
- else
49
- version = ""
50
- end
14
+ version = File.read 'VERSION' rescue nil
51
15
 
52
16
  rdoc.rdoc_dir = 'rdoc'
53
17
  rdoc.title = "maxmind #{version}"
54
18
  rdoc.rdoc_files.include('README*')
55
19
  rdoc.rdoc_files.include('lib/**/*.rb')
56
20
  end
57
-
data/examples/example.rb CHANGED
@@ -22,7 +22,7 @@ optional_fields = {
22
22
  :bin_name => 'MBNA America Bank',
23
23
  :bin_phone => '800-421-2110',
24
24
  :cust_phone => '212-242',
25
- :requested_type => 'premium',
25
+ :request_type => 'premium',
26
26
  :shipping_address => '145-50 157th Street',
27
27
  :shipping_city => 'Jamaica',
28
28
  :shipping_region => 'NY',
@@ -34,6 +34,8 @@ optional_fields = {
34
34
  :accept_language => 'en-us'
35
35
  }
36
36
 
37
- request = Maxmind::Request.new('LICENSE_KEY', required_fields.merge(recommended_fields).merge(optional_fields))
38
- response = Maxmind::Response.new(request.query)
39
- pp response
37
+ Maxmind.license_key = 'LICENSE_KEY'
38
+ Maxmind::Request.default_request_type = 'standard'
39
+ request = Maxmind::Request.new(required_fields.merge(recommended_fields).merge(optional_fields))
40
+ response = request.process!
41
+ pp response
data/lib/maxmind.rb CHANGED
@@ -1,6 +1,11 @@
1
- require 'rubygems'
2
- gem 'httparty'
3
- require 'httparty'
1
+ require 'active_support'
2
+ unless Module.respond_to?(:mattr_accessor)
3
+ require 'active_support/core_ext/module/attribute_accessors'# rescue nil # this may be needed for ActiveSupport versions >= 3.x
4
+ end
5
+ require 'active_support/core_ext/hash'
6
+ require 'net/http'
7
+ require 'net/https'
8
+ require 'uri'
4
9
  require 'digest/md5'
5
10
  require File.join(File.dirname(__FILE__), 'maxmind/request')
6
11
  require File.join(File.dirname(__FILE__), 'maxmind/response')
@@ -1,41 +1,107 @@
1
1
  module Maxmind
2
+ # Your license key
3
+ class << self
4
+ attr_accessor :license_key
5
+ end
6
+
7
+ SERVERS = %w(minfraud.maxmind.com minfraud-us-east.maxmind.com minfraud-us-west.maxmind.com)
8
+
2
9
  class Request
10
+ DefaultTimeout = 60
11
+
12
+ # optionally set a default request type (one of 'standard' or 'premium')
13
+ # Maxmind's default behavior is to use premium if you have credits, else use standard
14
+ class << self
15
+ attr_accessor :default_request_type
16
+ attr_accessor :timeout
17
+ end
18
+
19
+
3
20
  # Required Fields
4
- attr_accessor :client_ip, :city, :region, :postal, :country, :license_key
5
-
21
+ attr_accessor :client_ip, :city, :region, :postal, :country
22
+
6
23
  # Optional Fields
7
- attr_accessor :domain, :bin, :bin_name, :bin_phone, :cust_phone, :requested_type,
24
+ attr_accessor :domain, :bin, :bin_name, :bin_phone, :cust_phone, :request_type,
8
25
  :forwarded_ip, :email, :username, :password, :transaction_id, :session_id,
9
26
  :shipping_address, :shipping_city, :shipping_region, :shipping_postal,
10
- :shipping_country, :user_agent, :accept_language
11
-
12
- def initialize(license_key, options = {})
13
- @license_key = license_key
14
-
15
- options.each do |k, v|
16
- self.instance_variable_set("@#{k}", v)
27
+ :shipping_country, :user_agent, :accept_language, :order_amount,
28
+ :order_currency
29
+
30
+ def initialize(attrs={})
31
+ self.attributes = attrs
32
+ end
33
+
34
+
35
+ def attributes=(attrs={})
36
+ attrs.each do |k, v|
37
+ self.send("#{k}=", v)
17
38
  end
18
39
  end
19
-
20
- def query(string = false)
40
+
41
+
42
+ # email domain ... if a full email is provided, take just the domain portion
43
+ def domain=(email)
44
+ @domain = if email =~ /@(.+)/
45
+ $1
46
+ else
47
+ email
48
+ end
49
+ end
50
+
51
+ # customer email ... sends just an MD5 hash of the email.
52
+ # also sets the email domain at the same time.
53
+ def email=(email)
54
+ @email = Digest::MD5.hexdigest(email.downcase)
55
+ self.domain = email unless domain
56
+ end
57
+
58
+ def username=(username)
59
+ @username = Digest::MD5.hexdigest(username.downcase)
60
+ end
61
+
62
+ def password=(password)
63
+ @password = Digest::MD5.hexdigest(password.downcase)
64
+ end
65
+
66
+ # if a full card number is passed, grab just the first 6 digits (which is the bank id number)
67
+ def bin=(bin)
68
+ @bin = bin ? bin[0,6] : nil
69
+ end
70
+
71
+
72
+ def process!
73
+ resp = post(query)
74
+ Maxmind::Response.new(resp)
75
+ end
76
+
77
+ def process
78
+ process!
79
+ rescue Exception => e
80
+ false
81
+ end
82
+
83
+
84
+ private
85
+
86
+ def query
21
87
  validate
22
-
88
+
23
89
  required_fields = {
24
90
  :i => @client_ip,
25
91
  :city => @city,
26
92
  :region => @region,
27
93
  :postal => @postal,
28
94
  :country => @country,
29
- :license_key => @license_key
95
+ :license_key => Maxmind::license_key
30
96
  }
31
-
97
+
32
98
  optional_fields = {
33
99
  :domain => @domain,
34
100
  :bin => @bin,
35
101
  :binName => @bin_name,
36
102
  :binPhone => @bin_phone,
37
103
  :custPhone => @cust_phone,
38
- :requested_type => @requested_type,
104
+ :requested_type => @request_type || self.class.default_request_type,
39
105
  :forwardedIP => @forwarded_ip,
40
106
  :emailMD5 => @email,
41
107
  :usernameMD5 => @username,
@@ -50,40 +116,45 @@ module Maxmind
50
116
  :user_agent => @user_agent,
51
117
  :accept_language => @accept_langage
52
118
  }
53
-
54
- query = required_fields.merge(optional_fields)
55
- if string == false
56
- return get(query.reject {|k, v| v.nil? })
57
- else
58
- return query.reject {|k, v| v.nil? }.to_params
59
- end
119
+
120
+ field_set = required_fields.merge(optional_fields)
121
+ field_set.reject {|k, v| v.nil? }
60
122
  end
61
-
62
- def get(query)
63
- response = HTTParty.get('http://minfraud3.maxmind.com/app/ccv2r', :query => query)
64
- return response.body
65
- end
66
-
67
- def email=(email)
68
- @email = Digest::MD5.hexdigest(email.downcase)
69
- end
70
-
71
- def username=(username)
72
- @username = Digest::MD5.hexdigest(username.downcase)
73
- end
74
-
75
- def password=(password)
76
- @password = Digest::MD5.hexdigest(password.downcase)
123
+
124
+ # Upon a failure at the first URL, will automatically retry with the
125
+ # second & third ones before finally raising an exception
126
+ def post(query_params)
127
+ servers ||= SERVERS.map{|hostname| "https://#{hostname}/app/ccv2r"}
128
+ url = URI.parse(servers.shift)
129
+
130
+ req = Net::HTTP::Post.new(url.path)
131
+ req.set_form_data(query_params)
132
+
133
+ h = Net::HTTP.new(url.host, url.port)
134
+ h.use_ssl = true
135
+ h.verify_mode = OpenSSL::SSL::VERIFY_NONE
136
+
137
+ # set some timeouts
138
+ h.open_timeout = 60 # this blocks forever by default, lets be a bit less crazy.
139
+ h.read_timeout = self.class.timeout || DefaultTimeout
140
+ h.ssl_timeout = self.class.timeout || DefaultTimeout
141
+
142
+ response = h.start { |http| http.request(req) }
143
+ response.body
144
+
145
+ rescue Exception => e
146
+ retry if servers.size > 0
147
+ raise e
77
148
  end
78
149
 
79
150
  protected
80
151
  def validate
81
- raise ArgumentError, 'license key required' if @license_key.nil?
82
- raise ArgumentError, 'IP address required' if @client_ip.nil?
83
- raise ArgumentError, 'city required' if @city.nil?
84
- raise ArgumentError, 'region required' if @region.nil?
85
- raise ArgumentError, 'postal code required' if @postal.nil?
86
- raise ArgumentError, 'country required' if @country.nil?
152
+ raise ArgumentError, 'License key is required' unless Maxmind::license_key
153
+ raise ArgumentError, 'IP address is required' unless client_ip
154
+ raise ArgumentError, 'City is required' unless city
155
+ raise ArgumentError, 'Region is required' unless region
156
+ raise ArgumentError, 'Postal code is required' unless postal
157
+ raise ArgumentError, 'Country is required' unless country
87
158
  end
88
159
  end
89
- end
160
+ end