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