commit f0433106c1da705c9f76d8500c9918910ecb5240
parent 5eca882b053c170ac6f7e28c5715b45e23fbec16
Author: Antoine A <>
Date: Tue, 21 Jan 2025 12:28:28 +0100
common: support payto implementation
Diffstat:
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, ¶ms, &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, ¶ms, &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, ¶ms, || {
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, ¶ms, || {
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;
}