Split in main and lib
This commit is contained in:
parent
157cac6fb3
commit
3bae8420d9
290
src/lib.rs
Normal file
290
src/lib.rs
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use isahc::prelude::*;
|
||||||
|
use openssl::hash::MessageDigest;
|
||||||
|
use openssl::pkey::PKey;
|
||||||
|
use openssl::rsa::Rsa;
|
||||||
|
use openssl::sign::Signer;
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
|
const BASE: &str = "https://api.bunq.com";
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Installation<'a> {
|
||||||
|
client_public_key: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Token {
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
struct InstallationResponse {
|
||||||
|
token: Token,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct DeviceServer<'a> {
|
||||||
|
description: &'a str,
|
||||||
|
secret: &'a str,
|
||||||
|
permitted_ips: &'a [&'a str],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct SessionServer<'a> {
|
||||||
|
secret: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
struct SessionServerResponse {
|
||||||
|
token: Token,
|
||||||
|
user_person: UserPerson,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UserPerson {
|
||||||
|
id: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign<K: openssl::pkey::HasPrivate>(body: &str, key: &PKey<K>) -> Result<String> {
|
||||||
|
let mut signer = Signer::new(MessageDigest::sha256(), key)?;
|
||||||
|
|
||||||
|
let sig = signer.sign_oneshot_to_vec(body.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(base64::encode(&sig))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
struct RawResponse {
|
||||||
|
response: Vec<serde_json::Value>,
|
||||||
|
pagination: Option<Pagination>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Response<T> {
|
||||||
|
response: T,
|
||||||
|
pagination: Option<Pagination>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawResponse {
|
||||||
|
fn decode_retarded<T: DeserializeOwned>(self) -> Result<Response<T>> {
|
||||||
|
let mut map = serde_json::Map::new();
|
||||||
|
for e in self.response {
|
||||||
|
if let serde_json::Value::Object(e) = e {
|
||||||
|
let (k, v) = e
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("malformed response"))?;
|
||||||
|
map.insert(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Response {
|
||||||
|
response: serde_json::from_value(map.into())?,
|
||||||
|
pagination: self.pagination,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_retarded_response<T: DeserializeOwned>(r: &str) -> Result<Response<T>> {
|
||||||
|
let r: RawResponse = serde_json::from_str(r)?;
|
||||||
|
r.decode_retarded()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_normal_response<T: DeserializeOwned>(r: &str) -> Result<Response<T>> {
|
||||||
|
let r: RawResponse = serde_json::from_str(r)?;
|
||||||
|
Ok(Response {
|
||||||
|
response: serde_json::from_value(r.response.into())?,
|
||||||
|
pagination: r.pagination,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
struct AppState {
|
||||||
|
token: String,
|
||||||
|
pem_private: String,
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
pub struct BunqConfig {
|
||||||
|
api_key: String,
|
||||||
|
state: Option<AppState>,
|
||||||
|
}
|
||||||
|
pub struct BunqConfigReady {
|
||||||
|
token: String,
|
||||||
|
keypair: PKey<openssl::pkey::Private>,
|
||||||
|
user_id: i64,
|
||||||
|
}
|
||||||
|
impl BunqConfig {
|
||||||
|
pub fn load() -> Result<BunqConfig> {
|
||||||
|
Ok(confy::load("bunq-rs")?)
|
||||||
|
}
|
||||||
|
pub fn save(&self) -> Result<()> {
|
||||||
|
confy::store("bunq-rs", self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn install(mut self) -> Result<BunqConfigReady> {
|
||||||
|
let api_key = &self.api_key;
|
||||||
|
|
||||||
|
let keypair = if let Some(state) = &self.state {
|
||||||
|
PKey::private_key_from_pem(state.pem_private.as_bytes())?
|
||||||
|
} else {
|
||||||
|
let rsa = Rsa::generate(2048)?;
|
||||||
|
let pem_private = rsa.private_key_to_pem()?;
|
||||||
|
let pem_private = String::from_utf8(pem_private)?;
|
||||||
|
|
||||||
|
let keypair = PKey::from_rsa(rsa)?;
|
||||||
|
|
||||||
|
let pem_public = String::from_utf8(keypair.public_key_to_pem()?)?;
|
||||||
|
|
||||||
|
let body = Installation {
|
||||||
|
client_public_key: &pem_public,
|
||||||
|
};
|
||||||
|
let response = isahc::post(
|
||||||
|
format!("{}/v1/installation", BASE),
|
||||||
|
serde_json::to_string(&body)?,
|
||||||
|
)?
|
||||||
|
.text()?;
|
||||||
|
let response: InstallationResponse = deserialize_retarded_response(&response)?.response;
|
||||||
|
let token = response.token.token;
|
||||||
|
|
||||||
|
let body = DeviceServer {
|
||||||
|
description: "awesome",
|
||||||
|
secret: &api_key,
|
||||||
|
permitted_ips: &["31.21.118.143", "*"],
|
||||||
|
};
|
||||||
|
let body = serde_json::to_string(&body)?;
|
||||||
|
let mut response = isahc::http::Request::post(format!("{}/v1/device-server", BASE))
|
||||||
|
.header("X-Bunq-Client-Authentication", &token)
|
||||||
|
.body(body)?
|
||||||
|
.send()?;
|
||||||
|
println!("{}", response.text()?);
|
||||||
|
|
||||||
|
self.state = Some(AppState { pem_private, token });
|
||||||
|
self.save()?;
|
||||||
|
|
||||||
|
keypair
|
||||||
|
};
|
||||||
|
let token = self.state.unwrap().token;
|
||||||
|
let body = SessionServer { secret: &api_key };
|
||||||
|
let body = serde_json::to_string(&body)?;
|
||||||
|
let sig = sign(&body, &keypair)?;
|
||||||
|
let response = isahc::http::Request::post(format!("{}/v1/session-server", BASE))
|
||||||
|
.header("X-Bunq-Client-Authentication", &token)
|
||||||
|
.header("X-Bunq-Client-Signature", &sig)
|
||||||
|
.body(body)?
|
||||||
|
.send()?
|
||||||
|
.text()?;
|
||||||
|
let r: SessionServerResponse = deserialize_retarded_response(&response)?.response;
|
||||||
|
Ok(BunqConfigReady {
|
||||||
|
keypair,
|
||||||
|
token: r.token.token,
|
||||||
|
user_id: r.user_person.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BunqConfigReady {
|
||||||
|
pub fn monetary_accounts(&self) -> Result<Vec<MonetaryAccountBank>> {
|
||||||
|
let response = isahc::http::Request::get(format!(
|
||||||
|
"{}/v1/user/{}/monetary-account",
|
||||||
|
BASE, self.user_id
|
||||||
|
))
|
||||||
|
.header("X-Bunq-Client-Authentication", &self.token)
|
||||||
|
.body(())?
|
||||||
|
.send()?
|
||||||
|
.text()?;
|
||||||
|
Ok(
|
||||||
|
deserialize_normal_response::<Vec<MonetaryAccount>>(&response)?
|
||||||
|
.response
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| m.monetary_account_bank)
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn payments(&self, acc: &MonetaryAccountBank) -> Result<Vec<Payment>> {
|
||||||
|
let next_page = |url: &str| -> Result<(_, _)> {
|
||||||
|
let response = isahc::http::Request::get(url)
|
||||||
|
.header("X-Bunq-Client-Authentication", &self.token)
|
||||||
|
.body(())?
|
||||||
|
.send()?
|
||||||
|
.text()?;
|
||||||
|
let Response {
|
||||||
|
response,
|
||||||
|
pagination,
|
||||||
|
} = deserialize_normal_response::<Vec<PaymentPayment>>(&response)?;
|
||||||
|
Ok((
|
||||||
|
response.into_iter().map(|p| p.payment).collect(),
|
||||||
|
pagination,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
let mut url = format!(
|
||||||
|
"/v1/user/{}/monetary-account/{}/payment",
|
||||||
|
self.user_id, acc.id
|
||||||
|
);
|
||||||
|
let mut all = Vec::new();
|
||||||
|
loop {
|
||||||
|
let (mut payments, pag) = next_page(&format!("{}{}", BASE, url))?;
|
||||||
|
all.append(&mut payments);
|
||||||
|
if let Some(Pagination{older_url: Some(older_url),..}) = pag {
|
||||||
|
url = older_url;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(all)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct LabelMonetaryAccount {
|
||||||
|
pub iban: Option<String>,
|
||||||
|
pub display_name: String,
|
||||||
|
pub merchant_category_code: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Amount {
|
||||||
|
pub value: String,
|
||||||
|
pub currency: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
struct PaymentPayment {
|
||||||
|
payment: Payment,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct Pagination {
|
||||||
|
future_url: Option<String>,
|
||||||
|
newer_url: Option<String>,
|
||||||
|
older_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Payment {
|
||||||
|
pub alias: LabelMonetaryAccount,
|
||||||
|
pub counterparty_alias: LabelMonetaryAccount,
|
||||||
|
pub amount: Amount,
|
||||||
|
pub balance_after_mutation: Amount,
|
||||||
|
pub created: String,
|
||||||
|
pub updated: String,
|
||||||
|
pub description: String,
|
||||||
|
pub id: i64,
|
||||||
|
pub monetary_account_id: i64,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub type_: String,
|
||||||
|
pub sub_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
struct MonetaryAccount {
|
||||||
|
monetary_account_bank: MonetaryAccountBank,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct MonetaryAccountBank {
|
||||||
|
pub id: i64,
|
||||||
|
pub description: String,
|
||||||
|
}
|
||||||
|
|
293
src/main.rs
293
src/main.rs
@ -1,297 +1,8 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use isahc::prelude::*;
|
use bunqledger::BunqConfig;
|
||||||
use openssl::hash::MessageDigest;
|
|
||||||
use openssl::pkey::PKey;
|
|
||||||
use openssl::rsa::Rsa;
|
|
||||||
use openssl::sign::{Signer, Verifier};
|
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
||||||
|
|
||||||
const BASE: &str = "https://api.bunq.com";
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct Installation<'a> {
|
|
||||||
client_public_key: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Token {
|
|
||||||
token: String,
|
|
||||||
}
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct InstallationResponse {
|
|
||||||
token: Token,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct DeviceServer<'a> {
|
|
||||||
description: &'a str,
|
|
||||||
secret: &'a str,
|
|
||||||
permitted_ips: &'a [&'a str],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct SessionServer<'a> {
|
|
||||||
secret: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct SessionServerResponse {
|
|
||||||
token: Token,
|
|
||||||
user_person: UserPerson,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct UserPerson {
|
|
||||||
id: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sign<K: openssl::pkey::HasPrivate>(body: &str, key: &PKey<K>) -> Result<String> {
|
|
||||||
let mut signer = Signer::new(MessageDigest::sha256(), key)?;
|
|
||||||
|
|
||||||
let sig = signer.sign_oneshot_to_vec(body.as_bytes())?;
|
|
||||||
|
|
||||||
Ok(base64::encode(&sig))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct RawResponse {
|
|
||||||
response: Vec<serde_json::Value>,
|
|
||||||
pagination: Option<Pagination>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Response<T> {
|
|
||||||
response: T,
|
|
||||||
pagination: Option<Pagination>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawResponse {
|
|
||||||
fn decode_retarded<T: DeserializeOwned>(self) -> Result<Response<T>> {
|
|
||||||
let mut map = serde_json::Map::new();
|
|
||||||
for e in self.response {
|
|
||||||
if let serde_json::Value::Object(e) = e {
|
|
||||||
let (k, v) = e
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("malformed response"))?;
|
|
||||||
map.insert(k, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Response {
|
|
||||||
response: serde_json::from_value(map.into())?,
|
|
||||||
pagination: self.pagination,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_retarded_response<T: DeserializeOwned>(r: &str) -> Result<Response<T>> {
|
|
||||||
let r: RawResponse = serde_json::from_str(r)?;
|
|
||||||
r.decode_retarded()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_normal_response<T: DeserializeOwned>(r: &str) -> Result<Response<T>> {
|
|
||||||
let r: RawResponse = serde_json::from_str(r)?;
|
|
||||||
Ok(Response {
|
|
||||||
response: serde_json::from_value(r.response.into())?,
|
|
||||||
pagination: r.pagination,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
|
||||||
struct AppState {
|
|
||||||
token: String,
|
|
||||||
pem_private: String,
|
|
||||||
}
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
|
||||||
struct AppConfig {
|
|
||||||
api_key: String,
|
|
||||||
state: Option<AppState>,
|
|
||||||
}
|
|
||||||
struct AppConfigReady {
|
|
||||||
api_key: String,
|
|
||||||
token: String,
|
|
||||||
keypair: PKey<openssl::pkey::Private>,
|
|
||||||
user_id: i64,
|
|
||||||
}
|
|
||||||
impl AppConfig {
|
|
||||||
pub fn load() -> Result<AppConfig> {
|
|
||||||
Ok(confy::load("bunqledger")?)
|
|
||||||
}
|
|
||||||
pub fn save(&self) -> Result<()> {
|
|
||||||
confy::store("bunqledger", self)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn install(mut self) -> Result<AppConfigReady> {
|
|
||||||
let api_key = &self.api_key;
|
|
||||||
|
|
||||||
let keypair = if let Some(state) = &self.state {
|
|
||||||
PKey::private_key_from_pem(state.pem_private.as_bytes())?
|
|
||||||
} else {
|
|
||||||
let rsa = Rsa::generate(2048)?;
|
|
||||||
let pem_private = rsa.private_key_to_pem()?;
|
|
||||||
let pem_private = String::from_utf8(pem_private)?;
|
|
||||||
|
|
||||||
let keypair = PKey::from_rsa(rsa)?;
|
|
||||||
|
|
||||||
let pem_public = String::from_utf8(keypair.public_key_to_pem()?)?;
|
|
||||||
|
|
||||||
let body = Installation {
|
|
||||||
client_public_key: &pem_public,
|
|
||||||
};
|
|
||||||
let response = isahc::post(
|
|
||||||
format!("{}/v1/installation", BASE),
|
|
||||||
serde_json::to_string(&body)?,
|
|
||||||
)?
|
|
||||||
.text()?;
|
|
||||||
let response: InstallationResponse = deserialize_retarded_response(&response)?.response;
|
|
||||||
let token = response.token.token;
|
|
||||||
|
|
||||||
let body = DeviceServer {
|
|
||||||
description: "awesome",
|
|
||||||
secret: &api_key,
|
|
||||||
permitted_ips: &["31.21.118.143", "*"],
|
|
||||||
};
|
|
||||||
let body = serde_json::to_string(&body)?;
|
|
||||||
let mut response = isahc::http::Request::post(format!("{}/v1/device-server", BASE))
|
|
||||||
.header("X-Bunq-Client-Authentication", &token)
|
|
||||||
.body(body)?
|
|
||||||
.send()?;
|
|
||||||
println!("{}", response.text()?);
|
|
||||||
|
|
||||||
self.state = Some(AppState { pem_private, token });
|
|
||||||
self.save()?;
|
|
||||||
|
|
||||||
keypair
|
|
||||||
};
|
|
||||||
let token = self.state.unwrap().token;
|
|
||||||
let body = SessionServer { secret: &api_key };
|
|
||||||
let body = serde_json::to_string(&body)?;
|
|
||||||
let sig = sign(&body, &keypair)?;
|
|
||||||
let response = isahc::http::Request::post(format!("{}/v1/session-server", BASE))
|
|
||||||
.header("X-Bunq-Client-Authentication", &token)
|
|
||||||
.header("X-Bunq-Client-Signature", &sig)
|
|
||||||
.body(body)?
|
|
||||||
.send()?
|
|
||||||
.text()?;
|
|
||||||
let r: SessionServerResponse = deserialize_retarded_response(&response)?.response;
|
|
||||||
Ok(AppConfigReady {
|
|
||||||
api_key: self.api_key,
|
|
||||||
keypair,
|
|
||||||
token: r.token.token,
|
|
||||||
user_id: r.user_person.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AppConfigReady {
|
|
||||||
pub fn monetary_accounts(&self) -> Result<Vec<MonetaryAccountBank>> {
|
|
||||||
let response = isahc::http::Request::get(format!(
|
|
||||||
"{}/v1/user/{}/monetary-account",
|
|
||||||
BASE, self.user_id
|
|
||||||
))
|
|
||||||
.header("X-Bunq-Client-Authentication", &self.token)
|
|
||||||
.body(())?
|
|
||||||
.send()?
|
|
||||||
.text()?;
|
|
||||||
Ok(
|
|
||||||
deserialize_normal_response::<Vec<MonetaryAccount>>(&response)?
|
|
||||||
.response
|
|
||||||
.into_iter()
|
|
||||||
.map(|m| m.monetary_account_bank)
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pub fn payments(&self, acc: &MonetaryAccountBank) -> Result<Vec<Payment>> {
|
|
||||||
let next_page = |url: &str| -> Result<(_, _)> {
|
|
||||||
let response = isahc::http::Request::get(url)
|
|
||||||
.header("X-Bunq-Client-Authentication", &self.token)
|
|
||||||
.body(())?
|
|
||||||
.send()?
|
|
||||||
.text()?;
|
|
||||||
let Response {
|
|
||||||
response,
|
|
||||||
pagination,
|
|
||||||
} = deserialize_normal_response::<Vec<PaymentPayment>>(&response)?;
|
|
||||||
Ok((
|
|
||||||
response.into_iter().map(|p| p.payment).collect(),
|
|
||||||
pagination,
|
|
||||||
))
|
|
||||||
};
|
|
||||||
let mut url = format!(
|
|
||||||
"/v1/user/{}/monetary-account/{}/payment",
|
|
||||||
self.user_id, acc.id
|
|
||||||
);
|
|
||||||
let mut all = Vec::new();
|
|
||||||
loop {
|
|
||||||
let (mut payments, pag) = next_page(&format!("{}{}", BASE, url))?;
|
|
||||||
all.append(&mut payments);
|
|
||||||
if let Some(Pagination{older_url: Some(older_url),..}) = pag {
|
|
||||||
url = older_url;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(all)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct LabelMonetaryAccount {
|
|
||||||
iban: Option<String>,
|
|
||||||
display_name: String,
|
|
||||||
merchant_category_code: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct Amount {
|
|
||||||
value: String,
|
|
||||||
currency: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct PaymentPayment {
|
|
||||||
payment: Payment,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct Pagination {
|
|
||||||
future_url: Option<String>,
|
|
||||||
newer_url: Option<String>,
|
|
||||||
older_url: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct Payment {
|
|
||||||
alias: LabelMonetaryAccount,
|
|
||||||
counterparty_alias: LabelMonetaryAccount,
|
|
||||||
amount: Amount,
|
|
||||||
balance_after_mutation: Amount,
|
|
||||||
created: String,
|
|
||||||
updated: String,
|
|
||||||
description: String,
|
|
||||||
id: i64,
|
|
||||||
monetary_account_id: i64,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
type_: String,
|
|
||||||
sub_type: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct MonetaryAccount {
|
|
||||||
monetary_account_bank: MonetaryAccountBank,
|
|
||||||
}
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
struct MonetaryAccountBank {
|
|
||||||
id: i64,
|
|
||||||
description: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let cfg = AppConfig::load()?;
|
let cfg = BunqConfig::load()?;
|
||||||
|
|
||||||
let cfg = cfg.install()?;
|
let cfg = cfg.install()?;
|
||||||
let accs = cfg.monetary_accounts()?;
|
let accs = cfg.monetary_accounts()?;
|
||||||
|
Loading…
Reference in New Issue
Block a user