trufina 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ rdoc/
@@ -0,0 +1,16 @@
1
+ 0.2.4 [September 07, 2009]
2
+ * Switching from jimmyz-happymapper to kdonovan-happymapper, which includes ability to Marshal happymapped objects
3
+
4
+ 0.2.3 [September 07, 2009]
5
+ * Added a to_s method for Trufina::Elements::Name and Trufina::Elements::ResidenceAddressResponse
6
+
7
+ 0.2.2 [September 04, 2009]
8
+ * Fixed bug in present_and_verified where it choked handling arrays
9
+ * API Change: modified present_and_verified return value to be more direct (nested hashes rather than alternating hashes and arrays of hashes)
10
+
11
+ 0.2.1 [September 04, 2009]
12
+ * Embedded basic auth information directly in redirect URL when in staging mode
13
+
14
+ 0.2.0 [September 04, 2009]
15
+ * Finally supporting ResidenceAddress seed and request data
16
+ * API Change: using zip rather than postal_code
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,179 @@
1
+ = Trufina
2
+
3
+ The Trufina gem provides a DSL allowing you to easily interface with {Trufina.com}[http://www.trufina.com]'s identity verification API.
4
+ Trufina[http://www.trufina.com] provides an identity verification service, and this DSL basically lets you request verified information
5
+ from the user (who provides that information directly to Trufina[http://www.trufina.com], and then uses their website to control
6
+ permissions of who can access what parts of their personal data).
7
+
8
+ == Requirements
9
+
10
+ Before you begin you'll need to fill out some paperwork with Trufina[http://www.trufina.com] and, after a bit of administrative mucking around,
11
+ you'll be given two important identifiers: a PID (Partner ID) and a PAK (Partner Authentication Key). Place these in
12
+ your +config/trufina.yml+ file, replace the other defaults, and you'll be good to go.
13
+
14
+ == Installation
15
+
16
+ Getting the code on your system is as simple as
17
+
18
+ script/plugin install git://github.com/kdonovan/trufina.git
19
+
20
+ or
21
+
22
+ gem sources -a http://gems.github.com
23
+ sudo gem install kdonovan-trufina
24
+
25
+ # Add this to environment.rb
26
+ config.gem 'kdonovan-trufina', :lib => 'trufina', :source => "http://gems.github.com"
27
+
28
+
29
+ Once you have the code, you'll need to create a +trufina.yml+ file in your project's config directory. If you don't
30
+ do this by hand, a template file will be created automatically the first time you load your application after installation.
31
+ Trufina will raise a ConfigFileError until you fill out the config file with meaningful data.
32
+
33
+
34
+ == Examples
35
+
36
+ Once installation has been completed, using the code itself is really easy -- the most complicated step is understanding
37
+ Trufina[http://www.trufina.com]'s various flows. We'll walk through an example:
38
+
39
+ # We'll skip it here for verbosity, but note that you can turn on debugging
40
+ # to print out pretty versions of any XML sent or received.
41
+ # Trufina::Config.debug = true
42
+
43
+ Say we have a user in our database for whom we want verified information. The first step is to
44
+ establish a session key with Trufina[http://www.trufina.com] so we can associate later responses with this request. This
45
+ is done by sending them a PRT (Partner Request Token), which can be any arbitrary value. In a real app
46
+ I'd probably use a user-id + timestamp combination, but for this demo we'll use the {random number}[http://xkcd.com/221/] 4.
47
+
48
+ Note that you can also specify what data you want access to (name, address, country of birth, etcetera),
49
+ as well as any default values you may already know to prefill the form on the Trufina[http://www.trufina.com] website. See
50
+ Trufina.login_url or Trufina.login_request for more details, but the default is to request the user's first and last name.
51
+
52
+ Trufina.login_url(4, :demo => true) # => http://staging.trufina.com/DemoPartnerLogin/DemoLogin/6ZEeENWWD8@K
53
+
54
+
55
+ You can now visit this URL to create an account for a fake user on Trufina[http://www.trufina.com]'s demo server. When you're done,
56
+ you'll be redirected to whatever you put as your success endpoint in +trufina.yml+, and there will be a TLID
57
+ (Temporary Login ID) appended. In my case it was 870. You have 15 minutes to use this TLID to access the
58
+ information about the user Trufina[http://www.trufina.com] has verified.
59
+
60
+ info = Trufina.login_info_request(870)
61
+ info.data.present_and_verified # => {:name=>{:first=>"FIRSTNAME"}, {:last=>"LASTNAME"}} (or whatever names you entered on the staging server)
62
+
63
+
64
+ That completes the simplest use-case. Say we decide we want more information about the user, like their middle
65
+ name. We send Trufina[http://www.trufina.com] an access request, and if the user has already provided the information and given us
66
+ permission we'll get the info back immediately.
67
+
68
+ new_info = Trufina.access_request(info, {:name => [:middle]})
69
+ new_info.data.present_and_verified # => {:name=>{:middle=>"SomeMiddleName"}}
70
+
71
+
72
+ Note that here our demo user has already given Trufina[http://www.trufina.com] permission to see all the name components, so we get
73
+ the middle name back immediately. For a different component where Trufina[http://www.trufina.com] needs to ask the user there's no
74
+ useful data in the response (the XML contains a "pending" node, but the gem doesn't bother parsing it out).
75
+
76
+ Trufina.access_request(info, [:phone]).data.present_and_verified # => {}
77
+
78
+
79
+ In this case we would receive an AccessNotification to our servers once the user gave us permission to access
80
+ the requested data, and at that point we'd be able to re-request the data with another Trufina.access_request
81
+ call, for which the newly requested data would show up like the middle name did above.
82
+
83
+
84
+ == Advanced Topics
85
+
86
+ === Seed Info
87
+
88
+ Trufina.login_request (and therefore Trufina.login_url, which is just a wrapper) allows you to pass along seed
89
+ data used to prepopulate the fields the user will encounter on Trufina.com (see Trufina::Elements::SeedInfoGroup
90
+ for all options). Prepopulated data will be specified as the value of a :seed key in the options hash of either
91
+ method. Example:
92
+
93
+ Trufina.login_request(4, :seed => {:name => {:first => 'Foo', :last => 'Bar'}})
94
+
95
+ === Request Data
96
+
97
+ A number of the API calls allow you to supply a list of the data you'd like returned about the user in question.
98
+ You may do this as follows:
99
+
100
+ Trufina.login_request(4, :requested => [:age, {:name => [:first, :middle, :last]}])
101
+
102
+ or
103
+
104
+ Trufina.access_request({:pur => 4, :prt => 4}, [:age, {:name => [:first, :middle, :last]}])
105
+
106
+ Note that an array item creates an empty node (e.g. <age/>) unless it's a hash, in which case it creates a node named from the hash key containing and recurses into the hash value inside the node. That is to say,
107
+
108
+ :name => [:first, :middle, :last]
109
+
110
+ will generate something like:
111
+
112
+ <name>
113
+ <first/>
114
+ <middle/>
115
+ <last/>
116
+ </name>
117
+
118
+ which is exactly the format Trufina requires to indicate we want to receive the first and middle names of the specified user. Note that the gem does some additional work to hide irregularities of the Trufina API from the user (i.e. you), so the above code really renders:
119
+
120
+ <Name>
121
+ <First/>
122
+ <MiddleName/>
123
+ <Surname/>
124
+ </Name>
125
+
126
+ (meaning you don't have to remember that Trufina randomly uses Surname, and breaks their convention by using MiddleName rather than simply Middle).
127
+
128
+ == Rails Integration
129
+
130
+ Personally, I set up a controller dedicated to handling Trufina's postbacks (Success, Failure, and Cancel) as well as their AccessNotification. If (as I'd suggest) you use the +current_user+'s ID as your PRT, then when a user wants to sign up with Trufina:
131
+
132
+ redirect_url = Trufina.login_url(current_user.id, :requested => REQUESTED_DATA, :seed => SEED_DATA)
133
+
134
+
135
+
136
+ The biggest gotcha is that Trufina only uses the PRT for the login_info_request / login_info_response messages -- after that they use their own identifier (the PUR) to indicate which user a given response refers to, so you'll need to store the PUR provided in the login_info_request somewhere and use that going forward. Your success action will end up looking something like this:
137
+
138
+ def success
139
+ info = Trufina.login_info_request( params[:tlid )
140
+ user = User.find(info.prt)
141
+ user.pur = info.pur
142
+ ...
143
+ end
144
+
145
+
146
+ Also, note that an AccessNotification will be sent immediately upon a new user's registration with Trufina. You can just ignore any AccessNotification with an unknown PUR value, or it's also possible to use the Trufina.info_request in response to this first AccessNotification and avoid using the login_info_response altogether.
147
+
148
+ Once you've verified the user, you just need to handle any AccessNotifications you may receive:
149
+
150
+ def handle_access_notification
151
+ tnid = params[:trufina_access_notification][:tnid]
152
+ info = Trufina.info_request( tnid )
153
+ user = User.find_by_pur( info.pur )
154
+
155
+ # Now take appropriate actions to handle user either revoking or adding
156
+ # permissions for you to access certain subsets of their verified data.
157
+ ...
158
+ end
159
+
160
+
161
+
162
+
163
+ == Unsupported functionality
164
+
165
+ * Does not handle requesting comparisons or other request attributes
166
+ * (Note that Trufina[http://www.trufina.com] itself doesn't support maxAge or timeframe yet)
167
+
168
+ == API Version
169
+
170
+ This is compatible with the latest Trufina API version as of the creation date: Trufina API 1.0.4
171
+
172
+
173
+ == Compatibility
174
+
175
+ The goal of this module is to be relatively framework agnostic. That being said, I've personally only tried to use it
176
+ in conjunction with a Rails application. If you run into problems trying to use it elsewhere, well... patches happily
177
+ accepted. :)
178
+
179
+ Copyright (c) 2009 Kali Donovan, released under the MIT license
@@ -0,0 +1,54 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "trufina"
9
+ gem.summary = %Q{DSL to easily interact with Trufina's verification API}
10
+ gem.description = %Q{Provides a DSL to easily interact with the XML API offered by Trufina.com, an identity verification company.}
11
+ gem.email = "kali.donovan@gmail.com"
12
+ gem.homepage = "http://github.com/kdonovan/trufina"
13
+ gem.authors = ["Kali Donovan"]
14
+ gem.add_dependency "kdonovan-happymapper"
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ desc 'Default: run unit tests.'
22
+ task :default => :test
23
+
24
+ desc 'Test the trufina plugin.'
25
+ Rake::TestTask.new(:test) do |t|
26
+ t.libs << 'lib'
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = true
30
+ end
31
+
32
+ desc 'Generate documentation for the trufina plugin.'
33
+ Rake::RDocTask.new(:rdoc) do |rdoc|
34
+ rdoc.rdoc_dir = 'rdoc'
35
+ rdoc.title = 'Trufina API'
36
+ rdoc.options << '--line-numbers' << '--inline-source'
37
+ rdoc.rdoc_files.include('README.rdoc')
38
+ rdoc.rdoc_files.include('lib/**/*.rb')
39
+ rdoc.rdoc_files.exclude('init.rb')
40
+ rdoc.rdoc_files.exclude('rails/init.rb')
41
+ end
42
+
43
+ # gem 'darkfish-rdoc'
44
+ # require 'darkfish-rdoc'
45
+ #
46
+ # Rake::RDocTask.new(:darkfish) do |rdoc|
47
+ # rdoc.title = "Trufina API"
48
+ # rdoc.rdoc_files.include 'README.rdoc'
49
+ #
50
+ # rdoc.options += [
51
+ # '-SHN',
52
+ # '-f', 'darkfish', # This is the important bit
53
+ # ]
54
+ # end
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ PENDING:
2
+ * Schema validation against the provided .xsd fails
3
+
4
+ UNSUPPORTED:
5
+ * Comparison or other request attributes (note that maxAge and timeframe are not even supported by Trufina yet)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.5
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'rails', 'init')
@@ -0,0 +1,64 @@
1
+ require 'fileutils'
2
+
3
+ class Trufina
4
+ # Reads in configuration data from config/trufina.yml (and handles creating it if missing / complaining if it looks unfilled).
5
+ class Config
6
+ cattr_accessor :credentials, :staging_access, :endpoints,
7
+ :app_root, :config_file, :mode, :debug
8
+
9
+ # Allow range of config locations
10
+ self.app_root = RAILS_ROOT if defined?(RAILS_ROOT)
11
+ self.app_root = Merb.root if defined?(Merb)
12
+ self.app_root ||= app_root
13
+ self.app_root ||= Dir.pwd
14
+ self.config_file = File.join(self.app_root, 'config', 'trufina.yml')
15
+
16
+ # Symbolize hash keys - defined here so we don't rely on Rails
17
+ def self.symbolize_keys!(hash) # :nodoc:
18
+ return hash unless hash.is_a?(Hash)
19
+
20
+ hash.keys.each do |key|
21
+ unless key.is_a?(Symbol)
22
+ hash[key.to_sym] = hash[key]
23
+ hash.delete(key)
24
+ end
25
+ end
26
+ hash
27
+ end
28
+
29
+ # Ensure config exists
30
+ unless File.exists?(self.config_file)
31
+ config_template = File.join(File.dirname(__FILE__), '..', 'trufina.yml.template')
32
+ FileUtils.cp(config_template, self.config_file)
33
+ raise Exceptions::ConfigFileError.new("Unable to create configuration template at #{self.config_file}") unless File.exists?(self.config_file)
34
+ end
35
+
36
+ # Load keys from config file into the class
37
+ YAML.load(ERB.new(File.read(self.config_file)).result).each do |key, value|
38
+ self.send("#{key}=", symbolize_keys!(value)) if self.methods.include?("#{key}=")
39
+ end
40
+
41
+ # Set default mode unless already set in the config file
42
+ unless %w(production staging).include?(self.mode)
43
+ env = defined?(Merb) ? ENV['MERB_ENV'] : ENV['RAILS_ENV']
44
+ @@mode = env && env == 'production' ? 'production' : 'staging'
45
+ end
46
+
47
+ # Ensure template file has been modified with (hopefully) real data
48
+ if self.credentials.any?{|k,v| v == 'YOUR_DATA_HERE'}
49
+ raise Exceptions::ConfigFileError.new("Don't forget to update the Trufina config file with your own data! File is located at #{self.config_file}")
50
+ end
51
+
52
+ # Syntactic sugar for setting and checking the current operating mode
53
+ class << self
54
+ %w(staging production).each do |mode|
55
+ define_method("#{mode}!"){ @@mode = mode }
56
+ define_method("#{mode}?"){ @@mode == mode }
57
+ end
58
+ def debug?
59
+ !!@@debug
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,203 @@
1
+ # Contains smaller classes (essentially HappyMapper element classes) used to create and
2
+ # parse API calls and responses.
3
+
4
+ class Trufina
5
+
6
+ # Handle creating a HappyMapper object from array or hash (creating empty nodes as required).
7
+ module AllowCreationFromHash
8
+
9
+ def initialize(seed_data = {})
10
+ create_nodes(seed_data)
11
+
12
+ # Define attributes
13
+ self.class.attributes.each do |attr|
14
+ self.send("#{attr.method_name}=", nil) unless self.send(attr.method_name)
15
+ end
16
+
17
+ # Define elements
18
+ self.class.attributes.each do |elem|
19
+ self.send("#{elem.method_name}=", nil) unless self.send(elem.method_name)
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ def create_nodes(data)
26
+ data.each do |key, value|
27
+ create_node(key, value)
28
+ end
29
+ end
30
+
31
+ # Handle the actual node creation
32
+ def create_node(name, content = nil)
33
+ return create_nodes(name) if content.nil? && name.is_a?(Hash) # Handle accidentally passing in a full hash
34
+
35
+ element = self.class.elements.detect{|e| e.method_name.to_sym == name}
36
+ raise Exceptions::InvalidElement.new("No known element named '#{name}'") unless element || self.respond_to?("#{name}=")
37
+
38
+ case name
39
+ when Hash then create_nodes(name)
40
+ when Array then create_empty_nodes(name)
41
+ else
42
+ value = if content.nil?
43
+ make_object?(element) ? element.type.new : ''
44
+ elsif content.is_a?(Array) && (element && !element.options[:single])
45
+ # If elements expects multiple instances, instantiate them all (e.g. StreetAddresses)
46
+ # Note that this assumes the object (only known case is Trufina::Elements::StreetAddress) has a :name element
47
+ out = content.collect do |local_content|
48
+ make_object?(element) ? element.type.new(:name => local_content) : local_content
49
+ end
50
+ else
51
+ make_object?(element) ? element.type.new(content) : content
52
+ end
53
+
54
+ self.send("#{name}=", value)
55
+ end
56
+ end
57
+
58
+ # Returns false if the given content is a simple type like a string, and we should just assign it.
59
+ # Returns true if the given content is another HappyMapper class, and we should instantiate the class
60
+ # rather than merely assigning the value.
61
+ def make_object?(element)
62
+ element && !HappyMapper::Item::Types.include?(element.type)
63
+ end
64
+
65
+ end
66
+
67
+ module Elements
68
+ RESPONSE_XML_ATTRIBUTES = {:state => String, :age => String, :charged => String, :status => String, :errors => String }
69
+
70
+ module EasyElementAccess
71
+
72
+ # Shortcut to collecting any information that's present and available
73
+ def present_and_verified
74
+ yes = {}
75
+ self.class.elements.map(&:method_name).each do |p|
76
+ next unless val = self.send(p)
77
+ element = self.class.elements.detect{|e| e.method_name == p}
78
+
79
+ if val.respond_to?(:present_and_verified)
80
+ yes[p.to_sym] = val.present_and_verified
81
+ elsif element.options[:single]
82
+ yes[p.to_sym] = val if val.state == 'verified' && val.status == 'present'
83
+ else # street_addresses is an array...
84
+ values = []
85
+ val.each do |array_item|
86
+ values << array_item if array_item.state == 'verified' && array_item.status == 'present'
87
+ end
88
+ yes[p.to_sym] = values
89
+ end
90
+ end
91
+ yes
92
+ end
93
+
94
+ end
95
+
96
+ # Encapsulates the various name components Trufina accepts
97
+ class Name
98
+ include AllowCreationFromHash
99
+ include HappyMapper
100
+ include EasyElementAccess
101
+ tag 'Name'
102
+
103
+ element :prefix, String, :tag => 'Prefix', :attributes => RESPONSE_XML_ATTRIBUTES
104
+ element :first, String, :tag => 'First', :attributes => RESPONSE_XML_ATTRIBUTES
105
+ element :middle, String, :tag => 'MiddleName', :attributes => RESPONSE_XML_ATTRIBUTES
106
+ element :middle_initial, String, :tag => 'MiddleInitial', :attributes => RESPONSE_XML_ATTRIBUTES
107
+ element :last, String, :tag => 'Surname', :attributes => RESPONSE_XML_ATTRIBUTES
108
+ element :suffix, String, :tag => 'Suffix', :attributes => RESPONSE_XML_ATTRIBUTES
109
+
110
+ def to_s
111
+ name_parts = self.class.elements.map(&:method_name)
112
+ name_parts.collect {|name_part| self.send(name_part) }.compact.join(' ')
113
+ end
114
+ end
115
+
116
+ # Encapsulates Trufina's address fields - has multiple street address fields
117
+ class ResidenceAddressResponse
118
+ include AllowCreationFromHash
119
+ include HappyMapper
120
+ include EasyElementAccess
121
+ tag 'ResidenceAddress'
122
+
123
+ has_many :street_addresses, String, :tag => 'StreetAddress', :attributes => RESPONSE_XML_ATTRIBUTES
124
+ element :city, String, :tag => 'City', :attributes => RESPONSE_XML_ATTRIBUTES
125
+ element :state, String, :tag => 'State', :attributes => RESPONSE_XML_ATTRIBUTES
126
+ element :zip, String, :tag => 'PostalCode', :attributes => RESPONSE_XML_ATTRIBUTES
127
+ attribute :timeframe, String
128
+
129
+ def street_address=(adr)
130
+ self.street_addresses ||= []
131
+ self.street_addresses << adr
132
+ end
133
+
134
+ def to_s
135
+ [street_addresses[0], street_addresses[1], city, state, zip].compact.join(', ')
136
+ end
137
+ end
138
+
139
+ # Encapsulates Trufina's address fields - only one street address field
140
+ class ResidenceAddressRequest
141
+ include AllowCreationFromHash
142
+ include HappyMapper
143
+ include EasyElementAccess
144
+ tag 'ResidenceAddress'
145
+
146
+ element :street_address, String, :tag => 'StreetAddress', :attributes => RESPONSE_XML_ATTRIBUTES
147
+ element :city, String, :tag => 'City', :attributes => RESPONSE_XML_ATTRIBUTES
148
+ element :state, String, :tag => 'State', :attributes => RESPONSE_XML_ATTRIBUTES
149
+ element :zip, String, :tag => 'PostalCode', :attributes => RESPONSE_XML_ATTRIBUTES
150
+ end
151
+
152
+
153
+ # Encapsulates all response data Trufina may send back
154
+ class AccessResponseGroup
155
+ include AllowCreationFromHash
156
+ include HappyMapper
157
+ include EasyElementAccess
158
+ tag 'AccessResponse'
159
+
160
+ element :name, Name, :single => true
161
+ # element :birth_date, Date, :tag => 'DateOfBirth', :attributes => RESPONSE_XML_ATTRIBUTES
162
+ # element :birth_country, String, :tag => 'CountryOfBirth', :attributes => RESPONSE_XML_ATTRIBUTES
163
+ element :phone, String, :tag => 'Phone', :attributes => RESPONSE_XML_ATTRIBUTES
164
+ element :age, String, :tag => 'Age', :attributes => RESPONSE_XML_ATTRIBUTES
165
+ element :residence_address, ResidenceAddressResponse, :single => true
166
+ element :ssn, String, :tag => 'fullSSN', :attributes => RESPONSE_XML_ATTRIBUTES
167
+ element :last_4_ssn, String, :tag => 'Last4SSN', :attributes => RESPONSE_XML_ATTRIBUTES
168
+ end
169
+
170
+ # Encapsulates all data we can request from Trufina
171
+ class AccessRequest
172
+ include AllowCreationFromHash
173
+ include HappyMapper
174
+ tag 'AccessRequest'
175
+
176
+ element :name, Name, :single => true
177
+ element :birth_date, Date, :tag => 'DateOfBirth'
178
+ element :birth_country, String, :tag => 'CountryOfBirth'
179
+ element :phone, String, :tag => 'Phone' # If Trufina implemented it, could have timeframe and maxAge attributes
180
+ element :age, String, :tag => 'Age', :attributes => {:comparison => String}
181
+ element :residence_address, ResidenceAddressRequest, :single => true # If Trufina implemented it, could have timeframe and maxAge attributes
182
+ element :ssn, String, :tag => 'fullSSN'
183
+ element :last_4_ssn, String, :tag => 'Last4SSN'
184
+ end
185
+
186
+ # Encapsulates all seed data Trufina accepts
187
+ class SeedInfoGroup
188
+ include AllowCreationFromHash
189
+ include HappyMapper
190
+ tag 'SeedInfo'
191
+
192
+ element :name, Name, :single => true
193
+ element :birth_date, Date, :tag => 'DateOfBirth'
194
+ element :birth_country, String, :tag => 'CountryOfBirth'
195
+ element :phone, String, :tag => 'Phone'
196
+ element :age, String, :tag => 'Age'
197
+ element :residence_address, ResidenceAddressResponse, :single => true
198
+ element :ssn, String, :tag => 'fullSSN'
199
+ element :last_4_ssn, String, :tag => 'Last4SSN'
200
+ end
201
+
202
+ end
203
+ end