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 }