akahu_client/models/
transaction.rs

1//! Rust structs representing the Transaction Model Akahu uses, the documentation
2//! for the Akahu model this is derived from is
3//! [here](https://developers.akahu.nz/docs/the-transaction-model).
4
5use serde::{Deserialize, Serialize};
6
7use crate::{AccountId, BankAccountNumber, CategoryId, ConnectionId, MerchantId, TransactionId};
8
9/// A transaction is a record of money moving between two accounts. Akahu can
10/// provide transaction data from connected accounts for all bank integrations
11/// and a selection of non-bank integrations. See the [Supported
12/// Integrations](https://developers.akahu.nz/docs/integrations) reference for
13/// the full list of integrations that have transaction data available.
14///
15/// In addition to the basic transaction data that Akahu retrieves from the
16/// connected institution (such as date, amount, description), Akahu enriches
17/// transactions with merchant and categorisation data where possible. More
18/// information on enrichment data is provided in detail in this document.
19///
20/// Transaction data is only available to apps with the TRANSACTIONS scope. In
21/// addition, further permissions are required to access enriched transaction
22/// data. Personal apps have full access to enriched transactions by default. If
23/// you have a full Akahu app and would like access to transaction data, get in
24/// touch via [hello@akahu.nz](mailto:hello@akahu.nz) or our [Slack
25/// workspace](http://slack.akahu.io/).
26///
27/// See our [Accessing Transactional
28/// Data](https://developers.akahu.nz/docs/accessing-transactional-data/) guide
29/// to learn how to retrieve transactions from Akahu's API.
30#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
31pub struct Transaction {
32    /// The `id` key is a unique identifier for the transaction in the Akahu
33    /// system. It is always be prefixed by trans_ so that you can tell that it
34    /// refers to a transaction.
35    #[serde(rename = "_id")]
36    pub id: TransactionId,
37
38    /// The `account` key indicates which account this transaction belongs to.
39    /// See our guide to Accessing Account Data to learn how to get this
40    /// account, and our Account Model docs to learn more about accounts.
41    #[serde(rename = "_account")]
42    pub account: AccountId,
43
44    /// This is the ID of provider that Akahu has retrieved this transaction
45    /// from. You can get a list of connections from our
46    /// [/connections](https://developers.akahu.nz/reference/get_connections)
47    /// endpoint.
48    #[serde(rename = "_connection")]
49    pub connection: ConnectionId,
50
51    /// The time that Akahu first saw this transaction (as an ISO 8601
52    /// timestamp). This is unrelated to the transaction date (when the
53    /// transaction occurred) because Akahu may have retrieved an old
54    /// transaction.
55    pub created_at: chrono::DateTime<chrono::Utc>,
56
57    /// The date that the transaction was posted with the account holder, as an
58    /// ISO 8601 timestamp. In many cases this will only be accurate to the day,
59    /// due to the level of detail provided by the bank.
60    pub date: chrono::DateTime<chrono::Utc>,
61
62    /// The transacton description as provided by the bank. Some minor cleanup
63    /// is done by Akahu (such as whitespace normalisation), but this value is
64    /// otherwise direct from the bank.
65    pub description: String,
66
67    /// The amount of money that was moved by this transaction.
68    #[serde(with = "rust_decimal::serde::arbitrary_precision")]
69    pub amount: rust_decimal::Decimal,
70
71    /// If available, the account balance immediately after this transaction was
72    /// made. This value is direct from the bank and not modified by Akahu.
73    #[serde(
74        default,
75        skip_serializing_if = "Option::is_none",
76        with = "rust_decimal::serde::arbitrary_precision_option"
77    )]
78    pub balance: Option<rust_decimal::Decimal>,
79
80    /// What sort of transaction this is. Akahu tries to find a specific transaction
81    /// type, falling back to "CREDIT" or "DEBIT" if nothing else is available.
82    ///
83    /// [<https://developers.akahu.nz/docs/the-transaction-model#type>]
84    #[serde(rename = "type")]
85    pub kind: TransactionKind,
86
87    /// This is data added by the Akahu enrichment engine. You must have
88    /// additional permissions to view this data.
89    ///
90    /// [<https://developers.akahu.nz/docs/the-transaction-model#enriched-transaction-data>]
91    #[serde(flatten, default, skip_serializing_if = "Option::is_none")]
92    pub enriched_data: Option<EnrichedTransactionData>,
93}
94
95/// What sort of transaction this is. Akahu tries to find a specific transaction
96/// type, falling back to "CREDIT" or "DEBIT" if nothing else is available.
97///
98/// [<https://developers.akahu.nz/docs/the-transaction-model#type>]
99#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
100pub enum TransactionKind {
101    /// Money has entered the account.
102    #[serde(rename = "CREDIT")]
103    Credit,
104    /// Money has left the account.
105    #[serde(rename = "DEBIT")]
106    Debit,
107    /// A payment to an external account.
108    #[serde(rename = "PAYMENT")]
109    Payment,
110    /// A transfer between accounts that are associated with the same credentials.
111    #[serde(rename = "TRANSFER")]
112    Transfer,
113    /// An automatic payment.
114    #[serde(rename = "STANDING ORDER")]
115    StandingOrder,
116    /// A payment made via the EFTPOS system.
117    #[serde(rename = "EFTPOS")]
118    Eftpos,
119    /// An interest payment from the account provider.
120    #[serde(rename = "INTEREST")]
121    Interest,
122    /// A fee from the account provider.
123    #[serde(rename = "FEE")]
124    Fee,
125    /// A tax payment.
126    #[serde(rename = "TAX")]
127    Tax,
128    /// A credit card payment.
129    #[serde(rename = "CREDIT CARD")]
130    CreditCard,
131    /// A direct debit payment.
132    #[serde(rename = "DIRECT DEBIT")]
133    DirectDebit,
134    /// A direct credit (someone paying into the account).
135    #[serde(rename = "DIRECT CREDIT")]
136    DirectCredit,
137    /// An ATM deposit or withdrawal.
138    #[serde(rename = "ATM")]
139    Atm,
140    /// A payment related to a loan.
141    #[serde(rename = "LOAN")]
142    Loan,
143}
144
145impl TransactionKind {
146    /// Get the transaction kind as a string slice.
147    pub const fn as_str(&self) -> &'static str {
148        match self {
149            Self::Credit => "CREDIT",
150            Self::Debit => "DEBIT",
151            Self::Payment => "PAYMENT",
152            Self::Transfer => "TRANSFER",
153            Self::StandingOrder => "STANDING ORDER",
154            Self::Eftpos => "EFTPOS",
155            Self::Interest => "INTEREST",
156            Self::Fee => "FEE",
157            Self::Tax => "TAX",
158            Self::CreditCard => "CREDIT CARD",
159            Self::DirectDebit => "DIRECT DEBIT",
160            Self::DirectCredit => "DIRECT CREDIT",
161            Self::Atm => "ATM",
162            Self::Loan => "LOAN",
163        }
164    }
165
166    /// Get the transaction kind as bytes.
167    pub const fn as_bytes(&self) -> &'static [u8] {
168        self.as_str().as_bytes()
169    }
170}
171
172impl std::str::FromStr for TransactionKind {
173    type Err = ();
174    fn from_str(s: &str) -> Result<Self, Self::Err> {
175        match s {
176            "CREDIT" => Ok(Self::Credit),
177            "DEBIT" => Ok(Self::Debit),
178            "PAYMENT" => Ok(Self::Payment),
179            "TRANSFER" => Ok(Self::Transfer),
180            "STANDING ORDER" => Ok(Self::StandingOrder),
181            "EFTPOS" => Ok(Self::Eftpos),
182            "INTEREST" => Ok(Self::Interest),
183            "FEE" => Ok(Self::Fee),
184            "TAX" => Ok(Self::Tax),
185            "CREDIT CARD" => Ok(Self::CreditCard),
186            "DIRECT DEBIT" => Ok(Self::DirectDebit),
187            "DIRECT CREDIT" => Ok(Self::DirectCredit),
188            "ATM" => Ok(Self::Atm),
189            "LOAN" => Ok(Self::Loan),
190            _ => Err(()),
191        }
192    }
193}
194
195impl std::convert::TryFrom<String> for TransactionKind {
196    type Error = ();
197    fn try_from(value: String) -> Result<Self, Self::Error> {
198        value.parse()
199    }
200}
201
202impl std::convert::TryFrom<&str> for TransactionKind {
203    type Error = ();
204    fn try_from(value: &str) -> Result<Self, Self::Error> {
205        value.parse()
206    }
207}
208
209impl std::fmt::Display for TransactionKind {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        write!(f, "{}", self.as_str())
212    }
213}
214
215/// This is data added by the Akahu enrichment engine. You must have additional
216/// permissions to view this data.
217///
218/// [<https://developers.akahu.nz/docs/the-transaction-model#enriched-transaction-data>]
219#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
220pub struct EnrichedTransactionData {
221    /// Category information for this transaction
222    pub category: TransactionCategory,
223    /// Merchant information for this transaction
224    pub merchant: TransactionMerchant,
225}
226
227/// Transaction category information from Akahu enrichment.
228///
229/// Categories are based on the New Zealand Financial Category Codes (NZFCC) standard.
230#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
231pub struct TransactionCategory {
232    /// Unique category identifier
233    #[serde(rename = "_id")]
234    pub id: CategoryId,
235    /// NZFCC category code
236    pub name: nzfcc::NzfccCode,
237    /// Category groupings
238    pub groups: TransactionGroups,
239}
240
241/// Category groupings for different classification systems.
242#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
243pub struct TransactionGroups {
244    /// Personal finance category group
245    pub personal_finance: PersonalFinanceGroup,
246    /// Other category groupings (future extension)
247    #[serde(flatten)]
248    pub other_groups: Option<std::collections::HashMap<String, serde_json::Value>>,
249}
250
251/// Personal finance category group.
252#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
253pub struct PersonalFinanceGroup {
254    /// Category group identifier
255    #[serde(rename = "_id")]
256    pub id: CategoryId,
257    /// Category group name
258    pub name: nzfcc::CategoryGroup,
259}
260
261/// Akahu defines a merchant as the business who was party to this transaction.
262/// For example, "The Warehouse" is a merchant.
263///
264/// Merchant data is provided as a name, an optional website, and a merchant
265///
266/// [<https://developers.akahu.nz/docs/the-transaction-model#merchant>]
267#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
268pub struct TransactionMerchant {
269    /// A unique identifier for the merchant in the Akahu system.
270    ///
271    /// Will always be prefixed with `_merchant`.
272    #[serde(rename = "_id")]
273    pub id: MerchantId,
274    /// The name of the merchant, for example "The Warehouse".
275    pub name: String,
276    /// The merchant's website, if available.
277    #[serde(default, skip_serializing_if = "Option::is_none")]
278    pub website: Option<url::Url>,
279}
280
281/// This is other metadata that we extract from the transaction, including the
282/// following fields (where possible).
283///
284/// [<https://developers.akahu.nz/docs/the-transaction-model#meta>]
285#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
286pub struct TransactionMeta {
287    /// Fields that are entered when a payment is made.
288    #[serde(default, skip_serializing_if = "Option::is_none")]
289    pub particulars: Option<String>,
290
291    /// Fields that are entered when a payment is made.
292    #[serde(default, skip_serializing_if = "Option::is_none")]
293    pub code: Option<String>,
294
295    /// Fields that are entered when a payment is made.
296    #[serde(default, skip_serializing_if = "Option::is_none")]
297    pub reference: Option<String>,
298
299    /// The formatted NZ bank account number of the other party to this transaction.
300    #[serde(default, skip_serializing_if = "Option::is_none")]
301    pub other_account: Option<BankAccountNumber>,
302
303    /// If this transaction was made in another currency, details about the currency conversion.
304    #[serde(default, skip_serializing_if = "Option::is_none")]
305    pub conversion: Option<TransactionConversion>,
306
307    /// If this transaction was made with a credit or debit card, we may be able to extract the
308    /// card number used to make the transaction.
309    #[serde(default, skip_serializing_if = "Option::is_none")]
310    pub card_suffix: Option<String>,
311
312    /// URL of a .png image for this transaction. This is typically the logo of the transaction merchant.
313    /// If no logo is available, a placeholder image is provided.
314    #[serde(default, skip_serializing_if = "Option::is_none")]
315    pub logo: Option<url::Url>,
316}
317
318/// Details about a currency conversion for a transaction made in another currency.
319#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
320pub struct TransactionConversion {
321    /// The amount in the foreign currency.
322    #[serde(with = "rust_decimal::serde::arbitrary_precision")]
323    pub amount: rust_decimal::Decimal,
324    /// The currency code of the foreign currency (e.g. "GBP").
325    pub currency: iso_currency::Currency,
326    /// The conversion rate applied.
327    #[serde(with = "rust_decimal::serde::arbitrary_precision")]
328    pub rate: rust_decimal::Decimal,
329}
330
331/// A pending transaction that has not yet been settled.
332///
333/// Pending transactions are not stable - the date or description may change due to
334/// the unreliable nature of underlying NZ bank data. They are not assigned unique
335/// identifiers and are not enriched by Akahu.
336///
337/// [<https://developers.akahu.nz/docs/accessing-transactional-data#pending-transactions>]
338#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
339pub struct PendingTransaction {
340    /// The account this pending transaction belongs to.
341    #[serde(rename = "_account")]
342    pub account: AccountId,
343
344    /// This is the ID of provider that Akahu has retrieved this transaction from.
345    #[serde(rename = "_connection")]
346    pub connection: ConnectionId,
347
348    /// The time that this pending transaction was last updated (as an ISO 8601 timestamp).
349    pub updated_at: chrono::DateTime<chrono::Utc>,
350
351    /// The date that the transaction was posted with the account holder, as an
352    /// ISO 8601 timestamp. May change before the transaction settles.
353    pub date: chrono::DateTime<chrono::Utc>,
354
355    /// The transaction description as provided by the bank. May change before settlement.
356    pub description: String,
357
358    /// The amount of money that will be moved by this transaction.
359    #[serde(with = "rust_decimal::serde::arbitrary_precision")]
360    pub amount: rust_decimal::Decimal,
361
362    /// What sort of transaction this is.
363    #[serde(rename = "type")]
364    pub kind: TransactionKind,
365
366    /// Additional metadata about the transaction.
367    #[serde(flatten, default, skip_serializing_if = "Option::is_none")]
368    pub meta: Option<TransactionMeta>,
369}