prepared.rs (4477B)
1 /* 2 This file is part of TALER 3 Copyright (C) 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 std::sync::Arc; 18 19 use axum::{ 20 Json, Router, 21 extract::State, 22 http::StatusCode, 23 response::IntoResponse as _, 24 routing::{get, post}, 25 }; 26 use jiff::{SignedDuration, Timestamp}; 27 use taler_common::{ 28 api::prepared::{ 29 PreparedTransferConfig, RegistrationRequest, RegistrationResponse, SubjectFormat, 30 TransferSubject, Unregistration, 31 }, 32 db::IncomingType, 33 error_code::ErrorCode, 34 signature::Signature, 35 types::amount::Currency, 36 }; 37 38 use super::TalerApi; 39 use crate::{ 40 api::{Validation, check_currency}, 41 constants::PREPARED_TRANSFER_API_VERSION, 42 error::{ApiResult, failure_code}, 43 extract::Req, 44 subject::fmt_in_subject, 45 }; 46 47 pub trait PreparedTransfer: TalerApi { 48 fn supported_formats(&self) -> &[SubjectFormat]; 49 fn registration( 50 &self, 51 req: RegistrationRequest, 52 ) -> impl std::future::Future<Output = ApiResult<RegistrationResponse>> + Send; 53 fn unregistration( 54 &self, 55 req: Unregistration, 56 ) -> impl std::future::Future<Output = ApiResult<bool>> + Send; 57 } 58 59 impl Validation for RegistrationRequest { 60 fn check(&self, currency: &Currency) -> ApiResult<()> { 61 if !self.verify(&self.authorization_pub, &self.authorization_sig) { 62 return Err(failure_code(ErrorCode::BANK_BAD_SIGNATURE)); 63 } 64 check_currency(currency, &self.credit_amount) 65 } 66 } 67 68 impl Validation for Unregistration { 69 fn check(&self, _: &Currency) -> ApiResult<()> { 70 let duration = self.timestamp.duration_until(Timestamp::now()); 71 if duration > SignedDuration::from_mins(5) || duration.is_negative() { 72 return Err(failure_code(ErrorCode::BANK_OLD_TIMESTAMP)); 73 }; 74 75 if !self.verify(&self.authorization_pub, &self.authorization_sig) { 76 return Err(failure_code(ErrorCode::BANK_BAD_SIGNATURE)); 77 } 78 Ok(()) 79 } 80 } 81 82 pub fn simple_subject(req: RegistrationRequest) -> TransferSubject { 83 TransferSubject::Simple { 84 credit_amount: req.credit_amount, 85 subject: if req.authorization_pub == req.account_pub && !req.recurrent { 86 fmt_in_subject(req.r#type.into(), &req.account_pub).to_string() 87 } else { 88 fmt_in_subject(IncomingType::map, &req.authorization_pub).to_string() 89 }, 90 } 91 } 92 93 pub fn router<I: PreparedTransfer>(state: Arc<I>) -> Router { 94 Router::new() 95 .route( 96 "/registration", 97 post( 98 async |State(state): State<Arc<I>>, Req(req): Req<RegistrationRequest>| { 99 req.check(&state.currency())?; 100 let res = state.registration(req).await?; 101 ApiResult::Ok(Json(res)) 102 }, 103 ), 104 ) 105 .route( 106 "/unregistration", 107 post( 108 async |State(state): State<Arc<I>>, Req(req): Req<Unregistration>| { 109 req.check(&state.currency())?; 110 if state.unregistration(req).await? { 111 ApiResult::Ok(StatusCode::NO_CONTENT) 112 } else { 113 Err(failure_code(ErrorCode::BANK_TRANSACTION_NOT_FOUND)) 114 } 115 }, 116 ), 117 ) 118 .route( 119 "/config", 120 get(async |State(state): State<Arc<I>>| { 121 Json(PreparedTransferConfig { 122 name: (), 123 version: PREPARED_TRANSFER_API_VERSION, 124 currency: state.currency(), 125 implementation: Some(state.implementation()), 126 supported_formats: state.supported_formats().to_vec(), 127 }) 128 .into_response() 129 }), 130 ) 131 .with_state(state) 132 }