prepared.rs (4417B)
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 if self.timestamp.duration_until(Timestamp::now()) > SignedDuration::from_mins(5) { 71 return Err(failure_code(ErrorCode::BANK_OLD_TIMESTAMP)); 72 } 73 74 if !self.verify(&self.authorization_pub, &self.authorization_sig) { 75 return Err(failure_code(ErrorCode::BANK_BAD_SIGNATURE)); 76 } 77 Ok(()) 78 } 79 } 80 81 pub fn simple_subject(req: RegistrationRequest) -> TransferSubject { 82 TransferSubject::Simple { 83 credit_amount: req.credit_amount, 84 subject: if req.authorization_pub == req.account_pub && !req.recurrent { 85 fmt_in_subject(req.r#type.into(), &req.account_pub).to_string() 86 } else { 87 fmt_in_subject(IncomingType::map, &req.authorization_pub).to_string() 88 }, 89 } 90 } 91 92 pub fn router<I: PreparedTransfer>(state: Arc<I>) -> Router { 93 Router::new() 94 .route( 95 "/registration", 96 post( 97 async |State(state): State<Arc<I>>, Req(req): Req<RegistrationRequest>| { 98 req.check(&state.currency())?; 99 let res = state.registration(req).await?; 100 ApiResult::Ok(Json(res)) 101 }, 102 ), 103 ) 104 .route( 105 "/unregistration", 106 post( 107 async |State(state): State<Arc<I>>, Req(req): Req<Unregistration>| { 108 req.check(&state.currency())?; 109 if state.unregistration(req).await? { 110 ApiResult::Ok(StatusCode::NO_CONTENT) 111 } else { 112 Err(failure_code(ErrorCode::BANK_TRANSACTION_NOT_FOUND)) 113 } 114 }, 115 ), 116 ) 117 .route( 118 "/config", 119 get(async |State(state): State<Arc<I>>| { 120 Json(PreparedTransferConfig { 121 name: (), 122 version: PREPARED_TRANSFER_API_VERSION, 123 currency: state.currency(), 124 implementation: Some(state.implementation()), 125 supported_formats: state.supported_formats().to_vec(), 126 }) 127 .into_response() 128 }), 129 ) 130 .with_state(state) 131 }