rain_sdk/models/
transactions.rs

1//! Models for transaction endpoints
2
3use crate::models::cards::CardType;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8/// Transaction type
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10#[serde(rename_all = "lowercase")]
11pub enum TransactionType {
12    Spend,
13    Collateral,
14    Payment,
15    Fee,
16}
17
18/// Spend transaction status
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
20#[serde(rename_all = "lowercase")]
21pub enum SpendTransactionStatus {
22    Pending,
23    Reversed,
24    Declined,
25    Completed,
26}
27
28/// Payment transaction status
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
30#[serde(rename_all = "lowercase")]
31pub enum PaymentTransactionStatus {
32    Pending,
33    Completed,
34}
35
36/// Spend transaction details
37#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct SpendTransaction {
40    pub amount: i64,
41    pub currency: String,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub local_amount: Option<i64>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub local_currency: Option<String>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub authorized_amount: Option<i64>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub authorization_method: Option<String>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub memo: Option<String>,
52    pub receipt: bool,
53    pub merchant_name: String,
54    pub merchant_category: String,
55    pub merchant_category_code: String,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub merchant_id: Option<String>,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub enriched_merchant_icon: Option<String>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub enriched_merchant_name: Option<String>,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub enriched_merchant_category: Option<String>,
64    pub card_id: Uuid,
65    pub card_type: CardType,
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub company_id: Option<Uuid>,
68    pub user_id: Uuid,
69    pub user_first_name: String,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub user_last_name: Option<String>,
72    pub user_email: String,
73    pub status: SpendTransactionStatus,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub declined_reason: Option<String>,
76    pub authorized_at: String,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub posted_at: Option<String>,
79}
80
81/// Collateral transaction details
82#[derive(Debug, Clone, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct CollateralTransaction {
85    pub amount: f64,
86    pub currency: String,
87    pub chain_id: i64,
88    pub wallet_address: String,
89    pub transaction_hash: String,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub memo: Option<String>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub company_id: Option<Uuid>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub user_id: Option<Uuid>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub posted_at: Option<DateTime<Utc>>,
98}
99
100/// Payment transaction details
101#[derive(Debug, Clone, Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103pub struct PaymentTransaction {
104    pub amount: i64,
105    pub currency: String,
106    pub status: PaymentTransactionStatus,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub memo: Option<String>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub chain_id: Option<i64>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub wallet_address: Option<String>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub transaction_hash: Option<String>,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub company_id: Option<Uuid>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub user_id: Option<Uuid>,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub posted_at: Option<String>,
121}
122
123/// Fee transaction details
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct FeeTransaction {
127    pub amount: i64,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub description: Option<String>,
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub company_id: Option<Uuid>,
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub user_id: Option<Uuid>,
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub posted_at: Option<DateTime<Utc>>,
136}
137
138/// Transaction (discriminated union based on type)
139#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(tag = "type")]
141#[allow(clippy::large_enum_variant)]
142pub enum Transaction {
143    #[serde(rename = "spend")]
144    Spend {
145        id: Uuid,
146        #[serde(flatten)]
147        spend: SpendTransaction,
148    },
149    #[serde(rename = "collateral")]
150    Collateral {
151        id: Uuid,
152        #[serde(flatten)]
153        collateral: CollateralTransaction,
154    },
155    #[serde(rename = "payment")]
156    Payment {
157        id: Uuid,
158        #[serde(flatten)]
159        payment: PaymentTransaction,
160    },
161    #[serde(rename = "fee")]
162    Fee {
163        id: Uuid,
164        #[serde(flatten)]
165        fee: FeeTransaction,
166    },
167}
168
169/// Query parameters for listing transactions
170#[derive(Debug, Clone, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct ListTransactionsParams {
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub company_id: Option<Uuid>,
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub user_id: Option<Uuid>,
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub card_id: Option<Uuid>,
179    #[serde(
180        rename = "type",
181        skip_serializing_if = "Option::is_none",
182        serialize_with = "serialize_transaction_types"
183    )]
184    pub transaction_type: Option<Vec<TransactionType>>,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub transaction_hash: Option<String>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub authorized_before: Option<DateTime<Utc>>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub authorized_after: Option<DateTime<Utc>>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub posted_before: Option<DateTime<Utc>>,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub posted_after: Option<DateTime<Utc>>,
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub cursor: Option<String>,
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub limit: Option<u32>,
199}
200
201fn serialize_transaction_types<S>(
202    types: &Option<Vec<TransactionType>>,
203    serializer: S,
204) -> Result<S::Ok, S::Error>
205where
206    S: serde::Serializer,
207{
208    use serde::ser::SerializeSeq;
209
210    match types {
211        Some(ref vec) => {
212            let mut seq = serializer.serialize_seq(Some(vec.len()))?;
213            for item in vec {
214                let s = match item {
215                    TransactionType::Spend => "spend",
216                    TransactionType::Collateral => "collateral",
217                    TransactionType::Payment => "payment",
218                    TransactionType::Fee => "fee",
219                };
220                seq.serialize_element(s)?;
221            }
222            seq.end()
223        }
224        None => serializer.serialize_none(),
225    }
226}
227
228/// Request to update a transaction
229#[derive(Debug, Clone, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct UpdateTransactionRequest {
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub memo: Option<String>,
234}
235
236/// Request to upload a receipt
237#[derive(Debug, Clone)]
238pub struct UploadReceiptRequest {
239    pub receipt: Vec<u8>,
240    pub file_name: String,
241}
242
243/// Response type for list of transactions
244pub type ListTransactionsResponse = Vec<Transaction>;