taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

commit 301a9af06c7d39bf5b71116501c7970202bfd063
parent f0433106c1da705c9f76d8500c9918910ecb5240
Author: Antoine A <>
Date:   Tue, 21 Jan 2025 14:25:12 +0100

magnet-bank: dev transfer cmd and magnet-bank payto

Diffstat:
MCargo.lock | 1+
Mcommon/taler-api/src/lib.rs | 10+++++-----
Mcommon/taler-api/tests/api.rs | 9++++-----
Mcommon/taler-api/tests/common/db.rs | 20++++++++------------
Mcommon/taler-api/tests/common/mod.rs | 27+++++++++------------------
Mcommon/taler-common/Cargo.toml | 1+
Mcommon/taler-common/src/config.rs | 4++--
Mcommon/taler-common/src/types/amount.rs | 11++++++++++-
Mcommon/taler-common/src/types/payto.rs | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mcommon/taler-common/src/types/timestamp.rs | 13++++++++++++-
Mwire-gateway/magnet-bank/src/db.rs | 56+++++++++++++++++++++++++++++++++++++++++---------------
Mwire-gateway/magnet-bank/src/dev.rs | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mwire-gateway/magnet-bank/src/lib.rs | 20+++++++++++---------
Mwire-gateway/magnet-bank/src/magnet.rs | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mwire-gateway/magnet-bank/src/magnet/error.rs | 31++++++++++++-------------------
Mwire-gateway/magnet-bank/src/wire_gateway.rs | 33+++++++++++++++------------------
Mwire-gateway/magnet-bank/tests/api.rs | 11+++++------
17 files changed, 488 insertions(+), 212 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -2575,6 +2575,7 @@ dependencies = [ "rand", "serde", "serde_json", + "serde_path_to_error", "serde_urlencoded", "serde_with", "sqlx", diff --git a/common/taler-api/src/lib.rs b/common/taler-api/src/lib.rs @@ -51,7 +51,7 @@ use taler_common::{ error_code::ErrorCode, types::{ amount::Amount, - payto::{GenericPaytoImpl, PaytoImpl}, + payto::{AnyPayto, PaytoImpl}, }, }; use tokio::{ @@ -195,19 +195,19 @@ pub trait WireGatewayImpl<P: PaytoImpl>: Send + Sync { &self, page: Page, status: Option<TransferState>, - ) -> impl std::future::Future<Output = ApiResult<TransferList<GenericPaytoImpl>>> + Send; + ) -> impl std::future::Future<Output = ApiResult<TransferList<AnyPayto>>> + Send; fn transfer_by_id( &self, id: u64, - ) -> impl std::future::Future<Output = ApiResult<Option<TransferStatus<GenericPaytoImpl>>>> + Send; + ) -> impl std::future::Future<Output = ApiResult<Option<TransferStatus<AnyPayto>>>> + Send; fn outgoing_history( &self, params: History, - ) -> impl std::future::Future<Output = ApiResult<OutgoingHistory<GenericPaytoImpl>>> + Send; + ) -> impl std::future::Future<Output = ApiResult<OutgoingHistory<AnyPayto>>> + Send; fn incoming_history( &self, params: History, - ) -> impl std::future::Future<Output = ApiResult<IncomingHistory<GenericPaytoImpl>>> + Send; + ) -> impl std::future::Future<Output = ApiResult<IncomingHistory<AnyPayto>>> + Send; fn add_incoming_reserve( &self, req: AddIncomingRequest<P>, diff --git a/common/taler-api/tests/api.rs b/common/taler-api/tests/api.rs @@ -23,7 +23,7 @@ use taler_common::{ error_code::ErrorCode, types::{ amount::amount, - payto::{payto, GenericPaytoImpl}, + payto::{payto, AnyPayto}, url, }, }; @@ -71,8 +71,7 @@ async fn config() { #[tokio::test] async fn transfer() { let (server, _) = setup().await; - transfer_routine::<GenericPaytoImpl>(&server, TransferState::success, &payto("payto://test")) - .await; + transfer_routine::<AnyPayto>(&server, TransferState::success, &payto("payto://test")).await; } #[tokio::test] @@ -80,7 +79,7 @@ async fn outgoing_history() { let (server, _) = setup().await; server.get("/history/outgoing").await.assert_no_content(); - routine_pagination::<OutgoingHistory<GenericPaytoImpl>, _>( + routine_pagination::<OutgoingHistory<AnyPayto>, _>( &server, "/history/outgoing", |it| { @@ -109,5 +108,5 @@ async fn outgoing_history() { #[tokio::test] async fn admin_add_incoming() { let (server, _) = setup().await; - admin_add_incoming_routine::<GenericPaytoImpl>(&server, &payto("payto://test")).await; + admin_add_incoming_routine::<AnyPayto>(&server, &payto("payto://test")).await; } diff --git a/common/taler-api/tests/common/db.rs b/common/taler-api/tests/common/db.rs @@ -23,11 +23,7 @@ use taler_common::{ IncomingBankTransaction, OutgoingBankTransaction, TransferListStatus, TransferRequest, TransferResponse, TransferState, TransferStatus, }, - types::{ - amount::Amount, - payto::{GenericPayto, GenericPaytoImpl}, - timestamp::Timestamp, - }, + types::{amount::Amount, payto::{AnyPayto, Payto}, timestamp::Timestamp}, }; use tokio::sync::watch::{Receiver, Sender}; @@ -53,7 +49,7 @@ pub enum TransferResult { pub async fn transfer( db: &PgPool, - transfer: TransferRequest<GenericPaytoImpl>, + transfer: TransferRequest<AnyPayto>, ) -> sqlx::Result<TransferResult> { sqlx::query( " @@ -87,7 +83,7 @@ pub async fn transfer_page( status: &Option<TransferState>, params: &Page, currency: &str, -) -> sqlx::Result<Vec<TransferListStatus<GenericPaytoImpl>>> { +) -> sqlx::Result<Vec<TransferListStatus<AnyPayto>>> { page( db, "transfer_id", @@ -127,7 +123,7 @@ pub async fn transfer_by_id( db: &PgPool, id: u64, currency: &str, -) -> sqlx::Result<Option<TransferStatus<GenericPaytoImpl>>> { +) -> sqlx::Result<Option<TransferStatus<AnyPayto>>> { sqlx::query( " SELECT @@ -163,7 +159,7 @@ pub async fn outgoing_page( params: &History, currency: &str, listen: impl FnOnce() -> Receiver<i64>, -) -> sqlx::Result<Vec<OutgoingBankTransaction<GenericPaytoImpl>>> { +) -> sqlx::Result<Vec<OutgoingBankTransaction<AnyPayto>>> { history( db, "transfer_id", @@ -206,7 +202,7 @@ pub enum AddIncomingResult { pub async fn add_incoming( db: &PgPool, amount: &Amount, - debit_account: &GenericPayto, + debit_account: &Payto<AnyPayto>, subject: &str, timestamp: &Timestamp, kind: IncomingType, @@ -221,7 +217,7 @@ pub async fn add_incoming( .bind(key.as_slice()) .bind(subject) .bind_amount(amount) - .bind(debit_account.raw()) + .bind(debit_account .raw()) .bind_timestamp(timestamp) .bind(kind) .try_map(|r: PgRow| { @@ -240,7 +236,7 @@ pub async fn incoming_page( params: &History, currency: &str, listen: impl FnOnce() -> Receiver<i64>, -) -> sqlx::Result<Vec<IncomingBankTransaction<GenericPaytoImpl>>> { +) -> sqlx::Result<Vec<IncomingBankTransaction<AnyPayto>>> { history( db, "incoming_transaction_id", diff --git a/common/taler-api/tests/common/mod.rs b/common/taler-api/tests/common/mod.rs @@ -33,7 +33,7 @@ use taler_common::{ }, error_code::ErrorCode, types::{ - payto::{payto, GenericPaytoImpl}, + payto::{payto, AnyPayto}, timestamp::Timestamp, }, }; @@ -49,7 +49,7 @@ pub struct SampleState { incoming_channel: Sender<i64>, } -impl WireGatewayImpl<GenericPaytoImpl> for SampleState { +impl WireGatewayImpl<AnyPayto> for SampleState { fn name(&self) -> &str { "taler-wire-gateway" } @@ -62,10 +62,7 @@ impl WireGatewayImpl<GenericPaytoImpl> for SampleState { None } - async fn transfer( - &self, - req: TransferRequest<GenericPaytoImpl>, - ) -> ApiResult<TransferResponse> { + async fn transfer(&self, req: TransferRequest<AnyPayto>) -> ApiResult<TransferResponse> { let result = db::transfer(&self.pool, req).await?; match result { db::TransferResult::Success(transfer_response) => Ok(transfer_response), @@ -80,21 +77,18 @@ impl WireGatewayImpl<GenericPaytoImpl> for SampleState { &self, page: Page, status: Option<TransferState>, - ) -> ApiResult<TransferList<GenericPaytoImpl>> { + ) -> ApiResult<TransferList<AnyPayto>> { Ok(TransferList { transfers: db::transfer_page(&self.pool, &status, &page, &self.currency).await?, debit_account: payto("payto://test"), }) } - async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus<GenericPaytoImpl>>> { + async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus<AnyPayto>>> { Ok(db::transfer_by_id(&self.pool, id, &self.currency).await?) } - async fn outgoing_history( - &self, - params: History, - ) -> ApiResult<OutgoingHistory<GenericPaytoImpl>> { + async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory<AnyPayto>> { let txs = db::outgoing_page(&self.pool, &params, &self.currency, || { self.outgoing_channel.subscribe() }) @@ -105,10 +99,7 @@ impl WireGatewayImpl<GenericPaytoImpl> for SampleState { }) } - async fn incoming_history( - &self, - params: History, - ) -> ApiResult<IncomingHistory<GenericPaytoImpl>> { + async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory<AnyPayto>> { let txs = db::incoming_page(&self.pool, &params, &self.currency, || { self.incoming_channel.subscribe() }) @@ -121,7 +112,7 @@ impl WireGatewayImpl<GenericPaytoImpl> for SampleState { async fn add_incoming_reserve( &self, - req: AddIncomingRequest<GenericPaytoImpl>, + req: AddIncomingRequest<AnyPayto>, ) -> ApiResult<AddIncomingResponse> { let timestamp = Timestamp::now(); let res = db::add_incoming( @@ -145,7 +136,7 @@ impl WireGatewayImpl<GenericPaytoImpl> for SampleState { async fn add_incoming_kyc( &self, - req: AddKycauthRequest<GenericPaytoImpl>, + req: AddKycauthRequest<AnyPayto>, ) -> ApiResult<AddKycauthResponse> { let timestamp = Timestamp::now(); let res = db::add_incoming( diff --git a/common/taler-common/Cargo.toml b/common/taler-common/Cargo.toml @@ -13,6 +13,7 @@ tempfile.workspace = true jiff.workspace = true serde.workspace = true serde_json = { workspace = true, features = ["raw_value"] } +serde_path_to_error.workspace = true url.workspace = true thiserror.workspace = true fastrand.workspace = true diff --git a/common/taler-common/src/config.rs b/common/taler-common/src/config.rs @@ -26,7 +26,7 @@ use url::Url; use crate::types::{ amount::{Amount, Currency}, - payto::GenericPayto, + payto::{AnyPayto, Payto}, }; pub mod parser { @@ -721,7 +721,7 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> { } /** Access [option] as payto */ - pub fn payto(&self, option: &'arg str) -> Value<'arg, GenericPayto> { + pub fn payto(&self, option: &'arg str) -> Value<'arg, Payto<AnyPayto>> { self.parse("payto", option) } diff --git a/common/taler-common/src/types/amount.rs b/common/taler-common/src/types/amount.rs @@ -206,7 +206,16 @@ impl FromStr for Decimal { impl Display for Decimal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}.{:08}", self.val, self.frac)) + if self.frac == 0 { + f.write_fmt(format_args!("{}", self.val)) + } else { + let num = format!("{:08}", self.frac); + f.write_fmt(format_args!( + "{}.{:08}", + self.val, + num.trim_end_matches('0') + )) + } } } diff --git a/common/taler-common/src/types/payto.rs b/common/taler-common/src/types/payto.rs @@ -14,17 +14,17 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +use serde::de::DeserializeOwned; use std::{fmt::Debug, ops::Deref, str::FromStr}; use url::Url; +use super::url; + /// Parse a payto URI, panic if malformed pub fn payto<Impl: PaytoImpl>(url: impl AsRef<str>) -> Payto<Impl> { url.as_ref().parse().expect("invalid payto") } -/// A generic payto that accept any kind -pub type GenericPayto = Payto<GenericPaytoImpl>; - /// A payto implementation pub trait PaytoImpl: Sized { type ParseErr: std::error::Error; @@ -32,13 +32,15 @@ pub trait PaytoImpl: Sized { fn kind() -> &'static str; fn parse(path: &mut std::str::Split<'_, char>) -> Result<Self, Self::ParseErr>; + + fn path(&self) -> String; } -/// A generic payto implementation that accept any kind +/// A generic payto that accept any kind #[derive(Debug, Clone, PartialEq, Eq)] -pub struct GenericPaytoImpl {} +pub struct AnyPayto {} -impl PaytoImpl for GenericPaytoImpl { +impl PaytoImpl for AnyPayto { type ParseErr = std::convert::Infallible; fn parse(path: &mut std::str::Split<'_, char>) -> Result<Self, Self::ParseErr> { @@ -49,6 +51,10 @@ impl PaytoImpl for GenericPaytoImpl { fn kind() -> &'static str { "" } + + fn path(&self) -> String { + "".to_owned() + } } /// RFC 8905 payto URI @@ -65,10 +71,10 @@ impl<Impl: PaytoImpl> Payto<Impl> { self.raw.as_str() } - pub fn generic(self) -> GenericPayto { + pub fn generic(self) -> Payto<AnyPayto> { Payto { raw: self.raw, - parsed: GenericPaytoImpl {}, + parsed: AnyPayto {}, } } } @@ -87,16 +93,25 @@ impl<Impl: PaytoImpl> std::fmt::Display for Payto<Impl> { } } -impl AsRef<Url> for GenericPayto { +impl<Impl: PaytoImpl> AsRef<Url> for Payto<Impl> { fn as_ref(&self) -> &Url { &self.raw } } +impl<Impl: PaytoImpl> From<Impl> for Payto<Impl> { + fn from(parsed: Impl) -> Self { + let raw = url(&format!("payto://{}{}", Impl::kind(), parsed.path())); + Self { raw, parsed } + } +} + #[derive(Debug, thiserror::Error)] pub enum ParsePaytoErr<E> { - #[error("invalid URI: {0}")] + #[error("invalid payto URI: {0}")] Url(#[from] url::ParseError), + #[error("malformed payto URI: {0}")] + Malformed(#[from] serde_path_to_error::Error<serde_urlencoded::de::Error>), #[error("expected a payto URI got {0}")] NotPayto(String), #[error("unsupported payto kind, expected {0} got {1}")] @@ -107,24 +122,84 @@ pub enum ParsePaytoErr<E> { Custom(E), } +fn parse_payto<Impl: PaytoImpl, Query: DeserializeOwned>( + s: &str, +) -> Result<(Payto<Impl>, Query), ParsePaytoErr<Impl::ParseErr>> { + // Parse url + let raw: Url = s.parse()?; + // Check scheme + if raw.scheme() != "payto" { + return Err(ParsePaytoErr::NotPayto(raw.scheme().to_owned())); + } + // Check domain + let domain = raw.domain().unwrap_or_default(); + let kind = Impl::kind(); + if !kind.is_empty() && domain != kind { + return Err(ParsePaytoErr::UnsupportedKind(kind, domain.to_owned())); + } + // Parse path + let mut segments = raw.path_segments().unwrap_or_else(|| "".split('/')); + let parsed = Impl::parse(&mut segments).map_err(ParsePaytoErr::Custom)?; + if segments.next().is_some() { + return Err(ParsePaytoErr::TooLong(kind)); + } + // Parse query + let de = serde_urlencoded::Deserializer::new(url::form_urlencoded::parse( + raw.query().unwrap_or_default().as_bytes(), + )); + let query: Query = serde_path_to_error::deserialize(de)?; + + Ok((Payto { raw, parsed }, query)) +} + impl<Impl: PaytoImpl> FromStr for Payto<Impl> { type Err = ParsePaytoErr<Impl::ParseErr>; fn from_str(s: &str) -> Result<Self, Self::Err> { - let raw: Url = s.parse()?; - if raw.scheme() != "payto" { - return Err(ParsePaytoErr::NotPayto(raw.scheme().to_owned())); - } - let domain = raw.domain().unwrap_or_default(); - let kind = Impl::kind(); - if !kind.is_empty() && domain != kind { - return Err(ParsePaytoErr::UnsupportedKind(kind, domain.to_owned())); - } - let mut segments = raw.path_segments().unwrap_or_else(|| "".split('/')); - let parsed = Impl::parse(&mut segments).map_err(ParsePaytoErr::Custom)?; - if segments.next().is_some() { - return Err(ParsePaytoErr::TooLong(kind)); + #[derive(serde::Deserialize)] + struct Query {} + let (payto, _): (_, Query) = parse_payto(s)?; + Ok(payto) + } +} + +/// RFC 8905 payto URI +#[derive( + Debug, Clone, PartialEq, Eq, serde_with::DeserializeFromStr, serde_with::SerializeDisplay, +)] +pub struct FullPayto<Impl: PaytoImpl> { + payto: Payto<Impl>, + pub receiver_name: String, +} + +impl<Impl: PaytoImpl> Deref for FullPayto<Impl> { + type Target = Payto<Impl>; + + fn deref(&self) -> &Self::Target { + &self.payto + } +} + +impl<Impl: PaytoImpl> std::fmt::Display for FullPayto<Impl> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.payto, f) + } +} + +impl<Impl: PaytoImpl> FromStr for FullPayto<Impl> { + type Err = ParsePaytoErr<Impl::ParseErr>; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + #[derive(serde::Deserialize)] + struct Query { + #[serde(rename = "receiver-name")] + receiver_name: String, } - Ok(Self { raw, parsed }) + let (payto, query): (_, Query) = parse_payto(s)?; + + Ok(Self { + payto, + receiver_name: query.receiver_name, + }) } } diff --git a/common/taler-common/src/types/timestamp.rs b/common/taler-common/src/types/timestamp.rs @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024 Taler Systems SA + Copyright (C) 2024-2025 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -14,6 +14,8 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +use std::fmt::Display; + use serde::{de::Error, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; @@ -103,6 +105,15 @@ impl Serialize for Timestamp { } } +impl Display for Timestamp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Timestamp::Never => f.write_str("never"), + Timestamp::Time(timestamp) => timestamp.fmt(f), + } + } +} + impl From<jiff::Timestamp> for Timestamp { fn from(time: jiff::Timestamp) -> Self { Self::Time(time) diff --git a/wire-gateway/magnet-bank/src/db.rs b/wire-gateway/magnet-bank/src/db.rs @@ -29,13 +29,13 @@ use taler_common::{ }, types::{ amount::Amount, - payto::{GenericPayto, GenericPaytoImpl}, + payto::{AnyPayto, Payto}, timestamp::Timestamp, }, }; use tokio::sync::watch::{Receiver, Sender}; -use crate::{constant::CURRENCY, MagnetPayto, MagnetPaytoImpl}; +use crate::{constant::CURRENCY, MagnetPayto}; pub async fn notification_listener( pool: PgPool, @@ -65,24 +65,50 @@ pub struct TxIn { pub code: u64, pub amount: Amount, pub subject: String, - pub debit_payto: MagnetPayto, + pub debtor: Payto<MagnetPayto>, pub timestamp: Timestamp, } +impl Display for TxIn { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let TxIn { + code, + amount, + subject, + debtor, + timestamp, + } = self; + write!(f, "{timestamp} {amount} {code} {debtor} '{subject}'") + } +} + #[derive(Debug, Clone)] pub struct TxOut { pub code: u64, pub amount: Amount, pub subject: String, - pub credit_payto: MagnetPayto, + pub creditor: Payto<MagnetPayto>, pub timestamp: Timestamp, } +impl Display for TxOut { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let TxOut { + code, + amount, + subject, + creditor, + timestamp, + } = self; + write!(f, "{timestamp} {amount} {code} {creditor} '{subject}'") + } +} + #[derive(Debug, Clone)] pub struct TxInAdmin { pub amount: Amount, pub subject: String, - pub debit_payto: MagnetPayto, + pub debit_payto: Payto<MagnetPayto>, pub timestamp: Timestamp, pub metadata: IncomingSubject, } @@ -105,7 +131,7 @@ pub struct Initiated { pub id: u64, pub amount: Amount, pub subject: String, - pub creditor: GenericPayto, + pub creditor: Payto<MagnetPayto>, } impl Display for Initiated { @@ -175,7 +201,7 @@ pub async fn register_tx_in( .bind(tx.code as i64) .bind_amount(&tx.amount) .bind(&tx.subject) - .bind(tx.debit_payto.raw()) + .bind(tx.debtor.raw()) .bind_timestamp(&tx.timestamp) .bind(subject.as_ref().map(|it| it.ty())) .bind(subject.as_ref().map(|it| it.key())) @@ -208,7 +234,7 @@ pub async fn register_tx_out( .bind(tx.code as i64) .bind_amount(&tx.amount) .bind(&tx.subject) - .bind(tx.credit_payto.raw()) + .bind(tx.creditor.raw()) .bind_timestamp(&tx.timestamp) .bind(subject.as_ref().map(|it| it.0.as_ref())) .bind(subject.as_ref().map(|it| it.1.as_str())) @@ -232,7 +258,7 @@ pub enum TransferResult { pub async fn make_transfer<'a>( db: impl PgExecutor<'a>, - req: &TransferRequest<MagnetPaytoImpl>, + req: &TransferRequest<MagnetPayto>, timestamp: &Timestamp, ) -> sqlx::Result<TransferResult> { let subject = format!("{} {}", req.wtid, req.exchange_base_url); @@ -269,7 +295,7 @@ pub async fn transfer_page<'a>( db: impl PgExecutor<'a>, status: &Option<TransferState>, params: &Page, -) -> sqlx::Result<Vec<TransferListStatus<GenericPaytoImpl>>> { +) -> sqlx::Result<Vec<TransferListStatus<AnyPayto>>> { page( db, "initiated_id", @@ -311,7 +337,7 @@ pub async fn outgoing_history( db: &PgPool, params: &History, listen: impl FnOnce() -> Receiver<i64>, -) -> sqlx::Result<Vec<OutgoingBankTransaction<GenericPaytoImpl>>> { +) -> sqlx::Result<Vec<OutgoingBankTransaction<AnyPayto>>> { history( db, "tx_out_id", @@ -352,7 +378,7 @@ pub async fn incoming_history( db: &PgPool, params: &History, listen: impl FnOnce() -> Receiver<i64>, -) -> sqlx::Result<Vec<IncomingBankTransaction<GenericPaytoImpl>>> { +) -> sqlx::Result<Vec<IncomingBankTransaction<AnyPayto>>> { history( db, "tx_in_id", @@ -403,7 +429,7 @@ pub async fn incoming_history( pub async fn transfer_by_id<'a>( db: impl PgExecutor<'a>, id: u64, -) -> sqlx::Result<Option<TransferStatus<GenericPaytoImpl>>> { +) -> sqlx::Result<Option<TransferStatus<AnyPayto>>> { sqlx::query( " SELECT @@ -559,7 +585,7 @@ mod test { code: code, amount: amount("EUR:10"), subject: "subject".to_owned(), - debit_payto: payto("payto://magnet-bank/todo"), + debtor: payto("payto://magnet-bank/todo"), timestamp: Timestamp::now_stable(), }; // Insert @@ -743,7 +769,7 @@ mod test { code, amount: amount("EUR:10"), subject: "subject".to_owned(), - credit_payto: payto("payto://magnet-bank/todo"), + creditor: payto("payto://magnet-bank/todo"), timestamp: Timestamp::now_stable(), }; // Insert diff --git a/wire-gateway/magnet-bank/src/dev.rs b/wire-gateway/magnet-bank/src/dev.rs @@ -15,9 +15,15 @@ */ use clap::ValueEnum; +use jiff::Zoned; use taler_common::{ config::Config, - types::{payto::payto, timestamp::Timestamp}, + types::{ + amount::Amount, + payto::{payto, FullPayto, Payto}, + timestamp::Timestamp, + url, + }, }; use tracing::info; @@ -26,6 +32,7 @@ use crate::{ db::{TxIn, TxOut}, keys, magnet::{AuthClient, Direction}, + MagnetPayto, }; #[derive(Debug, Clone, PartialEq, Eq, ValueEnum)] @@ -42,11 +49,20 @@ pub enum DevCmd { /// Print account info Accounts, Tx { - #[clap(long, short)] - account: String, + account: Payto<MagnetPayto>, #[clap(long, short, value_enum, default_value_t = DirArg::Both)] direction: DirArg, }, + Transfer { + #[clap(long)] + debtor: Payto<MagnetPayto>, + #[clap(long)] + creditor: FullPayto<MagnetPayto>, + #[clap(long)] + amount: Amount, + #[clap(long)] + subject: String, + }, } pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> { @@ -57,10 +73,17 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> { match cmd { DevCmd::Accounts => { let res = client.list_accounts().await?; - dbg!(res); + for partner in res.partners { + for account in partner.bank_accounts { + let mut payto = url(&format!("payto://magnet-bank/{}", account.number)); + payto + .query_pairs_mut() + .append_pair("receiver-name", &partner.partner.name); + info!("{} {} {payto}", account.code, account.currency.symbol); + } + } } DevCmd::Tx { account, direction } => { - let client = client.account(&account); let dir = match direction { DirArg::Incoming => Direction::Incoming, DirArg::Outgoing => Direction::Outgoing, @@ -69,7 +92,9 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> { // Register incoming let mut next = None; loop { - let page = client.page_tx(dir, 5, &next, &None).await?; + let page = client + .page_tx(dir, 5, &account.account, &next, &None) + .await?; next = page.next; for item in page.list { let tx = item.tx; @@ -79,23 +104,20 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> { code: tx.code, amount: amount.parse().unwrap(), subject: tx.subject, - debit_payto: payto("payto://magnet-bank/todo"), + debtor: payto("payto://magnet-bank/todo"), timestamp: Timestamp::from(tx.value_date), }; - info!("incoming {} '{}'", tx.amount, tx.subject); + info!("in {tx}"); } else { let amount = format!("{}:{}", tx.currency, -tx.amount); - let tx_out = TxOut { + let tx = TxOut { code: tx.code, amount: amount.parse().unwrap(), subject: tx.subject, - credit_payto: payto("payto://magnet-bank/todo"), + creditor: payto("payto://magnet-bank/todo"), timestamp: Timestamp::from(tx.value_date), }; - info!( - "outgoing {} {} {} '{}' {:?}", - tx_out.code, tx.tx_date, tx_out.amount, tx_out.subject, tx.status - ); + info!("out {tx}"); } } if next.is_none() { @@ -103,6 +125,37 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> { } } } + DevCmd::Transfer { + debtor, + creditor, + amount, + subject, + } => { + let debtor = client.account(&debtor.account).await?; + let now = Zoned::now(); + let date = now.date(); + + let init = client + .init_tx( + debtor.code, + amount.val as f64, + &subject, + date, + &creditor.receiver_name, + &creditor.account, + ) + .await?; + client + .sign_tx( + &keys.signing_key, + &debtor.number, + init.code, + init.amount, + date, + &creditor.account, + ) + .await?; + } } Ok(()) } diff --git a/wire-gateway/magnet-bank/src/lib.rs b/wire-gateway/magnet-bank/src/lib.rs @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -use taler_common::types::payto::{Payto, PaytoImpl}; +use taler_common::types::payto::PaytoImpl; pub mod config; pub mod constant; @@ -24,20 +24,18 @@ pub mod keys; pub mod magnet; pub mod wire_gateway; -pub type MagnetPayto = Payto<MagnetPaytoImpl>; - #[derive(Debug, Clone, PartialEq, Eq)] -pub struct MagnetPaytoImpl { - pub account_number: String, +pub struct MagnetPayto { + pub account: String, } #[derive(Debug, thiserror::Error)] pub enum MagnetPaytoErr { #[error("missing Magnet Bank account number in path")] - MissingAccountNumber, + MissingAccount, } -impl PaytoImpl for MagnetPaytoImpl { +impl PaytoImpl for MagnetPayto { type ParseErr = MagnetPaytoErr; fn kind() -> &'static str { @@ -45,9 +43,13 @@ impl PaytoImpl for MagnetPaytoImpl { } fn parse(path: &mut std::str::Split<'_, char>) -> Result<Self, Self::ParseErr> { - let account_number = path.next().ok_or(MagnetPaytoErr::MissingAccountNumber)?; + let account = path.next().ok_or(MagnetPaytoErr::MissingAccount)?; Ok(Self { - account_number: account_number.to_owned(), + account: account.to_owned(), }) } + + fn path(&self) -> String { + format!("/{}", self.account) + } } diff --git a/wire-gateway/magnet-bank/src/magnet.rs b/wire-gateway/magnet-bank/src/magnet.rs @@ -16,7 +16,12 @@ use base64::{prelude::BASE64_STANDARD, Engine}; use error::ApiResult; -use p256::{ecdsa::SigningKey, PublicKey}; +use jiff::Timestamp; +use p256::{ + ecdsa::{signature::Signer as _, DerSignature, SigningKey}, + PublicKey, +}; +use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use spki::EncodePublicKey; use taler_common::types::amount; @@ -26,7 +31,7 @@ use crate::magnet::{error::MagnetBuilder, oauth::OAuthBuilder}; pub mod error; mod oauth; -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Token { #[serde(rename = "oauth_token")] pub key: String, @@ -34,13 +39,13 @@ pub struct Token { pub secret: String, } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct TokenAuth { pub oauth_token: String, pub oauth_verifier: String, } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct Consumer { #[serde(rename = "consumerKey")] pub key: String, @@ -52,7 +57,7 @@ pub struct Consumer { pub lifetime: u64, } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct TokenInfo { #[serde(rename = "keszult")] pub created: jiff::Timestamp, @@ -65,7 +70,7 @@ pub struct TokenInfo { pub authenticated: bool, } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct SmsCodeSubmission { #[serde(rename = "csatorna")] pub channel: String, @@ -73,7 +78,7 @@ pub struct SmsCodeSubmission { pub sent_to: Vec<String>, } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct ScaResult { #[serde(rename = "csatorna")] pub channel: String, @@ -81,7 +86,7 @@ pub struct ScaResult { pub sent_to: Vec<String>, } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct Partner { #[serde(rename = "megnevezes")] pub name: String, @@ -93,61 +98,61 @@ pub struct Partner { pub status: String, // TODO enum } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct AccountType { #[serde(rename = "kod")] - code: u64, + pub code: u64, #[serde(rename = "megnevezes")] - name: String, + pub name: String, } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct Currency { #[serde(rename = "jel")] - symbol: amount::Currency, + pub symbol: amount::Currency, #[serde(rename = "megnevezes")] - name: String, + pub name: String, } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct Account { #[serde(rename = "alapertelmezett")] - default: bool, + pub default: bool, #[serde(rename = "bankszamlaTipus")] - ty: AccountType, + pub ty: AccountType, #[serde(rename = "deviza")] - currency: Currency, + pub currency: Currency, #[serde(rename = "ibanSzamlaszam")] - iban: String, + pub iban: String, #[serde(rename = "kod")] - code: u64, + pub code: u64, #[serde(rename = "szamlaszam")] - number: String, + pub number: String, #[serde(rename = "tulajdonosKod")] - owner_code: u64, + pub owner_code: u64, #[serde(rename = "lakossagi")] - resident: bool, + pub resident: bool, #[serde(rename = "megnevezes")] - name: Option<String>, - partner: Partner, + pub name: Option<String>, + pub partner: Partner, } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct PartnerAccounts { - partner: Partner, + pub partner: Partner, #[serde(rename = "bankszamlaList")] - bank_accounts: Vec<Account>, + pub bank_accounts: Vec<Account>, #[serde(rename = "kertJogosultsag")] - requested_permission: u64, + pub requested_permission: u64, } -#[derive(serde::Deserialize, Debug)] +#[derive(Debug, Deserialize)] pub struct PartnerList { #[serde(rename = "partnerSzamlaList")] - parteners: Vec<PartnerAccounts>, + pub partners: Vec<PartnerAccounts>, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum TxStatus { #[serde(rename = "G")] ToBeRecorded, @@ -171,7 +176,7 @@ pub enum TxStatus { UnderReview, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Direction { #[serde(rename = "T")] Outgoing, @@ -181,7 +186,50 @@ pub enum Direction { Both, } -#[derive(Debug, serde::Deserialize)] +#[derive(Debug, Deserialize)] +pub struct TxInfo { + #[serde(rename = "alairas1idopont")] + pub first_signature: Option<Timestamp>, + #[serde(rename = "alairas2idopont")] + pub second_signature: Option<Timestamp>, + #[serde(rename = "alairo1")] + pub first_signatory: Option<Partner>, + #[serde(rename = "alairo2")] + pub second_signatory: Option<Partner>, + #[serde(rename = "kod")] + pub code: u64, + #[serde(rename = "bankszamla")] + pub account: Account, + #[serde(rename = "deviza")] + pub currency: Currency, + #[serde(rename = "eszamla")] + pub counter_account: String, + #[serde(rename = "epartner")] + pub counter_name: String, + #[serde(rename = "statusz")] + pub status: TxStatus, + #[serde(rename = "osszegSigned")] + pub amount: f64, + #[serde(rename = "reszteljesites")] + pub partial_execution: bool, + #[serde(rename = "sorbaallitas")] + pub queued: bool, + pub eam: Option<u64>, +} + +#[derive(Debug, Deserialize)] +struct TxInfoWrapper { + #[serde(rename = "tranzakcio")] + info: TxInfo, +} + +#[derive(Debug, Deserialize)] +struct AccountWrapper { + #[serde(rename = "bankszamla")] + account: Account, +} + +#[derive(Debug, Deserialize)] pub struct Transaction { #[serde(rename = "kod")] pub code: u64, @@ -206,10 +254,10 @@ pub struct Transaction { #[serde(rename = "eszamla")] pub debtor: String, #[serde(rename = "tranzakcioTipus")] - pub ty: String, + pub ty: Option<String>, } -#[derive(Debug, serde::Deserialize)] +#[derive(Debug, Deserialize)] pub struct Next { #[serde(rename = "next")] pub next_id: u64, @@ -217,7 +265,7 @@ pub struct Next { pub next_type: String, } -#[derive(Debug, serde::Deserialize)] +#[derive(Debug, Deserialize)] pub struct TransactionPage { #[serde(flatten)] pub next: Option<Next>, @@ -225,7 +273,7 @@ pub struct TransactionPage { pub list: Vec<TransactionWrapper>, } -#[derive(Debug, serde::Deserialize)] +#[derive(Debug, Deserialize)] pub struct TransactionWrapper { #[serde(rename = "tranzakcioDto")] pub tx: Transaction, @@ -298,7 +346,7 @@ pub struct ApiClient<'a> { access: &'a Token, } -impl<'a> ApiClient<'a> { +impl ApiClient<'_> { fn join(&self, path: &str) -> reqwest::Url { self.api_url.join(path).unwrap() } @@ -356,40 +404,27 @@ impl<'a> ApiClient<'a> { .await } - pub fn account(self, account: &'a str) -> AccountClient<'a> { - AccountClient { - client: self.client, - api_url: self.api_url, - consumer: self.consumer, - access: self.access, - account, - } - } -} - -pub struct AccountClient<'a> { - client: &'a reqwest::Client, - api_url: &'a reqwest::Url, - consumer: &'a Token, - access: &'a Token, - account: &'a str, -} - -impl AccountClient<'_> { - fn join(&self, path: &str) -> reqwest::Url { - self.api_url.join(path).unwrap() + pub async fn account(&self, account: &str) -> ApiResult<Account> { + Ok(self + .client + .get(self.join(&format!("/RESTApi/resources/v2/bankszamla/{account}"))) + .oauth(self.consumer, Some(self.access), None) + .await + .magnet_json::<AccountWrapper>() + .await? + .account) } pub async fn page_tx( &self, direction: Direction, limit: u16, + account: &str, next: &Option<Next>, status: &Option<TxStatus>, ) -> ApiResult<TransactionPage> { let mut req = self.client.get(self.join(&format!( - "/RESTApi/resources/v2/tranzakcio/paginator/{}/{limit}", - self.account + "/RESTApi/resources/v2/tranzakcio/paginator/{account}/{limit}" ))); if let Some(next) = next { req = req @@ -409,4 +444,92 @@ impl AccountClient<'_> { .magnet_call() .await } + + pub async fn init_tx( + &self, + account_code: u64, + amount: f64, + subject: &str, + date: jiff::civil::Date, + creditor_name: &str, + creditor_account: &str, + ) -> ApiResult<TxInfo> { + #[derive(Serialize)] + struct Req<'a> { + #[serde(rename = "bankszamlaKod")] + account_code: u64, + #[serde(rename = "osszeg")] + amount: f64, + #[serde(rename = "kozlemeny")] + subject: &'a str, + #[serde(rename = "ertekNap")] + date: jiff::civil::Date, + #[serde(rename = "ellenpartner")] + creditor_name: &'a str, + #[serde(rename = "ellenszamla")] + creditor_account: &'a str, + } + + Ok(self + .client + .post(self.join("/RESTApi/resources/v2/esetiatutalas")) + .json(&Req { + account_code, + amount, + subject, + date, + creditor_name, + creditor_account, + }) + .oauth(self.consumer, Some(self.access), None) + .await + .magnet_call::<TxInfoWrapper>() + .await? + .info) + } + + pub async fn sign_tx( + &self, + signing_key: &SigningKey, + account: &str, + tx_code: u64, + amount: f64, + date: jiff::civil::Date, + creditor: &str, + ) -> ApiResult<TxInfo> { + #[derive(Serialize)] + struct Req<'a> { + #[serde(rename = "tranzakcioKod")] + tx_code: u64, + #[serde(rename = "forrasszamla")] + debtor: &'a str, + #[serde(rename = "ellenszamla")] + creditor: &'a str, + #[serde(rename = "osszeg")] + amount: f64, + #[serde(rename = "ertekNap")] + date: jiff::civil::Date, + signature: &'a str, + } + + let content: String = format!("{tx_code};{account};{creditor};{amount};{date};"); + let signature: DerSignature = signing_key.sign(content.as_bytes()); + let encoded = BASE64_STANDARD.encode(signature.as_bytes()); + Ok(self + .client + .put(self.join("/RESTApi/resources/v2/tranzakcio/alairas")) + .json(&Req { + tx_code, + debtor: account, + creditor, + amount, + date, + signature: &encoded, + }) + .oauth(self.consumer, Some(self.access), None) + .await + .magnet_call::<TxInfoWrapper>() + .await? + .info) + } } diff --git a/wire-gateway/magnet-bank/src/magnet/error.rs b/wire-gateway/magnet-bank/src/magnet/error.rs @@ -20,21 +20,13 @@ use thiserror::Error; use tracing::error; #[derive(Deserialize, Debug)] -pub struct MagnetResponse<T> { - timestamp: jiff::civil::DateTime, - #[serde(flatten)] - body: MagnetBody<T>, +struct Header { + #[serde(alias = "errorCode")] + pub error_code: Option<u16>, } #[derive(Deserialize, Debug)] -pub struct Empty {} - -#[derive(Deserialize, Debug)] -#[serde(untagged)] -pub enum MagnetBody<T> { - Error(MagnetError), - Ok(T), -} +struct Empty {} #[derive(Deserialize, Error, Debug)] #[error("{error_code} {short_message} '{long_message}'")] @@ -128,13 +120,14 @@ async fn error_handling(res: reqwest::Result<Response>) -> ApiResult<String> { /** Parse magnet JSON response */ async fn magnet_json<T: DeserializeOwned>(res: reqwest::Result<Response>) -> ApiResult<T> { let body = error_handling(res).await?; - let deserializer = &mut serde_json::Deserializer::from_str(&body); - - let body: MagnetResponse<T> = - serde_path_to_error::deserialize(deserializer).map_err(ApiError::Json)?; - match body.body { - MagnetBody::Error(e) => Err(ApiError::Magnet(e)), - MagnetBody::Ok(t) => Ok(t), + fn parse<T: DeserializeOwned>(str: &str) -> ApiResult<T> { + let deserializer = &mut serde_json::Deserializer::from_str(str); + serde_path_to_error::deserialize(deserializer).map_err(ApiError::Json) + } + let header: Header = parse(&body)?; + match header.error_code { + Some(_) => Err(ApiError::Magnet(parse(&body)?)), + None => parse(&body), } } diff --git a/wire-gateway/magnet-bank/src/wire_gateway.rs b/wire-gateway/magnet-bank/src/wire_gateway.rs @@ -28,19 +28,22 @@ use taler_common::{ TransferState, TransferStatus, }, error_code::ErrorCode, - types::{payto::GenericPaytoImpl, timestamp::Timestamp}, + types::{ + payto::{AnyPayto, Payto}, + timestamp::Timestamp, + }, }; use tokio::sync::watch::Sender; use crate::{ constant::CURRENCY, db::{self, AddIncomingResult, TxInAdmin}, - MagnetPayto, MagnetPaytoImpl, + MagnetPayto, }; pub struct MagnetWireGateway { pub pool: sqlx::PgPool, - pub payto: MagnetPayto, + pub payto: Payto<MagnetPayto>, pub in_channel: Sender<i64>, pub taler_in_channel: Sender<i64>, pub out_channel: Sender<i64>, @@ -48,7 +51,7 @@ pub struct MagnetWireGateway { } impl MagnetWireGateway { - pub async fn start(pool: sqlx::PgPool, payto: MagnetPayto) -> Self { + pub async fn start(pool: sqlx::PgPool, payto: Payto<MagnetPayto>) -> Self { let in_channel = Sender::new(0); let taler_in_channel = Sender::new(0); let out_channel = Sender::new(0); @@ -72,7 +75,7 @@ impl MagnetWireGateway { } } -impl WireGatewayImpl<MagnetPaytoImpl> for MagnetWireGateway { +impl WireGatewayImpl<MagnetPayto> for MagnetWireGateway { fn name(&self) -> &str { "magnet-bank" } @@ -85,7 +88,7 @@ impl WireGatewayImpl<MagnetPaytoImpl> for MagnetWireGateway { None } - async fn transfer(&self, req: TransferRequest<MagnetPaytoImpl>) -> ApiResult<TransferResponse> { + async fn transfer(&self, req: TransferRequest<MagnetPayto>) -> ApiResult<TransferResponse> { let result = db::make_transfer(&self.pool, &req, &Timestamp::now()).await?; match result { db::TransferResult::Success { id, timestamp } => Ok(TransferResponse { @@ -104,21 +107,18 @@ impl WireGatewayImpl<MagnetPaytoImpl> for MagnetWireGateway { &self, page: Page, status: Option<TransferState>, - ) -> ApiResult<TransferList<GenericPaytoImpl>> { + ) -> ApiResult<TransferList<AnyPayto>> { Ok(TransferList { transfers: db::transfer_page(&self.pool, &status, &page).await?, debit_account: self.payto.clone().generic(), }) } - async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus<GenericPaytoImpl>>> { + async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus<AnyPayto>>> { Ok(db::transfer_by_id(&self.pool, id).await?) } - async fn outgoing_history( - &self, - params: History, - ) -> ApiResult<OutgoingHistory<GenericPaytoImpl>> { + async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory<AnyPayto>> { Ok(OutgoingHistory { outgoing_transactions: db::outgoing_history(&self.pool, &params, || { self.taler_out_channel.subscribe() @@ -128,10 +128,7 @@ impl WireGatewayImpl<MagnetPaytoImpl> for MagnetWireGateway { }) } - async fn incoming_history( - &self, - params: History, - ) -> ApiResult<IncomingHistory<GenericPaytoImpl>> { + async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory<AnyPayto>> { Ok(IncomingHistory { incoming_transactions: db::incoming_history(&self.pool, &params, || { self.taler_in_channel.subscribe() @@ -143,7 +140,7 @@ impl WireGatewayImpl<MagnetPaytoImpl> for MagnetWireGateway { async fn add_incoming_reserve( &self, - req: AddIncomingRequest<MagnetPaytoImpl>, + req: AddIncomingRequest<MagnetPayto>, ) -> ApiResult<AddIncomingResponse> { let res = db::register_tx_in_admin( &self.pool, @@ -170,7 +167,7 @@ impl WireGatewayImpl<MagnetPaytoImpl> for MagnetWireGateway { async fn add_incoming_kyc( &self, - req: AddKycauthRequest<MagnetPaytoImpl>, + req: AddKycauthRequest<MagnetPayto>, ) -> ApiResult<AddKycauthResponse> { let res = db::register_tx_in_admin( &self.pool, diff --git a/wire-gateway/magnet-bank/tests/api.rs b/wire-gateway/magnet-bank/tests/api.rs @@ -16,7 +16,7 @@ use std::sync::Arc; -use magnet_bank::{db, wire_gateway::MagnetWireGateway, MagnetPaytoImpl}; +use magnet_bank::{db, wire_gateway::MagnetWireGateway, MagnetPayto}; use sqlx::PgPool; use taler_api::{auth::AuthMethod, standard_layer, subject::OutgoingSubject}; use taler_common::{ @@ -47,7 +47,7 @@ async fn setup() -> (TestServer, PgPool) { #[tokio::test] async fn transfer() { let (server, _) = setup().await; - transfer_routine::<MagnetPaytoImpl>( + transfer_routine::<MagnetPayto>( &server, TransferState::pending, &payto("payto://magnet-bank/todo"), @@ -59,7 +59,7 @@ async fn transfer() { async fn outgoing_history() { let (server, pool) = setup().await; server.get("/history/outgoing").await.assert_no_content(); - routine_pagination::<OutgoingHistory<MagnetPaytoImpl>, _>( + routine_pagination::<OutgoingHistory<MagnetPayto>, _>( &server, "/history/outgoing", |it| { @@ -78,7 +78,7 @@ async fn outgoing_history() { code: i as u64, amount: amount("EUR:10"), subject: "subject".to_owned(), - credit_payto: payto("payto://magnet-bank/todo"), + creditor: payto("payto://magnet-bank/todo"), timestamp: Timestamp::now_stable(), }, &Some(OutgoingSubject( @@ -97,6 +97,5 @@ async fn outgoing_history() { #[tokio::test] async fn admin_add_incoming() { let (server, _) = setup().await; - admin_add_incoming_routine::<MagnetPaytoImpl>(&server, &payto("payto://magnet-bank/todo")) - .await; + admin_add_incoming_routine::<MagnetPayto>(&server, &payto("payto://magnet-bank/todo")).await; }