experian 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 73a404dfa3f7928172bb01a332a7d7acd008963a
4
+ data.tar.gz: 1cbe21e733ba63c7be1e096f726b1805a48d44ae
5
+ SHA512:
6
+ metadata.gz: e4c01acf12f4073d200c2c9b8613aa1ad46536dcf9ee44a31fed93d8a52f797f79812bf832210db0b97107f8877581488112bdf15bb1f10fd1d9000ee773b769
7
+ data.tar.gz: 4fa06a3a87bbec3a8736d546693c4b20da618d4dd71e4eff41a9e7969617c42051aa8c8154b95aa09ad64c7ddfb05374473d491ba6b8c72a8c73374c3402695a
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rake'
4
+
5
+ group :development do
6
+ gem 'pry'
7
+ end
8
+
9
+ group :test do
10
+ gem 'minitest'
11
+ gem 'webmock'
12
+ gem 'mocha', require: false
13
+ gem 'timecop'
14
+ end
15
+
16
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Eric Hutzelman
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,157 @@
1
+ # Experian Ruby Gem
2
+
3
+ Ruby Wrapper for portions of the Experian Net Connect API. Experian exposes nearly 30 different services through the Net Connect API.
4
+ This gem currently only implements the Connect Check product (consumer credit scoring and identity validation), although
5
+ expanding it to support the other products should be straightforward.
6
+
7
+ *Net Connect is a business-to-business application gateway designed to allow access to Experian legacy systems via the public
8
+ Internet or Experian’s private TCP/IP extranet transport. It is a secure 168-bit encrypted transaction, using HTTPS.
9
+ Net Connect is a non-browser-based system requiring Experian certified client or vendor software at the user's location.
10
+ It utilizes XML for the input inquiry and has the capability of returning field-level XML, as well as our standard Automated
11
+ Response Format (ARF) (computer readable), Teletype Response Format (TTY) (human readable) and Parallel Profile
12
+ (both ARF and TTY in one response). Net Connect meets the encryption standards requirement in the Safeguards section of the
13
+ Gramm- Leach-Bliley (GLB) Act.*
14
+
15
+ #### Net Connect Products
16
+
17
+ * Address Update
18
+ * Authentication Services
19
+ * BizID
20
+ * Bullseye
21
+ * Checkpoint - File One Verification Solution
22
+ * Collection Advantage interactive
23
+ * Collection Report
24
+ * **Connect Check (implemented in this gem)**
25
+ * Credit Profile
26
+ * Custom Solution
27
+ * Cross View
28
+ * CU Decision Expert
29
+ * Custom Strategist
30
+ * Decode
31
+ * Demographics
32
+ * Direct Check
33
+ * Employment Insight
34
+ * Fraud Shield
35
+ * Instant Prescreen
36
+ * New Consumer Identification
37
+ * Numeric Inquiry
38
+ * Parallel Profile
39
+ * Profile Summary
40
+ * Precise ID
41
+ * Precise ID Distributed
42
+ * Risk Models
43
+ * Social Search
44
+ * Truvue
45
+
46
+ ## Installation
47
+
48
+ Add this line to your application's Gemfile:
49
+
50
+ gem 'experian'
51
+
52
+ And then execute:
53
+
54
+ $ bundle
55
+
56
+ Or install it yourself as:
57
+
58
+ $ gem install experian
59
+
60
+ ## Usage
61
+
62
+ ### Configuration
63
+
64
+ Experian will provide you with the following authentication credentials when you sign up for their service:
65
+ ```ruby
66
+ # Provide authentication credentials
67
+ Experian.configure do |config|
68
+ config.eai = "X42PB93F"
69
+ config.preamble = "FCD2"
70
+ config.op_initials = "AB"
71
+ config.subcode = "1968543"
72
+ config.user = "user"
73
+ config.password = "password"
74
+ config.vendor_number = "P55"
75
+ end
76
+
77
+ # Route requests to Experian test server instead of production
78
+ Experian.test_mode = true
79
+
80
+ ```
81
+
82
+ ### Using a product client
83
+
84
+ Products are namespaced under the Experian module. Example of how to create a client for the Connect Check product:
85
+ ```ruby
86
+ client = Experian::ConnectCheck::Client.new
87
+ ```
88
+
89
+ Once you have a client, you can make requests:
90
+ ```ruby
91
+ response = client.check_credit(first_name: "Homer", last_name: "Simpson", ssn: "123456789")
92
+
93
+ response.success?
94
+ # => true
95
+ response.completion_message
96
+ # => "Request processed successfully"
97
+ response.credit_match_code
98
+ # => "C"
99
+ response.credit_match_code_message
100
+ # => "ID Match"
101
+ response.credit_score
102
+ # => 846
103
+ response.customer_message
104
+ # => "Credit and ID have been verified and no deposit is required."
105
+ response.customer_names
106
+ # => ["HOMER J SIMPSON", "HOMER SIMPSON", "H SIMPSON", "PLOW KING"]
107
+ response.customer_addresses
108
+ # => ["96 JAMESTOWN BLVD /HAMMONTON NJ 080372110",
109
+ # => "1466 BALLY BUNION DR /EGG HARBOR CITY NJ 082155118",
110
+ # => "100 WEST LN /HAMMONTON NJ 080371151"]
111
+ ```
112
+
113
+ Alternatively, you can skip the explicit client instantiation and use the module level convenience method instead:
114
+ ```ruby
115
+ response = Experian::ConnectCheck.check_credit(...)
116
+ ```
117
+
118
+
119
+ ### Handling errors from Experian
120
+ ```ruby
121
+ response = client.check_credit(first_name: "Homer", last_name: "Simpson", ssn: "NaN")
122
+
123
+ response.success?
124
+ # => false
125
+ response.error_message
126
+ # => "Invalid request format"
127
+ response.error_action_indicator_message
128
+ # => "Correct and/or resubmit"
129
+ ```
130
+
131
+ ### Examine raw request and response XML
132
+ If you need to troubleshoot by viewing the raw xml, it is accesssible on the request and response objects of the client:
133
+ ```ruby
134
+ # Inspect the request XML that was sent to Experian
135
+ client.request.xml
136
+ # => "<?xml version='1.0' encoding='utf-8'?>..."
137
+
138
+ # Inspect the response XML that was received from Experian
139
+ client.response.xml
140
+ # => "<?xml version='1.0' encoding='utf-8'?>..."
141
+ ```
142
+
143
+
144
+ ## Contributing
145
+
146
+ 1. Fork it
147
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
148
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
149
+ 4. Push to the branch (`git push origin my-new-feature`)
150
+ 5. Create new Pull Request
151
+
152
+ ## Copyright
153
+
154
+ Copyright (c) 2012-2013 Eric Hutzelman.
155
+ See [LICENSE][] for details.
156
+
157
+ [license]: LICENSE.txt
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = "test/**/*_test.rb"
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'experian/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "experian"
8
+ spec.version = Experian::VERSION
9
+ spec.authors = ["Eric Hutzelman"]
10
+ spec.email = ["ehutzelman@gmail.com"]
11
+ spec.description = "Ruby gem wrapper for the Experian net connect API."
12
+ spec.summary = "Ruby gem wrapper for the Experian net connect API."
13
+ spec.homepage = "http://github.com/ehutzelman/experian"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "excon"
22
+ spec.add_dependency "builder"
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ end
@@ -0,0 +1,73 @@
1
+ require "excon"
2
+ require "builder"
3
+ require "experian/version"
4
+ require "experian/constants"
5
+ require "experian/error"
6
+ require "experian/client"
7
+ require "experian/request"
8
+ require "experian/response"
9
+ require "experian/connect_check"
10
+
11
+ module Experian
12
+ include Experian::Constants
13
+
14
+ class << self
15
+
16
+ attr_accessor :eai, :preamble, :op_initials, :subcode, :user, :password, :vendor_number
17
+ attr_accessor :test_mode
18
+
19
+ def configure
20
+ yield self
21
+ end
22
+
23
+ def test_mode?
24
+ !!test_mode
25
+ end
26
+
27
+ def ecals_uri
28
+ uri = URI(Experian::LOOKUP_SERVLET_URL)
29
+ uri.query = URI.encode_www_form(
30
+ 'lookupServiceName' => Experian::LOOKUP_SERVICE_NAME,
31
+ 'lookupServiceVersion' => Experian::LOOKUP_SERVICE_VERSION,
32
+ 'serviceName' => service_name,
33
+ 'serviceVersion' => Experian::SERVICE_VERSION,
34
+ 'responseType' => 'text/plain'
35
+ )
36
+ uri
37
+ end
38
+
39
+ def net_connect_uri
40
+ perform_ecals_lookup if ecals_lookup_required?
41
+
42
+ # setup basic authentication
43
+ @net_connect_uri.user = Experian.user
44
+ @net_connect_uri.password = Experian.password
45
+
46
+ @net_connect_uri
47
+ end
48
+
49
+ def perform_ecals_lookup
50
+ @net_connect_uri = URI.parse(Excon.get(ecals_uri.to_s).body)
51
+ assert_experian_domain
52
+ @ecals_last_update = Time.now
53
+ rescue Excon::Errors::SocketError => e
54
+ raise Experian::ClientError, "Could not connect to Experian: #{e.message}"
55
+ end
56
+
57
+ def ecals_lookup_required?
58
+ @net_connect_uri.nil? || @ecals_last_update.nil? || Time.now - @ecals_last_update > Experian::ECALS_TIMEOUT
59
+ end
60
+
61
+ def assert_experian_domain
62
+ unless @net_connect_uri.host.end_with?('.experian.com')
63
+ @net_connect_uri = nil
64
+ raise Experian::ClientError, "Could not authenticate connection to Experian, unexpected host name."
65
+ end
66
+ end
67
+
68
+ def service_name
69
+ test_mode? ? Experian::SERVICE_NAME_TEST : Experian::SERVICE_NAME
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,29 @@
1
+ module Experian
2
+ class Client
3
+
4
+ attr_reader :request, :response
5
+
6
+ def submit_request
7
+ connection = Excon.new(Experian.net_connect_uri.to_s, idempotent: true)
8
+ @raw_response = connection.post(body: request_body, headers: request_headers)
9
+ raise Experian::Forbidden, "Invalid Experian login credentials" if invalid_login?
10
+ @raw_response.body
11
+
12
+ rescue Excon::Errors::SocketError => e
13
+ raise Experian::ClientError, "Could not connect to Experian: #{e.message}"
14
+ end
15
+
16
+ def request_body
17
+ URI.encode_www_form('NETCONNECT_TRANSACTION' => request.xml)
18
+ end
19
+
20
+ def request_headers
21
+ { "Content-Type" => "application/x-www-form-urlencoded" }
22
+ end
23
+
24
+ def invalid_login?
25
+ !!(@raw_response.headers["Location"] =~ /sso_logon/)
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require 'experian/connect_check/client'
2
+ require 'experian/connect_check/request'
3
+ require 'experian/connect_check/response'
4
+
5
+ module Experian
6
+ module ConnectCheck
7
+
8
+ MATCH_CODES = {
9
+ "A" => "Deceased/Non-Issued Social Security Number",
10
+ "B" => "No Record Found",
11
+ "C" => "ID Match",
12
+ "D" => "ID Match to Other Name",
13
+ "E" => "ID No Match"
14
+ }
15
+
16
+ DB_HOST = "CIS"
17
+ DB_HOST_TEST = "STAR"
18
+
19
+ def self.db_host
20
+ Experian.test_mode? ? DB_HOST_TEST : DB_HOST
21
+ end
22
+
23
+ # convenience method
24
+ def self.check_credit(options = {})
25
+ Client.new.check_credit(options)
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module Experian
2
+ module ConnectCheck
3
+ class Client < Experian::Client
4
+
5
+ def check_credit(options = {})
6
+ assert_check_credit_options(options)
7
+ @request = Request.new(options)
8
+ @response = Response.new(submit_request)
9
+ end
10
+
11
+ def assert_check_credit_options(options)
12
+ return if options[:first_name] && options[:last_name] && options[:ssn]
13
+ return if options[:first_name] && options[:last_name] && options[:street] && options[:zip]
14
+ raise Experian::ArgumentError, "Required options missing: first_name, last_name, ssn OR first_name, last_name, street, zip"
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,116 @@
1
+ module Experian
2
+ module ConnectCheck
3
+ class Request < Experian::Request
4
+
5
+ def build_request
6
+ super do |xml|
7
+ xml.tag!('EAI', Experian.eai)
8
+ xml.tag!('DBHost', ConnectCheck.db_host)
9
+ add_reference_id(xml)
10
+ xml.tag!('Request', :xmlns => Experian::XML_REQUEST_NAMESPACE, :version => '1.0') do
11
+ xml.tag!('Products') do
12
+ xml.tag!('ConnectCheck') do
13
+ add_subscriber(xml)
14
+ add_applicant(xml)
15
+ add_output_type(xml)
16
+ add_vendor(xml)
17
+ add_options(xml)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def add_reference_id(xml)
25
+ xml.tag!('ReferenceId', @options[:reference_id]) if @options[:reference_id]
26
+ end
27
+
28
+ def add_subscriber(xml)
29
+ xml.tag!('Subscriber') do
30
+ xml.tag!('Preamble', Experian.preamble)
31
+ xml.tag!('OpInitials', Experian.op_initials)
32
+ xml.tag!('SubCode', Experian.subcode)
33
+ end
34
+ end
35
+
36
+ def add_applicant(xml)
37
+ xml.tag!('PrimaryApplicant') do
38
+ xml.tag!('Name') do
39
+ xml.tag!('Surname', @options[:last_name])
40
+ xml.tag!('First', @options[:first_name])
41
+ xml.tag!('Middle', @options[:middle_name]) if @options[:middle_name]
42
+ xml.tag!('Gen', @options[:generation_code]) if @options[:generation_code]
43
+ end
44
+ xml.tag!('SSN', @options[:ssn]) if @options[:ssn]
45
+ add_current_address(xml)
46
+ add_previous_address(xml)
47
+ add_driver_license(xml)
48
+ add_employment(xml)
49
+ xml.tag!('Age', @options[:age]) if @options[:age]
50
+ xml.tag!('DOB', @options[:dob]) if @options[:dob]
51
+ xml.tag!('YOB', @options[:yob]) if @options[:yob]
52
+ end
53
+ end
54
+
55
+ def add_current_address(xml)
56
+ xml.tag!('CurrentAddress') do
57
+ xml.tag!('Street', @options[:street])
58
+ xml.tag!('City', @options[:city])
59
+ xml.tag!('State', @options[:state])
60
+ xml.tag!('Zip', @options[:zip])
61
+ end if @options[:zip]
62
+ end
63
+
64
+ def add_previous_address(xml)
65
+ xml.tag!('PreviousAddress') do
66
+ xml.tag!('Street', @options[:previous_street])
67
+ xml.tag!('City', @options[:previous_city])
68
+ xml.tag!('State', @options[:previous_state])
69
+ xml.tag!('Zip', @options[:previous_zip])
70
+ end if @options[:previous_zip]
71
+ end
72
+
73
+ def add_driver_license(xml)
74
+ xml.tag!('DriverLicense') do
75
+ xml.tag!('State', @options[:driver_license_state])
76
+ xml.tag!('Number', @options[:driver_license_number])
77
+ end if @options[:driver_license_number]
78
+ end
79
+
80
+ def add_employment(xml)
81
+ # Not Implemented
82
+ end
83
+
84
+ def add_account_type(xml)
85
+ xml.tag!('AccountType') do
86
+ xml.tag!('Type', @options[:account_type])
87
+ xml.tag!('Terms', @options[:account_terms])
88
+ xml.tag!('FullAmount', @options[:account_full_amount])
89
+ xml.tag!('AbbreviatedAmount', @options[:account_abbreviated_amount])
90
+ end if @options[:account_type]
91
+ end
92
+
93
+ def add_output_type(xml)
94
+ xml.tag!('OutputType') do
95
+ xml.tag!('ARF') do
96
+ xml.tag!('ARFVersion', Experian::ARF_VERSION)
97
+ end
98
+ end
99
+ end
100
+
101
+ def add_vendor(xml)
102
+ xml.tag!('Vendor') do
103
+ xml.tag!('VendorNumber', Experian.vendor_number)
104
+ end
105
+ end
106
+
107
+ def add_options(xml)
108
+ xml.tag!('Options') do
109
+ xml.tag!('ReferenceNumber', @options[:reference_number])
110
+ xml.tag!('EndUser', @options[:end_user])
111
+ end if @options[:reference_number]
112
+ end
113
+
114
+ end
115
+ end
116
+ end