use payrix_macros::PayrixEntity;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use super::{bool_from_int_default_false, PayrixId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum AccountHolderType {
#[default]
Individual = 1,
Business = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum AccountStatus {
NotReady = 0,
#[default]
Ready = 1,
Challenged = 2,
Verified = 3,
Manual = 4,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum AccountReserved {
#[default]
NoReserve = 0,
BlockTransaction = 1,
HoldTransaction = 3,
ReserveFunds = 4,
BlockCurrentActivity = 5,
PassedDecisions = 6,
NoPolicies = 7,
OnboardPendingManual = 8,
ScheduleAutoRelease = 9,
HoldAutoRelease = 10,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AccountType {
#[default]
All,
Credit,
Debit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum AccountCheckStage {
#[default]
#[serde(rename = "createAccount")]
CreateAccount,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum UpdateMethod {
#[default]
Noc,
Plaid,
Manual,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PayrixEntity)]
#[payrix(create = CreateAccount, update = UpdateAccount)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
#[serde(rename_all = "camelCase")]
pub struct Account {
#[payrix(readonly)]
pub id: PayrixId,
#[payrix(readonly)]
#[serde(default)]
pub created: Option<String>,
#[payrix(readonly)]
#[serde(default)]
pub modified: Option<String>,
#[payrix(readonly)]
#[serde(default)]
pub creator: Option<PayrixId>,
#[payrix(readonly)]
#[serde(default)]
pub modifier: Option<PayrixId>,
#[payrix(create_only)]
#[serde(default)]
pub entity: Option<PayrixId>,
#[serde(default)]
pub account: Option<PayrixId>,
#[serde(default)]
pub token: Option<String>,
#[payrix(mutable)]
#[serde(default)]
pub name: Option<String>,
#[payrix(mutable)]
#[serde(default)]
pub description: Option<String>,
#[serde(default, with = "bool_from_int_default_false")]
pub primary: bool,
#[serde(default, rename = "type")]
pub account_type: Option<AccountType>,
#[serde(default)]
pub status: Option<AccountStatus>,
#[serde(default)]
pub reserved: Option<AccountReserved>,
#[serde(default)]
pub check_stage: Option<AccountCheckStage>,
#[serde(default)]
pub expiration: Option<String>,
#[serde(default)]
pub currency: Option<String>,
#[serde(default)]
pub public_token: Option<String>,
#[serde(default)]
pub mask: Option<String>,
#[payrix(mutable)]
#[serde(default, with = "bool_from_int_default_false")]
pub inactive: bool,
#[payrix(mutable)]
#[serde(default, with = "bool_from_int_default_false")]
pub frozen: bool,
#[serde(default)]
pub plaid_account_id: Option<String>,
#[serde(default)]
pub update_method: Option<UpdateMethod>,
#[cfg(not(feature = "sqlx"))]
#[serde(default)]
pub change_requests: Option<Vec<serde_json::Value>>,
#[cfg(not(feature = "sqlx"))]
#[serde(default)]
pub payment_updates: Option<Vec<serde_json::Value>>,
#[cfg(not(feature = "sqlx"))]
#[serde(default)]
pub payouts: Option<Vec<serde_json::Value>>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn account_holder_type_serialize_all_variants() {
assert_eq!(serde_json::to_string(&AccountHolderType::Individual).unwrap(), "1");
assert_eq!(serde_json::to_string(&AccountHolderType::Business).unwrap(), "2");
}
#[test]
fn account_holder_type_deserialize_all_variants() {
assert_eq!(serde_json::from_str::<AccountHolderType>("1").unwrap(), AccountHolderType::Individual);
assert_eq!(serde_json::from_str::<AccountHolderType>("2").unwrap(), AccountHolderType::Business);
}
#[test]
fn account_holder_type_default() {
assert_eq!(AccountHolderType::default(), AccountHolderType::Individual);
}
#[test]
fn account_status_serialize_all_variants() {
assert_eq!(serde_json::to_string(&AccountStatus::NotReady).unwrap(), "0");
assert_eq!(serde_json::to_string(&AccountStatus::Ready).unwrap(), "1");
assert_eq!(serde_json::to_string(&AccountStatus::Challenged).unwrap(), "2");
assert_eq!(serde_json::to_string(&AccountStatus::Verified).unwrap(), "3");
assert_eq!(serde_json::to_string(&AccountStatus::Manual).unwrap(), "4");
}
#[test]
fn account_status_deserialize_all_variants() {
assert_eq!(serde_json::from_str::<AccountStatus>("0").unwrap(), AccountStatus::NotReady);
assert_eq!(serde_json::from_str::<AccountStatus>("1").unwrap(), AccountStatus::Ready);
assert_eq!(serde_json::from_str::<AccountStatus>("2").unwrap(), AccountStatus::Challenged);
assert_eq!(serde_json::from_str::<AccountStatus>("3").unwrap(), AccountStatus::Verified);
assert_eq!(serde_json::from_str::<AccountStatus>("4").unwrap(), AccountStatus::Manual);
}
#[test]
fn account_status_default() {
assert_eq!(AccountStatus::default(), AccountStatus::Ready);
}
#[test]
fn account_reserved_serialize_all_variants() {
assert_eq!(serde_json::to_string(&AccountReserved::NoReserve).unwrap(), "0");
assert_eq!(serde_json::to_string(&AccountReserved::BlockTransaction).unwrap(), "1");
assert_eq!(serde_json::to_string(&AccountReserved::HoldTransaction).unwrap(), "3");
assert_eq!(serde_json::to_string(&AccountReserved::ReserveFunds).unwrap(), "4");
assert_eq!(serde_json::to_string(&AccountReserved::BlockCurrentActivity).unwrap(), "5");
assert_eq!(serde_json::to_string(&AccountReserved::PassedDecisions).unwrap(), "6");
assert_eq!(serde_json::to_string(&AccountReserved::NoPolicies).unwrap(), "7");
assert_eq!(serde_json::to_string(&AccountReserved::OnboardPendingManual).unwrap(), "8");
assert_eq!(serde_json::to_string(&AccountReserved::ScheduleAutoRelease).unwrap(), "9");
assert_eq!(serde_json::to_string(&AccountReserved::HoldAutoRelease).unwrap(), "10");
}
#[test]
fn account_reserved_deserialize_all_variants() {
assert_eq!(serde_json::from_str::<AccountReserved>("0").unwrap(), AccountReserved::NoReserve);
assert_eq!(serde_json::from_str::<AccountReserved>("1").unwrap(), AccountReserved::BlockTransaction);
assert_eq!(serde_json::from_str::<AccountReserved>("3").unwrap(), AccountReserved::HoldTransaction);
assert_eq!(serde_json::from_str::<AccountReserved>("4").unwrap(), AccountReserved::ReserveFunds);
assert_eq!(serde_json::from_str::<AccountReserved>("5").unwrap(), AccountReserved::BlockCurrentActivity);
assert_eq!(serde_json::from_str::<AccountReserved>("6").unwrap(), AccountReserved::PassedDecisions);
assert_eq!(serde_json::from_str::<AccountReserved>("7").unwrap(), AccountReserved::NoPolicies);
assert_eq!(serde_json::from_str::<AccountReserved>("8").unwrap(), AccountReserved::OnboardPendingManual);
assert_eq!(serde_json::from_str::<AccountReserved>("9").unwrap(), AccountReserved::ScheduleAutoRelease);
assert_eq!(serde_json::from_str::<AccountReserved>("10").unwrap(), AccountReserved::HoldAutoRelease);
}
#[test]
fn account_reserved_default() {
assert_eq!(AccountReserved::default(), AccountReserved::NoReserve);
}
#[test]
fn account_type_serialize_all_variants() {
assert_eq!(serde_json::to_string(&AccountType::All).unwrap(), "\"all\"");
assert_eq!(serde_json::to_string(&AccountType::Credit).unwrap(), "\"credit\"");
assert_eq!(serde_json::to_string(&AccountType::Debit).unwrap(), "\"debit\"");
}
#[test]
fn account_type_deserialize_all_variants() {
assert_eq!(serde_json::from_str::<AccountType>("\"all\"").unwrap(), AccountType::All);
assert_eq!(serde_json::from_str::<AccountType>("\"credit\"").unwrap(), AccountType::Credit);
assert_eq!(serde_json::from_str::<AccountType>("\"debit\"").unwrap(), AccountType::Debit);
}
#[test]
fn account_type_default() {
assert_eq!(AccountType::default(), AccountType::All);
}
#[test]
fn update_method_serialize_all_variants() {
assert_eq!(serde_json::to_string(&UpdateMethod::Noc).unwrap(), "\"NOC\"");
assert_eq!(serde_json::to_string(&UpdateMethod::Plaid).unwrap(), "\"PLAID\"");
assert_eq!(serde_json::to_string(&UpdateMethod::Manual).unwrap(), "\"MANUAL\"");
}
#[test]
fn update_method_deserialize_all_variants() {
assert_eq!(serde_json::from_str::<UpdateMethod>("\"NOC\"").unwrap(), UpdateMethod::Noc);
assert_eq!(serde_json::from_str::<UpdateMethod>("\"PLAID\"").unwrap(), UpdateMethod::Plaid);
assert_eq!(serde_json::from_str::<UpdateMethod>("\"MANUAL\"").unwrap(), UpdateMethod::Manual);
}
#[test]
fn account_deserialize_full() {
let json = r#"{
"id": "t1_act_12345678901234567890123",
"created": "2024-01-01 00:00:00.0000",
"modified": "2024-01-02 23:59:59.9999",
"creator": "t1_lgn_12345678901234567890123",
"modifier": "t1_lgn_12345678901234567890124",
"entity": "t1_ent_12345678901234567890123",
"account": "t1_pmt_12345678901234567890123",
"token": "tok_12345",
"name": "Primary Checking",
"description": "Primary business account",
"primary": 1,
"type": "credit",
"status": 3,
"reserved": 0,
"checkStage": "createAccount",
"expiration": "0125",
"currency": "USD",
"publicToken": "public-sandbox-xxx",
"mask": "1234",
"inactive": 0,
"frozen": 1,
"plaidAccountId": "plaid_acc_123",
"updateMethod": "PLAID"
}"#;
let account: Account = serde_json::from_str(json).unwrap();
assert_eq!(account.id.as_str(), "t1_act_12345678901234567890123");
assert_eq!(account.created, Some("2024-01-01 00:00:00.0000".to_string()));
assert_eq!(account.modified, Some("2024-01-02 23:59:59.9999".to_string()));
assert_eq!(account.creator.as_ref().map(|c| c.as_str()), Some("t1_lgn_12345678901234567890123"));
assert_eq!(account.modifier.as_ref().map(|m| m.as_str()), Some("t1_lgn_12345678901234567890124"));
assert_eq!(account.entity.as_ref().map(|e| e.as_str()), Some("t1_ent_12345678901234567890123"));
assert_eq!(account.account.as_ref().map(|a| a.as_str()), Some("t1_pmt_12345678901234567890123"));
assert_eq!(account.token, Some("tok_12345".to_string()));
assert_eq!(account.name, Some("Primary Checking".to_string()));
assert_eq!(account.description, Some("Primary business account".to_string()));
assert!(account.primary);
assert_eq!(account.account_type, Some(AccountType::Credit));
assert_eq!(account.status, Some(AccountStatus::Verified));
assert_eq!(account.reserved, Some(AccountReserved::NoReserve));
assert_eq!(account.check_stage, Some(AccountCheckStage::CreateAccount));
assert_eq!(account.expiration, Some("0125".to_string()));
assert_eq!(account.currency, Some("USD".to_string()));
assert_eq!(account.public_token, Some("public-sandbox-xxx".to_string()));
assert_eq!(account.mask, Some("1234".to_string()));
assert!(!account.inactive);
assert!(account.frozen);
assert_eq!(account.plaid_account_id, Some("plaid_acc_123".to_string()));
assert_eq!(account.update_method, Some(UpdateMethod::Plaid));
}
#[test]
fn account_deserialize_minimal() {
let json = r#"{"id": "t1_act_12345678901234567890123"}"#;
let account: Account = serde_json::from_str(json).unwrap();
assert_eq!(account.id.as_str(), "t1_act_12345678901234567890123");
assert!(account.created.is_none());
assert!(account.modified.is_none());
assert!(account.creator.is_none());
assert!(account.modifier.is_none());
assert!(account.entity.is_none());
assert!(account.account.is_none());
assert!(account.token.is_none());
assert!(account.name.is_none());
assert!(account.description.is_none());
assert!(!account.primary);
assert!(account.account_type.is_none());
assert!(account.status.is_none());
assert!(account.reserved.is_none());
assert!(account.check_stage.is_none());
assert!(account.expiration.is_none());
assert!(account.currency.is_none());
assert!(account.public_token.is_none());
assert!(account.mask.is_none());
assert!(!account.inactive);
assert!(!account.frozen);
assert!(account.plaid_account_id.is_none());
assert!(account.update_method.is_none());
}
#[test]
fn account_bool_from_int() {
let json = r#"{"id": "t1_act_12345678901234567890123", "primary": 1, "inactive": 1, "frozen": 1}"#;
let account: Account = serde_json::from_str(json).unwrap();
assert!(account.primary);
assert!(account.inactive);
assert!(account.frozen);
let json = r#"{"id": "t1_act_12345678901234567890123", "primary": 0, "inactive": 0, "frozen": 0}"#;
let account: Account = serde_json::from_str(json).unwrap();
assert!(!account.primary);
assert!(!account.inactive);
assert!(!account.frozen);
}
#[test]
#[cfg(not(feature = "sqlx"))]
fn account_with_nested_relations() {
let json = r#"{
"id": "t1_act_12345678901234567890123",
"changeRequests": [{"id": "req1"}],
"paymentUpdates": [{"id": "upd1"}],
"payouts": [{"id": "t1_pyt_12345678901234567890123"}]
}"#;
let account: Account = serde_json::from_str(json).unwrap();
assert!(account.change_requests.is_some());
assert_eq!(account.change_requests.as_ref().unwrap().len(), 1);
assert!(account.payment_updates.is_some());
assert_eq!(account.payment_updates.as_ref().unwrap().len(), 1);
assert!(account.payouts.is_some());
assert_eq!(account.payouts.as_ref().unwrap().len(), 1);
}
}