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, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum HoldAction {
None = 0,
Block = 1,
Hold = 3,
Reserve = 4,
Limit = 5,
Pass = 6,
PostReviewOnly = 8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum ReleaseAction {
Approved = 1,
Cancelled = 2,
Refunded = 3,
Failed = 4,
Expired = 5,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum HoldSource {
DsModelPolicyRun,
ApiDecision,
PolicyRun,
RiskAlert,
Manual,
Error,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, PayrixEntity)]
#[payrix(create = CreateHold, update = UpdateHold)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
#[serde(rename_all = "camelCase")]
pub struct Hold {
#[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 login: Option<PayrixId>,
#[payrix(create_only)]
#[serde(default)]
pub entity: Option<PayrixId>,
#[payrix(create_only)]
#[serde(default)]
pub txn: Option<PayrixId>,
#[payrix(create_only)]
#[serde(default)]
pub terminal_txn: Option<PayrixId>,
#[payrix(create_only)]
#[serde(default)]
pub account: Option<PayrixId>,
#[payrix(create_only)]
#[serde(default)]
pub verification: Option<PayrixId>,
#[payrix(create_only)]
#[serde(default)]
pub verification_ref: Option<PayrixId>,
#[payrix(create_only)]
#[serde(default)]
pub decision_action: Option<PayrixId>,
#[payrix(create_only)]
#[serde(default)]
pub action: Option<HoldAction>,
#[payrix(create_only)]
#[serde(default)]
pub hold_source: Option<HoldSource>,
#[payrix(create_only)]
#[serde(default)]
pub hold_source_id: Option<String>,
#[payrix(create_only)]
#[serde(default)]
pub hold_source_details: Option<String>,
#[payrix(create_only)]
#[serde(default)]
pub division: Option<PayrixId>,
#[payrix(mutable)]
#[serde(default)]
pub released: Option<String>,
#[payrix(mutable)]
#[serde(default)]
pub reviewed: 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,
#[payrix(mutable)]
#[serde(default)]
pub release_action: Option<ReleaseAction>,
#[payrix(mutable)]
#[serde(default)]
pub delayed_funding_start_date: Option<String>,
#[payrix(mutable)]
#[serde(default)]
pub delayed_funding_end_date: Option<String>,
#[payrix(mutable)]
#[serde(default)]
pub analyst: Option<PayrixId>,
#[payrix(mutable)]
#[serde(default)]
pub claimed: Option<String>,
#[payrix(readonly)]
#[cfg(not(feature = "sqlx"))]
#[serde(default)]
pub reserve: Option<serde_json::Value>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hold_deserialize_full() {
let json = r#"{
"id": "t1_hld_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",
"login": "t1_lgn_12345678901234567890125",
"entity": "t1_ent_12345678901234567890123",
"txn": "t1_txn_12345678901234567890123",
"action": 3,
"released": "2024-01-05 12:00:00",
"reviewed": "2024-01-05 11:00:00",
"inactive": 0,
"frozen": 0,
"releaseAction": 1,
"analyst": "t1_lgn_12345678901234567890126",
"holdSource": "POLICY_RUN",
"division": "t1_div_12345678901234567890123"
}"#;
let hold: Hold = serde_json::from_str(json).unwrap();
assert_eq!(hold.id.as_str(), "t1_hld_12345678901234567890123");
assert_eq!(hold.created, Some("2024-01-01 00:00:00.0000".to_string()));
assert_eq!(
hold.entity.as_ref().map(|e| e.as_str()),
Some("t1_ent_12345678901234567890123")
);
assert_eq!(hold.action, Some(HoldAction::Hold));
assert_eq!(hold.released, Some("2024-01-05 12:00:00".to_string()));
assert_eq!(hold.release_action, Some(ReleaseAction::Approved));
assert_eq!(hold.hold_source, Some(HoldSource::PolicyRun));
assert!(!hold.inactive);
assert!(!hold.frozen);
}
#[test]
fn hold_deserialize_minimal() {
let json = r#"{"id": "t1_hld_12345678901234567890123"}"#;
let hold: Hold = serde_json::from_str(json).unwrap();
assert_eq!(hold.id.as_str(), "t1_hld_12345678901234567890123");
assert!(hold.created.is_none());
assert!(hold.entity.is_none());
assert!(hold.action.is_none());
assert!(!hold.inactive);
}
#[test]
fn hold_action_values() {
let test_cases = vec![
(0, HoldAction::None),
(1, HoldAction::Block),
(3, HoldAction::Hold),
(4, HoldAction::Reserve),
(5, HoldAction::Limit),
(6, HoldAction::Pass),
];
for (val, expected) in test_cases {
let json = format!(
r#"{{"id": "t1_hld_12345678901234567890123", "action": {}}}"#,
val
);
let hold: Hold = serde_json::from_str(&json).unwrap();
assert_eq!(hold.action, Some(expected));
}
}
#[test]
fn hold_serialize_roundtrip() {
let json = r#"{
"id": "t1_hld_12345678901234567890123",
"entity": "t1_ent_12345678901234567890123",
"action": 3
}"#;
let hold: Hold = serde_json::from_str(json).unwrap();
let serialized = serde_json::to_string(&hold).unwrap();
let deserialized: Hold = serde_json::from_str(&serialized).unwrap();
assert_eq!(hold.id, deserialized.id);
assert_eq!(hold.entity, deserialized.entity);
assert_eq!(hold.action, deserialized.action);
}
}