taler-rust

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

api.rs (7164B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025 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, 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) -> Option<&str> {
     83         Some("taler-magnet-bank")
     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(&self.pool, &req, &creditor, &Timestamp::now()).await?;
     91         match result {
     92             db::TransferResult::Success { id, initiated_at } => Ok(TransferResponse {
     93                 timestamp: initiated_at.into(),
     94                 row_id: SafeU64::try_from(id).unwrap(),
     95             }),
     96             db::TransferResult::RequestUidReuse => Err(failure(
     97                 ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED,
     98                 "request_uid used already",
     99             )),
    100             db::TransferResult::WtidReuse => Err(failure(
    101                 ErrorCode::BANK_TRANSFER_WTID_REUSED,
    102                 "wtid used already",
    103             )),
    104         }
    105     }
    106 
    107     async fn transfer_page(
    108         &self,
    109         page: Page,
    110         status: Option<TransferState>,
    111     ) -> ApiResult<TransferList> {
    112         Ok(TransferList {
    113             transfers: db::transfer_page(&self.pool, &status, &page).await?,
    114             debit_account: self.payto.clone(),
    115         })
    116     }
    117 
    118     async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> {
    119         Ok(db::transfer_by_id(&self.pool, id).await?)
    120     }
    121 
    122     async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> {
    123         Ok(OutgoingHistory {
    124             outgoing_transactions: db::outgoing_history(&self.pool, &params, || {
    125                 self.taler_out_channel.subscribe()
    126             })
    127             .await?,
    128             debit_account: self.payto.clone(),
    129         })
    130     }
    131 
    132     async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> {
    133         Ok(IncomingHistory {
    134             incoming_transactions: db::incoming_history(&self.pool, &params, || {
    135                 self.taler_in_channel.subscribe()
    136             })
    137             .await?,
    138             credit_account: self.payto.clone(),
    139         })
    140     }
    141 
    142     async fn add_incoming_reserve(
    143         &self,
    144         req: AddIncomingRequest,
    145     ) -> ApiResult<AddIncomingResponse> {
    146         let debtor = FullHuPayto::try_from(&req.debit_account)?;
    147         let res = db::register_tx_in_admin(
    148             &self.pool,
    149             &TxInAdmin {
    150                 amount: req.amount,
    151                 subject: format!("Admin incoming {}", req.reserve_pub),
    152                 debtor,
    153                 metadata: IncomingSubject::Reserve(req.reserve_pub),
    154             },
    155             &Timestamp::now(),
    156         )
    157         .await?;
    158         match res {
    159             AddIncomingResult::Success {
    160                 row_id, valued_at, ..
    161             } => Ok(AddIncomingResponse {
    162                 row_id: safe_u64(row_id),
    163                 timestamp: date_to_utc_timestamp(&valued_at).into(),
    164             }),
    165             AddIncomingResult::ReservePubReuse => Err(failure(
    166                 ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
    167                 "reserve_pub used already".to_owned(),
    168             )),
    169         }
    170     }
    171 
    172     async fn add_incoming_kyc(&self, req: AddKycauthRequest) -> ApiResult<AddKycauthResponse> {
    173         let debtor = FullHuPayto::try_from(&req.debit_account)?;
    174         let res = db::register_tx_in_admin(
    175             &self.pool,
    176             &TxInAdmin {
    177                 amount: req.amount,
    178                 subject: format!("Admin incoming KYC:{}", req.account_pub),
    179                 debtor,
    180                 metadata: IncomingSubject::Kyc(req.account_pub),
    181             },
    182             &Timestamp::now(),
    183         )
    184         .await?;
    185         match res {
    186             AddIncomingResult::Success {
    187                 row_id, valued_at, ..
    188             } => Ok(AddKycauthResponse {
    189                 row_id: safe_u64(row_id),
    190                 timestamp: date_to_utc_timestamp(&valued_at).into(),
    191             }),
    192             AddIncomingResult::ReservePubReuse => Err(failure(
    193                 ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
    194                 "reserve_pub used already".to_owned(),
    195             )),
    196         }
    197     }
    198 
    199     fn support_account_check(&self) -> bool {
    200         false
    201     }
    202 }
    203 
    204 impl Revenue for MagnetApi {
    205     async fn history(&self, params: History) -> ApiResult<RevenueIncomingHistory> {
    206         Ok(RevenueIncomingHistory {
    207             incoming_transactions: db::revenue_history(&self.pool, &params, || {
    208                 self.in_channel.subscribe()
    209             })
    210             .await?,
    211             credit_account: self.payto.clone(),
    212         })
    213     }
    214 }