Compare commits
5 Commits
c0a444e833
...
c9fab18b83
Author | SHA1 | Date | |
---|---|---|---|
c9fab18b83 | |||
7b8c9453d6 | |||
07f08bd253 | |||
4d347a657c | |||
54cec9ac04 |
@ -1,35 +0,0 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use bunq::{BunqConfig, CsvType, StatementFormat};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let cfg = BunqConfig::load()?;
|
|
||||||
|
|
||||||
let client = cfg.install()?;
|
|
||||||
|
|
||||||
let accs = client.monetary_accounts()?;
|
|
||||||
|
|
||||||
let acc = &accs[0];
|
|
||||||
|
|
||||||
let stmt_id = client.request_statement(
|
|
||||||
acc,
|
|
||||||
StatementFormat::CSV(CsvType::Semicolon),
|
|
||||||
"2023-12-05",
|
|
||||||
"2023-12-15",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
println!("Waiting for statement {:?}...", stmt_id);
|
|
||||||
|
|
||||||
let mut ready = client.is_statement_ready(acc, stmt_id)?;
|
|
||||||
while !ready {
|
|
||||||
println!("Statement is not ready yet. Waiting 5 seconds...");
|
|
||||||
std::thread::sleep(Duration::from_secs(5));
|
|
||||||
ready = client.is_statement_ready(acc, stmt_id)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let contents = client.download_statement(acc, stmt_id)?;
|
|
||||||
|
|
||||||
println!("{contents}");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
0
src/installation.rs
Normal file
0
src/installation.rs
Normal file
19
src/lib.rs
19
src/lib.rs
@ -6,7 +6,7 @@ use rsa::RsaPrivateKey;
|
|||||||
use serde::{de::DeserializeOwned, Deserialize};
|
use serde::{de::DeserializeOwned, Deserialize};
|
||||||
|
|
||||||
pub use config::BunqConfig;
|
pub use config::BunqConfig;
|
||||||
pub use monetary_account::{CsvType, MonetaryAccount, StatementFormat};
|
pub use monetary_account::MonetaryAccount;
|
||||||
pub use payment::Payment;
|
pub use payment::Payment;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
@ -38,23 +38,6 @@ impl BunqClient {
|
|||||||
) -> anyhow::Result<Vec<Payment>> {
|
) -> anyhow::Result<Vec<Payment>> {
|
||||||
payment::get_from_to(self, acc, from, to)
|
payment::get_from_to(self, acc, from, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_statement(
|
|
||||||
&self,
|
|
||||||
acc: &MonetaryAccount,
|
|
||||||
format: StatementFormat,
|
|
||||||
date_start: &str,
|
|
||||||
date_end: &str,
|
|
||||||
) -> Result<i64> {
|
|
||||||
monetary_account::request_statement(self, acc, format, date_start, date_end)
|
|
||||||
}
|
|
||||||
pub fn is_statement_ready(&self, acc: &MonetaryAccount, statement_id: i64) -> Result<bool> {
|
|
||||||
monetary_account::is_statement_ready(self, acc, statement_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn download_statement(&self, acc: &MonetaryAccount, statement_id: i64) -> Result<String> {
|
|
||||||
monetary_account::download_statement(self, acc, statement_id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use isahc::http::StatusCode;
|
|
||||||
use isahc::{ReadResponseExt, RequestExt};
|
use isahc::{ReadResponseExt, RequestExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{deserialize_normal_response, sign, BunqClient, BASE_URL};
|
use crate::{BunqClient, BASE_URL};
|
||||||
|
|
||||||
pub(super) fn get(client: &BunqClient) -> anyhow::Result<Vec<MonetaryAccount>> {
|
pub(super) fn get(client: &BunqClient) -> anyhow::Result<Vec<MonetaryAccount>> {
|
||||||
let response = isahc::http::Request::get(format!(
|
let response = isahc::http::Request::get(format!(
|
||||||
@ -23,115 +18,6 @@ pub(super) fn get(client: &BunqClient) -> anyhow::Result<Vec<MonetaryAccount>> {
|
|||||||
Ok(accounts.response)
|
Ok(accounts.response)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn request_statement(
|
|
||||||
client: &BunqClient,
|
|
||||||
account: &MonetaryAccount,
|
|
||||||
format: StatementFormat,
|
|
||||||
start: &str,
|
|
||||||
end: &str,
|
|
||||||
) -> Result<i64> {
|
|
||||||
let statement_request = CreateStatementRequest {
|
|
||||||
statement_format: &format.to_string(),
|
|
||||||
regional_format: match format {
|
|
||||||
StatementFormat::CSV(CsvType::Comma) => "UK_US",
|
|
||||||
StatementFormat::CSV(CsvType::Semicolon) => "EUROPEAN",
|
|
||||||
_ => "",
|
|
||||||
},
|
|
||||||
date_start: start,
|
|
||||||
date_end: end,
|
|
||||||
include_attachment: match format {
|
|
||||||
StatementFormat::PDF(v) => v,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let body = serde_json::to_string(&statement_request)?;
|
|
||||||
let sig = sign(&body, &client.keypair)?;
|
|
||||||
|
|
||||||
let request_url = format!(
|
|
||||||
"{}/v1/user/{}/monetary-account/{}/customer-statement",
|
|
||||||
BASE_URL,
|
|
||||||
client.user_id,
|
|
||||||
account.id()
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut response = isahc::http::Request::post(request_url)
|
|
||||||
.header("X-Bunq-Client-Authentication", &client.token)
|
|
||||||
.header("X-Bunq-Client-Signature", &sig)
|
|
||||||
.body(body)?
|
|
||||||
.send()?;
|
|
||||||
|
|
||||||
if response.status() != StatusCode::OK {
|
|
||||||
return Err(anyhow!(response.text()?));
|
|
||||||
}
|
|
||||||
|
|
||||||
let response_body = response.text()?;
|
|
||||||
|
|
||||||
//let response_body = r#"{"Response":[{"Id":{"id":4203007}}]}"#;
|
|
||||||
//println!("{response_body}");
|
|
||||||
|
|
||||||
let r = deserialize_normal_response::<CreateStatementResponse>(&response_body)?.response;
|
|
||||||
|
|
||||||
let statement_id = r.id.id.id;
|
|
||||||
|
|
||||||
Ok(statement_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn is_statement_ready(
|
|
||||||
client: &BunqClient,
|
|
||||||
account: &MonetaryAccount,
|
|
||||||
statement_id: i64,
|
|
||||||
) -> Result<bool> {
|
|
||||||
let url = format!(
|
|
||||||
"{}/v1/user/{}/monetary-account/{}/customer-statement/{}",
|
|
||||||
BASE_URL,
|
|
||||||
client.user_id,
|
|
||||||
account.id(),
|
|
||||||
statement_id
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut response = isahc::http::Request::get(url)
|
|
||||||
.header("X-Bunq-Client-Authentication", &client.token)
|
|
||||||
.body(())?
|
|
||||||
.send()?;
|
|
||||||
|
|
||||||
let body = response.text()?;
|
|
||||||
|
|
||||||
let statement = deserialize_normal_response::<Vec<CustomerStatementWrapper>>(&body)?
|
|
||||||
.response
|
|
||||||
.iter()
|
|
||||||
.map(|v| v.customer_statement.clone())
|
|
||||||
.take(1)
|
|
||||||
.collect::<Vec<CustomerStatement>>()
|
|
||||||
.pop()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
//let statement = statements.first().unwrap();
|
|
||||||
|
|
||||||
Ok(statement.status == "COMPLETED")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn download_statement(
|
|
||||||
client: &BunqClient,
|
|
||||||
account: &MonetaryAccount,
|
|
||||||
id: i64,
|
|
||||||
) -> Result<String> {
|
|
||||||
let content_url = format!(
|
|
||||||
"{}/v1/user/{}/monetary-account/{}/customer-statement/{}/content",
|
|
||||||
BASE_URL,
|
|
||||||
client.user_id,
|
|
||||||
account.id(),
|
|
||||||
id
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut response = isahc::http::Request::get(content_url)
|
|
||||||
.header("X-Bunq-Client-Authentication", &client.token)
|
|
||||||
.body(())?
|
|
||||||
.send()?;
|
|
||||||
|
|
||||||
Ok(response.text()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub enum MonetaryAccount {
|
pub enum MonetaryAccount {
|
||||||
@ -139,15 +25,6 @@ pub enum MonetaryAccount {
|
|||||||
MonetaryAccountSavings(MonetaryAccountSavings),
|
MonetaryAccountSavings(MonetaryAccountSavings),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MonetaryAccount {
|
|
||||||
pub fn id(&self) -> i64 {
|
|
||||||
match &self {
|
|
||||||
MonetaryAccount::MonetaryAccountBank(b) => b.id,
|
|
||||||
MonetaryAccount::MonetaryAccountSavings(s) => s.id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct MonetaryAccountBank {
|
pub struct MonetaryAccountBank {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
@ -159,77 +36,3 @@ pub struct MonetaryAccountSavings {
|
|||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CsvType {
|
|
||||||
Comma,
|
|
||||||
Semicolon,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum StatementFormat {
|
|
||||||
CSV(CsvType),
|
|
||||||
MT940,
|
|
||||||
PDF(bool),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for StatementFormat {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let str = match self {
|
|
||||||
StatementFormat::CSV(_) => "CSV",
|
|
||||||
StatementFormat::MT940 => "MT940",
|
|
||||||
StatementFormat::PDF(_) => "PDF",
|
|
||||||
};
|
|
||||||
write!(f, "{}", str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for StatementFormat {
|
|
||||||
type Err = ();
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.to_ascii_uppercase().as_str() {
|
|
||||||
"CSV" => Ok(StatementFormat::CSV(CsvType::Semicolon)),
|
|
||||||
"MT940" => Ok(StatementFormat::MT940),
|
|
||||||
"PDF" => Ok(StatementFormat::PDF(false)),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct CustomerStatementWrapper {
|
|
||||||
customer_statement: CustomerStatement,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
pub struct CustomerStatement {
|
|
||||||
pub id: i64,
|
|
||||||
status: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct CreateStatementRequest<'a> {
|
|
||||||
statement_format: &'a str,
|
|
||||||
date_start: &'a str,
|
|
||||||
date_end: &'a str,
|
|
||||||
regional_format: &'a str,
|
|
||||||
include_attachment: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct CreateStatementResponse {
|
|
||||||
id: Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
//#[derive(Deserialize, Debug)]
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct Id {
|
|
||||||
id: Id2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct Id2 {
|
|
||||||
id: i64,
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user