taler-rust

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

commit b73126f1a2ff05f433a8b2558195043fa4c5e580
parent 9ffc007031be5a1935de958b79303938d5bf8128
Author: Antoine A <>
Date:   Fri, 25 Sep 2026 16:27:57 +0200

wise: add api env

Diffstat:
MCargo.lock | 11+++++++++++
Madapters/taler-wise/Cargo.toml | 1+
Madapters/taler-wise/src/config.rs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Madapters/taler-wise/src/setup.rs | 2+-
Madapters/taler-wise/src/wise_api/client.rs | 16+++++++++-------
Madapters/taler-wise/src/worker.rs | 2+-
Madapters/taler-wise/wise.conf | 3+++
Mcommon/taler-common/src/config.rs | 2+-
8 files changed, 87 insertions(+), 12 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1654,6 +1654,16 @@ dependencies = [ ] [[package]] +name = "pem" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120" +dependencies = [ + "base64 0.23.1", + "serde_core", +] + +[[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2711,6 +2721,7 @@ dependencies = [ "hyper", "jiff", "owo-colors", + "pem", "prometheus-client", "regex", "serde", diff --git a/adapters/taler-wise/Cargo.toml b/adapters/taler-wise/Cargo.toml @@ -9,6 +9,7 @@ homepage.workspace = true repository.workspace = true [dependencies] +pem = "4" clap.workspace = true compact_str.workspace = true jiff.workspace = true diff --git a/adapters/taler-wise/src/config.rs b/adapters/taler-wise/src/config.rs @@ -14,8 +14,9 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -use std::time::Duration; +use std::{str::FromStr as _, sync::LazyLock, time::Duration}; +use aws_lc_rs::rsa::PublicKey; use compact_str::CompactString; use taler_api::{ Serve, @@ -29,6 +30,8 @@ use taler_common::{ payto::{ACH, BankID}, }, }; +use taler_macros::EnumMeta; +use url::Url; use crate::payto::WiseAccount; @@ -38,6 +41,56 @@ pub enum AccountType { Normal, } +#[derive(Debug, Clone, Copy, EnumMeta)] +#[allow(non_camel_case_types)] +#[enum_meta(Str)] +pub enum ApiEnv { + production, + sandbox, +} + +impl ApiEnv { + pub const fn api(self) -> &'static LazyLock<Url> { + static PROD: LazyLock<Url> = + LazyLock::new(|| Url::from_str("https://api.wise.com").unwrap()); + static SANDBOX: LazyLock<Url> = + LazyLock::new(|| Url::from_str("https://api.wise-sandbox.com").unwrap()); + match self { + ApiEnv::production => &PROD, + ApiEnv::sandbox => &SANDBOX, + } + } + + pub fn public_key(self) -> PublicKey { + let pem = match self { + ApiEnv::production => { + "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvO8vXV+JksBzZAY6GhSO +XdoTCfhXaaiZ+qAbtaDBiu2AGkGVpmEygFmWP4Li9m5+Ni85BhVvZOodM9epgW3F +bA5Q1SexvAF1PPjX4JpMstak/QhAgl1qMSqEevL8cmUeTgcMuVWCJmlge9h7B1CS +D4rtlimGZozG39rUBDg6Qt2K+P4wBfLblL0k4C4YUdLnpGYEDIth+i8XsRpFlogx +CAFyH9+knYsDbR43UJ9shtc42Ybd40Afihj8KnYKXzchyQ42aC8aZ/h5hyZ28yVy +Oj3Vos0VdBIs/gAyJ/4yyQFCXYte64I7ssrlbGRaco4nKF3HmaNhxwyKyJafz19e +HwIDAQAB +-----END PUBLIC KEY-----" + } + ApiEnv::sandbox => { + "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwpb91cEYuyJNQepZAVfP +ZIlPZfNUefH+n6w9SW3fykqKu938cR7WadQv87oF2VuT+fDt7kqeRziTmPSUhqPU +ys/V2Q1rlfJuXbE+Gga37t7zwd0egQ+KyOEHQOpcTwKmtZ81ieGHynAQzsn1We3j +wt760MsCPJ7GMT141ByQM+yW1Bx+4SG3IGjXWyqOWrcXsxAvIXkpUD/jK/L958Cg +nZEgz0BSEh0QxYLITnW1lLokSx/dTianWPFEhMC9BgijempgNXHNfcVirg1lPSyg +z7KqoKUN0oHqWLr2U1A+7kqrl6O2nx3CKs1bj1hToT1+p4kcMoHXA7kA+VBLUpEs +VwIDAQAB +-----END PUBLIC KEY-----" + } + }; + let parsed = pem::parse(pem).unwrap(); + aws_lc_rs::rsa::PublicKey::from_der(parsed.contents()).unwrap() + } +} + pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, CfgErr> { DbCfg::parse(cfg.section("wisedb-postgres")) } @@ -83,6 +136,7 @@ fn balances(cfg: &Config) -> Result<Vec<WiseBalance>, CfgErr> { /// taler-wise setup config pub struct SetupCfg { + pub api: ApiEnv, pub token: String, pub profile_id: Option<u64>, pub name: Option<CompactString>, @@ -91,11 +145,13 @@ pub struct SetupCfg { impl SetupCfg { pub fn parse(cfg: &Config) -> Result<Self, CfgErr> { + let ms = cfg.section("wise"); let s = cfg.section("wise-worker"); Ok(Self { + api: ms.parse("env", "API_ENV").require()?, profile_id: s.number("PROFILE_ID").opt()?, token: s.str("TOKEN").require()?, - name: cfg.section("wise").cstr("NAME").opt()?, + name: ms.cstr("NAME").opt()?, balances: balances(cfg)?, }) } @@ -127,6 +183,7 @@ impl ServeCfg { /// taler-wise worker config pub struct WorkerCfg { + pub api: ApiEnv, pub profile_id: u64, pub token: String, pub frequency: Duration, @@ -137,6 +194,7 @@ impl WorkerCfg { pub fn parse(cfg: &Config) -> Result<Self, CfgErr> { let s = cfg.section("wise-worker"); let cfg = Self { + api: cfg.section("wise").parse("env", "API_ENV").require()?, profile_id: s.number("PROFILE_ID").require()?, token: s.str("TOKEN").require()?, frequency: s.duration("FREQUENCY").require()?, diff --git a/adapters/taler-wise/src/setup.rs b/adapters/taler-wise/src/setup.rs @@ -24,7 +24,7 @@ use crate::{ pub async fn setup(cfg: &Config, client: &http_client::Client) -> anyhow::Result<()> { let cfg = SetupCfg::parse(cfg)?; - let client = Client::new(client, &cfg.token); + let client = Client::new(client, cfg.api, &cfg.token); info!(target: "setup", "Check API access and configuration"); let mut ready = true; diff --git a/adapters/taler-wise/src/wise_api/client.rs b/adapters/taler-wise/src/wise_api/client.rs @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -use std::{borrow::Cow, str::FromStr as _, sync::LazyLock}; +use std::borrow::Cow; use http_client::{ApiErr, ClientErr, builder::Req, headers::HeaderParser as _}; use hyper::{ @@ -25,9 +25,11 @@ use jiff::Timestamp; use serde::de::DeserializeOwned; use taler_common::types::amount::Currency; use thiserror::Error; -use url::Url; -use crate::wise_api::types::{Balance, BalanceStatement, MultiCurrencyAccount, Profile}; +use crate::{ + config::ApiEnv, + wise_api::types::{Balance, BalanceStatement, MultiCurrencyAccount, Profile}, +}; #[derive(Error, Debug)] pub enum WiseErr { @@ -42,23 +44,23 @@ pub enum WiseErr { } pub type ApiResult<R> = std::result::Result<R, ApiErr<WiseErr>>; -static API_URL: LazyLock<Url> = LazyLock::new(|| Url::from_str("https://api.wise.com").unwrap()); - pub struct Client<'a> { + env: ApiEnv, client: &'a http_client::Client, token: HeaderValue, } impl<'a> Client<'a> { - pub fn new(client: &'a http_client::Client, token: &'a str) -> Self { + pub fn new(client: &'a http_client::Client, env: ApiEnv, token: &'a str) -> Self { Self { client, + env, token: HeaderValue::from_str(&format!("Bearer {token}")).unwrap(), } } fn request(&self, method: Method, path: impl Into<Cow<'static, str>>) -> Req { - Req::new(self.client, method, &API_URL, path) + Req::new(self.client, method, self.env.api(), path) } async fn send<T: DeserializeOwned>(&self, req: Req) -> ApiResult<T> { diff --git a/adapters/taler-wise/src/worker.rs b/adapters/taler-wise/src/worker.rs @@ -99,7 +99,7 @@ pub async fn run_worker( transient: bool, ) -> anyhow::Result<()> { let cfg = WorkerCfg::parse(cfg)?; - let client = Client::new(client, &cfg.token); + let client = Client::new(client, cfg.api, &cfg.token); let cfg = &cfg; let client = &client; let workers = cfg.balances.iter().map(|balance| { diff --git a/adapters/taler-wise/wise.conf b/adapters/taler-wise/wise.conf @@ -2,6 +2,9 @@ # Legal entity that is associated with the Wise account NAME = +# Api environment, this can either be production or sandbox +API_ENV = production + [wise-worker] # Adapter Wise profile ID PROFILE_ID = diff --git a/common/taler-common/src/config.rs b/common/taler-common/src/config.rs @@ -1384,7 +1384,7 @@ mod test { |sect, value| sect.duration(value), &[(&["1.5s", "1500ms"], Duration::from_secs_f64(1.5))], &[(&["-1s", "-1ns"], |_| { - format!("temporal must not be negative") + "temporal must not be negative".to_string() })], ) }