taler-rust

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

api.rs (8284B)


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