taler-rust

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

api.rs (8908B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024, 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 prometheus_client::registry::Registry;
     19 use sqlx::PgPool;
     20 use taler_common::{
     21     api::{
     22         params::{History, Page},
     23         prepared::{RegistrationRequest, RegistrationResponse, SubjectFormat, Unregistration},
     24         revenue::RevenueIncomingHistory,
     25         wire::{
     26             AddIncomingRequest, AddIncomingResponse, AddKycauthRequest, AddMappedRequest,
     27             IncomingHistory, OutgoingHistory, TransferList, TransferRequest, TransferResponse,
     28             TransferState, TransferStatus,
     29         },
     30     },
     31     db::IncomingType,
     32     error_code::ErrorCode::{self},
     33     types::{amount::Currency, payto::FullIbanPayto, time::TalerTimestamp},
     34 };
     35 use tokio::sync::watch::Sender;
     36 
     37 use crate::{
     38     api::{
     39         TalerApi,
     40         observability::Observability,
     41         prepared::{PreparedTransfer, simple_subject},
     42         revenue::Revenue,
     43         wire::WireGateway,
     44     },
     45     error::{ApiResult, failure_code},
     46     test::db::{self, AddIncomingResult, RegistrationResult, TransferResult},
     47 };
     48 
     49 /// Taler API implementation for tests
     50 pub struct TestApi {
     51     pub currency: Currency,
     52     pub pool: PgPool,
     53     pub outgoing_channel: Sender<i64>,
     54     pub incoming_channel: Sender<i64>,
     55     pub payto: FullIbanPayto,
     56     registry: Registry,
     57 }
     58 
     59 impl TestApi {
     60     pub fn new(
     61         currency: Currency,
     62         pool: PgPool,
     63         outgoing_channel: Sender<i64>,
     64         incoming_channel: Sender<i64>,
     65         payto: FullIbanPayto,
     66     ) -> Self {
     67         Self {
     68             currency,
     69             pool,
     70             outgoing_channel,
     71             incoming_channel,
     72             payto,
     73             registry: Registry::default(),
     74         }
     75     }
     76 }
     77 
     78 impl TalerApi for TestApi {
     79     fn currency(&self) -> Currency {
     80         self.currency
     81     }
     82 
     83     fn implementation(&self) -> &'static str {
     84         "urn:net:taler:specs:taler-test-api:taler-rust"
     85     }
     86 }
     87 
     88 impl WireGateway for TestApi {
     89     async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> {
     90         FullIbanPayto::try_from(&req.credit_account)?;
     91         let result = db::transfer(&self.pool, &req).await?;
     92         match result {
     93             TransferResult::Success(transfer_response) => Ok(transfer_response),
     94             TransferResult::RequestUidReuse => {
     95                 Err(failure_code(ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED))
     96             }
     97             TransferResult::WtidReuse => Err(failure_code(ErrorCode::BANK_TRANSFER_WTID_REUSED)),
     98         }
     99     }
    100 
    101     async fn transfer_page(
    102         &self,
    103         page: Page,
    104         status: Option<TransferState>,
    105     ) -> ApiResult<TransferList> {
    106         Ok(TransferList {
    107             transfers: db::transfer_page(&self.pool, &status, &page, &self.currency).await?,
    108             debit_account: self.payto.as_uri(),
    109         })
    110     }
    111 
    112     async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> {
    113         Ok(db::transfer_by_id(&self.pool, id, &self.currency).await?)
    114     }
    115 
    116     async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> {
    117         let txs = db::outgoing_revenue(&self.pool, &params, &self.currency, || {
    118             self.outgoing_channel.subscribe()
    119         })
    120         .await?;
    121         Ok(OutgoingHistory {
    122             outgoing_transactions: txs,
    123             debit_account: self.payto.as_uri(),
    124         })
    125     }
    126 
    127     async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> {
    128         let txs = db::incoming_history(&self.pool, &params, &self.currency, || {
    129             self.incoming_channel.subscribe()
    130         })
    131         .await?;
    132         Ok(IncomingHistory {
    133             incoming_transactions: txs,
    134             credit_account: self.payto.as_uri(),
    135         })
    136     }
    137 
    138     async fn add_incoming_reserve(
    139         &self,
    140         req: AddIncomingRequest,
    141     ) -> ApiResult<AddIncomingResponse> {
    142         FullIbanPayto::try_from(&req.debit_account)?;
    143         let res = db::add_incoming(
    144             &self.pool,
    145             &req.amount,
    146             &req.debit_account,
    147             "",
    148             &Timestamp::now(),
    149             IncomingType::reserve,
    150             &req.reserve_pub,
    151         )
    152         .await?;
    153         match res {
    154             AddIncomingResult::Success { id, created_at } => Ok(AddIncomingResponse {
    155                 timestamp: created_at.into(),
    156                 row_id: id,
    157             }),
    158             AddIncomingResult::ReservePubReuse => {
    159                 Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT))
    160             }
    161             AddIncomingResult::UnknownMapping | AddIncomingResult::MappingReuse => {
    162                 unreachable!("mapping not used")
    163             }
    164         }
    165     }
    166 
    167     async fn add_incoming_kyc(&self, req: AddKycauthRequest) -> ApiResult<AddIncomingResponse> {
    168         FullIbanPayto::try_from(&req.debit_account)?;
    169         let res = db::add_incoming(
    170             &self.pool,
    171             &req.amount,
    172             &req.debit_account,
    173             "",
    174             &Timestamp::now(),
    175             IncomingType::kyc,
    176             &req.account_pub,
    177         )
    178         .await?;
    179         match res {
    180             AddIncomingResult::Success { id, created_at } => Ok(AddIncomingResponse {
    181                 timestamp: created_at.into(),
    182                 row_id: id,
    183             }),
    184             AddIncomingResult::ReservePubReuse => {
    185                 Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT))
    186             }
    187             AddIncomingResult::UnknownMapping | AddIncomingResult::MappingReuse => {
    188                 unreachable!("mapping not used")
    189             }
    190         }
    191     }
    192 
    193     async fn add_incoming_mapped(&self, req: AddMappedRequest) -> ApiResult<AddIncomingResponse> {
    194         FullIbanPayto::try_from(&req.debit_account)?;
    195         let res = db::add_incoming(
    196             &self.pool,
    197             &req.amount,
    198             &req.debit_account,
    199             "",
    200             &Timestamp::now(),
    201             IncomingType::map,
    202             &req.authorization_pub,
    203         )
    204         .await?;
    205         match res {
    206             AddIncomingResult::Success { id, created_at } => Ok(AddIncomingResponse {
    207                 timestamp: created_at.into(),
    208                 row_id: id,
    209             }),
    210             AddIncomingResult::ReservePubReuse => {
    211                 Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT))
    212             }
    213             AddIncomingResult::UnknownMapping => {
    214                 Err(failure_code(ErrorCode::BANK_TRANSFER_MAPPING_UNKNOWN))
    215             }
    216             AddIncomingResult::MappingReuse => {
    217                 Err(failure_code(ErrorCode::BANK_TRANSFER_MAPPING_REUSED))
    218             }
    219         }
    220     }
    221 
    222     fn support_account_check(&self) -> bool {
    223         false
    224     }
    225 }
    226 
    227 impl Revenue for TestApi {
    228     async fn history(&self, params: History) -> ApiResult<RevenueIncomingHistory> {
    229         let txs = db::revenue_history(&self.pool, &params, &self.currency, || {
    230             self.incoming_channel.subscribe()
    231         })
    232         .await?;
    233         Ok(RevenueIncomingHistory {
    234             incoming_transactions: txs,
    235             credit_account: self.payto.as_uri(),
    236         })
    237     }
    238 }
    239 
    240 impl PreparedTransfer for TestApi {
    241     fn supported_formats(&self) -> &[SubjectFormat] {
    242         &[SubjectFormat::SIMPLE]
    243     }
    244 
    245     async fn registration(&self, req: RegistrationRequest) -> ApiResult<RegistrationResponse> {
    246         let creditor = FullIbanPayto::try_from(&req.credit_account)?;
    247         if *creditor != *self.payto {
    248             return Err(failure_code(ErrorCode::BANK_UNKNOWN_CREDITOR));
    249         }
    250         match db::transfer_register(&self.pool, &req).await? {
    251             RegistrationResult::Success => Ok(RegistrationResponse {
    252                 subjects: vec![simple_subject(req)],
    253                 expiration: TalerTimestamp::Never,
    254             }),
    255             RegistrationResult::ReservePubReuse => {
    256                 Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT))
    257             }
    258         }
    259     }
    260 
    261     async fn unregistration(&self, req: Unregistration) -> ApiResult<bool> {
    262         Ok(db::transfer_unregister(&self.pool, &req).await?)
    263     }
    264 }
    265 
    266 impl Observability for TestApi {
    267     async fn metrics(&self) -> ApiResult<&Registry> {
    268         Ok(&self.registry)
    269     }
    270 }