taler-rust

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

api.rs (7479B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use jiff::Timestamp;
     18 use taler_api::{
     19     api::{TalerApi, revenue::Revenue, wire::WireGateway},
     20     error::{ApiResult, failure},
     21     subject::IncomingSubject,
     22 };
     23 use taler_common::{
     24     api_common::{SafeU64, safe_u64},
     25     api_params::{History, Page},
     26     api_revenue::RevenueIncomingHistory,
     27     api_wire::{
     28         AddIncomingRequest, AddIncomingResponse, AddKycauthRequest, AddKycauthResponse,
     29         IncomingHistory, OutgoingHistory, TransferList, TransferRequest, TransferResponse,
     30         TransferState, TransferStatus,
     31     },
     32     error_code::ErrorCode,
     33     types::{payto::PaytoURI, utils::date_to_utc_timestamp},
     34 };
     35 use tokio::sync::watch::Sender;
     36 
     37 use crate::{
     38     FullHuPayto,
     39     constants::CURRENCY,
     40     db::{self, AddIncomingResult, Transfer, TxInAdmin},
     41 };
     42 
     43 pub struct MagnetApi {
     44     pub pool: sqlx::PgPool,
     45     pub payto: PaytoURI,
     46     pub in_channel: Sender<i64>,
     47     pub taler_in_channel: Sender<i64>,
     48     pub out_channel: Sender<i64>,
     49     pub taler_out_channel: Sender<i64>,
     50 }
     51 
     52 impl MagnetApi {
     53     pub async fn start(pool: sqlx::PgPool, payto: PaytoURI) -> Self {
     54         let in_channel = Sender::new(0);
     55         let taler_in_channel = Sender::new(0);
     56         let out_channel = Sender::new(0);
     57         let taler_out_channel = Sender::new(0);
     58         let tmp = Self {
     59             pool: pool.clone(),
     60             payto,
     61             in_channel: in_channel.clone(),
     62             taler_in_channel: taler_in_channel.clone(),
     63             out_channel: out_channel.clone(),
     64             taler_out_channel: taler_out_channel.clone(),
     65         };
     66         tokio::spawn(db::notification_listener(
     67             pool,
     68             in_channel,
     69             taler_in_channel,
     70             out_channel,
     71             taler_out_channel,
     72         ));
     73         tmp
     74     }
     75 }
     76 
     77 impl TalerApi for MagnetApi {
     78     fn currency(&self) -> &str {
     79         CURRENCY.as_ref()
     80     }
     81 
     82     fn implementation(&self) -> &'static str {
     83         "urn:net:taler:specs:taler-magnet-bank:taler-rust"
     84     }
     85 }
     86 
     87 impl WireGateway for MagnetApi {
     88     async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> {
     89         let creditor = FullHuPayto::try_from(&req.credit_account)?;
     90         let result = db::make_transfer(
     91             &self.pool,
     92             &Transfer {
     93                 request_uid: req.request_uid,
     94                 wtid: req.wtid,
     95                 amount: req.amount.decimal(),
     96                 creditor,
     97                 exchange_base_url: req.exchange_base_url,
     98             },
     99             &Timestamp::now(),
    100         )
    101         .await?;
    102         match result {
    103             db::TransferResult::Success { id, initiated_at } => Ok(TransferResponse {
    104                 timestamp: initiated_at.into(),
    105                 row_id: SafeU64::try_from(id).unwrap(),
    106             }),
    107             db::TransferResult::RequestUidReuse => Err(failure(
    108                 ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED,
    109                 "request_uid used already",
    110             )),
    111             db::TransferResult::WtidReuse => Err(failure(
    112                 ErrorCode::BANK_TRANSFER_WTID_REUSED,
    113                 "wtid used already",
    114             )),
    115         }
    116     }
    117 
    118     async fn transfer_page(
    119         &self,
    120         page: Page,
    121         status: Option<TransferState>,
    122     ) -> ApiResult<TransferList> {
    123         Ok(TransferList {
    124             transfers: db::transfer_page(&self.pool, &status, &page).await?,
    125             debit_account: self.payto.clone(),
    126         })
    127     }
    128 
    129     async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> {
    130         Ok(db::transfer_by_id(&self.pool, id).await?)
    131     }
    132 
    133     async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> {
    134         Ok(OutgoingHistory {
    135             outgoing_transactions: db::outgoing_history(&self.pool, &params, || {
    136                 self.taler_out_channel.subscribe()
    137             })
    138             .await?,
    139             debit_account: self.payto.clone(),
    140         })
    141     }
    142 
    143     async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> {
    144         Ok(IncomingHistory {
    145             incoming_transactions: db::incoming_history(&self.pool, &params, || {
    146                 self.taler_in_channel.subscribe()
    147             })
    148             .await?,
    149             credit_account: self.payto.clone(),
    150         })
    151     }
    152 
    153     async fn add_incoming_reserve(
    154         &self,
    155         req: AddIncomingRequest,
    156     ) -> ApiResult<AddIncomingResponse> {
    157         let debtor = FullHuPayto::try_from(&req.debit_account)?;
    158         let res = db::register_tx_in_admin(
    159             &self.pool,
    160             &TxInAdmin {
    161                 amount: req.amount,
    162                 subject: format!("Admin incoming {}", req.reserve_pub),
    163                 debtor,
    164                 metadata: IncomingSubject::Reserve(req.reserve_pub),
    165             },
    166             &Timestamp::now(),
    167         )
    168         .await?;
    169         match res {
    170             AddIncomingResult::Success {
    171                 row_id, valued_at, ..
    172             } => Ok(AddIncomingResponse {
    173                 row_id: safe_u64(row_id),
    174                 timestamp: date_to_utc_timestamp(&valued_at).into(),
    175             }),
    176             AddIncomingResult::ReservePubReuse => Err(failure(
    177                 ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
    178                 "reserve_pub used already".to_owned(),
    179             )),
    180         }
    181     }
    182 
    183     async fn add_incoming_kyc(&self, req: AddKycauthRequest) -> ApiResult<AddKycauthResponse> {
    184         let debtor = FullHuPayto::try_from(&req.debit_account)?;
    185         let res = db::register_tx_in_admin(
    186             &self.pool,
    187             &TxInAdmin {
    188                 amount: req.amount,
    189                 subject: format!("Admin incoming KYC:{}", req.account_pub),
    190                 debtor,
    191                 metadata: IncomingSubject::Kyc(req.account_pub),
    192             },
    193             &Timestamp::now(),
    194         )
    195         .await?;
    196         match res {
    197             AddIncomingResult::Success {
    198                 row_id, valued_at, ..
    199             } => Ok(AddKycauthResponse {
    200                 row_id: safe_u64(row_id),
    201                 timestamp: date_to_utc_timestamp(&valued_at).into(),
    202             }),
    203             AddIncomingResult::ReservePubReuse => Err(failure(
    204                 ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
    205                 "reserve_pub used already".to_owned(),
    206             )),
    207         }
    208     }
    209 
    210     fn support_account_check(&self) -> bool {
    211         false
    212     }
    213 }
    214 
    215 impl Revenue for MagnetApi {
    216     async fn history(&self, params: History) -> ApiResult<RevenueIncomingHistory> {
    217         Ok(RevenueIncomingHistory {
    218             incoming_transactions: db::revenue_history(&self.pool, &params, || {
    219                 self.in_channel.subscribe()
    220             })
    221             .await?,
    222             credit_account: self.payto.clone(),
    223         })
    224     }
    225 }