1use bytemuck::{Pod, Zeroable};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
7#[repr(u8)]
8pub enum TransactionType {
9 #[default]
11 Wire = 0,
12 ACH = 1,
14 Check = 2,
16 Card = 3,
18 Cash = 4,
20 Internal = 5,
22}
23
24impl TransactionType {
25 pub fn name(&self) -> &'static str {
27 match self {
28 TransactionType::Wire => "Wire",
29 TransactionType::ACH => "ACH",
30 TransactionType::Check => "Check",
31 TransactionType::Card => "Card",
32 TransactionType::Cash => "Cash",
33 TransactionType::Internal => "Internal",
34 }
35 }
36
37 pub fn from_u8(v: u8) -> Option<Self> {
39 match v {
40 0 => Some(TransactionType::Wire),
41 1 => Some(TransactionType::ACH),
42 2 => Some(TransactionType::Check),
43 3 => Some(TransactionType::Card),
44 4 => Some(TransactionType::Cash),
45 5 => Some(TransactionType::Internal),
46 _ => None,
47 }
48 }
49}
50
51impl std::fmt::Display for TransactionType {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 write!(f, "{}", self.name())
54 }
55}
56
57#[derive(Debug, Clone, Copy, Pod, Zeroable)]
61#[repr(C, align(128))]
62pub struct Transaction {
63 pub transaction_id: u64,
65 pub customer_id: u64,
67 pub destination_id: u64,
69 pub amount_cents: u64,
71 pub country_code: u16,
73 pub tx_type: u8,
75 pub currency: u8,
77 pub flags: u32,
79 pub timestamp: u64,
81 pub source_hash: u32,
83 pub dest_hash: u32,
85 pub reference: u64,
87 _reserved: [u8; 64],
89}
90
91const _: () = assert!(std::mem::size_of::<Transaction>() == 128);
92
93impl Transaction {
94 pub fn new(
96 transaction_id: u64,
97 customer_id: u64,
98 amount_cents: u64,
99 country_code: u16,
100 timestamp: u64,
101 ) -> Self {
102 Self {
103 transaction_id,
104 customer_id,
105 destination_id: 0,
106 amount_cents,
107 country_code,
108 tx_type: TransactionType::Wire as u8,
109 currency: 184, flags: 0,
111 timestamp,
112 source_hash: 0,
113 dest_hash: 0,
114 reference: 0,
115 _reserved: [0; 64],
116 }
117 }
118
119 pub fn tx_type(&self) -> TransactionType {
121 TransactionType::from_u8(self.tx_type).unwrap_or_default()
122 }
123
124 pub fn format_amount(&self) -> String {
126 let dollars = self.amount_cents / 100;
127 let cents = self.amount_cents % 100;
128 format!("${}.{:02}", dollars, cents)
129 }
130
131 pub fn is_high_value(&self) -> bool {
133 self.amount_cents >= 1_000_000 }
135
136 pub fn is_international(&self, home_country: u16) -> bool {
138 self.country_code != home_country
139 }
140
141 pub fn country_name(&self) -> &'static str {
143 match self.country_code {
144 1 => "US",
145 2 => "UK",
146 3 => "DE",
147 4 => "FR",
148 5 => "JP",
149 6 => "CN",
150 7 => "RU",
151 8 => "BR",
152 9 => "IN",
153 10 => "CA",
154 11 => "AU",
155 12 => "MX",
156 13 => "KR",
157 14 => "IT",
158 15 => "ES",
159 _ => "??",
160 }
161 }
162}
163
164impl Default for Transaction {
165 fn default() -> Self {
166 Self::zeroed()
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn test_transaction_size() {
176 assert_eq!(std::mem::size_of::<Transaction>(), 128);
177 }
178
179 #[test]
180 fn test_format_amount() {
181 let tx = Transaction::new(1, 1, 123_456, 1, 0);
182 assert_eq!(tx.format_amount(), "$1234.56");
183 }
184
185 #[test]
186 fn test_high_value() {
187 let low = Transaction::new(1, 1, 50_000, 1, 0);
188 let high = Transaction::new(2, 1, 1_500_000, 1, 0);
189
190 assert!(!low.is_high_value());
191 assert!(high.is_high_value());
192 }
193}