taler-rust

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

api.rs (8640B)


      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_params::{History, Page},
     21     api_revenue::RevenueIncomingHistory,
     22     api_transfer::{
     23         RegistrationRequest, RegistrationResponse, SubjectFormat, TransferSubject, Unregistration,
     24     },
     25     api_wire::{
     26         AddIncomingRequest, AddIncomingResponse, AddKycauthRequest, AddMappedRequest,
     27         IncomingHistory, OutgoingHistory, TransferList, TransferRequest, TransferResponse,
     28         TransferState, TransferStatus,
     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::{TalerApi, revenue::Revenue, transfer::PreparedTransfer, wire::WireGateway},
     42     error::{ApiResult, failure, failure_code},
     43     subject::fmt_in_subject,
     44     test::db::{self, AddIncomingResult},
     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 }
     54 
     55 impl TalerApi for TestApi {
     56     fn currency(&self) -> &str {
     57         self.currency.as_ref()
     58     }
     59 
     60     fn implementation(&self) -> &'static str {
     61         "urn:net:taler:specs:taler-test-api:taler-rust"
     62     }
     63 }
     64 
     65 impl WireGateway for TestApi {
     66     async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> {
     67         req.credit_account.query::<FullQuery>()?;
     68         let result = db::transfer(&self.pool, &req).await?;
     69         match result {
     70             db::TransferResult::Success(transfer_response) => Ok(transfer_response),
     71             db::TransferResult::RequestUidReuse => {
     72                 Err(failure_code(ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED))
     73             }
     74             db::TransferResult::WtidReuse => {
     75                 Err(failure_code(ErrorCode::BANK_TRANSFER_WTID_REUSED))
     76             }
     77         }
     78     }
     79 
     80     async fn transfer_page(
     81         &self,
     82         page: Page,
     83         status: Option<TransferState>,
     84     ) -> ApiResult<TransferList> {
     85         Ok(TransferList {
     86             transfers: db::transfer_page(&self.pool, &status, &page, &self.currency).await?,
     87             debit_account: payto("payto://test"),
     88         })
     89     }
     90 
     91     async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> {
     92         Ok(db::transfer_by_id(&self.pool, id, &self.currency).await?)
     93     }
     94 
     95     async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> {
     96         let txs = db::outgoing_revenue(&self.pool, &params, &self.currency, || {
     97             self.outgoing_channel.subscribe()
     98         })
     99         .await?;
    100         Ok(OutgoingHistory {
    101             outgoing_transactions: txs,
    102             debit_account: payto("payto://test"),
    103         })
    104     }
    105 
    106     async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> {
    107         let txs = db::incoming_history(&self.pool, &params, &self.currency, || {
    108             self.incoming_channel.subscribe()
    109         })
    110         .await?;
    111         Ok(IncomingHistory {
    112             incoming_transactions: txs,
    113             credit_account: payto("payto://test"),
    114         })
    115     }
    116 
    117     async fn add_incoming_reserve(
    118         &self,
    119         req: AddIncomingRequest,
    120     ) -> ApiResult<AddIncomingResponse> {
    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         let res = db::add_incoming(
    147             &self.pool,
    148             &req.amount,
    149             &req.debit_account,
    150             "",
    151             &Timestamp::now(),
    152             IncomingType::kyc,
    153             &req.account_pub,
    154         )
    155         .await?;
    156         match res {
    157             AddIncomingResult::Success { id, created_at } => Ok(AddIncomingResponse {
    158                 timestamp: created_at.into(),
    159                 row_id: id,
    160             }),
    161             AddIncomingResult::ReservePubReuse => {
    162                 Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT))
    163             }
    164             AddIncomingResult::UnknownMapping | AddIncomingResult::MappingReuse => {
    165                 unreachable!("mapping not used")
    166             }
    167         }
    168     }
    169 
    170     async fn add_incoming_mapped(&self, req: AddMappedRequest) -> ApiResult<AddIncomingResponse> {
    171         let res = db::add_incoming(
    172             &self.pool,
    173             &req.amount,
    174             &req.debit_account,
    175             "",
    176             &Timestamp::now(),
    177             IncomingType::map,
    178             &req.authorization_pub,
    179         )
    180         .await?;
    181         match res {
    182             AddIncomingResult::Success { id, created_at } => Ok(AddIncomingResponse {
    183                 timestamp: created_at.into(),
    184                 row_id: id,
    185             }),
    186             AddIncomingResult::ReservePubReuse => {
    187                 Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT))
    188             }
    189             AddIncomingResult::UnknownMapping => {
    190                 Err(failure_code(ErrorCode::BANK_TRANSFER_MAPPING_UNKNOWN))
    191             }
    192             AddIncomingResult::MappingReuse => {
    193                 Err(failure_code(ErrorCode::BANK_TRANSFER_MAPPING_REUSED))
    194             }
    195         }
    196     }
    197 
    198     fn support_account_check(&self) -> bool {
    199         false
    200     }
    201 }
    202 
    203 impl Revenue for TestApi {
    204     async fn history(&self, params: History) -> ApiResult<RevenueIncomingHistory> {
    205         let txs = db::revenue_history(&self.pool, &params, &self.currency, || {
    206             self.incoming_channel.subscribe()
    207         })
    208         .await?;
    209         Ok(RevenueIncomingHistory {
    210             incoming_transactions: txs,
    211             credit_account: payto("payto://test"),
    212         })
    213     }
    214 }
    215 
    216 impl PreparedTransfer for TestApi {
    217     fn supported_formats(&self) -> &[SubjectFormat] {
    218         &[SubjectFormat::SIMPLE]
    219     }
    220 
    221     async fn registration(&self, req: RegistrationRequest) -> ApiResult<RegistrationResponse> {
    222         match db::transfer_register(&self.pool, &req).await? {
    223             db::RegistrationResult::Success => {
    224                 let simple = TransferSubject::Simple {
    225                     credit_amount: req.credit_amount,
    226                     subject: if req.authorization_pub == req.account_pub && !req.recurrent {
    227                         fmt_in_subject(req.r#type.into(), &req.account_pub)
    228                     } else {
    229                         fmt_in_subject(IncomingType::map, &req.authorization_pub)
    230                     },
    231                 };
    232                 ApiResult::Ok(RegistrationResponse {
    233                     subjects: vec![simple],
    234                     expiration: TalerTimestamp::Never,
    235                 })
    236             }
    237             db::RegistrationResult::ReservePubReuse => {
    238                 ApiResult::Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT))
    239             }
    240         }
    241     }
    242 
    243     async fn unregistration(&self, req: Unregistration) -> ApiResult<()> {
    244         if !db::transfer_unregister(&self.pool, &req).await? {
    245             Err(failure(
    246                 ErrorCode::BANK_TRANSACTION_NOT_FOUND,
    247                 format!("Prepared transfer '{}' not found", req.authorization_pub),
    248             ))
    249         } else {
    250             Ok(())
    251         }
    252     }
    253 }