api.rs (7164B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025 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, 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) -> Option<&str> { 83 Some("taler-magnet-bank") 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(&self.pool, &req, &creditor, &Timestamp::now()).await?; 91 match result { 92 db::TransferResult::Success { id, initiated_at } => Ok(TransferResponse { 93 timestamp: initiated_at.into(), 94 row_id: SafeU64::try_from(id).unwrap(), 95 }), 96 db::TransferResult::RequestUidReuse => Err(failure( 97 ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED, 98 "request_uid used already", 99 )), 100 db::TransferResult::WtidReuse => Err(failure( 101 ErrorCode::BANK_TRANSFER_WTID_REUSED, 102 "wtid used already", 103 )), 104 } 105 } 106 107 async fn transfer_page( 108 &self, 109 page: Page, 110 status: Option<TransferState>, 111 ) -> ApiResult<TransferList> { 112 Ok(TransferList { 113 transfers: db::transfer_page(&self.pool, &status, &page).await?, 114 debit_account: self.payto.clone(), 115 }) 116 } 117 118 async fn transfer_by_id(&self, id: u64) -> ApiResult<Option<TransferStatus>> { 119 Ok(db::transfer_by_id(&self.pool, id).await?) 120 } 121 122 async fn outgoing_history(&self, params: History) -> ApiResult<OutgoingHistory> { 123 Ok(OutgoingHistory { 124 outgoing_transactions: db::outgoing_history(&self.pool, ¶ms, || { 125 self.taler_out_channel.subscribe() 126 }) 127 .await?, 128 debit_account: self.payto.clone(), 129 }) 130 } 131 132 async fn incoming_history(&self, params: History) -> ApiResult<IncomingHistory> { 133 Ok(IncomingHistory { 134 incoming_transactions: db::incoming_history(&self.pool, ¶ms, || { 135 self.taler_in_channel.subscribe() 136 }) 137 .await?, 138 credit_account: self.payto.clone(), 139 }) 140 } 141 142 async fn add_incoming_reserve( 143 &self, 144 req: AddIncomingRequest, 145 ) -> ApiResult<AddIncomingResponse> { 146 let debtor = FullHuPayto::try_from(&req.debit_account)?; 147 let res = db::register_tx_in_admin( 148 &self.pool, 149 &TxInAdmin { 150 amount: req.amount, 151 subject: format!("Admin incoming {}", req.reserve_pub), 152 debtor, 153 metadata: IncomingSubject::Reserve(req.reserve_pub), 154 }, 155 &Timestamp::now(), 156 ) 157 .await?; 158 match res { 159 AddIncomingResult::Success { 160 row_id, valued_at, .. 161 } => Ok(AddIncomingResponse { 162 row_id: safe_u64(row_id), 163 timestamp: date_to_utc_timestamp(&valued_at).into(), 164 }), 165 AddIncomingResult::ReservePubReuse => Err(failure( 166 ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT, 167 "reserve_pub used already".to_owned(), 168 )), 169 } 170 } 171 172 async fn add_incoming_kyc(&self, req: AddKycauthRequest) -> ApiResult<AddKycauthResponse> { 173 let debtor = FullHuPayto::try_from(&req.debit_account)?; 174 let res = db::register_tx_in_admin( 175 &self.pool, 176 &TxInAdmin { 177 amount: req.amount, 178 subject: format!("Admin incoming KYC:{}", req.account_pub), 179 debtor, 180 metadata: IncomingSubject::Kyc(req.account_pub), 181 }, 182 &Timestamp::now(), 183 ) 184 .await?; 185 match res { 186 AddIncomingResult::Success { 187 row_id, valued_at, .. 188 } => Ok(AddKycauthResponse { 189 row_id: safe_u64(row_id), 190 timestamp: date_to_utc_timestamp(&valued_at).into(), 191 }), 192 AddIncomingResult::ReservePubReuse => Err(failure( 193 ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT, 194 "reserve_pub used already".to_owned(), 195 )), 196 } 197 } 198 199 fn support_account_check(&self) -> bool { 200 false 201 } 202 } 203 204 impl Revenue for MagnetApi { 205 async fn history(&self, params: History) -> ApiResult<RevenueIncomingHistory> { 206 Ok(RevenueIncomingHistory { 207 incoming_transactions: db::revenue_history(&self.pool, ¶ms, || { 208 self.in_channel.subscribe() 209 }) 210 .await?, 211 credit_account: self.payto.clone(), 212 }) 213 } 214 }