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}