taler-rust

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

commit f0433106c1da705c9f76d8500c9918910ecb5240
parent 5eca882b053c170ac6f7e28c5715b45e23fbec16
Author: Antoine A <>
Date:   Tue, 21 Jan 2025 12:28:28 +0100

common: support payto implementation

Diffstat:
MCargo.lock | 38+++++++++++++++++++-------------------
Mcommon/taler-api/src/db.rs | 13++++++++++---
Mcommon/taler-api/src/lib.rs | 31++++++++++++++++++-------------
Mcommon/taler-api/tests/api.rs | 17+++++++++++------
Mcommon/taler-api/tests/common/db.rs | 21++++++++++++++-------
Mcommon/taler-api/tests/common/mod.rs | 41++++++++++++++++++++++++++++-------------
Mcommon/taler-common/src/api_wire.rs | 85+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mcommon/taler-common/src/config.rs | 4++--
Mcommon/taler-common/src/types/payto.rs | 117++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mcommon/test-utils/src/routine.rs | 49+++++++++++++++++++++++++++++++++----------------
Mwire-gateway/magnet-bank/src/db.rs | 38+++++++++++++++++++++-----------------
Mwire-gateway/magnet-bank/src/dev.rs | 4++--
Mwire-gateway/magnet-bank/src/lib.rs | 33+++++++++++++++++++++++++++++++--
Mwire-gateway/magnet-bank/src/magnet.rs | 21++++++++++-----------
Mwire-gateway/magnet-bank/src/main.rs | 2+-
Mwire-gateway/magnet-bank/src/wire_gateway.rs | 41++++++++++++++++++++++++-----------------
Mwire-gateway/magnet-bank/tests/api.rs | 18++++++++++++------
17 files changed, 374 insertions(+), 199 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.26" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -369,9 +369,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.26" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -1343,9 +1343,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -1354,9 +1354,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" @@ -1450,9 +1450,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "listenfd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0500463acd96259d219abb05dc57e5a076ef04b2db9a2112846929b5f174c96" +checksum = "b87bc54a4629b4294d0b3ef041b64c40c611097a677d9dc07b2c67739fe39dba" dependencies = [ "libc", "uuid", @@ -2177,9 +2177,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" @@ -2203,9 +2203,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.136" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336a0c23cf42a38d9eaa7cd22c7040d04e1228a19a933890805ffd00a16437d2" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ "itoa", "memchr", @@ -2245,7 +2245,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.0", + "indexmap 2.7.1", "serde", "serde_derive", "serde_json", @@ -2387,7 +2387,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.2", "hashlink", - "indexmap 2.7.0", + "indexmap 2.7.1", "log", "memchr", "native-tls", @@ -2570,7 +2570,7 @@ dependencies = [ "criterion", "fastrand", "glob", - "indexmap 2.7.0", + "indexmap 2.7.1", "jiff", "rand", "serde", @@ -2968,9 +2968,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744018581f9a3454a9e15beb8a33b017183f1e7c0cd170232a2d1453b23a51c4" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" [[package]] name = "valuable" diff --git a/common/taler-api/src/db.rs b/common/taler-api/src/db.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 @@ -27,7 +27,7 @@ use taler_common::{ types::{ amount::{Amount, Decimal}, base32::Base32, - payto::Payto, + payto::{Payto, PaytoImpl}, timestamp::Timestamp, }, }; @@ -173,7 +173,14 @@ pub trait TypeHelper { fn try_get_url<I: sqlx::ColumnIndex<Self>>(&self, index: I) -> sqlx::Result<Url> { self.try_get_map(index, Url::parse) } - fn try_get_payto<I: sqlx::ColumnIndex<Self>>(&self, index: I) -> sqlx::Result<Payto> { + fn try_get_payto< + E: 'static + std::error::Error + Sync + Send, + P: PaytoImpl<ParseErr = E>, + I: sqlx::ColumnIndex<Self>, + >( + &self, + index: I, + ) -> sqlx::Result<Payto<P>> { self.try_get_map(index, |s: &str| s.parse()) } fn try_get_amount(&self, index: &str, currency: &str) -> sqlx::Result<Amount>; diff --git a/common/taler-api/src/lib.rs b/common/taler-api/src/lib.rs @@ -49,7 +49,10 @@ use taler_common::{ TransferState, TransferStatus, WireConfig, }, error_code::ErrorCode, - types::amount::Amount, + types::{ + amount::Amount, + payto::{GenericPaytoImpl, PaytoImpl}, + }, }; use tokio::{ net::{TcpListener, UnixListener}, @@ -180,38 +183,38 @@ where } } -pub trait WireGatewayImpl: Send + Sync { +pub trait WireGatewayImpl<P: PaytoImpl>: Send + Sync { fn name(&self) -> &str; fn currency(&self) -> &str; fn implementation(&self) -> Option<&str>; fn transfer( &self, - req: TransferRequest, + req: TransferRequest<P>, ) -> impl std::future::Future<Output = ApiResult<TransferResponse>> + Send; fn transfer_page( &self, page: Page, status: Option<TransferState>, - ) -> impl std::future::Future<Output = ApiResult<TransferList>> + Send; + ) -> impl std::future::Future<Output = ApiResult<TransferList<GenericPaytoImpl>>> + Send; fn transfer_by_id( &self, id: u64, - ) -> impl std::future::Future<Output = ApiResult<Option<TransferStatus>>> + Send; + ) -> impl std::future::Future<Output = ApiResult<Option<TransferStatus<GenericPaytoImpl>>>> + Send; fn outgoing_history( &self, params: History, - ) -> impl std::future::Future<Output = ApiResult<OutgoingHistory>> + Send; + ) -> impl std::future::Future<Output = ApiResult<OutgoingHistory<GenericPaytoImpl>>> + Send; fn incoming_history( &self, params: History, - ) -> impl std::future::Future<Output = ApiResult<IncomingHistory>> + Send; + ) -> impl std::future::Future<Output = ApiResult<IncomingHistory<GenericPaytoImpl>>> + Send; fn add_incoming_reserve( &self, - req: AddIncomingRequest, + req: AddIncomingRequest<P>, ) -> impl std::future::Future<Output = ApiResult<AddIncomingResponse>> + Send; fn add_incoming_kyc( &self, - req: AddKycauthRequest, + req: AddKycauthRequest<P>, ) -> impl std::future::Future<Output = ApiResult<AddKycauthResponse>> + Send; fn check_currency(&self, amount: &Amount) -> ApiResult<()> { @@ -410,7 +413,9 @@ async fn logger_middleware(request: Request, next: Next) -> Response { response } -pub fn wire_gateway_api<I: WireGatewayImpl + 'static>(wg: Arc<I>) -> Router { +pub fn wire_gateway_api<P: PaytoImpl + Send + 'static, I: WireGatewayImpl<P> + 'static>( + wg: Arc<I>, +) -> Router { Router::new() .route( "/config", @@ -427,7 +432,7 @@ pub fn wire_gateway_api<I: WireGatewayImpl + 'static>(wg: Arc<I>) -> Router { .route( "/transfer", post( - |State(state): State<Arc<I>>, Req(req): Req<TransferRequest>| async move { + |State(state): State<Arc<I>>, Req(req): Req<TransferRequest<P>>| async move { state.check_currency(&req.amount)?; ApiResult::Ok(Json(state.transfer(req).await?)) }, @@ -492,7 +497,7 @@ pub fn wire_gateway_api<I: WireGatewayImpl + 'static>(wg: Arc<I>) -> Router { .route( "/admin/add-incoming", post( - |State(state): State<Arc<I>>, Req(req): Req<AddIncomingRequest>| async move { + |State(state): State<Arc<I>>, Req(req): Req<AddIncomingRequest<P>>| async move { state.check_currency(&req.amount)?; ApiResult::Ok(Json(state.add_incoming_reserve(req).await?)) }, @@ -501,7 +506,7 @@ pub fn wire_gateway_api<I: WireGatewayImpl + 'static>(wg: Arc<I>) -> Router { .route( "/admin/add-kycauth", post( - |State(state): State<Arc<I>>, Req(req): Req<AddKycauthRequest>| async move { + |State(state): State<Arc<I>>, Req(req): Req<AddKycauthRequest<P>>| async move { state.check_currency(&req.amount)?; ApiResult::Ok(Json(state.add_incoming_kyc(req).await?)) }, diff --git a/common/taler-api/tests/api.rs b/common/taler-api/tests/api.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 @@ -21,7 +21,11 @@ use taler_common::{ api_common::{HashCode, ShortHashCode}, api_wire::{OutgoingHistory, TransferResponse, TransferState}, error_code::ErrorCode, - types::{amount::amount, url}, + types::{ + amount::amount, + payto::{payto, GenericPaytoImpl}, + url, + }, }; use test_utils::{ axum_test::TestServer, @@ -67,7 +71,8 @@ async fn config() { #[tokio::test] async fn transfer() { let (server, _) = setup().await; - transfer_routine(&server, TransferState::success).await; + transfer_routine::<GenericPaytoImpl>(&server, TransferState::success, &payto("payto://test")) + .await; } #[tokio::test] @@ -75,7 +80,7 @@ async fn outgoing_history() { let (server, _) = setup().await; server.get("/history/outgoing").await.assert_no_content(); - routine_pagination::<OutgoingHistory, _>( + routine_pagination::<OutgoingHistory<GenericPaytoImpl>, _>( &server, "/history/outgoing", |it| { @@ -92,7 +97,7 @@ async fn outgoing_history() { "amount": amount(&format!("EUR:0.0{i}")), "exchange_base_url": url("http://exchange.taler"), "wtid": ShortHashCode::rand(), - "credit_account": url("payto://todo"), + "credit_account": url("payto://test"), })) .await .assert_ok_json::<TransferResponse>(); @@ -104,5 +109,5 @@ async fn outgoing_history() { #[tokio::test] async fn admin_add_incoming() { let (server, _) = setup().await; - admin_add_incoming_routine(&server).await; + admin_add_incoming_routine::<GenericPaytoImpl>(&server, &payto("payto://test")).await; } diff --git a/common/taler-api/tests/common/db.rs b/common/taler-api/tests/common/db.rs @@ -23,7 +23,11 @@ use taler_common::{ IncomingBankTransaction, OutgoingBankTransaction, TransferListStatus, TransferRequest, TransferResponse, TransferState, TransferStatus, }, - types::{amount::Amount, payto::Payto, timestamp::Timestamp}, + types::{ + amount::Amount, + payto::{GenericPayto, GenericPaytoImpl}, + timestamp::Timestamp, + }, }; use tokio::sync::watch::{Receiver, Sender}; @@ -47,7 +51,10 @@ pub enum TransferResult { RequestUidReuse, } -pub async fn transfer(db: &PgPool, transfer: TransferRequest) -> sqlx::Result<TransferResult> { +pub async fn transfer( + db: &PgPool, + transfer: TransferRequest<GenericPaytoImpl>, +) -> sqlx::Result<TransferResult> { sqlx::query( " SELECT out_request_uid_reuse, out_tx_row_id, out_timestamp @@ -80,7 +87,7 @@ pub async fn transfer_page( status: &Option<TransferState>, params: &Page, currency: &str, -) -> sqlx::Result<Vec<TransferListStatus>> { +) -> sqlx::Result<Vec<TransferListStatus<GenericPaytoImpl>>> { page( db, "transfer_id", @@ -120,7 +127,7 @@ pub async fn transfer_by_id( db: &PgPool, id: u64, currency: &str, -) -> sqlx::Result<Option<TransferStatus>> { +) -> sqlx::Result<Option<TransferStatus<GenericPaytoImpl>>> { sqlx::query( " SELECT @@ -156,7 +163,7 @@ pub async fn outgoing_page( params: &History, currency: &str, listen: impl FnOnce() -> Receiver<i64>, -) -> sqlx::Result<Vec<OutgoingBankTransaction>> { +) -> sqlx::Result<Vec<OutgoingBankTransaction<GenericPaytoImpl>>> { history( db, "transfer_id", @@ -199,7 +206,7 @@ pub enum AddIncomingResult { pub async fn add_incoming( db: &PgPool, amount: &Amount, - debit_account: &Payto, + debit_account: &GenericPayto, subject: &str, timestamp: &Timestamp, kind: IncomingType, @@ -233,7 +240,7 @@ pub async fn incoming_page( params: &History, currency: &str, listen: impl FnOnce() -> Receiver<i64>, -) -> sqlx::Result<Vec<IncomingBankTransaction>> { +) -> sqlx::Result<Vec<IncomingBankTransaction<GenericPaytoImpl>>> { history( db, "incoming_transaction_id", diff --git a/common/taler-api/tests/common/mod.rs b/common/taler-api/tests/common/mod.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 @@ -32,7 +32,10 @@ use taler_common::{ TransferState, TransferStatus, }, error_code::ErrorCode, - types::{payto::payto, timestamp::Timestamp}, + types::{ + payto::{payto, GenericPaytoImpl}, + timestamp::Timestamp, + }, }; use tokio::sync::watch::Sender; @@ -46,7 +49,7 @@ pub struct SampleState { incoming_channel: Sender<i64>, } -impl WireGatewayImpl for SampleState { +impl WireGatewayImpl<GenericPaytoImpl> for SampleState { fn name(&self) -> &str { "taler-wire-gateway" } @@ -59,7 +62,10 @@ impl WireGatewayImpl for SampleState { None } - async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> { + async fn transfer( + &self, + req: TransferRequest<GenericPaytoImpl>, + ) -> ApiResult<TransferResponse> { let result = db::transfer(&self.pool, req).await?; match result { db::TransferResult::Success(transfer_response) => Ok(transfer_response), @@ -74,42 +80,48 @@ impl WireGatewayImpl for SampleState { &self, page: Page, status: Option<TransferState>, - ) -> ApiResult<TransferList> { + ) -> ApiResult<TransferList<GenericPaytoImpl>> { Ok(TransferList { transfers: db::transfer_page(&self.pool, &status, &page, &self.currency).await?, - debit_account: payto("payto://todo"), + debit_account: payto("payto://test"), }) } - async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> { + async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus<GenericPaytoImpl>>> { Ok(db::transfer_by_id(&self.pool, id, &self.currency).await?) } - async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> { + async fn outgoing_history( + &self, + params: History, + ) -> ApiResult<OutgoingHistory<GenericPaytoImpl>> { let txs = db::outgoing_page(&self.pool, &params, &self.currency, || { self.outgoing_channel.subscribe() }) .await?; Ok(OutgoingHistory { outgoing_transactions: txs, - debit_account: payto("payto://todo"), + debit_account: payto("payto://test"), }) } - async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> { + async fn incoming_history( + &self, + params: History, + ) -> ApiResult<IncomingHistory<GenericPaytoImpl>> { let txs = db::incoming_page(&self.pool, &params, &self.currency, || { self.incoming_channel.subscribe() }) .await?; Ok(IncomingHistory { incoming_transactions: txs, - credit_account: payto("payto://todo"), + credit_account: payto("payto://test"), }) } async fn add_incoming_reserve( &self, - req: AddIncomingRequest, + req: AddIncomingRequest<GenericPaytoImpl>, ) -> ApiResult<AddIncomingResponse> { let timestamp = Timestamp::now(); let res = db::add_incoming( @@ -131,7 +143,10 @@ impl WireGatewayImpl for SampleState { } } - async fn add_incoming_kyc(&self, req: AddKycauthRequest) -> ApiResult<AddKycauthResponse> { + async fn add_incoming_kyc( + &self, + req: AddKycauthRequest<GenericPaytoImpl>, + ) -> ApiResult<AddKycauthResponse> { let timestamp = Timestamp::now(); let res = db::add_incoming( &self.pool, diff --git a/common/taler-common/src/api_wire.rs b/common/taler-common/src/api_wire.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 @@ -18,7 +18,11 @@ use url::Url; -use crate::types::{amount::Amount, payto::Payto, timestamp::Timestamp}; +use crate::types::{ + amount::Amount, + payto::{Payto, PaytoImpl}, + timestamp::Timestamp, +}; use super::api_common::{EddsaPublicKey, HashCode, SafeU64, ShortHashCode, WadId}; use serde::{Deserialize, Serialize}; @@ -41,78 +45,85 @@ pub struct TransferResponse { /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransferRequest> #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct TransferRequest { +#[serde(bound = "")] +pub struct TransferRequest<P: PaytoImpl> { pub request_uid: HashCode, pub amount: Amount, pub exchange_base_url: Url, pub wtid: ShortHashCode, - pub credit_account: Payto, + pub credit_account: Payto<P>, } /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransferList> #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct TransferList { - pub transfers: Vec<TransferListStatus>, - pub debit_account: Payto, +#[serde(bound = "")] +pub struct TransferList<P: PaytoImpl> { + pub transfers: Vec<TransferListStatus<P>>, + pub debit_account: Payto<P>, } /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransferListStatus> #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct TransferListStatus { +#[serde(bound = "")] +pub struct TransferListStatus<P: PaytoImpl> { pub row_id: SafeU64, pub status: TransferState, pub amount: Amount, - pub credit_account: Payto, + pub credit_account: Payto<P>, pub timestamp: Timestamp, } /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-TransfertSatus> #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -pub struct TransferStatus { +#[serde(bound = "")] +pub struct TransferStatus<P: PaytoImpl> { pub status: TransferState, pub status_msg: Option<String>, pub amount: Amount, pub origin_exchange_url: String, pub wtid: ShortHashCode, - pub credit_account: Payto, + pub credit_account: Payto<P>, pub timestamp: Timestamp, } -#[derive(Debug, Clone, Serialize, Deserialize)] /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-OutgoingHistory> -pub struct OutgoingHistory { - pub outgoing_transactions: Vec<OutgoingBankTransaction>, - pub debit_account: Payto, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct OutgoingHistory<P: PaytoImpl> { + pub outgoing_transactions: Vec<OutgoingBankTransaction<P>>, + pub debit_account: Payto<P>, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-OutgoingBankTransaction> -pub struct OutgoingBankTransaction { +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(bound = "")] +pub struct OutgoingBankTransaction<P: PaytoImpl> { pub row_id: SafeU64, pub date: Timestamp, pub amount: Amount, - pub credit_account: Payto, + pub credit_account: Payto<P>, pub wtid: ShortHashCode, pub exchange_base_url: Url, } -#[derive(Debug, Clone, Serialize, Deserialize)] /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-IncomingHistory> -pub struct IncomingHistory { - pub credit_account: Payto, - pub incoming_transactions: Vec<IncomingBankTransaction>, +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct IncomingHistory<P: PaytoImpl> { + pub credit_account: Payto<P>, + pub incoming_transactions: Vec<IncomingBankTransaction<P>>, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(tag = "type")] /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-IncomingBankTransaction> -pub enum IncomingBankTransaction { +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(bound = "")] +pub enum IncomingBankTransaction<P: PaytoImpl> { #[serde(rename = "RESERVE")] Reserve { row_id: SafeU64, date: Timestamp, amount: Amount, - debit_account: Payto, + debit_account: Payto<P>, reserve_pub: EddsaPublicKey, }, #[serde(rename = "WAD")] @@ -120,7 +131,7 @@ pub enum IncomingBankTransaction { row_id: SafeU64, date: Timestamp, amount: Amount, - debit_account: Payto, + debit_account: Payto<P>, origin_exchange_url: Url, wad_id: WadId, }, @@ -129,36 +140,38 @@ pub enum IncomingBankTransaction { row_id: SafeU64, date: Timestamp, amount: Amount, - debit_account: Payto, + debit_account: Payto<P>, account_pub: EddsaPublicKey, }, } -#[derive(Debug, Clone, Serialize, Deserialize)] /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddIncomingRequest> -pub struct AddIncomingRequest { +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct AddIncomingRequest<P: PaytoImpl> { pub amount: Amount, pub reserve_pub: EddsaPublicKey, - pub debit_account: Payto, + pub debit_account: Payto<P>, } -#[derive(Debug, Clone, Serialize, Deserialize)] /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddIncomingResponse> +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct AddIncomingResponse { pub row_id: SafeU64, pub timestamp: Timestamp, } -#[derive(Debug, Clone, Serialize, Deserialize)] /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddKycauthRequest> -pub struct AddKycauthRequest { +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct AddKycauthRequest<P: PaytoImpl> { pub amount: Amount, pub account_pub: EddsaPublicKey, - pub debit_account: Payto, + pub debit_account: Payto<P>, } -#[derive(Debug, Clone, Serialize, Deserialize)] /// <https://docs.taler.net/core/api-bank-wire.html#tsref-type-AddKycauthResponse> +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct AddKycauthResponse { pub row_id: SafeU64, pub timestamp: Timestamp, 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::Payto, + payto::GenericPayto, }; 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, Payto> { + pub fn payto(&self, option: &'arg str) -> Value<'arg, GenericPayto> { self.parse("payto", option) } diff --git a/common/taler-common/src/types/payto.rs b/common/taler-common/src/types/payto.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,56 +14,117 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -use std::{fmt::Debug, str::FromStr}; +use std::{fmt::Debug, ops::Deref, str::FromStr}; use url::Url; -#[derive(PartialEq, Eq, Clone, serde_with::DeserializeFromStr, serde_with::SerializeDisplay)] -pub struct Payto(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; + + fn kind() -> &'static str; + + fn parse(path: &mut std::str::Split<'_, char>) -> Result<Self, Self::ParseErr>; +} + +/// A generic payto implementation that accept any kind +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GenericPaytoImpl {} + +impl PaytoImpl for GenericPaytoImpl { + type ParseErr = std::convert::Infallible; + + fn parse(path: &mut std::str::Split<'_, char>) -> Result<Self, Self::ParseErr> { + for _ in path {} + Ok(Self {}) + } + + fn kind() -> &'static str { + "" + } +} + +/// RFC 8905 payto URI +#[derive( + Debug, Clone, PartialEq, Eq, serde_with::DeserializeFromStr, serde_with::SerializeDisplay, +)] +pub struct Payto<Impl: PaytoImpl> { + raw: Url, + parsed: Impl, +} -impl Payto { +impl<Impl: PaytoImpl> Payto<Impl> { pub fn raw(&self) -> &str { - self.0.as_str() + self.raw.as_str() + } + + pub fn generic(self) -> GenericPayto { + Payto { + raw: self.raw, + parsed: GenericPaytoImpl {}, + } } } -impl std::fmt::Display for Payto { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) +impl<Impl: PaytoImpl> Deref for Payto<Impl> { + type Target = Impl; + + fn deref(&self) -> &Self::Target { + &self.parsed } } -impl std::fmt::Debug for Payto { +impl<Impl: PaytoImpl> std::fmt::Display for Payto<Impl> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Debug::fmt(&self.0, f) + std::fmt::Display::fmt(&self.raw, f) } } -pub fn payto(url: impl AsRef<str>) -> Payto { - url.as_ref().parse().expect("invalid payto") +impl AsRef<Url> for GenericPayto { + fn as_ref(&self) -> &Url { + &self.raw + } } #[derive(Debug, thiserror::Error)] -pub enum ParsePaytoError { +pub enum ParsePaytoErr<E> { #[error("invalid URI: {0}")] Url(#[from] url::ParseError), - #[error("not a payto URI")] - NotPayto, + #[error("expected a payto URI got {0}")] + NotPayto(String), + #[error("unsupported payto kind, expected {0} got {1}")] + UnsupportedKind(&'static str, String), + #[error("to much path segment for a {0} payto uri")] + TooLong(&'static str), + #[error(transparent)] + Custom(E), } -impl FromStr for Payto { - type Err = ParsePaytoError; +impl<Impl: PaytoImpl> FromStr for Payto<Impl> { + type Err = ParsePaytoErr<Impl::ParseErr>; fn from_str(s: &str) -> Result<Self, Self::Err> { - let url: Url = s.parse()?; - if url.scheme() != "payto" { - return Err(ParsePaytoError::NotPayto); + let raw: Url = s.parse()?; + if raw.scheme() != "payto" { + return Err(ParsePaytoErr::NotPayto(raw.scheme().to_owned())); } - Ok(Self(url)) - } -} - -impl AsRef<Url> for Payto { - fn as_ref(&self) -> &Url { - &self.0 + 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)); + } + Ok(Self { raw, parsed }) } } diff --git a/common/test-utils/src/routine.rs b/common/test-utils/src/routine.rs @@ -34,7 +34,12 @@ use taler_common::{ TransferStatus, }, error_code::ErrorCode, - types::{amount::amount, base32::Base32, url}, + types::{ + amount::amount, + base32::Base32, + payto::{Payto, PaytoImpl}, + url, + }, }; use tokio::time::sleep; @@ -263,7 +268,11 @@ async fn get_currency(server: &TestServer) -> String { } /// Test standard behavior of the transfer endpoints -pub async fn transfer_routine(server: &TestServer, default_status: TransferState) { +pub async fn transfer_routine<P: PaytoImpl + Eq + Debug>( + server: &TestServer, + default_status: TransferState, + credit_account: &Payto<P>, +) { let currency = &get_currency(server).await; let default_amount = amount(format!("{currency}:42")); let transfer_request = json!({ @@ -271,7 +280,7 @@ pub async fn transfer_routine(server: &TestServer, default_status: TransferState "amount": default_amount, "exchange_base_url": "http://exchange.taler", "wtid": ShortHashCode::rand(), - "credit_account": "payto://todo", + "credit_account": credit_account, }); // Check empty db @@ -333,7 +342,7 @@ pub async fn transfer_routine(server: &TestServer, default_status: TransferState let tx = server .get(&format!("/transfers/{}", resp.row_id)) .await - .assert_ok_json::<TransferStatus>(); + .assert_ok_json::<TransferStatus<P>>(); assert_eq!(default_status, tx.status); assert_eq!(default_amount, tx.amount); assert_eq!("http://exchange.taler/", tx.origin_exchange_url); @@ -363,19 +372,19 @@ pub async fn transfer_routine(server: &TestServer, default_status: TransferState let list = server .get("/transfers") .await - .assert_ok_json::<TransferList>(); + .assert_ok_json::<TransferList<P>>(); assert_eq!(list.transfers.len(), 6); assert_eq!( list, server .get(&format!("/transfers?status={}", default_status.as_ref())) .await - .assert_ok_json::<TransferList>() + .assert_ok_json::<TransferList<P>>() ); } // Pagination test - routine_pagination::<TransferList, _>( + routine_pagination::<TransferList<P>, _>( server, "/transfers", |it| { @@ -392,7 +401,7 @@ pub async fn transfer_routine(server: &TestServer, default_status: TransferState "amount": amount(format!("{currency}:0.0{i}")), "exchange_base_url": url("http://exchange.taler"), "wtid": ShortHashCode::rand(), - "credit_account": url("payto://todo"), + "credit_account": credit_account, })) .await .assert_ok_json::<TransferResponse>(); @@ -402,7 +411,12 @@ pub async fn transfer_routine(server: &TestServer, default_status: TransferState } } -async fn add_incoming_routine(server: &TestServer, currency: &str, kind: IncomingType) { +async fn add_incoming_routine<P: PaytoImpl>( + server: &TestServer, + currency: &str, + kind: IncomingType, + debit_acount: &Payto<P>, +) { let (path, key) = match kind { IncomingType::reserve => ("/admin/add-incoming", "reserve_pub"), IncomingType::kyc => ("/admin/add-kycauth", "account_pub"), @@ -411,7 +425,7 @@ async fn add_incoming_routine(server: &TestServer, currency: &str, kind: Incomin let valid_req = json!({ "amount": format!("{currency}:44"), key: EddsaPublicKey::rand(), - "debit_account": "payto://todo", + "debit_account": debit_acount, }); // Check OK @@ -470,7 +484,10 @@ async fn add_incoming_routine(server: &TestServer, currency: &str, kind: Incomin } /// Test standard behavior of the admin add incoming endpoints -pub async fn admin_add_incoming_routine(server: &TestServer) { +pub async fn admin_add_incoming_routine<P: PaytoImpl>( + server: &TestServer, + debit_acount: &Payto<P>, +) { let currency = &get_currency(server).await; // History @@ -478,7 +495,7 @@ pub async fn admin_add_incoming_routine(server: &TestServer) { routine_history( server, "/history/incoming", - |it: IncomingHistory| { + |it: IncomingHistory<P>| { it.incoming_transactions .into_iter() .map(|it| match it { @@ -496,7 +513,7 @@ pub async fn admin_add_incoming_routine(server: &TestServer) { .json(&json!({ "amount": format!("{currency}:0.0{i}"), "reserve_pub": EddsaPublicKey::rand(), - "debit_account": "payto://todo", + "debit_account": debit_acount, })) .await .assert_ok_json::<TransferResponse>(); @@ -506,7 +523,7 @@ pub async fn admin_add_incoming_routine(server: &TestServer) { .json(&json!({ "amount": format!("{currency}:0.0{i}"), "account_pub": EddsaPublicKey::rand(), - "debit_account": "payto://todo", + "debit_account": debit_acount, })) .await .assert_ok_json::<TransferResponse>(); @@ -517,7 +534,7 @@ pub async fn admin_add_incoming_routine(server: &TestServer) { ) .await; // Add incoming reserve - add_incoming_routine(server, currency, IncomingType::reserve).await; + add_incoming_routine(server, currency, IncomingType::reserve, debit_acount).await; // Add incoming kyc - add_incoming_routine(server, currency, IncomingType::kyc).await; + add_incoming_routine(server, currency, IncomingType::kyc, debit_acount).await; } diff --git a/wire-gateway/magnet-bank/src/db.rs b/wire-gateway/magnet-bank/src/db.rs @@ -27,11 +27,15 @@ use taler_common::{ IncomingBankTransaction, OutgoingBankTransaction, TransferListStatus, TransferRequest, TransferState, TransferStatus, }, - types::{amount::Amount, payto::Payto, timestamp::Timestamp}, + types::{ + amount::Amount, + payto::{GenericPayto, GenericPaytoImpl}, + timestamp::Timestamp, + }, }; use tokio::sync::watch::{Receiver, Sender}; -use crate::constant::CURRENCY; +use crate::{constant::CURRENCY, MagnetPayto, MagnetPaytoImpl}; pub async fn notification_listener( pool: PgPool, @@ -61,7 +65,7 @@ pub struct TxIn { pub code: u64, pub amount: Amount, pub subject: String, - pub debit_payto: Payto, + pub debit_payto: MagnetPayto, pub timestamp: Timestamp, } @@ -70,7 +74,7 @@ pub struct TxOut { pub code: u64, pub amount: Amount, pub subject: String, - pub credit_payto: Payto, + pub credit_payto: MagnetPayto, pub timestamp: Timestamp, } @@ -78,7 +82,7 @@ pub struct TxOut { pub struct TxInAdmin { pub amount: Amount, pub subject: String, - pub debit_payto: Payto, + pub debit_payto: MagnetPayto, pub timestamp: Timestamp, pub metadata: IncomingSubject, } @@ -101,7 +105,7 @@ pub struct Initiated { pub id: u64, pub amount: Amount, pub subject: String, - pub creditor: Payto, + pub creditor: GenericPayto, } impl Display for Initiated { @@ -228,7 +232,7 @@ pub enum TransferResult { pub async fn make_transfer<'a>( db: impl PgExecutor<'a>, - req: &TransferRequest, + req: &TransferRequest<MagnetPaytoImpl>, timestamp: &Timestamp, ) -> sqlx::Result<TransferResult> { let subject = format!("{} {}", req.wtid, req.exchange_base_url); @@ -265,7 +269,7 @@ pub async fn transfer_page<'a>( db: impl PgExecutor<'a>, status: &Option<TransferState>, params: &Page, -) -> sqlx::Result<Vec<TransferListStatus>> { +) -> sqlx::Result<Vec<TransferListStatus<GenericPaytoImpl>>> { page( db, "initiated_id", @@ -307,7 +311,7 @@ pub async fn outgoing_history( db: &PgPool, params: &History, listen: impl FnOnce() -> Receiver<i64>, -) -> sqlx::Result<Vec<OutgoingBankTransaction>> { +) -> sqlx::Result<Vec<OutgoingBankTransaction<GenericPaytoImpl>>> { history( db, "tx_out_id", @@ -348,7 +352,7 @@ pub async fn incoming_history( db: &PgPool, params: &History, listen: impl FnOnce() -> Receiver<i64>, -) -> sqlx::Result<Vec<IncomingBankTransaction>> { +) -> sqlx::Result<Vec<IncomingBankTransaction<GenericPaytoImpl>>> { history( db, "tx_in_id", @@ -399,7 +403,7 @@ pub async fn incoming_history( pub async fn transfer_by_id<'a>( db: impl PgExecutor<'a>, id: u64, -) -> sqlx::Result<Option<TransferStatus>> { +) -> sqlx::Result<Option<TransferStatus<GenericPaytoImpl>>> { sqlx::query( " SELECT @@ -555,7 +559,7 @@ mod test { code: code, amount: amount("EUR:10"), subject: "subject".to_owned(), - debit_payto: payto("payto://"), + debit_payto: payto("payto://magnet-bank/todo"), timestamp: Timestamp::now_stable(), }; // Insert @@ -659,7 +663,7 @@ mod test { let tx = TxInAdmin { amount: amount("EUR:10"), subject: "subject".to_owned(), - debit_payto: payto("payto://"), + debit_payto: payto("payto://magnet-bank/todo"), timestamp: Timestamp::now_stable(), metadata: IncomingSubject::Reserve(EddsaPublicKey::rand()), }; @@ -739,7 +743,7 @@ mod test { code, amount: amount("EUR:10"), subject: "subject".to_owned(), - credit_payto: payto("payto://"), + credit_payto: payto("payto://magnet-bank/todo"), timestamp: Timestamp::now_stable(), }; // Insert @@ -844,7 +848,7 @@ mod test { amount: amount("EUR:10"), exchange_base_url: url("https://exchange.test.com/"), wtid: ShortHashCode::rand(), - credit_account: payto("payto://"), + credit_account: payto("payto://magnet-bank/todo"), }; let timestamp = Timestamp::now_stable(); // Insert @@ -960,7 +964,7 @@ mod test { amount: amount(format!("{CURRENCY}:{}", i + 1)), exchange_base_url: url("https://exchange.test.com/"), wtid: ShortHashCode::rand(), - credit_account: payto("payto://"), + credit_account: payto("payto://magnet-bank/todo"), }, &Timestamp::now(), ) @@ -981,7 +985,7 @@ mod test { amount: amount(format!("{CURRENCY}:{}", i + 1)), exchange_base_url: url("https://exchange.test.com/"), wtid: ShortHashCode::rand(), - credit_account: payto("payto://"), + credit_account: payto("payto://magnet-bank/todo"), }, &Timestamp::now(), ) diff --git a/wire-gateway/magnet-bank/src/dev.rs b/wire-gateway/magnet-bank/src/dev.rs @@ -79,7 +79,7 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> { code: tx.code, amount: amount.parse().unwrap(), subject: tx.subject, - debit_payto: payto("payto://"), + debit_payto: payto("payto://magnet-bank/todo"), timestamp: Timestamp::from(tx.value_date), }; info!("incoming {} '{}'", tx.amount, tx.subject); @@ -89,7 +89,7 @@ pub async fn dev(cfg: Config, cmd: DevCmd) -> anyhow::Result<()> { code: tx.code, amount: amount.parse().unwrap(), subject: tx.subject, - credit_payto: payto("payto://"), + credit_payto: payto("payto://magnet-bank/todo"), timestamp: Timestamp::from(tx.value_date), }; info!( diff --git a/wire-gateway/magnet-bank/src/lib.rs b/wire-gateway/magnet-bank/src/lib.rs @@ -14,10 +14,40 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +use taler_common::types::payto::{Payto, PaytoImpl}; + pub mod config; pub mod constant; pub mod db; +pub mod dev; pub mod keys; pub mod magnet; pub mod wire_gateway; -pub mod dev; -\ No newline at end of file + +pub type MagnetPayto = Payto<MagnetPaytoImpl>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MagnetPaytoImpl { + pub account_number: String, +} + +#[derive(Debug, thiserror::Error)] +pub enum MagnetPaytoErr { + #[error("missing Magnet Bank account number in path")] + MissingAccountNumber, +} + +impl PaytoImpl for MagnetPaytoImpl { + type ParseErr = MagnetPaytoErr; + + fn kind() -> &'static str { + "magnet-bank" + } + + fn parse(path: &mut std::str::Split<'_, char>) -> Result<Self, Self::ParseErr> { + let account_number = path.next().ok_or(MagnetPaytoErr::MissingAccountNumber)?; + Ok(Self { + account_number: account_number.to_owned(), + }) + } +} diff --git a/wire-gateway/magnet-bank/src/magnet.rs b/wire-gateway/magnet-bank/src/magnet.rs @@ -16,7 +16,6 @@ use base64::{prelude::BASE64_STANDARD, Engine}; use error::ApiResult; -use jiff::Timestamp; use p256::{ecdsa::SigningKey, PublicKey}; use serde_json::{json, Value}; use spki::EncodePublicKey; @@ -259,7 +258,7 @@ impl<'a> AuthClient<'a> { self.client .get(self.join("/NetBankOAuth/token/request")) .query(&[("oauth_callback", "oob")]) - .oauth(&self.consumer, None, None) + .oauth(self.consumer, None, None) .await .magnet_call_encoded() .await @@ -273,7 +272,7 @@ impl<'a> AuthClient<'a> { self.client .get(self.join("/NetBankOAuth/token/access")) .oauth( - &self.consumer, + self.consumer, Some(token_request), Some(&token_auth.oauth_verifier), ) @@ -307,7 +306,7 @@ impl<'a> ApiClient<'a> { pub async fn token_info(&self) -> ApiResult<TokenInfo> { self.client .get(self.join("/RESTApi/resources/v2/token")) - .oauth(&self.consumer, Some(&self.access), None) + .oauth(self.consumer, Some(self.access), None) .await .magnet_json() .await @@ -316,7 +315,7 @@ impl<'a> ApiClient<'a> { pub async fn request_sms_code(&self) -> ApiResult<SmsCodeSubmission> { self.client .get(self.join("/RESTApi/resources/v2/kodszo/sms/token")) - .oauth(&self.consumer, Some(&self.access), None) + .oauth(self.consumer, Some(self.access), None) .await .magnet_json() .await @@ -328,7 +327,7 @@ impl<'a> ApiClient<'a> { .json(&json!({ "kodszo": code })) - .oauth(&self.consumer, Some(&self.access), None) + .oauth(self.consumer, Some(self.access), None) .await .magnet_empty() .await @@ -342,7 +341,7 @@ impl<'a> ApiClient<'a> { .json(&json!({ "keyData": BASE64_STANDARD.encode(der) })) - .oauth(&self.consumer, Some(&self.access), None) + .oauth(self.consumer, Some(self.access), None) .await .magnet_json() .await @@ -351,7 +350,7 @@ impl<'a> ApiClient<'a> { pub async fn list_accounts(&self) -> ApiResult<PartnerList> { self.client .get(self.join("/RESTApi/resources/v2/partnerszamla/0")) - .oauth(&self.consumer, Some(&self.access), None) + .oauth(self.consumer, Some(self.access), None) .await .magnet_json() .await @@ -363,7 +362,7 @@ impl<'a> ApiClient<'a> { api_url: self.api_url, consumer: self.consumer, access: self.access, - account: account, + account, } } } @@ -376,7 +375,7 @@ pub struct AccountClient<'a> { account: &'a str, } -impl<'a> AccountClient<'a> { +impl AccountClient<'_> { fn join(&self, path: &str) -> reqwest::Url { self.api_url.join(path).unwrap() } @@ -405,7 +404,7 @@ impl<'a> AccountClient<'a> { } req.query(&[("tranzakciofrissites", "true")]) - .oauth(&self.consumer, Some(&self.access), None) + .oauth(self.consumer, Some(self.access), None) .await .magnet_call() .await diff --git a/wire-gateway/magnet-bank/src/main.rs b/wire-gateway/magnet-bank/src/main.rs @@ -110,7 +110,7 @@ async fn app(args: Args) -> anyhow::Result<()> { let db = DbConfig::parse(&cfg)?; let pool = PgPool::connect_with(db.cfg).await?; let cfg = WireGatewayConfig::parse(&cfg)?; - let gateway = MagnetWireGateway::start(pool, payto("payto://todo")).await; + let gateway = MagnetWireGateway::start(pool, payto("payto://magnet-bank/todo")).await; taler_api::server( taler_api::wire_gateway_api(Arc::new(gateway)), cfg.serve, diff --git a/wire-gateway/magnet-bank/src/wire_gateway.rs b/wire-gateway/magnet-bank/src/wire_gateway.rs @@ -28,21 +28,19 @@ use taler_common::{ TransferState, TransferStatus, }, error_code::ErrorCode, - types::{ - payto::{payto, Payto}, - timestamp::Timestamp, - }, + types::{payto::GenericPaytoImpl, timestamp::Timestamp}, }; use tokio::sync::watch::Sender; use crate::{ constant::CURRENCY, db::{self, AddIncomingResult, TxInAdmin}, + MagnetPayto, MagnetPaytoImpl, }; pub struct MagnetWireGateway { pub pool: sqlx::PgPool, - pub payto: Payto, + pub payto: MagnetPayto, pub in_channel: Sender<i64>, pub taler_in_channel: Sender<i64>, pub out_channel: Sender<i64>, @@ -50,7 +48,7 @@ pub struct MagnetWireGateway { } impl MagnetWireGateway { - pub async fn start(pool: sqlx::PgPool, payto: Payto) -> Self { + pub async fn start(pool: sqlx::PgPool, payto: MagnetPayto) -> Self { let in_channel = Sender::new(0); let taler_in_channel = Sender::new(0); let out_channel = Sender::new(0); @@ -74,7 +72,7 @@ impl MagnetWireGateway { } } -impl WireGatewayImpl for MagnetWireGateway { +impl WireGatewayImpl<MagnetPaytoImpl> for MagnetWireGateway { fn name(&self) -> &str { "magnet-bank" } @@ -87,7 +85,7 @@ impl WireGatewayImpl for MagnetWireGateway { None } - async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> { + async fn transfer(&self, req: TransferRequest<MagnetPaytoImpl>) -> ApiResult<TransferResponse> { let result = db::make_transfer(&self.pool, &req, &Timestamp::now()).await?; match result { db::TransferResult::Success { id, timestamp } => Ok(TransferResponse { @@ -106,40 +104,46 @@ impl WireGatewayImpl for MagnetWireGateway { &self, page: Page, status: Option<TransferState>, - ) -> ApiResult<TransferList> { + ) -> ApiResult<TransferList<GenericPaytoImpl>> { Ok(TransferList { transfers: db::transfer_page(&self.pool, &status, &page).await?, - debit_account: payto("payto://todo"), + debit_account: self.payto.clone().generic(), }) } - async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> { + async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus<GenericPaytoImpl>>> { Ok(db::transfer_by_id(&self.pool, id).await?) } - async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> { + async fn outgoing_history( + &self, + params: History, + ) -> ApiResult<OutgoingHistory<GenericPaytoImpl>> { Ok(OutgoingHistory { outgoing_transactions: db::outgoing_history(&self.pool, &params, || { self.taler_out_channel.subscribe() }) .await?, - debit_account: self.payto.clone(), + debit_account: self.payto.clone().generic(), }) } - async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> { + async fn incoming_history( + &self, + params: History, + ) -> ApiResult<IncomingHistory<GenericPaytoImpl>> { Ok(IncomingHistory { incoming_transactions: db::incoming_history(&self.pool, &params, || { self.taler_in_channel.subscribe() }) .await?, - credit_account: self.payto.clone(), + credit_account: self.payto.clone().generic(), }) } async fn add_incoming_reserve( &self, - req: AddIncomingRequest, + req: AddIncomingRequest<MagnetPaytoImpl>, ) -> ApiResult<AddIncomingResponse> { let res = db::register_tx_in_admin( &self.pool, @@ -164,7 +168,10 @@ impl WireGatewayImpl for MagnetWireGateway { } } - async fn add_incoming_kyc(&self, req: AddKycauthRequest) -> ApiResult<AddKycauthResponse> { + async fn add_incoming_kyc( + &self, + req: AddKycauthRequest<MagnetPaytoImpl>, + ) -> ApiResult<AddKycauthResponse> { let res = db::register_tx_in_admin( &self.pool, &TxInAdmin { 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}; +use magnet_bank::{db, wire_gateway::MagnetWireGateway, MagnetPaytoImpl}; use sqlx::PgPool; use taler_api::{auth::AuthMethod, standard_layer, subject::OutgoingSubject}; use taler_common::{ @@ -34,7 +34,7 @@ use test_utils::{ async fn setup() -> (TestServer, PgPool) { let pool = db_test_setup().await; db::db_init(&pool, false).await.unwrap(); - let gateway = MagnetWireGateway::start(pool.clone(), payto("payto://test")).await; + let gateway = MagnetWireGateway::start(pool.clone(), payto("payto://magnet-bank/todo")).await; let server = TestServer::new(standard_layer( taler_api::wire_gateway_api(Arc::new(gateway)), AuthMethod::None, @@ -47,14 +47,19 @@ async fn setup() -> (TestServer, PgPool) { #[tokio::test] async fn transfer() { let (server, _) = setup().await; - transfer_routine(&server, TransferState::pending).await; + transfer_routine::<MagnetPaytoImpl>( + &server, + TransferState::pending, + &payto("payto://magnet-bank/todo"), + ) + .await; } #[tokio::test] async fn outgoing_history() { let (server, pool) = setup().await; server.get("/history/outgoing").await.assert_no_content(); - routine_pagination::<OutgoingHistory, _>( + routine_pagination::<OutgoingHistory<MagnetPaytoImpl>, _>( &server, "/history/outgoing", |it| { @@ -73,7 +78,7 @@ async fn outgoing_history() { code: i as u64, amount: amount("EUR:10"), subject: "subject".to_owned(), - credit_payto: payto("payto://"), + credit_payto: payto("payto://magnet-bank/todo"), timestamp: Timestamp::now_stable(), }, &Some(OutgoingSubject( @@ -92,5 +97,6 @@ async fn outgoing_history() { #[tokio::test] async fn admin_add_incoming() { let (server, _) = setup().await; - admin_add_incoming_routine(&server).await; + admin_add_incoming_routine::<MagnetPaytoImpl>(&server, &payto("payto://magnet-bank/todo")) + .await; }