taler-rust

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

api.rs (7970B)


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