api.rs (8908B)
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 prometheus_client::registry::Registry; 19 use sqlx::PgPool; 20 use taler_common::{ 21 api::{ 22 params::{History, Page}, 23 prepared::{RegistrationRequest, RegistrationResponse, SubjectFormat, Unregistration}, 24 revenue::RevenueIncomingHistory, 25 wire::{ 26 AddIncomingRequest, AddIncomingResponse, AddKycauthRequest, AddMappedRequest, 27 IncomingHistory, OutgoingHistory, TransferList, TransferRequest, TransferResponse, 28 TransferState, TransferStatus, 29 }, 30 }, 31 db::IncomingType, 32 error_code::ErrorCode::{self}, 33 types::{amount::Currency, payto::FullIbanPayto, time::TalerTimestamp}, 34 }; 35 use tokio::sync::watch::Sender; 36 37 use crate::{ 38 api::{ 39 TalerApi, 40 observability::Observability, 41 prepared::{PreparedTransfer, simple_subject}, 42 revenue::Revenue, 43 wire::WireGateway, 44 }, 45 error::{ApiResult, failure_code}, 46 test::db::{self, AddIncomingResult, RegistrationResult, TransferResult}, 47 }; 48 49 /// Taler API implementation for tests 50 pub struct TestApi { 51 pub currency: Currency, 52 pub pool: PgPool, 53 pub outgoing_channel: Sender<i64>, 54 pub incoming_channel: Sender<i64>, 55 pub payto: FullIbanPayto, 56 registry: Registry, 57 } 58 59 impl TestApi { 60 pub fn new( 61 currency: Currency, 62 pool: PgPool, 63 outgoing_channel: Sender<i64>, 64 incoming_channel: Sender<i64>, 65 payto: FullIbanPayto, 66 ) -> Self { 67 Self { 68 currency, 69 pool, 70 outgoing_channel, 71 incoming_channel, 72 payto, 73 registry: Registry::default(), 74 } 75 } 76 } 77 78 impl TalerApi for TestApi { 79 fn currency(&self) -> Currency { 80 self.currency 81 } 82 83 fn implementation(&self) -> &'static str { 84 "urn:net:taler:specs:taler-test-api:taler-rust" 85 } 86 } 87 88 impl WireGateway for TestApi { 89 async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> { 90 FullIbanPayto::try_from(&req.credit_account)?; 91 let result = db::transfer(&self.pool, &req).await?; 92 match result { 93 TransferResult::Success(transfer_response) => Ok(transfer_response), 94 TransferResult::RequestUidReuse => { 95 Err(failure_code(ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED)) 96 } 97 TransferResult::WtidReuse => Err(failure_code(ErrorCode::BANK_TRANSFER_WTID_REUSED)), 98 } 99 } 100 101 async fn transfer_page( 102 &self, 103 page: Page, 104 status: Option<TransferState>, 105 ) -> ApiResult<TransferList> { 106 Ok(TransferList { 107 transfers: db::transfer_page(&self.pool, &status, &page, &self.currency).await?, 108 debit_account: self.payto.as_uri(), 109 }) 110 } 111 112 async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> { 113 Ok(db::transfer_by_id(&self.pool, id, &self.currency).await?) 114 } 115 116 async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> { 117 let txs = db::outgoing_revenue(&self.pool, ¶ms, &self.currency, || { 118 self.outgoing_channel.subscribe() 119 }) 120 .await?; 121 Ok(OutgoingHistory { 122 outgoing_transactions: txs, 123 debit_account: self.payto.as_uri(), 124 }) 125 } 126 127 async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> { 128 let txs = db::incoming_history(&self.pool, ¶ms, &self.currency, || { 129 self.incoming_channel.subscribe() 130 }) 131 .await?; 132 Ok(IncomingHistory { 133 incoming_transactions: txs, 134 credit_account: self.payto.as_uri(), 135 }) 136 } 137 138 async fn add_incoming_reserve( 139 &self, 140 req: AddIncomingRequest, 141 ) -> ApiResult<AddIncomingResponse> { 142 FullIbanPayto::try_from(&req.debit_account)?; 143 let res = db::add_incoming( 144 &self.pool, 145 &req.amount, 146 &req.debit_account, 147 "", 148 &Timestamp::now(), 149 IncomingType::reserve, 150 &req.reserve_pub, 151 ) 152 .await?; 153 match res { 154 AddIncomingResult::Success { id, created_at } => Ok(AddIncomingResponse { 155 timestamp: created_at.into(), 156 row_id: id, 157 }), 158 AddIncomingResult::ReservePubReuse => { 159 Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT)) 160 } 161 AddIncomingResult::UnknownMapping | AddIncomingResult::MappingReuse => { 162 unreachable!("mapping not used") 163 } 164 } 165 } 166 167 async fn add_incoming_kyc(&self, req: AddKycauthRequest) -> ApiResult<AddIncomingResponse> { 168 FullIbanPayto::try_from(&req.debit_account)?; 169 let res = db::add_incoming( 170 &self.pool, 171 &req.amount, 172 &req.debit_account, 173 "", 174 &Timestamp::now(), 175 IncomingType::kyc, 176 &req.account_pub, 177 ) 178 .await?; 179 match res { 180 AddIncomingResult::Success { id, created_at } => Ok(AddIncomingResponse { 181 timestamp: created_at.into(), 182 row_id: id, 183 }), 184 AddIncomingResult::ReservePubReuse => { 185 Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT)) 186 } 187 AddIncomingResult::UnknownMapping | AddIncomingResult::MappingReuse => { 188 unreachable!("mapping not used") 189 } 190 } 191 } 192 193 async fn add_incoming_mapped(&self, req: AddMappedRequest) -> ApiResult<AddIncomingResponse> { 194 FullIbanPayto::try_from(&req.debit_account)?; 195 let res = db::add_incoming( 196 &self.pool, 197 &req.amount, 198 &req.debit_account, 199 "", 200 &Timestamp::now(), 201 IncomingType::map, 202 &req.authorization_pub, 203 ) 204 .await?; 205 match res { 206 AddIncomingResult::Success { id, created_at } => Ok(AddIncomingResponse { 207 timestamp: created_at.into(), 208 row_id: id, 209 }), 210 AddIncomingResult::ReservePubReuse => { 211 Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT)) 212 } 213 AddIncomingResult::UnknownMapping => { 214 Err(failure_code(ErrorCode::BANK_TRANSFER_MAPPING_UNKNOWN)) 215 } 216 AddIncomingResult::MappingReuse => { 217 Err(failure_code(ErrorCode::BANK_TRANSFER_MAPPING_REUSED)) 218 } 219 } 220 } 221 222 fn support_account_check(&self) -> bool { 223 false 224 } 225 } 226 227 impl Revenue for TestApi { 228 async fn history(&self, params: History) -> ApiResult<RevenueIncomingHistory> { 229 let txs = db::revenue_history(&self.pool, ¶ms, &self.currency, || { 230 self.incoming_channel.subscribe() 231 }) 232 .await?; 233 Ok(RevenueIncomingHistory { 234 incoming_transactions: txs, 235 credit_account: self.payto.as_uri(), 236 }) 237 } 238 } 239 240 impl PreparedTransfer for TestApi { 241 fn supported_formats(&self) -> &[SubjectFormat] { 242 &[SubjectFormat::SIMPLE] 243 } 244 245 async fn registration(&self, req: RegistrationRequest) -> ApiResult<RegistrationResponse> { 246 let creditor = FullIbanPayto::try_from(&req.credit_account)?; 247 if *creditor != *self.payto { 248 return Err(failure_code(ErrorCode::BANK_UNKNOWN_CREDITOR)); 249 } 250 match db::transfer_register(&self.pool, &req).await? { 251 RegistrationResult::Success => Ok(RegistrationResponse { 252 subjects: vec![simple_subject(req)], 253 expiration: TalerTimestamp::Never, 254 }), 255 RegistrationResult::ReservePubReuse => { 256 Err(failure_code(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT)) 257 } 258 } 259 } 260 261 async fn unregistration(&self, req: Unregistration) -> ApiResult<bool> { 262 Ok(db::transfer_unregister(&self.pool, &req).await?) 263 } 264 } 265 266 impl Observability for TestApi { 267 async fn metrics(&self) -> ApiResult<&Registry> { 268 Ok(&self.registry) 269 } 270 }