commit 8ac0bdbd74316b5f6957f40114c90a0b38c269e0
parent b49e2c1855081a70b8a790c72a230e8e1976645b
Author: Antoine A <>
Date: Tue, 30 Dec 2025 18:05:16 +0100
cyclos: new payto format
Diffstat:
16 files changed, 327 insertions(+), 173 deletions(-)
diff --git a/common/taler-common/src/config.rs b/common/taler-common/src/config.rs
@@ -849,6 +849,35 @@ impl<'cfg, 'arg> Section<'cfg, 'arg> {
self.parse("url", option)
}
+ /** Access [option] as base url */
+ pub fn base_url(&self, option: &'arg str) -> Value<'arg, Url> {
+ self.value("url", option, |s| {
+ let url = Url::from_str(s).map_err(|e| e.to_string())?;
+ if url.scheme() != "http" && url.scheme() != "https" {
+ Err(format!(
+ "only 'http' and 'https' are accepted for baseURL got '{}''",
+ url.scheme()
+ ))
+ } else if !url.has_host() {
+ Err(format!("missing host in baseURL got '{url}'"))
+ } else if url.query().is_some() {
+ Err(format!(
+ "require no query in baseURL got '{}'",
+ url.query().unwrap()
+ ))
+ } else if url.fragment().is_some() {
+ Err(format!(
+ "require no fragment in baseURL got '{}'",
+ url.fragment().unwrap()
+ ))
+ } else if !url.path().ends_with('/') {
+ Err(format!("baseURL path must end with / got '{}'", url.path()))
+ } else {
+ Ok(url)
+ }
+ })
+ }
+
/** Access [option] as payto */
pub fn payto(&self, option: &'arg str) -> Value<'arg, PaytoURI> {
self.parse("payto", option)
diff --git a/debian/etc/taler-cyclos/conf.d/cyclos-worker.conf b/debian/etc/taler-cyclos/conf.d/cyclos-worker.conf
@@ -1,9 +1,6 @@
# Configuration the cyclos adapter worker.
[cyclos-worker]
-# URL of the Cyclos API server
-API_URL =
-
# Cyclos account type ID to index
ACCOUNT_TYPE_ID =
diff --git a/debian/etc/taler-cyclos/taler-cyclos.conf b/debian/etc/taler-cyclos/taler-cyclos.conf
@@ -20,10 +20,12 @@
# ".secret.conf".
[cyclos]
-
# Adapter currency
CURRENCY =
+# Cyclos root url with an optional specific network
+CYCLOS_URL =
+
# Account ID of the Cyclos account to sync
ACCOUNT_ID =
diff --git a/taler-cyclos/README.md b/taler-cyclos/README.md
@@ -29,8 +29,8 @@ Configure the exchange account like you would in production and add the addition
```ini
# dev.json
-[cyclos-harness]
-API_URL = http://localhost:8080/api/
+[cyclos]
+CYCLOS_URL = http://localhost:8080/
[cyclos-harness]
USERNAME = client
@@ -55,8 +55,8 @@ Create manually two accounts with some money.
```ini
# dev.json
-[cyclos-harness]
-API_URL = https://demo.cyclos.org/api/
+[cyclos]
+CYCLOS_URL = https://demo.cyclos.org/
[cyclos-harness]
USERNAME = client
diff --git a/taler-cyclos/cyclos.conf b/taler-cyclos/cyclos.conf
@@ -1,6 +1,9 @@
[cyclos]
# Adapter currency
-CURRENCY =
+CURRENCY =
+
+# Cyclos root url with an optional specific network
+CYCLOS_URL =
# Account ID of the Cyclos account to sync
ACCOUNT_ID =
@@ -12,9 +15,6 @@ NAME =
# How often should worker run when no notification is received
FREQUENCY = 30m
-# URL of the Cyclos API server
-API_URL =
-
# Account username
USERNAME =
diff --git a/taler-cyclos/src/api.rs b/taler-cyclos/src/api.rs
@@ -14,6 +14,7 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+use compact_str::CompactString;
use jiff::Timestamp;
use taler_api::{
api::{TalerApi, revenue::Revenue, wire::WireGateway},
@@ -47,10 +48,16 @@ pub struct CyclosApi {
pub taler_in_channel: Sender<i64>,
pub out_channel: Sender<i64>,
pub taler_out_channel: Sender<i64>,
+ pub root: CompactString,
}
impl CyclosApi {
- pub async fn start(pool: sqlx::PgPool, payto: PaytoURI, currency: Currency) -> Self {
+ pub async fn start(
+ pool: sqlx::PgPool,
+ root: CompactString,
+ payto: PaytoURI,
+ currency: Currency,
+ ) -> Self {
let in_channel = Sender::new(0);
let taler_in_channel = Sender::new(0);
let out_channel = Sender::new(0);
@@ -59,6 +66,7 @@ impl CyclosApi {
pool: pool.clone(),
payto,
currency,
+ root,
in_channel: in_channel.clone(),
taler_in_channel: taler_in_channel.clone(),
out_channel: out_channel.clone(),
@@ -88,7 +96,14 @@ impl TalerApi for CyclosApi {
impl WireGateway for CyclosApi {
async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> {
let creditor = FullCyclosPayto::try_from(&req.credit_account)?;
- let result = db::make_transfer(&self.pool, &req, &creditor, &Timestamp::now()).await?;
+ let result = db::make_transfer(
+ &self.pool,
+ &req,
+ *creditor.id,
+ &creditor.name,
+ &Timestamp::now(),
+ )
+ .await?;
match result {
db::TransferResult::Success { id, initiated_at } => Ok(TransferResponse {
timestamp: initiated_at.into(),
@@ -111,13 +126,14 @@ impl WireGateway for CyclosApi {
status: Option<TransferState>,
) -> ApiResult<TransferList> {
Ok(TransferList {
- transfers: db::transfer_page(&self.pool, &status, &self.currency, &page).await?,
+ transfers: db::transfer_page(&self.pool, &status, &self.currency, &self.root, &page)
+ .await?,
debit_account: self.payto.clone(),
})
}
async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> {
- Ok(db::transfer_by_id(&self.pool, id, &self.currency).await?)
+ Ok(db::transfer_by_id(&self.pool, id, &self.currency, &self.root).await?)
}
async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> {
@@ -126,6 +142,7 @@ impl WireGateway for CyclosApi {
&self.pool,
¶ms,
&self.currency,
+ &self.root,
|| self.taler_out_channel.subscribe(),
)
.await?,
@@ -139,6 +156,7 @@ impl WireGateway for CyclosApi {
&self.pool,
¶ms,
&self.currency,
+ &self.root,
|| self.taler_in_channel.subscribe(),
)
.await?,
@@ -211,9 +229,13 @@ impl WireGateway for CyclosApi {
impl Revenue for CyclosApi {
async fn history(&self, params: History) -> ApiResult<RevenueIncomingHistory> {
Ok(RevenueIncomingHistory {
- incoming_transactions: db::revenue_history(&self.pool, ¶ms, &self.currency, || {
- self.in_channel.subscribe()
- })
+ incoming_transactions: db::revenue_history(
+ &self.pool,
+ ¶ms,
+ &self.currency,
+ &self.root,
+ || self.in_channel.subscribe(),
+ )
.await?,
credit_account: self.payto.clone(),
})
diff --git a/taler-cyclos/src/bin/cyclos-harness.rs b/taler-cyclos/src/bin/cyclos-harness.rs
@@ -17,6 +17,7 @@
use std::time::Duration;
use clap::Parser as _;
+use compact_str::CompactString;
use failure_injection::{InjectedErr, set_failure_scenario};
use jiff::Timestamp;
use owo_colors::OwoColorize as _;
@@ -32,11 +33,12 @@ use taler_common::{
taler_main,
types::{
amount::{Amount, Currency, Decimal, decimal},
+ payto::payto,
url,
},
};
use taler_cyclos::{
- CyclosId, FullCyclosPayto,
+ FullCyclosPayto,
config::{AccountType, HarnessCfg},
constants::CONFIG_SOURCE,
cyclos_api::{
@@ -87,6 +89,7 @@ struct Harness<'a> {
payment_type_id: u64,
account_type_id: u64,
currency: Currency,
+ root: CompactString,
}
impl<'a> Harness<'a> {
@@ -109,7 +112,7 @@ impl<'a> Harness<'a> {
async fn client_send(&self, subject: &str, amount: Decimal) -> u64 {
*self
.client
- .direct_payment(**self.wire_payto, self.payment_type_id, amount, subject)
+ .direct_payment(*self.wire_payto.id, self.payment_type_id, amount, subject)
.await
.unwrap()
.id
@@ -119,7 +122,7 @@ impl<'a> Harness<'a> {
async fn exchange_send(&self, subject: &str, amount: Decimal) -> u64 {
*self
.wire
- .direct_payment(**self.client_payto, self.payment_type_id, amount, subject)
+ .direct_payment(*self.client_payto.id, self.payment_type_id, amount, subject)
.await
.unwrap()
.id
@@ -133,7 +136,7 @@ impl<'a> Harness<'a> {
/// Fetch last transfer related to client
async fn client_last_transfer(&self) -> HistoryItem {
self.client
- .history(**self.client_payto, OrderBy::DateDesc, 0)
+ .history(*self.client_payto.id, OrderBy::DateDesc, 0)
.await
.unwrap()
.page
@@ -166,6 +169,7 @@ impl<'a> Harness<'a> {
timeout_ms: None,
},
&self.currency,
+ &self.root,
|| tokio::sync::watch::channel(0).1,
)
.await
@@ -176,7 +180,7 @@ impl<'a> Harness<'a> {
));
}
- async fn custom_transfer(&self, amount: Decimal, creditor: &FullCyclosPayto) -> u64 {
+ async fn custom_transfer(&self, amount: Decimal, creditor_id: u64, creditor_name: &str) -> u64 {
let res = db::make_transfer(
self.pool,
&TransferRequest {
@@ -184,9 +188,10 @@ impl<'a> Harness<'a> {
amount: Amount::new_decimal(&self.currency, amount),
exchange_base_url: url("https://test.com"),
wtid: ShortHashCode::rand(),
- credit_account: creditor.as_payto(),
+ credit_account: payto("payto://"),
},
- creditor,
+ creditor_id,
+ creditor_name,
&Timestamp::now(),
)
.await
@@ -198,7 +203,8 @@ impl<'a> Harness<'a> {
}
async fn transfer(&self, amount: Decimal) -> u64 {
- self.custom_transfer(amount, &self.client_payto).await
+ self.custom_transfer(amount, *self.client_payto.id, &self.client_payto.name)
+ .await
}
async fn transfer_id(&self, transfer_id: u64) -> u64 {
@@ -219,7 +225,7 @@ impl<'a> Harness<'a> {
async fn expect_transfer_status(&self, id: u64, status: TransferState, msg: Option<&str>) {
let mut attempts = 0;
loop {
- let transfer = db::transfer_by_id(self.pool, id, &self.currency)
+ let transfer = db::transfer_by_id(self.pool, id, &self.currency, &self.root)
.await
.unwrap()
.unwrap();
@@ -330,11 +336,16 @@ async fn logic_harness(cfg: &Config, reset: bool) -> anyhow::Result<()> {
};
let harness = Harness {
pool: &pool,
- client_payto: client.whoami().await.unwrap().payto(),
- wire_payto: wire.whoami().await.unwrap().payto(),
+ client_payto: client
+ .whoami()
+ .await
+ .unwrap()
+ .payto(cfg.worker.root.clone()),
+ wire_payto: wire.whoami().await.unwrap().payto(cfg.worker.root.clone()),
client,
wire,
currency: cfg.worker.currency,
+ root: cfg.worker.root,
payment_type_id: *cfg.worker.payment_type_id,
account_type_id: *cfg.worker.account_type_id,
};
@@ -389,7 +400,11 @@ async fn logic_harness(cfg: &Config, reset: bool) -> anyhow::Result<()> {
step("Test transfer to self");
// Init a transfer to self
let transfer_id = harness
- .custom_transfer(decimal("10.1"), &harness.wire_payto)
+ .custom_transfer(
+ decimal("10.1"),
+ *harness.wire_payto.id,
+ &harness.wire_payto.name,
+ )
.await;
// Should failed
harness.worker().await?;
@@ -405,10 +420,7 @@ async fn logic_harness(cfg: &Config, reset: bool) -> anyhow::Result<()> {
step("Test transfer to unknown account");
// Init a transfer to unknown
let transfer_id = harness
- .custom_transfer(
- decimal("10.1"),
- &FullCyclosPayto::new(CyclosId(42), "Unknown"),
- )
+ .custom_transfer(decimal("10.1"), 42, "Unknown")
.await;
// Should failed
harness.worker().await?;
@@ -542,11 +554,16 @@ async fn online_harness(config: &Config, reset: bool) -> anyhow::Result<()> {
let harness = Harness {
pool: &pool,
- client_payto: client.whoami().await.unwrap().payto(),
- wire_payto: wire.whoami().await.unwrap().payto(),
+ client_payto: client
+ .whoami()
+ .await
+ .unwrap()
+ .payto(cfg.worker.root.clone()),
+ wire_payto: wire.whoami().await.unwrap().payto(cfg.worker.root.clone()),
client,
wire,
currency: cfg.worker.currency,
+ root: cfg.worker.root,
payment_type_id: *cfg.worker.payment_type_id,
account_type_id: *cfg.worker.account_type_id,
};
@@ -580,7 +597,11 @@ async fn online_harness(config: &Config, reset: bool) -> anyhow::Result<()> {
let taler_amount = decimal("2");
let transfer_self = harness
- .custom_transfer(self_amount, &harness.wire_payto)
+ .custom_transfer(
+ self_amount,
+ *harness.wire_payto.id,
+ &harness.wire_payto.name,
+ )
.await;
let transfer_id = harness.transfer(taler_amount).await;
balance.expect_sub(taler_amount).await;
diff --git a/taler-cyclos/src/config.rs b/taler-cyclos/src/config.rs
@@ -16,6 +16,7 @@
use std::time::Duration;
+use compact_str::{CompactString, format_compact};
use reqwest::Url;
use taler_api::{
Serve,
@@ -27,7 +28,7 @@ use taler_common::{
types::{amount::Currency, payto::PaytoURI},
};
-use crate::{CyclosId, FullCyclosPayto};
+use crate::{CyclosAccount, CyclosId, FullCyclosPayto};
#[derive(Debug, Clone, Copy)]
pub enum AccountType {
@@ -39,18 +40,63 @@ pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, ValueErr> {
DbCfg::parse(cfg.section("cyclosdb-postgres"))
}
-pub fn parse_account_payto(cfg: &Config) -> Result<FullCyclosPayto, ValueErr> {
+pub fn parse_account_payto(cfg: &Config, main: &MainCfg) -> Result<FullCyclosPayto, ValueErr> {
let sect = cfg.section("cyclos");
let id = sect.parse("cyclos account id", "ACCOUNT_ID").require()?;
let name = sect.str("NAME").require()?;
- Ok(FullCyclosPayto::new(id, &name))
+ Ok(FullCyclosPayto::new(
+ CyclosAccount {
+ id,
+ root: main.root.clone(),
+ },
+ &name,
+ ))
+}
+
+/// Cyclos main config
+pub struct MainCfg {
+ pub currency: Currency,
+ pub url: Url,
+ pub root: CompactString,
+}
+
+impl MainCfg {
+ pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ let sect = cfg.section("cyclos");
+ let url = sect.base_url("CYCLOS_URL").require()?;
+ let root = format_compact!("{}{}", url.host_str().unwrap_or_default(), url.path());
+ Ok(Self {
+ currency: sect.parse("currency", "CURRENCY").require()?,
+ url,
+ root,
+ })
+ }
+}
+
+/// Cyclos API config
+pub struct HostCfg {
+ pub api_url: Url,
+ pub username: String,
+ pub password: String,
+}
+
+impl HostCfg {
+ pub fn parse(cfg: &Config, main: &MainCfg) -> Result<Self, ValueErr> {
+ let sect = cfg.section("cyclos-worker");
+ Ok(Self {
+ username: sect.str("USERNAME").require()?,
+ api_url: main.url.join("api/").unwrap(),
+ password: sect.str("PASSWORD").require()?,
+ })
+ }
}
/// taler-cyclos httpd config
pub struct ServeCfg {
pub payto: PaytoURI,
pub currency: Currency,
+ pub root: CompactString,
pub serve: Serve,
pub wire_gateway: Option<ApiCfg>,
pub revenue: Option<ApiCfg>,
@@ -58,9 +104,9 @@ pub struct ServeCfg {
impl ServeCfg {
pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
- let payto = parse_account_payto(cfg)?;
+ let main = MainCfg::parse(cfg)?;
+ let payto = parse_account_payto(cfg, &main)?;
- let main_sect = cfg.section("cyclos");
let sect = cfg.section("cyclos-httpd");
let serve = Serve::parse(sect)?;
@@ -70,7 +116,8 @@ impl ServeCfg {
Ok(Self {
payto: payto.as_payto(),
- currency: main_sect.parse("currency", "CURRENCY").require()?,
+ currency: main.currency,
+ root: main.root,
serve,
wire_gateway,
revenue,
@@ -78,24 +125,6 @@ impl ServeCfg {
}
}
-/// Cyclos API config
-pub struct HostCfg {
- pub api_url: Url,
- pub username: String,
- pub password: String,
-}
-
-impl HostCfg {
- pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
- let sect = cfg.section("cyclos-worker");
- Ok(Self {
- username: sect.str("USERNAME").require()?,
- api_url: sect.parse("URL", "API_URL").require()?,
- password: sect.str("PASSWORD").require()?,
- })
- }
-}
-
/// taler-cyclos setup config
pub struct SetupCfg {
pub currency: Currency,
@@ -108,10 +137,10 @@ pub struct SetupCfg {
impl SetupCfg {
pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
+ let main = MainCfg::parse(cfg)?;
let main_s = cfg.section("cyclos");
let worker_s = cfg.section("cyclos-worker");
Ok(Self {
- currency: main_s.parse("currency", "CURRENCY").require()?,
id: main_s.parse("cyclos account id", "ACCOUNT_ID").opt()?,
account_type_id: worker_s
.parse("cyclos account type id", "ACCOUNT_TYPE_ID")
@@ -120,7 +149,8 @@ impl SetupCfg {
.parse("cyclos payment type id", "PAYMENT_TYPE_ID")
.opt()?,
name: main_s.str("NAME").opt()?,
- host: HostCfg::parse(cfg)?,
+ host: HostCfg::parse(cfg, &main)?,
+ currency: main.currency
})
}
}
@@ -128,6 +158,7 @@ impl SetupCfg {
/// taler-cyclos worker config
pub struct WorkerCfg {
pub currency: Currency,
+ pub root: CompactString,
pub frequency: Duration,
pub host: HostCfg,
pub account_type: AccountType,
@@ -137,10 +168,9 @@ pub struct WorkerCfg {
impl WorkerCfg {
pub fn parse(cfg: &Config) -> Result<Self, ValueErr> {
- let main_sect = cfg.section("cyclos");
+ let main = MainCfg::parse(&cfg)?;
let sect = cfg.section("cyclos-worker");
Ok(Self {
- currency: main_sect.parse("currency", "CURRENCY").require()?,
frequency: sect.duration("FREQUENCY").require()?,
account_type: map_config!(sect, "account type", "ACCOUNT_TYPE",
"exchange" => { Ok(AccountType::Exchange) },
@@ -153,7 +183,9 @@ impl WorkerCfg {
payment_type_id: sect
.parse("cyclos payment type id", "PAYMENT_TYPE_ID")
.require()?,
- host: HostCfg::parse(cfg)?,
+ host: HostCfg::parse(cfg, &main)?,
+ currency: main.currency,
+ root: main.root
})
}
}
@@ -162,7 +194,7 @@ impl WorkerCfg {
pub struct HarnessCfg {
pub worker: WorkerCfg,
pub username: String,
- pub password: String,
+ pub password: String
}
impl HarnessCfg {
diff --git a/taler-cyclos/src/cyclos_api/api.rs b/taler-cyclos/src/cyclos_api/api.rs
@@ -319,6 +319,7 @@ impl SseClient {
}
pub async fn next(&mut self) -> Option<Result<SseMessage, LinesCodecError>> {
+ // TODO add tests
let mut event = CompactString::new("message");
let mut data = String::new();
while let Some(res) = self.stream.next().await {
diff --git a/taler-cyclos/src/cyclos_api/types.rs b/taler-cyclos/src/cyclos_api/types.rs
@@ -16,11 +16,12 @@
use std::collections::BTreeMap;
+use compact_str::CompactString;
use jiff::Timestamp;
use serde::{Deserialize, Serialize};
use taler_common::types::{amount::Decimal, payto::FullPayto};
-use crate::{CyclosId, FullCyclosPayto};
+use crate::{CyclosAccount, CyclosId, FullCyclosPayto};
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
@@ -75,8 +76,8 @@ pub struct User {
}
impl User {
- pub fn payto(&self) -> FullCyclosPayto {
- FullPayto::new(self.id, &self.name)
+ pub fn payto(&self, root: CompactString) -> FullCyclosPayto {
+ FullPayto::new(CyclosAccount { id: self.id, root }, &self.name)
}
}
diff --git a/taler-cyclos/src/db.rs b/taler-cyclos/src/db.rs
@@ -16,6 +16,7 @@
use std::fmt::Display;
+use compact_str::CompactString;
use jiff::Timestamp;
use serde::{Serialize, de::DeserializeOwned};
use sqlx::{PgConnection, PgExecutor, PgPool, QueryBuilder, Row, postgres::PgRow};
@@ -38,7 +39,7 @@ use taler_common::{
};
use tokio::sync::watch::{Receiver, Sender};
-use crate::{CyclosId, FullCyclosPayto, config::parse_db_cfg};
+use crate::{CyclosAccount, CyclosId, FullCyclosPayto, config::parse_db_cfg};
const SCHEMA: &str = "cyclos";
@@ -85,7 +86,8 @@ pub struct TxIn {
pub tx_id: Option<u64>,
pub amount: Decimal,
pub subject: String,
- pub debtor: FullCyclosPayto,
+ pub debtor_id: u64,
+ pub debtor_name: String,
pub valued_at: Timestamp,
}
@@ -96,8 +98,9 @@ impl Display for TxIn {
tx_id,
amount,
subject,
- debtor,
valued_at,
+ debtor_id,
+ debtor_name,
} = self;
let tx_id = match tx_id {
Some(id) => format_args!(":{}", *id),
@@ -105,8 +108,7 @@ impl Display for TxIn {
};
write!(
f,
- "{valued_at} {transfer_id}{tx_id} {amount} ({} {}) '{subject}'",
- debtor.0, debtor.name
+ "{valued_at} {transfer_id}{tx_id} {amount} ({debtor_id} {debtor_name}) '{subject}'"
)
}
}
@@ -117,7 +119,8 @@ pub struct TxOut {
pub tx_id: Option<u64>,
pub amount: Decimal,
pub subject: String,
- pub creditor: FullCyclosPayto,
+ pub creditor_id: u64,
+ pub creditor_name: String,
pub valued_at: Timestamp,
}
@@ -128,7 +131,8 @@ impl Display for TxOut {
tx_id,
amount,
subject,
- creditor,
+ creditor_id,
+ creditor_name,
valued_at,
} = self;
let tx_id = match tx_id {
@@ -137,8 +141,7 @@ impl Display for TxOut {
};
write!(
f,
- "{valued_at} {transfer_id}{tx_id} {amount} ({} {}) '{subject}'",
- creditor.0, creditor.name
+ "{valued_at} {transfer_id}{tx_id} {amount} ({creditor_id} {creditor_name}) '{subject}'"
)
}
}
@@ -148,7 +151,8 @@ pub struct Initiated {
pub id: u64,
pub amount: Decimal,
pub subject: String,
- pub creditor: FullCyclosPayto,
+ pub creditor_id: u64,
+ pub creditor_name: String,
}
impl Display for Initiated {
@@ -157,12 +161,12 @@ impl Display for Initiated {
id,
amount,
subject,
- creditor,
+ creditor_id,
+ creditor_name,
} = self;
write!(
f,
- "{id} {amount} ({} {}) '{subject}'",
- creditor.0, creditor.name
+ "{id} {amount} ({creditor_id} {creditor_name}) '{subject}'"
)
}
}
@@ -206,7 +210,7 @@ pub async fn register_tx_in_admin(
)
.bind_decimal(&tx.amount)
.bind(&tx.subject)
- .bind(tx.debtor.0 as i64)
+ .bind(*tx.debtor.id as i64)
.bind(&tx.debtor.name)
.bind_timestamp(now)
.bind(tx.metadata.ty())
@@ -242,8 +246,8 @@ pub async fn register_tx_in(
.bind(tx.tx_id.map(|it| it as i64))
.bind_decimal(&tx.amount)
.bind(&tx.subject)
- .bind(tx.debtor.0 as i64)
- .bind(&tx.debtor.name)
+ .bind(tx.debtor_id as i64)
+ .bind(&tx.debtor_name)
.bind(tx.valued_at.as_microsecond())
.bind(subject.as_ref().map(|it| it.ty()))
.bind(subject.as_ref().map(|it| it.key()))
@@ -304,8 +308,8 @@ pub async fn register_tx_out(
.bind(tx.tx_id.map(|it| it as i64))
.bind_decimal(&tx.amount)
.bind(&tx.subject)
- .bind(tx.creditor.0 as i64)
- .bind(&tx.creditor.name)
+ .bind(tx.creditor_id as i64)
+ .bind(&tx.creditor_name)
.bind_timestamp(&tx.valued_at);
let query = match kind {
TxOutKind::Simple => query
@@ -343,7 +347,8 @@ pub enum TransferResult {
pub async fn make_transfer<'a>(
db: impl PgExecutor<'a>,
req: &TransferRequest,
- creditor: &FullCyclosPayto,
+ creditor_id: u64,
+ creditor_name: &str,
now: &Timestamp,
) -> sqlx::Result<TransferResult> {
let subject = format!("{} {}", req.wtid, req.exchange_base_url);
@@ -358,8 +363,8 @@ pub async fn make_transfer<'a>(
.bind(&subject)
.bind_amount(&req.amount)
.bind(req.exchange_base_url.as_str())
- .bind(creditor.0 as i64)
- .bind(&creditor.name)
+ .bind(creditor_id as i64)
+ .bind(creditor_name)
.bind_timestamp(now)
.try_map(|r: PgRow| {
Ok(if r.try_get(0)? {
@@ -400,8 +405,8 @@ pub async fn register_bounced_tx_in(
.bind(tx.tx_id.map(|it| it as i64))
.bind_decimal(&tx.amount)
.bind(&tx.subject)
- .bind(tx.debtor.0 as i64)
- .bind(&tx.debtor.name)
+ .bind(tx.debtor_id as i64)
+ .bind(&tx.debtor_name)
.bind_timestamp(&tx.valued_at)
.bind(chargeback_id as i64)
.bind(reason)
@@ -420,6 +425,7 @@ pub async fn transfer_page<'a>(
db: impl PgExecutor<'a>,
status: &Option<TransferState>,
currency: &Currency,
+ root: &CompactString,
params: &Page,
) -> sqlx::Result<Vec<TransferListStatus>> {
page(
@@ -452,7 +458,7 @@ pub async fn transfer_page<'a>(
row_id: r.try_get_safeu64(0)?,
status: r.try_get(1)?,
amount: r.try_get_amount_i(2, currency)?,
- credit_account: r.try_get_cyclos_fullpaytouri(4, 5)?,
+ credit_account: r.try_get_cyclos_fullpaytouri(4, 5, root)?,
timestamp: r.try_get_timestamp(6)?.into(),
})
},
@@ -464,6 +470,7 @@ pub async fn outgoing_history(
db: &PgPool,
params: &History,
currency: &Currency,
+ root: &CompactString,
listen: impl FnOnce() -> Receiver<i64>,
) -> sqlx::Result<Vec<OutgoingBankTransaction>> {
history(
@@ -493,7 +500,7 @@ pub async fn outgoing_history(
Ok(OutgoingBankTransaction {
row_id: r.try_get_safeu64(0)?,
amount: r.try_get_amount_i(1, currency)?,
- credit_account: r.try_get_cyclos_fullpaytouri(3, 4)?,
+ credit_account: r.try_get_cyclos_fullpaytouri(3, 4, root)?,
date: r.try_get_timestamp(5)?.into(),
exchange_base_url: r.try_get_url(6)?,
wtid: r.try_get_base32(7)?,
@@ -507,6 +514,7 @@ pub async fn incoming_history(
db: &PgPool,
params: &History,
currency: &Currency,
+ root: &CompactString,
listen: impl FnOnce() -> Receiver<i64>,
) -> sqlx::Result<Vec<IncomingBankTransaction>> {
history(
@@ -537,14 +545,14 @@ pub async fn incoming_history(
IncomingType::reserve => IncomingBankTransaction::Reserve {
row_id: r.try_get_safeu64(1)?,
amount: r.try_get_amount_i(2, currency)?,
- debit_account: r.try_get_cyclos_fullpaytouri(4, 5)?,
+ debit_account: r.try_get_cyclos_fullpaytouri(4, 5, root)?,
date: r.try_get_timestamp(6)?.into(),
reserve_pub: r.try_get_base32(7)?,
},
IncomingType::kyc => IncomingBankTransaction::Kyc {
row_id: r.try_get_safeu64(1)?,
amount: r.try_get_amount_i(2, currency)?,
- debit_account: r.try_get_cyclos_fullpaytouri(4, 5)?,
+ debit_account: r.try_get_cyclos_fullpaytouri(4, 5, root)?,
date: r.try_get_timestamp(6)?.into(),
account_pub: r.try_get_base32(7)?,
},
@@ -561,6 +569,7 @@ pub async fn revenue_history(
db: &PgPool,
params: &History,
currency: &Currency,
+ root: &CompactString,
listen: impl FnOnce() -> Receiver<i64>,
) -> sqlx::Result<Vec<RevenueIncomingBankTransaction>> {
history(
@@ -590,7 +599,7 @@ pub async fn revenue_history(
date: r.try_get_timestamp(1)?.into(),
amount: r.try_get_amount_i(2, currency)?,
credit_fee: None,
- debit_account: r.try_get_cyclos_fullpaytouri(4, 5)?,
+ debit_account: r.try_get_cyclos_fullpaytouri(4, 5, root)?,
subject: r.try_get(6)?,
})
},
@@ -602,6 +611,7 @@ pub async fn transfer_by_id<'a>(
db: impl PgExecutor<'a>,
id: u64,
currency: &Currency,
+ root: &CompactString,
) -> sqlx::Result<Option<TransferStatus>> {
sqlx::query(
"
@@ -628,7 +638,7 @@ pub async fn transfer_by_id<'a>(
amount: r.try_get_amount_i(2, currency)?,
origin_exchange_url: r.try_get(4)?,
wtid: r.try_get_base32(5)?,
- credit_account: r.try_get_cyclos_fullpaytouri(6, 7)?,
+ credit_account: r.try_get_cyclos_fullpaytouri(6, 7, root)?,
timestamp: r.try_get_timestamp(8)?.into(),
})
})
@@ -657,7 +667,8 @@ pub async fn pending_batch<'a>(
id: r.try_get_u64(0)?,
amount: r.try_get_decimal(1, 2)?,
subject: r.try_get(3)?,
- creditor: r.try_get_cyclos_fullpayto(4, 5)?,
+ creditor_id: r.try_get_u64(4)?,
+ creditor_name: r.try_get(5)?,
})
})
.fetch_all(db)
@@ -766,11 +777,13 @@ pub trait CyclosTypeHelper {
&self,
idx: I,
name: I,
+ root: &CompactString,
) -> sqlx::Result<FullCyclosPayto>;
fn try_get_cyclos_fullpaytouri<I: sqlx::ColumnIndex<Self>>(
&self,
idx: I,
name: I,
+ root: &CompactString,
) -> sqlx::Result<PaytoURI>;
}
@@ -779,19 +792,31 @@ impl CyclosTypeHelper for PgRow {
&self,
idx: I,
name: I,
+ root: &CompactString,
) -> sqlx::Result<FullCyclosPayto> {
let idx = self.try_get_u64(idx)?;
let name = self.try_get(name)?;
- Ok(FullCyclosPayto::new(CyclosId(idx), name))
+ Ok(FullCyclosPayto::new(
+ CyclosAccount {
+ id: CyclosId(idx),
+ root: root.clone(),
+ },
+ name,
+ ))
}
fn try_get_cyclos_fullpaytouri<I: sqlx::ColumnIndex<Self>>(
&self,
idx: I,
name: I,
+ root: &CompactString,
) -> sqlx::Result<PaytoURI> {
let idx = self.try_get_u64(idx)?;
let name = self.try_get(name)?;
- Ok(CyclosId(idx).as_full_payto(name))
+ Ok(CyclosAccount {
+ id: CyclosId(idx),
+ root: root.clone(),
+ }
+ .as_full_payto(name))
}
}
@@ -799,6 +824,7 @@ impl CyclosTypeHelper for PgRow {
mod test {
use std::sync::LazyLock;
+ use compact_str::CompactString;
use jiff::{Span, Timestamp};
use serde_json::json;
use sqlx::{PgConnection, PgPool, postgres::PgRow};
@@ -829,6 +855,7 @@ mod test {
};
pub static CURRENCY: LazyLock<Currency> = LazyLock::new(|| "TEST".parse().unwrap());
+ pub const ROOT: CompactString = CompactString::const_new("localhost");
fn fake_listen<T: Default>() -> Receiver<T> {
tokio::sync::watch::channel(T::default()).1
@@ -886,7 +913,8 @@ mod test {
tx_id: None,
amount: decimal("10"),
subject: "subject".to_owned(),
- debtor: cyclos_payto("payto://cyclos/31000163100000000?receiver-name=name"),
+ debtor_id: 31000163100000000,
+ debtor_name: "Name".to_string(),
valued_at: now,
};
// Insert
@@ -943,13 +971,13 @@ mod test {
// Empty db
assert_eq!(
- db::revenue_history(&pool, &History::default(), &CURRENCY, fake_listen)
+ db::revenue_history(&pool, &History::default(), &CURRENCY, &ROOT, fake_listen)
.await
.unwrap(),
Vec::new()
);
assert_eq!(
- db::incoming_history(&pool, &History::default(), &CURRENCY, fake_listen)
+ db::incoming_history(&pool, &History::default(), &CURRENCY, &ROOT, fake_listen)
.await
.unwrap(),
Vec::new()
@@ -976,14 +1004,14 @@ mod test {
// History
assert_eq!(
- db::revenue_history(&pool, &History::default(), &CURRENCY, fake_listen)
+ db::revenue_history(&pool, &History::default(), &CURRENCY, &ROOT, fake_listen)
.await
.unwrap()
.len(),
6
);
assert_eq!(
- db::incoming_history(&pool, &History::default(), &CURRENCY, fake_listen)
+ db::incoming_history(&pool, &History::default(), &CURRENCY, &ROOT, fake_listen)
.await
.unwrap()
.len(),
@@ -997,7 +1025,7 @@ mod test {
// Empty db
assert_eq!(
- db::incoming_history(&pool, &History::default(), &CURRENCY, fake_listen)
+ db::incoming_history(&pool, &History::default(), &CURRENCY, &ROOT, fake_listen)
.await
.unwrap(),
Vec::new()
@@ -1008,7 +1036,7 @@ mod test {
let tx = TxInAdmin {
amount: decimal("10"),
subject: "subject".to_owned(),
- debtor: cyclos_payto("payto://cyclos/31000163100000000?receiver-name=name"),
+ debtor: cyclos_payto("payto://cyclos/localhost/31000163100000000?receiver-name=name"),
metadata: IncomingSubject::Reserve(EddsaPublicKey::rand()),
};
// Insert
@@ -1055,7 +1083,7 @@ mod test {
// History
assert_eq!(
- db::incoming_history(&pool, &History::default(), &CURRENCY, fake_listen)
+ db::incoming_history(&pool, &History::default(), &CURRENCY, &ROOT, fake_listen)
.await
.unwrap()
.len(),
@@ -1080,7 +1108,8 @@ mod test {
tx_id: Some(transfer_id),
amount: decimal("10"),
subject: "subject".to_owned(),
- creditor: cyclos_payto("payto://cyclos/31000163100000000?receiver-name=name"),
+ creditor_id: 31000163100000000,
+ creditor_name: "Name".to_string(),
valued_at: now,
};
assert!(matches!(
@@ -1092,10 +1121,11 @@ mod test {
exchange_base_url: url("https://exchange.test.com/"),
wtid: ShortHashCode::rand(),
credit_account: payto(
- "payto://cyclos/31000163100000000?receiver-name=name"
+ "payto://cyclos/localhost/31000163100000000?receiver-name=name"
),
},
- &tx.creditor,
+ 31000163100000000,
+ "Name",
&now
)
.await
@@ -1158,7 +1188,7 @@ mod test {
// Empty db
assert_eq!(
- db::outgoing_history(&pool, &History::default(), &CURRENCY, fake_listen)
+ db::outgoing_history(&pool, &History::default(), &CURRENCY, &ROOT, fake_listen)
.await
.unwrap(),
Vec::new()
@@ -1186,7 +1216,7 @@ mod test {
// History
assert_eq!(
- db::outgoing_history(&pool, &History::default(), &CURRENCY, fake_listen)
+ db::outgoing_history(&pool, &History::default(), &CURRENCY, &ROOT, fake_listen)
.await
.unwrap()
.len(),
@@ -1202,11 +1232,13 @@ mod test {
// Empty db
assert_eq!(
- db::transfer_by_id(&mut db, 0, &CURRENCY).await.unwrap(),
+ db::transfer_by_id(&mut db, 0, &CURRENCY, &ROOT)
+ .await
+ .unwrap(),
None
);
assert_eq!(
- db::transfer_page(&mut db, &None, &CURRENCY, &Page::default())
+ db::transfer_page(&mut db, &None, &CURRENCY, &ROOT, &Page::default())
.await
.unwrap(),
Vec::new()
@@ -1219,12 +1251,11 @@ mod test {
wtid: ShortHashCode::rand(),
credit_account: payto("payto://iban/HU02162000031000164800000000?receiver-name=name"),
};
- let payto = cyclos_payto("payto://cyclos/31000163100000000?receiver-name=name");
let now = now_sql_stable_timestamp();
let later = now + Span::new().hours(2);
// Insert
assert_eq!(
- db::make_transfer(&mut db, &req, &payto, &now)
+ db::make_transfer(&mut db, &req, 31000163100000000, "Name", &now)
.await
.expect("transfer"),
TransferResult::Success {
@@ -1234,7 +1265,7 @@ mod test {
);
// Idempotent
assert_eq!(
- db::make_transfer(&mut db, &req, &payto, &later)
+ db::make_transfer(&mut db, &req, 31000163100000000, "Name", &later)
.await
.expect("transfer"),
TransferResult::Success {
@@ -1250,7 +1281,8 @@ mod test {
wtid: ShortHashCode::rand(),
..req.clone()
},
- &payto,
+ 31000163100000000,
+ "Name",
&now
)
.await
@@ -1265,7 +1297,8 @@ mod test {
request_uid: HashCode::rand(),
..req.clone()
},
- &payto,
+ 31000163100000000,
+ "Name",
&now
)
.await
@@ -1281,7 +1314,8 @@ mod test {
wtid: ShortHashCode::rand(),
..req
},
- &payto,
+ 31000163100000000,
+ "Name",
&later
)
.await
@@ -1294,25 +1328,25 @@ mod test {
// Get
assert!(
- db::transfer_by_id(&mut db, 1, &CURRENCY)
+ db::transfer_by_id(&mut db, 1, &CURRENCY, &ROOT)
.await
.unwrap()
.is_some()
);
assert!(
- db::transfer_by_id(&mut db, 2, &CURRENCY)
+ db::transfer_by_id(&mut db, 2, &CURRENCY, &ROOT)
.await
.unwrap()
.is_some()
);
assert!(
- db::transfer_by_id(&mut db, 3, &CURRENCY)
+ db::transfer_by_id(&mut db, 3, &CURRENCY, &ROOT)
.await
.unwrap()
.is_none()
);
assert_eq!(
- db::transfer_page(&mut db, &None, &CURRENCY, &Page::default())
+ db::transfer_page(&mut db, &None, &CURRENCY, &ROOT, &Page::default())
.await
.unwrap()
.len(),
@@ -1325,7 +1359,6 @@ mod test {
let (mut db, _) = setup().await;
let amount = decimal("10");
- let payto = cyclos_payto("payto://cyclos/31000163100000000?receiver-name=name");
let now = now_sql_stable_timestamp();
// Bounce
@@ -1337,7 +1370,8 @@ mod test {
tx_id: None,
amount,
subject: "subject".to_owned(),
- debtor: payto.clone(),
+ debtor_id: 31000163100000000,
+ debtor_name: "Name".to_string(),
valued_at: now
},
22,
@@ -1360,7 +1394,8 @@ mod test {
tx_id: None,
amount: amount.clone(),
subject: "subject".to_owned(),
- debtor: payto.clone(),
+ debtor_id: 31000163100000000,
+ debtor_name: "Name".to_string(),
valued_at: now
},
22,
@@ -1384,7 +1419,8 @@ mod test {
tx_id: None,
amount: amount.clone(),
subject: "subject".to_owned(),
- debtor: payto.clone(),
+ debtor_id: 31000163100000000,
+ debtor_name: "Name".to_string(),
valued_at: now
},
23,
@@ -1403,7 +1439,6 @@ mod test {
#[tokio::test]
async fn status() {
let (mut db, _) = setup().await;
- let cyclos_payto = cyclos_payto("payto://cyclos/31000163100000000?receiver-name=name");
async fn check_status(
db: &mut PgConnection,
@@ -1411,7 +1446,7 @@ mod test {
status: TransferState,
msg: Option<&str>,
) {
- let transfer = db::transfer_by_id(db, id, &CURRENCY)
+ let transfer = db::transfer_by_id(db, id, &CURRENCY, &ROOT)
.await
.unwrap()
.unwrap();
@@ -1445,7 +1480,8 @@ mod test {
"payto://iban/HU02162000031000164800000000?receiver-name=name",
),
},
- &cyclos_payto,
+ 31000163100000000,
+ "Name",
&Timestamp::now(),
)
.await
@@ -1474,7 +1510,8 @@ mod test {
"payto://iban/HU02162000031000164800000000?receiver-name=name",
),
},
- &cyclos_payto,
+ 31000163100000000,
+ "Name",
&Timestamp::now(),
)
.await
@@ -1491,7 +1528,8 @@ mod test {
tx_id: Some(3),
amount: decimal("2"),
subject: "".to_string(),
- creditor: cyclos_payto,
+ creditor_id: 31000163100000000,
+ creditor_name: "Name".to_string(),
valued_at: Timestamp::now(),
},
&TxOutKind::Simple,
@@ -1523,7 +1561,6 @@ mod test {
async fn batch() {
let (mut db, _) = setup().await;
let start = Timestamp::now();
- let cyclos_payto = cyclos_payto("payto://cyclos/31000163100000000?receiver-name=name");
// Empty db
let pendings = db::pending_batch(&mut db, &start)
@@ -1544,7 +1581,8 @@ mod test {
"payto://iban/HU02162000031000164800000000?receiver-name=name",
),
},
- &cyclos_payto,
+ 31000163100000000,
+ "Name",
&Timestamp::now(),
)
.await
@@ -1568,7 +1606,8 @@ mod test {
"payto://iban/HU02162000031000164800000000?receiver-name=name",
),
},
- &cyclos_payto,
+ 31000163100000000,
+ "Name",
&Timestamp::now(),
)
.await
diff --git a/taler-cyclos/src/lib.rs b/taler-cyclos/src/lib.rs
@@ -16,6 +16,7 @@
use std::{fmt::Display, num::ParseIntError, ops::Deref, str::FromStr, sync::Arc};
+use compact_str::CompactString;
use sqlx::PgPool;
use taler_api::api::{Router, TalerRouter as _};
use taler_common::{
@@ -30,14 +31,14 @@ pub mod config;
pub mod constants;
pub mod cyclos_api;
pub mod db;
+pub mod dev;
pub mod notification;
pub mod setup;
pub mod worker;
-pub mod dev;
pub async fn run_serve(cfg: &Config, pool: PgPool) -> anyhow::Result<()> {
let cfg = ServeCfg::parse(cfg)?;
- let api = Arc::new(CyclosApi::start(pool, cfg.payto, cfg.currency).await);
+ let api = Arc::new(CyclosApi::start(pool, cfg.root, cfg.payto, cfg.currency).await);
let mut router = Router::new();
if let Some(cfg) = cfg.wire_gateway {
router = router.wire_gateway(api.clone(), cfg.auth.method());
@@ -49,6 +50,12 @@ pub async fn run_serve(cfg: &Config, pool: PgPool) -> anyhow::Result<()> {
Ok(())
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct CyclosAccount {
+ pub id: CyclosId,
+ pub root: CompactString,
+}
+
#[derive(
Debug, Clone, Copy, PartialEq, Eq, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
)]
@@ -83,12 +90,12 @@ impl FromStr for CyclosId {
const CYCLOS: &str = "cyclos";
#[derive(Debug, thiserror::Error)]
-#[error("missing cyclos account id in path")]
-pub struct MissingCyclos;
+#[error("missing cyclos root and account id in path")]
+pub struct MissingParts;
-impl PaytoImpl for CyclosId {
+impl PaytoImpl for CyclosAccount {
fn as_payto(&self) -> PaytoURI {
- PaytoURI::from_parts(CYCLOS, format_args!("/{}", self.0))
+ PaytoURI::from_parts(CYCLOS, format_args!("{}/{}", self.root, self.id))
}
fn parse(raw: &PaytoURI) -> Result<Self, PaytoErr> {
@@ -99,14 +106,14 @@ impl PaytoImpl for CyclosId {
url.domain().unwrap_or_default().to_owned(),
));
}
- let Some(mut segments) = url.path_segments() else {
- return Err(PaytoErr::custom(MissingCyclos));
- };
- let Some(first) = segments.next() else {
- return Err(PaytoErr::custom(MissingCyclos));
+ let Some((root, id)) = url.path().trim_start_matches("/").rsplit_once('/') else {
+ return Err(PaytoErr::custom(MissingParts));
};
- CyclosId::from_str(first).map_err(PaytoErr::custom)
+ Ok(CyclosAccount {
+ id: CyclosId::from_str(id).map_err(PaytoErr::custom)?,
+ root: CompactString::new(root),
+ })
}
}
@@ -115,6 +122,8 @@ pub fn cyclos_payto(url: impl AsRef<str>) -> FullCyclosPayto {
url.as_ref().parse().expect("invalid cyclos payto")
}
-pub type CyclosPayto = Payto<CyclosId>;
-pub type FullCyclosPayto = FullPayto<CyclosId>;
-pub type TransferCyclosPayto = TransferPayto<CyclosId>;
+// TODO should we check the root url ?
+
+pub type CyclosPayto = Payto<CyclosAccount>;
+pub type FullCyclosPayto = FullPayto<CyclosAccount>;
+pub type TransferCyclosPayto = TransferPayto<CyclosAccount>;
diff --git a/taler-cyclos/src/main.rs b/taler-cyclos/src/main.rs
@@ -20,7 +20,7 @@ use taler_api::config::{ApiCfg, AuthCfg};
use taler_build::long_version;
use taler_common::{CommonArgs, cli::ConfigCmd, config::Config, taler_main};
use taler_cyclos::{
- config::{ServeCfg, parse_account_payto},
+ config::{MainCfg, ServeCfg, parse_account_payto},
constants::CONFIG_SOURCE,
db::{dbinit, pool},
dev::{self, DevCmd},
@@ -112,7 +112,8 @@ async fn run(cmd: Command, cfg: &Config) -> anyhow::Result<()> {
Command::Dev(cmd) => dev::dev(cfg, cmd).await?,
Command::TalerDeployment(cmd) => match cmd {
TalerDeployment::ExchangePayto => {
- let payto = parse_account_payto(cfg)?;
+ let main = MainCfg::parse(&cfg)?;
+ let payto = parse_account_payto(cfg, &main)?;
println!("{payto}");
}
TalerDeployment::WireGatewayCredentials => {
diff --git a/taler-cyclos/src/worker.rs b/taler-cyclos/src/worker.rs
@@ -29,7 +29,6 @@ use tokio::{join, sync::Notify};
use tracing::{debug, error, info, trace, warn};
use crate::{
- FullCyclosPayto,
config::{AccountType, WorkerCfg},
cyclos_api::{
api::{ApiErr, CyclosAuth, ErrKind},
@@ -205,7 +204,7 @@ impl Worker<'_> {
let res = self
.client
.direct_payment(
- initiated.creditor.0,
+ initiated.creditor_id,
self.payment_type_id,
initiated.amount,
&initiated.subject,
@@ -455,11 +454,9 @@ pub enum Tx {
pub fn extract_tx_info(tx: HistoryItem) -> Tx {
let amount = amount::decimal(tx.amount.trim_start_matches('-'));
- let payto = match tx.related_account.kind {
- AccountKind::System => {
- FullCyclosPayto::new(tx.related_account.ty.id, &tx.related_account.ty.name)
- }
- AccountKind::User { user } => FullCyclosPayto::new(user.id, &user.display),
+ let (id, name) = match tx.related_account.kind {
+ AccountKind::System => (tx.related_account.ty.id, tx.related_account.ty.name),
+ AccountKind::User { user } => (user.id, user.display),
};
if tx.amount.starts_with("-") {
Tx::Out(TxOut {
@@ -467,7 +464,8 @@ pub fn extract_tx_info(tx: HistoryItem) -> Tx {
tx_id: tx.transaction.map(|it| *it.id),
amount,
subject: tx.description.unwrap_or_default(),
- creditor: payto,
+ creditor_id: *id,
+ creditor_name: name,
valued_at: tx.date,
})
} else {
@@ -476,7 +474,8 @@ pub fn extract_tx_info(tx: HistoryItem) -> Tx {
tx_id: tx.transaction.map(|it| *it.id),
amount,
subject: tx.description.unwrap_or_default(),
- debtor: payto,
+ debtor_id: *id,
+ debtor_name: name,
valued_at: tx.date,
})
}
diff --git a/taler-cyclos/tests/api.rs b/taler-cyclos/tests/api.rs
@@ -16,6 +16,7 @@
use std::{str::FromStr, sync::Arc};
+use compact_str::CompactString;
use jiff::Timestamp;
use sqlx::PgPool;
use taler_api::{api::TalerRouter as _, auth::AuthMethod, subject::OutgoingSubject};
@@ -47,6 +48,7 @@ async fn setup() -> (Router, PgPool) {
let api = Arc::new(
CyclosApi::start(
pool.clone(),
+ CompactString::const_new("localhost"),
payto("payto://iban/HU02162000031000164800000000?receiver-name=name"),
Currency::from_str("TEST").unwrap(),
)
@@ -79,7 +81,7 @@ async fn transfer() {
transfer_routine(
&server,
TransferState::pending,
- &payto("payto://cyclos/7762070814178012479?receiver-name=name"),
+ &payto("payto://cyclos/localhost/7762070814178012479?receiver-name=name"),
)
.await;
}
@@ -111,9 +113,8 @@ async fn outgoing_history() {
},
amount: decimal("10"),
subject: "subject".to_owned(),
- creditor: cyclos_payto(
- "payto://cyclos/7762070814178012479?receiver-name=name",
- ),
+ creditor_id: 31000163100000000,
+ creditor_name: "Name".to_string(),
valued_at: Timestamp::now(),
},
&TxOutKind::Talerable(OutgoingSubject(
@@ -135,7 +136,7 @@ async fn admin_add_incoming() {
let (server, _) = setup().await;
admin_add_incoming_routine(
&server,
- &payto("payto://cyclos/7762070814178012479?receiver-name=name"),
+ &payto("payto://cyclos/localhost/7762070814178012479?receiver-name=name"),
true,
)
.await;
@@ -146,7 +147,7 @@ async fn revenue() {
let (server, _) = setup().await;
revenue_routine(
&server,
- &payto("payto://cyclos/7762070814178012479?receiver-name=name"),
+ &payto("payto://cyclos/localhost/7762070814178012479?receiver-name=name"),
true,
)
.await;
diff --git a/taler-magnet-bank/src/config.rs b/taler-magnet-bank/src/config.rs
@@ -100,7 +100,7 @@ impl WorkerCfg {
"normal" => { Ok(AccountType::Normal) }
)
.require()?,
- api_url: sect.parse("URL", "API_URL").require()?,
+ api_url: sect.base_url("API_URL").require()?,
consumer: Token {
key: sect.str("CONSUMER_KEY").require()?,
secret: sect.str("CONSUMER_SECRET").require()?,