akahu_client/models/
identity.rs

1//! Identity verification and party information models.
2//!
3//! Types for working with identity verification, party data, and address information.
4
5use serde::{Deserialize, Serialize};
6
7use crate::{BankAccountNumber, ConnectionId, space_separated_strings_as_vec};
8
9/// Status of an identity verification
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
12pub enum IdentityStatus {
13    /// Identity verification is still being processed
14    Processing,
15    /// Identity verification is complete
16    Complete,
17    /// Identity verification encountered an error
18    Error,
19}
20
21impl IdentityStatus {
22    /// Get the status as a string slice.
23    pub const fn as_str(&self) -> &'static str {
24        match self {
25            Self::Processing => "PROCESSING",
26            Self::Complete => "COMPLETE",
27            Self::Error => "ERROR",
28        }
29    }
30
31    /// Get the status as bytes.
32    pub const fn as_bytes(&self) -> &'static [u8] {
33        self.as_str().as_bytes()
34    }
35}
36
37impl std::str::FromStr for IdentityStatus {
38    type Err = ();
39    fn from_str(s: &str) -> Result<Self, Self::Err> {
40        match s {
41            "PROCESSING" => Ok(Self::Processing),
42            "COMPLETE" => Ok(Self::Complete),
43            "ERROR" => Ok(Self::Error),
44            _ => Err(()),
45        }
46    }
47}
48
49impl std::convert::TryFrom<String> for IdentityStatus {
50    type Error = ();
51    fn try_from(value: String) -> Result<Self, ()> {
52        value.parse()
53    }
54}
55
56impl std::convert::TryFrom<&str> for IdentityStatus {
57    type Error = ();
58    fn try_from(value: &str) -> Result<Self, ()> {
59        value.parse()
60    }
61}
62
63impl std::fmt::Display for IdentityStatus {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        write!(f, "{}", self.as_str())
66    }
67}
68
69/// Identity item containing account holder information
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
71pub struct Identity {
72    /// Account holder's name
73    pub name: String,
74
75    /// New Zealand bank account number in standard format (00-0000-0000000-00)
76    pub formatted_account: BankAccountNumber,
77
78    /// Reserved metadata object
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub meta: Option<serde_json::Value>,
81}
82
83/// Address information from financial institution
84#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
85pub struct Address {
86    /// Type of address
87    #[serde(rename = "type")]
88    pub kind: AddressKind,
89
90    /// Raw address string as provided by the bank
91    pub value: String,
92
93    /// Parsed and formatted address string
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub formatted_address: Option<String>,
96
97    /// Google Places API identifier
98    #[serde(default, skip_serializing_if = "Option::is_none")]
99    pub place_id: Option<String>,
100
101    /// Structured address components
102    #[serde(default, skip_serializing_if = "Option::is_none")]
103    pub components: Option<AddressComponents>,
104}
105
106/// Type of address
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
108#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
109pub enum AddressKind {
110    /// Residential address
111    Residential,
112    /// Postal address
113    Postal,
114    /// Unknown address type
115    Unknown,
116}
117
118impl AddressKind {
119    /// Get the address kind as a string slice.
120    pub const fn as_str(&self) -> &'static str {
121        match self {
122            Self::Residential => "RESIDENTIAL",
123            Self::Postal => "POSTAL",
124            Self::Unknown => "UNKNOWN",
125        }
126    }
127
128    /// Get the address kind as bytes.
129    pub const fn as_bytes(&self) -> &'static [u8] {
130        self.as_str().as_bytes()
131    }
132}
133
134impl std::str::FromStr for AddressKind {
135    type Err = ();
136    fn from_str(s: &str) -> Result<Self, Self::Err> {
137        match s {
138            "RESIDENTIAL" => Ok(Self::Residential),
139            "POSTAL" => Ok(Self::Postal),
140            "UNKNOWN" => Ok(Self::Unknown),
141            _ => Err(()),
142        }
143    }
144}
145
146impl std::convert::TryFrom<String> for AddressKind {
147    type Error = ();
148    fn try_from(value: String) -> Result<Self, Self::Error> {
149        value.parse()
150    }
151}
152
153impl std::convert::TryFrom<&str> for AddressKind {
154    type Error = ();
155    fn try_from(value: &str) -> Result<Self, Self::Error> {
156        value.parse()
157    }
158}
159
160impl std::fmt::Display for AddressKind {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        write!(f, "{}", self.as_str())
163    }
164}
165
166/// Structured address components
167#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
168pub struct AddressComponents {
169    /// Street address
170    #[serde(default, skip_serializing_if = "Option::is_none")]
171    pub street: Option<String>,
172
173    /// Suburb name
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub suburb: Option<String>,
176
177    /// City name
178    #[serde(default, skip_serializing_if = "Option::is_none")]
179    pub city: Option<String>,
180
181    /// Region or state
182    #[serde(default, skip_serializing_if = "Option::is_none")]
183    pub region: Option<String>,
184
185    /// Postal code
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub postal_code: Option<String>,
188
189    /// Country name
190    #[serde(default, skip_serializing_if = "Option::is_none")]
191    pub country: Option<String>,
192}
193
194/// Account information from identity verification
195#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
196pub struct IdentityAccount {
197    /// Account nickname or product name (e.g., "Spending", "Everyday")
198    pub name: String,
199
200    /// Account number in NZ format or masked identifier
201    pub account_number: BankAccountNumber,
202
203    /// Account holder name as displayed by the bank
204    pub holder: String,
205
206    /// Whether there are additional unlisted joint account holders
207    pub has_unlisted_holders: bool,
208
209    /// Optional address string
210    #[serde(default, skip_serializing_if = "Option::is_none")]
211    pub address: Option<String>,
212
213    /// Bank/institution name
214    pub bank: String,
215
216    /// Optional branch information
217    #[serde(default, skip_serializing_if = "Option::is_none")]
218    pub branch: Option<BranchInfo>,
219}
220
221/// Bank branch information
222#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
223pub struct BranchInfo {
224    /// Unique Akahu ID beginning with `bank_branch_`
225    #[serde(rename = "_id")]
226    pub id: String,
227
228    /// Descriptive name of the branch
229    pub description: String,
230
231    /// Phone number in E.164 format
232    #[serde(default, skip_serializing_if = "Option::is_none")]
233    pub phone: Option<String>,
234
235    /// Branch address
236    #[serde(default, skip_serializing_if = "Option::is_none")]
237    pub address: Option<String>,
238}
239
240/// Information about the institution connection
241#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
242pub struct IdentitySource {
243    /// Akahu Connection ID beginning with `conn_`
244    #[serde(rename = "_id")]
245    pub id: ConnectionId,
246}
247
248/// OAuth profile information
249#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
250pub struct IdentityProfile {
251    /// Profile ID beginning with `profile_`
252    #[serde(rename = "_id")]
253    pub id: String,
254}
255
256/// Request to verify a name
257#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
258pub struct VerifyNameRequest {
259    /// Family name (surname) - required
260    pub family_name: String,
261
262    /// Given name (first name) - optional
263    #[serde(default, skip_serializing_if = "Option::is_none")]
264    pub given_name: Option<String>,
265
266    /// Middle name(s) - optional
267    /// If multiple middle names, separate with spaces
268    #[serde(
269        rename = "middle_name",
270        default,
271        skip_serializing_if = "Option::is_none",
272        with = "space_separated_strings_as_vec"
273    )]
274    pub middle_names: Option<Vec<String>>,
275}
276
277/// Response from name verification
278#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
279pub struct VerifyNameResponse {
280    /// Whether the verification was successful
281    pub success: bool,
282
283    /// Verification details
284    pub item: VerifyNameItem,
285}
286
287/// Verification details
288#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
289pub struct VerifyNameItem {
290    /// Array of verification sources (empty if no matches)
291    pub sources: Vec<VerificationSource>,
292
293    /// Echo of the input parameters
294    pub name: VerifyNameRequest,
295}
296
297/// A single verification source result
298#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
299pub struct VerificationSource {
300    /// Type of verification source
301    #[serde(rename = "type")]
302    pub source_type: VerificationSourceType,
303
304    /// Source-specific metadata
305    pub meta: serde_json::Value,
306
307    /// Match result (only present if matched)
308    #[serde(default, skip_serializing_if = "Option::is_none")]
309    pub match_result: Option<MatchResult>,
310
311    /// Boolean flags indicating which name components matched
312    #[serde(default, skip_serializing_if = "Option::is_none")]
313    pub verification: Option<NameVerification>,
314}
315
316/// Type of verification source
317#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
318#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
319pub enum VerificationSourceType {
320    /// Bank account holder name
321    HolderName,
322    /// Party name from financial institution
323    PartyName,
324}
325
326impl VerificationSourceType {
327    /// Get the verification source type as a string slice.
328    pub const fn as_str(&self) -> &'static str {
329        match self {
330            Self::HolderName => "HOLDER_NAME",
331            Self::PartyName => "PARTY_NAME",
332        }
333    }
334
335    /// Get the verification source type as bytes.
336    pub const fn as_bytes(&self) -> &'static [u8] {
337        self.as_str().as_bytes()
338    }
339}
340
341impl std::str::FromStr for VerificationSourceType {
342    type Err = ();
343    fn from_str(s: &str) -> Result<Self, Self::Err> {
344        match s {
345            "HOLDER_NAME" => Ok(Self::HolderName),
346            "PARTY_NAME" => Ok(Self::PartyName),
347            _ => Err(()),
348        }
349    }
350}
351
352impl std::convert::TryFrom<String> for VerificationSourceType {
353    type Error = ();
354    fn try_from(value: String) -> Result<Self, Self::Error> {
355        value.parse()
356    }
357}
358
359impl std::convert::TryFrom<&str> for VerificationSourceType {
360    type Error = ();
361    fn try_from(value: &str) -> Result<Self, Self::Error> {
362        value.parse()
363    }
364}
365
366impl std::fmt::Display for VerificationSourceType {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        write!(f, "{}", self.as_str())
369    }
370}
371
372/// Match result from verification
373#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
374#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
375pub enum MatchResult {
376    /// All supplied parameters match the verification source
377    Match,
378    /// Family name matches but other supplied parameters don't
379    PartialMatch,
380}
381
382impl MatchResult {
383    /// Get the match result as a string slice.
384    pub const fn as_str(&self) -> &'static str {
385        match self {
386            Self::Match => "MATCH",
387            Self::PartialMatch => "PARTIAL_MATCH",
388        }
389    }
390
391    /// Get the match result as bytes.
392    pub const fn as_bytes(&self) -> &'static [u8] {
393        self.as_str().as_bytes()
394    }
395}
396
397impl std::str::FromStr for MatchResult {
398    type Err = ();
399    fn from_str(s: &str) -> Result<Self, Self::Err> {
400        match s {
401            "MATCH" => Ok(Self::Match),
402            "PARTIAL_MATCH" => Ok(Self::PartialMatch),
403            _ => Err(()),
404        }
405    }
406}
407
408impl std::convert::TryFrom<String> for MatchResult {
409    type Error = ();
410    fn try_from(value: String) -> Result<Self, Self::Error> {
411        value.parse()
412    }
413}
414
415impl std::convert::TryFrom<&str> for MatchResult {
416    type Error = ();
417    fn try_from(value: &str) -> Result<Self, Self::Error> {
418        value.parse()
419    }
420}
421
422impl std::fmt::Display for MatchResult {
423    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424        write!(f, "{}", self.as_str())
425    }
426}
427
428/// Boolean flags for name component verification
429#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
430pub struct NameVerification {
431    /// Whether family name matched
432    #[serde(default, skip_serializing_if = "Option::is_none")]
433    pub family_name: Option<bool>,
434
435    /// Whether given name matched
436    #[serde(default, skip_serializing_if = "Option::is_none")]
437    pub given_name: Option<bool>,
438
439    /// Whether middle name matched
440    #[serde(default, skip_serializing_if = "Option::is_none")]
441    pub middle_name: Option<bool>,
442
443    /// Whether middle initial matched
444    #[serde(default, skip_serializing_if = "Option::is_none")]
445    pub middle_initial: Option<bool>,
446
447    /// Whether given initial matched
448    #[serde(default, skip_serializing_if = "Option::is_none")]
449    pub given_initial: Option<bool>,
450}
451
452/// Party information from enduring access
453///
454/// Contains customer profile information from financial institutions.
455/// This is returned from the GET /parties endpoint.
456#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
457pub struct Party {
458    /// Unique identifier
459    #[serde(rename = "_id")]
460    pub id: String,
461
462    /// Party name
463    pub name: String,
464
465    /// Email address
466    #[serde(default, skip_serializing_if = "Option::is_none")]
467    pub email: Option<String>,
468
469    /// Phone number
470    #[serde(default, skip_serializing_if = "Option::is_none")]
471    pub phone: Option<String>,
472
473    /// Addresses associated with this party
474    #[serde(default, skip_serializing_if = "Option::is_none")]
475    pub addresses: Option<Vec<Address>>,
476
477    /// Tax identification number
478    #[serde(default, skip_serializing_if = "Option::is_none")]
479    pub tax_number: Option<String>,
480
481    /// Additional metadata
482    #[serde(default, skip_serializing_if = "Option::is_none")]
483    pub meta: Option<serde_json::Value>,
484}