taler-rust

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

wire.rs (7406B)


      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 std::sync::Arc;
     18 
     19 use axum::{
     20     Json, Router,
     21     extract::{Path, Query, State},
     22     http::StatusCode,
     23     response::IntoResponse as _,
     24     routing::{get, post},
     25 };
     26 use taler_common::{
     27     api_params::{AccountParams, History, HistoryParams, Page, TransferParams},
     28     api_wire::{
     29         AccountInfo, AddIncomingRequest, AddIncomingResponse, AddKycauthRequest,
     30         AddKycauthResponse, IncomingHistory, OutgoingHistory, TransferList, TransferRequest,
     31         TransferResponse, TransferState, TransferStatus, WireConfig,
     32     },
     33     error_code::ErrorCode,
     34 };
     35 
     36 use crate::{
     37     api::RouterUtils as _,
     38     auth::AuthMethod,
     39     constants::{MAX_PAGE_SIZE, MAX_TIMEOUT_MS, WIRE_GATEWAY_API_VERSION},
     40     error::{ApiResult, failure, failure_code, failure_status},
     41     json::Req,
     42 };
     43 
     44 use super::TalerApi;
     45 
     46 pub trait WireGateway: TalerApi {
     47     fn transfer(
     48         &self,
     49         req: TransferRequest,
     50     ) -> impl std::future::Future<Output = ApiResult<TransferResponse>> + Send;
     51     fn transfer_page(
     52         &self,
     53         page: Page,
     54         status: Option<TransferState>,
     55     ) -> impl std::future::Future<Output = ApiResult<TransferList>> + Send;
     56     fn transfer_by_id(
     57         &self,
     58         id: u64,
     59     ) -> impl std::future::Future<Output = ApiResult<Option<TransferStatus>>> + Send;
     60     fn outgoing_history(
     61         &self,
     62         params: History,
     63     ) -> impl std::future::Future<Output = ApiResult<OutgoingHistory>> + Send;
     64     fn incoming_history(
     65         &self,
     66         params: History,
     67     ) -> impl std::future::Future<Output = ApiResult<IncomingHistory>> + Send;
     68     fn add_incoming_reserve(
     69         &self,
     70         req: AddIncomingRequest,
     71     ) -> impl std::future::Future<Output = ApiResult<AddIncomingResponse>> + Send;
     72     fn add_incoming_kyc(
     73         &self,
     74         req: AddKycauthRequest,
     75     ) -> impl std::future::Future<Output = ApiResult<AddKycauthResponse>> + Send;
     76 
     77     fn support_account_check(&self) -> bool;
     78 
     79     fn account_check(
     80         &self,
     81         _params: AccountParams,
     82     ) -> impl std::future::Future<Output = ApiResult<Option<AccountInfo>>> + Send {
     83         async {
     84             Err(failure_status(
     85                 ErrorCode::END,
     86                 "API not implemented",
     87                 StatusCode::NOT_IMPLEMENTED,
     88             ))
     89         }
     90     }
     91 }
     92 
     93 pub fn router<I: WireGateway>(state: Arc<I>, auth: AuthMethod) -> Router {
     94     Router::new()
     95         .route(
     96             "/transfer",
     97             post(
     98                 |State(state): State<Arc<I>>, Req(req): Req<TransferRequest>| async move {
     99                     state.check_currency(&req.amount)?;
    100                     ApiResult::Ok(Json(state.transfer(req).await?))
    101                 },
    102             ),
    103         )
    104         .route(
    105             "/transfers",
    106             get(
    107                 |State(state): State<Arc<I>>, Query(params): Query<TransferParams>| async move {
    108                     let page = params.pagination.check(MAX_PAGE_SIZE)?;
    109                     let list = state.transfer_page(page, params.status).await?;
    110                     ApiResult::Ok(if list.transfers.is_empty() {
    111                         StatusCode::NO_CONTENT.into_response()
    112                     } else {
    113                         Json(list).into_response()
    114                     })
    115                 },
    116             ),
    117         )
    118         .route(
    119             "/transfers/{id}",
    120             get(
    121                 |State(state): State<Arc<I>>, Path(id): Path<u64>| async move {
    122                     match state.transfer_by_id(id).await? {
    123                         Some(it) => Ok(Json(it)),
    124                         None => Err(failure(
    125                             ErrorCode::BANK_TRANSACTION_NOT_FOUND,
    126                             format!("Transfer '{id}' not found"),
    127                         )),
    128                     }
    129                 },
    130             ),
    131         )
    132         .route(
    133             "/history/incoming",
    134             get(
    135                 |State(state): State<Arc<I>>, Query(params): Query<HistoryParams>| async move {
    136                     let params = params.check(MAX_PAGE_SIZE, MAX_TIMEOUT_MS)?;
    137                     let history = state.incoming_history(params).await?;
    138                     ApiResult::Ok(if history.incoming_transactions.is_empty() {
    139                         StatusCode::NO_CONTENT.into_response()
    140                     } else {
    141                         Json(history).into_response()
    142                     })
    143                 },
    144             ),
    145         )
    146         .route(
    147             "/history/outgoing",
    148             get(
    149                 |State(state): State<Arc<I>>, Query(params): Query<HistoryParams>| async move {
    150                     let params = params.check(MAX_PAGE_SIZE, MAX_TIMEOUT_MS)?;
    151                     let history = state.outgoing_history(params).await?;
    152                     ApiResult::Ok(if history.outgoing_transactions.is_empty() {
    153                         StatusCode::NO_CONTENT.into_response()
    154                     } else {
    155                         Json(history).into_response()
    156                     })
    157                 },
    158             ),
    159         )
    160         .route(
    161             "/admin/add-incoming",
    162             post(
    163                 |State(state): State<Arc<I>>, Req(req): Req<AddIncomingRequest>| async move {
    164                     state.check_currency(&req.amount)?;
    165                     ApiResult::Ok(Json(state.add_incoming_reserve(req).await?))
    166                 },
    167             ),
    168         )
    169         .route(
    170             "/admin/add-kycauth",
    171             post(
    172                 |State(state): State<Arc<I>>, Req(req): Req<AddKycauthRequest>| async move {
    173                     state.check_currency(&req.amount)?;
    174                     ApiResult::Ok(Json(state.add_incoming_kyc(req).await?))
    175                 },
    176             ),
    177         )
    178         .route(
    179             "/account/check",
    180             get(
    181                 |State(state): State<Arc<I>>, Query(params): Query<AccountParams>| async move {
    182                     match state.account_check(params).await? {
    183                         Some(it) => Ok(Json(it)),
    184                         None => Err(failure_code(ErrorCode::BANK_UNKNOWN_ACCOUNT)),
    185                     }
    186                 },
    187             ),
    188         )
    189         .auth(auth, "taler-wire-gateway")
    190         .route(
    191             "/config",
    192             get(|State(state): State<Arc<I>>| async move {
    193                 Json(WireConfig {
    194                     name: "taler-wire-gateway",
    195                     version: WIRE_GATEWAY_API_VERSION,
    196                     currency: state.currency(),
    197                     implementation: state.implementation(),
    198                     support_account_check: state.support_account_check(),
    199                 })
    200                 .into_response()
    201             }),
    202         )
    203         .with_state(state)
    204 }