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)]
38pub struct SpendTransaction {
39    pub amount: i64,
40    pub currency: String,
41    #[serde(rename = "localAmount", skip_serializing_if = "Option::is_none")]
42    pub local_amount: Option<i64>,
43    #[serde(rename = "localCurrency", skip_serializing_if = "Option::is_none")]
44    pub local_currency: Option<String>,
45    #[serde(rename = "authorizedAmount", skip_serializing_if = "Option::is_none")]
46    pub authorized_amount: Option<i64>,
47    #[serde(
48        rename = "authorizationMethod",
49        skip_serializing_if = "Option::is_none"
50    )]
51    pub authorization_method: Option<String>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub memo: Option<String>,
54    pub receipt: bool,
55    #[serde(rename = "merchantName")]
56    pub merchant_name: String,
57    #[serde(rename = "merchantCategory")]
58    pub merchant_category: String,
59    #[serde(rename = "merchantCategoryCode")]
60    pub merchant_category_code: String,
61    #[serde(rename = "merchantId", skip_serializing_if = "Option::is_none")]
62    pub merchant_id: Option<String>,
63    #[serde(
64        rename = "enrichedMerchantIcon",
65        skip_serializing_if = "Option::is_none"
66    )]
67    pub enriched_merchant_icon: Option<String>,
68    #[serde(
69        rename = "enrichedMerchantName",
70        skip_serializing_if = "Option::is_none"
71    )]
72    pub enriched_merchant_name: Option<String>,
73    #[serde(
74        rename = "enrichedMerchantCategory",
75        skip_serializing_if = "Option::is_none"
76    )]
77    pub enriched_merchant_category: Option<String>,
78    #[serde(rename = "cardId")]
79    pub card_id: Uuid,
80    #[serde(rename = "cardType")]
81    pub card_type: CardType,
82    #[serde(rename = "companyId", skip_serializing_if = "Option::is_none")]
83    pub company_id: Option<Uuid>,
84    #[serde(rename = "userId")]
85    pub user_id: Uuid,
86    #[serde(rename = "userFirstName")]
87    pub user_first_name: String,
88    #[serde(rename = "userLastName", skip_serializing_if = "Option::is_none")]
89    pub user_last_name: Option<String>,
90    #[serde(rename = "userEmail")]
91    pub user_email: String,
92    pub status: SpendTransactionStatus,
93    #[serde(rename = "declinedReason", skip_serializing_if = "Option::is_none")]
94    pub declined_reason: Option<String>,
95    #[serde(rename = "authorizedAt")]
96    pub authorized_at: String,
97    #[serde(rename = "postedAt", skip_serializing_if = "Option::is_none")]
98    pub posted_at: Option<String>,
99}
100
101/// Collateral transaction details
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct CollateralTransaction {
104    pub amount: f64,
105    pub currency: String,
106    #[serde(rename = "chainId")]
107    pub chain_id: i64,
108    #[serde(rename = "walletAddress")]
109    pub wallet_address: String,
110    #[serde(rename = "transactionHash")]
111    pub transaction_hash: String,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub memo: Option<String>,
114    #[serde(rename = "companyId", skip_serializing_if = "Option::is_none")]
115    pub company_id: Option<Uuid>,
116    #[serde(rename = "userId", skip_serializing_if = "Option::is_none")]
117    pub user_id: Option<Uuid>,
118    #[serde(rename = "postedAt", skip_serializing_if = "Option::is_none")]
119    pub posted_at: Option<DateTime<Utc>>,
120}
121
122/// Payment transaction details
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct PaymentTransaction {
125    pub amount: i64,
126    pub currency: String,
127    pub status: PaymentTransactionStatus,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub memo: Option<String>,
130    #[serde(rename = "chainId", skip_serializing_if = "Option::is_none")]
131    pub chain_id: Option<i64>,
132    #[serde(rename = "walletAddress", skip_serializing_if = "Option::is_none")]
133    pub wallet_address: Option<String>,
134    #[serde(rename = "transactionHash", skip_serializing_if = "Option::is_none")]
135    pub transaction_hash: Option<String>,
136    #[serde(rename = "companyId", skip_serializing_if = "Option::is_none")]
137    pub company_id: Option<Uuid>,
138    #[serde(rename = "userId", skip_serializing_if = "Option::is_none")]
139    pub user_id: Option<Uuid>,
140    #[serde(rename = "postedAt", skip_serializing_if = "Option::is_none")]
141    pub posted_at: Option<String>,
142}
143
144/// Fee transaction details
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct FeeTransaction {
147    pub amount: i64,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub description: Option<String>,
150    #[serde(rename = "companyId", skip_serializing_if = "Option::is_none")]
151    pub company_id: Option<Uuid>,
152    #[serde(rename = "userId", skip_serializing_if = "Option::is_none")]
153    pub user_id: Option<Uuid>,
154    #[serde(rename = "postedAt", skip_serializing_if = "Option::is_none")]
155    pub posted_at: Option<DateTime<Utc>>,
156}
157
158/// Transaction (discriminated union based on type)
159#[derive(Debug, Clone, Serialize, Deserialize)]
160#[serde(tag = "type")]
161#[allow(clippy::large_enum_variant)]
162pub enum Transaction {
163    #[serde(rename = "spend")]
164    Spend {
165        id: Uuid,
166        #[serde(flatten)]
167        spend: SpendTransaction,
168    },
169    #[serde(rename = "collateral")]
170    Collateral {
171        id: Uuid,
172        #[serde(flatten)]
173        collateral: CollateralTransaction,
174    },
175    #[serde(rename = "payment")]
176    Payment {
177        id: Uuid,
178        #[serde(flatten)]
179        payment: PaymentTransaction,
180    },
181    #[serde(rename = "fee")]
182    Fee {
183        id: Uuid,
184        #[serde(flatten)]
185        fee: FeeTransaction,
186    },
187}
188
189/// Query parameters for listing transactions
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct ListTransactionsParams {
192    #[serde(rename = "companyId", skip_serializing_if = "Option::is_none")]
193    pub company_id: Option<Uuid>,
194    #[serde(rename = "userId", skip_serializing_if = "Option::is_none")]
195    pub user_id: Option<Uuid>,
196    #[serde(rename = "cardId", skip_serializing_if = "Option::is_none")]
197    pub card_id: Option<Uuid>,
198    #[serde(
199        rename = "type",
200        skip_serializing_if = "Option::is_none",
201        serialize_with = "serialize_transaction_types"
202    )]
203    pub transaction_type: Option<Vec<TransactionType>>,
204    #[serde(rename = "transactionHash", skip_serializing_if = "Option::is_none")]
205    pub transaction_hash: Option<String>,
206    #[serde(rename = "authorizedBefore", skip_serializing_if = "Option::is_none")]
207    pub authorized_before: Option<DateTime<Utc>>,
208    #[serde(rename = "authorizedAfter", skip_serializing_if = "Option::is_none")]
209    pub authorized_after: Option<DateTime<Utc>>,
210    #[serde(rename = "postedBefore", skip_serializing_if = "Option::is_none")]
211    pub posted_before: Option<DateTime<Utc>>,
212    #[serde(rename = "postedAfter", skip_serializing_if = "Option::is_none")]
213    pub posted_after: Option<DateTime<Utc>>,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub cursor: Option<String>,
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub limit: Option<u32>,
218}
219
220fn serialize_transaction_types<S>(
221    types: &Option<Vec<TransactionType>>,
222    serializer: S,
223) -> Result<S::Ok, S::Error>
224where
225    S: serde::Serializer,
226{
227    use serde::ser::SerializeSeq;
228
229    match types {
230        Some(ref vec) => {
231            let mut seq = serializer.serialize_seq(Some(vec.len()))?;
232            for item in vec {
233                let s = match item {
234                    TransactionType::Spend => "spend",
235                    TransactionType::Collateral => "collateral",
236                    TransactionType::Payment => "payment",
237                    TransactionType::Fee => "fee",
238                };
239                seq.serialize_element(s)?;
240            }
241            seq.end()
242        }
243        None => serializer.serialize_none(),
244    }
245}
246
247/// Request to update a transaction
248#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct UpdateTransactionRequest {
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub memo: Option<String>,
252}
253
254/// Request to upload a receipt
255#[derive(Debug, Clone)]
256pub struct UploadReceiptRequest {
257    pub receipt: Vec<u8>,
258    pub file_name: String,
259}
260
261/// Response type for list of transactions
262pub type ListTransactionsResponse = Vec<Transaction>;