api.rs (7479B)
1 /* 2 This file is part of TALER 3 Copyright (C) 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 taler_api::{ 19 api::{TalerApi, revenue::Revenue, wire::WireGateway}, 20 error::{ApiResult, failure}, 21 subject::IncomingSubject, 22 }; 23 use taler_common::{ 24 api_common::{SafeU64, safe_u64}, 25 api_params::{History, Page}, 26 api_revenue::RevenueIncomingHistory, 27 api_wire::{ 28 AddIncomingRequest, AddIncomingResponse, AddKycauthRequest, AddKycauthResponse, 29 IncomingHistory, OutgoingHistory, TransferList, TransferRequest, TransferResponse, 30 TransferState, TransferStatus, 31 }, 32 error_code::ErrorCode, 33 types::{payto::PaytoURI, utils::date_to_utc_timestamp}, 34 }; 35 use tokio::sync::watch::Sender; 36 37 use crate::{ 38 FullHuPayto, 39 constants::CURRENCY, 40 db::{self, AddIncomingResult, Transfer, TxInAdmin}, 41 }; 42 43 pub struct MagnetApi { 44 pub pool: sqlx::PgPool, 45 pub payto: PaytoURI, 46 pub in_channel: Sender<i64>, 47 pub taler_in_channel: Sender<i64>, 48 pub out_channel: Sender<i64>, 49 pub taler_out_channel: Sender<i64>, 50 } 51 52 impl MagnetApi { 53 pub async fn start(pool: sqlx::PgPool, payto: PaytoURI) -> Self { 54 let in_channel = Sender::new(0); 55 let taler_in_channel = Sender::new(0); 56 let out_channel = Sender::new(0); 57 let taler_out_channel = Sender::new(0); 58 let tmp = Self { 59 pool: pool.clone(), 60 payto, 61 in_channel: in_channel.clone(), 62 taler_in_channel: taler_in_channel.clone(), 63 out_channel: out_channel.clone(), 64 taler_out_channel: taler_out_channel.clone(), 65 }; 66 tokio::spawn(db::notification_listener( 67 pool, 68 in_channel, 69 taler_in_channel, 70 out_channel, 71 taler_out_channel, 72 )); 73 tmp 74 } 75 } 76 77 impl TalerApi for MagnetApi { 78 fn currency(&self) -> &str { 79 CURRENCY.as_ref() 80 } 81 82 fn implementation(&self) -> &'static str { 83 "urn:net:taler:specs:taler-magnet-bank:taler-rust" 84 } 85 } 86 87 impl WireGateway for MagnetApi { 88 async fn transfer(&self, req: TransferRequest) -> ApiResult<TransferResponse> { 89 let creditor = FullHuPayto::try_from(&req.credit_account)?; 90 let result = db::make_transfer( 91 &self.pool, 92 &Transfer { 93 request_uid: req.request_uid, 94 wtid: req.wtid, 95 amount: req.amount.decimal(), 96 creditor, 97 exchange_base_url: req.exchange_base_url, 98 }, 99 &Timestamp::now(), 100 ) 101 .await?; 102 match result { 103 db::TransferResult::Success { id, initiated_at } => Ok(TransferResponse { 104 timestamp: initiated_at.into(), 105 row_id: SafeU64::try_from(id).unwrap(), 106 }), 107 db::TransferResult::RequestUidReuse => Err(failure( 108 ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED, 109 "request_uid used already", 110 )), 111 db::TransferResult::WtidReuse => Err(failure( 112 ErrorCode::BANK_TRANSFER_WTID_REUSED, 113 "wtid used already", 114 )), 115 } 116 } 117 118 async fn transfer_page( 119 &self, 120 page: Page, 121 status: Option<TransferState>, 122 ) -> ApiResult<TransferList> { 123 Ok(TransferList { 124 transfers: db::transfer_page(&self.pool, &status, &page).await?, 125 debit_account: self.payto.clone(), 126 }) 127 } 128 129 async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> { 130 Ok(db::transfer_by_id(&self.pool, id).await?) 131 } 132 133 async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> { 134 Ok(OutgoingHistory { 135 outgoing_transactions: db::outgoing_history(&self.pool, ¶ms, || { 136 self.taler_out_channel.subscribe() 137 }) 138 .await?, 139 debit_account: self.payto.clone(), 140 }) 141 } 142 143 async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> { 144 Ok(IncomingHistory { 145 incoming_transactions: db::incoming_history(&self.pool, ¶ms, || { 146 self.taler_in_channel.subscribe() 147 }) 148 .await?, 149 credit_account: self.payto.clone(), 150 }) 151 } 152 153 async fn add_incoming_reserve( 154 &self, 155 req: AddIncomingRequest, 156 ) -> ApiResult<AddIncomingResponse> { 157 let debtor = FullHuPayto::try_from(&req.debit_account)?; 158 let res = db::register_tx_in_admin( 159 &self.pool, 160 &TxInAdmin { 161 amount: req.amount, 162 subject: format!("Admin incoming {}", req.reserve_pub), 163 debtor, 164 metadata: IncomingSubject::Reserve(req.reserve_pub), 165 }, 166 &Timestamp::now(), 167 ) 168 .await?; 169 match res { 170 AddIncomingResult::Success { 171 row_id, valued_at, .. 172 } => Ok(AddIncomingResponse { 173 row_id: safe_u64(row_id), 174 timestamp: date_to_utc_timestamp(&valued_at).into(), 175 }), 176 AddIncomingResult::ReservePubReuse => Err(failure( 177 ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT, 178 "reserve_pub used already".to_owned(), 179 )), 180 } 181 } 182 183 async fn add_incoming_kyc(&self, req: AddKycauthRequest) -> ApiResult<AddKycauthResponse> { 184 let debtor = FullHuPayto::try_from(&req.debit_account)?; 185 let res = db::register_tx_in_admin( 186 &self.pool, 187 &TxInAdmin { 188 amount: req.amount, 189 subject: format!("Admin incoming KYC:{}", req.account_pub), 190 debtor, 191 metadata: IncomingSubject::Kyc(req.account_pub), 192 }, 193 &Timestamp::now(), 194 ) 195 .await?; 196 match res { 197 AddIncomingResult::Success { 198 row_id, valued_at, .. 199 } => Ok(AddKycauthResponse { 200 row_id: safe_u64(row_id), 201 timestamp: date_to_utc_timestamp(&valued_at).into(), 202 }), 203 AddIncomingResult::ReservePubReuse => Err(failure( 204 ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT, 205 "reserve_pub used already".to_owned(), 206 )), 207 } 208 } 209 210 fn support_account_check(&self) -> bool { 211 false 212 } 213 } 214 215 impl Revenue for MagnetApi { 216 async fn history(&self, params: History) -> ApiResult<RevenueIncomingHistory> { 217 Ok(RevenueIncomingHistory { 218 incoming_transactions: db::revenue_history(&self.pool, ¶ms, || { 219 self.in_channel.subscribe() 220 }) 221 .await?, 222 credit_account: self.payto.clone(), 223 }) 224 } 225 }