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