list.rs (9665B)
1 /* 2 This file is part of TALER 3 Copyright (C) 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 compact_str::CompactString; 18 use jiff::Timestamp; 19 use libeufin_ebics::iso20022::model::{InId, OutId}; 20 use sqlx::{PgPool, Row as _, postgres::PgRow}; 21 use taler_api::{db::TypeHelper as _, serialized}; 22 use taler_common::{ 23 api::{EddsaPublicKey, ShortHashCode}, 24 types::amount::{Amount, Currency, Decimal}, 25 }; 26 27 /** Incoming transaction metadata for debugging */ 28 pub struct InMetadata { 29 pub id: InId, 30 pub date: Timestamp, 31 pub amount: Amount, 32 pub credit_fee: Option<Decimal>, 33 pub subject: Option<String>, 34 pub debtor: Option<String>, 35 pub talerable: Option<String>, 36 pub bounced: Option<String>, 37 } 38 39 /** Outgoing transaction metadata for debugging */ 40 pub struct OutMetadata { 41 pub id: OutId, 42 pub date: Timestamp, 43 pub amount: Amount, 44 pub subject: Option<String>, 45 pub creditor: Option<String>, 46 pub wtid: Option<ShortHashCode>, 47 pub exchange_base_url: Option<String>, 48 } 49 50 /** Initiated metadata for debugging */ 51 pub struct InitMetadata { 52 pub date: Timestamp, 53 pub amount: Amount, 54 pub subject: String, 55 pub creditor: String, 56 pub id: String, 57 pub batch: Option<String>, 58 pub batch_order: Option<String>, 59 pub status: String, 60 pub msg: Option<String>, 61 pub submission_time: Option<Timestamp>, 62 pub submission_counter: u32, 63 } 64 65 /** Initiated metadata for debugging */ 66 pub struct InitMetadataAck { 67 pub date: Timestamp, 68 pub amount: Amount, 69 pub subject: String, 70 pub creditor: String, 71 pub db_id: u64, 72 pub id: String, 73 } 74 75 /** List incoming transaction metadata for debugging */ 76 pub async fn incoming( 77 db: &PgPool, 78 incomplete: bool, 79 currency: &Currency, 80 ) -> sqlx::Result<Vec<InMetadata>> { 81 let query = if incomplete { 82 " 83 SELECT 84 incoming.amount AS amount 85 ,credit_fee 86 ,incoming.subject 87 ,end_to_end_id AS bounced 88 ,execution_time 89 ,debit_payto 90 ,type::text 91 ,metadata 92 ,uetr 93 ,tx_id 94 ,acct_svcr_ref 95 ,talerable_incoming_transactions.authorization_pub as auth_pub 96 ,pending_recurrent_incoming_transactions.authorization_pub as pending_pub 97 FROM incoming_transactions AS incoming 98 LEFT JOIN talerable_incoming_transactions USING (incoming_transaction_id) 99 LEFT JOIN bounced_transactions USING (incoming_transaction_id) 100 LEFT JOIN initiated_outgoing_transactions USING (initiated_outgoing_transaction_id) 101 LEFT JOIN pending_recurrent_incoming_transactions USING (incoming_transaction_id) 102 WHERE debit_payto IS NULL OR incoming.subject IS NULL 103 ORDER BY execution_time 104 " 105 } else { 106 " 107 SELECT 108 incoming.amount AS amount 109 ,credit_fee 110 ,incoming.subject 111 ,end_to_end_id AS bounced 112 ,execution_time 113 ,debit_payto 114 ,type::text 115 ,metadata 116 ,uetr 117 ,tx_id 118 ,acct_svcr_ref 119 ,talerable_incoming_transactions.authorization_pub as auth_pub 120 ,pending_recurrent_incoming_transactions.authorization_pub as pending_pub 121 FROM incoming_transactions AS incoming 122 LEFT JOIN talerable_incoming_transactions USING (incoming_transaction_id) 123 LEFT JOIN bounced_transactions USING (incoming_transaction_id) 124 LEFT JOIN initiated_outgoing_transactions USING (initiated_outgoing_transaction_id) 125 LEFT JOIN pending_recurrent_incoming_transactions USING (incoming_transaction_id) 126 ORDER BY execution_time 127 " 128 }; 129 serialized!( 130 sqlx::query(query) 131 .try_map(|r: PgRow| { 132 let auth_pub: Option<EddsaPublicKey> = r.try_get("auth_pub")?; 133 let pending_pub: Option<EddsaPublicKey> = r.try_get("pending_pub")?; 134 let map = if let Some(auth_pub) = auth_pub { 135 format!(" mapped by {auth_pub}") 136 } else { 137 String::new() 138 }; 139 Ok(InMetadata { 140 id: InId { 141 uetr: r.try_get("uetr")?, 142 tx_id: r.try_get("tx_id")?, 143 sref: r.try_get("acct_svcr_ref")?, 144 }, 145 date: r.try_get_timestamp("execution_time")?, 146 amount: r.try_get_amount("amount", currency)?, 147 credit_fee: r.try_get("credit_fee")?, 148 subject: r.try_get("subject")?, 149 debtor: r.try_get("debit_payto")?, 150 bounced: r.try_get("bounced")?, 151 talerable: match r.try_get::<Option<CompactString>, _>("type")? { 152 None => pending_pub.map(|pending| format!("pending mapped by {pending}")), 153 Some(ty) => Some(format!( 154 "{ty} {}{map}", 155 r.try_get::<EddsaPublicKey, _>("metadata")? 156 )), 157 }, 158 }) 159 }) 160 .fetch_all(db) 161 ) 162 } 163 164 /** List outgoing transaction metadata for debugging */ 165 pub async fn outgoing(db: &PgPool, currency: &Currency) -> sqlx::Result<Vec<OutMetadata>> { 166 serialized!( 167 sqlx::query( 168 " 169 SELECT 170 amount 171 ,subject 172 ,execution_time 173 ,credit_payto 174 ,end_to_end_id 175 ,acct_svcr_ref 176 ,wtid 177 ,exchange_base_url 178 FROM outgoing_transactions 179 LEFT JOIN talerable_outgoing_transactions using (outgoing_transaction_id) 180 ORDER BY execution_time 181 ", 182 ) 183 .try_map(|r: PgRow| { 184 Ok(OutMetadata { 185 id: OutId { 186 msg_id: None, 187 e2e_id: r.try_get("end_to_end_id")?, 188 sref: r.try_get("acct_svcr_ref")?, 189 }, 190 date: r.try_get_timestamp("execution_time")?, 191 amount: r.try_get_amount("amount", currency)?, 192 subject: r.try_get("subject")?, 193 creditor: r.try_get("credit_payto")?, 194 wtid: r.try_get("wtid")?, 195 exchange_base_url: r.try_get("exchange_base_url")?, 196 }) 197 }) 198 .fetch_all(db) 199 ) 200 } 201 202 /** List initiated transaction metadata for debugging */ 203 pub async fn initiated(db: &PgPool, currency: &Currency) -> sqlx::Result<Vec<InitMetadata>> { 204 serialized!( 205 sqlx::query( 206 " 207 SELECT 208 amount 209 ,subject 210 ,initiation_time 211 ,submission_date 212 ,submission_counter 213 ,credit_payto 214 ,end_to_end_id 215 ,message_id 216 ,order_id 217 ,initiated_outgoing_transactions.status::text 218 ,initiated_outgoing_transactions.status_msg 219 FROM initiated_outgoing_transactions 220 LEFT JOIN initiated_outgoing_batches USING (initiated_outgoing_batch_id) 221 ORDER BY initiation_time 222 ", 223 ) 224 .try_map(|r: PgRow| { 225 Ok(InitMetadata { 226 date: r.try_get_timestamp("initiation_time")?, 227 amount: r.try_get_amount("amount", currency)?, 228 subject: r.try_get("subject")?, 229 creditor: r.try_get("credit_payto")?, 230 id: r.try_get("end_to_end_id")?, 231 batch: r.try_get("message_id")?, 232 batch_order: r.try_get("order_id")?, 233 status: r.try_get("status")?, 234 msg: r.try_get("status_msg")?, 235 submission_time: r.try_get_opt_timestamp("submission_date")?, 236 submission_counter: r.try_get_opt_u32("submission_counter")?.unwrap_or_default(), 237 }) 238 }) 239 .fetch_all(db) 240 ) 241 } 242 243 /** List initiated transaction metadata pending acknowledgment for debugging */ 244 pub async fn initiated_ack(db: &PgPool, currency: &Currency) -> sqlx::Result<Vec<InitMetadataAck>> { 245 serialized!( 246 sqlx::query( 247 " 248 SELECT 249 amount 250 ,subject 251 ,initiation_time 252 ,credit_payto 253 ,end_to_end_id 254 ,initiated_outgoing_transaction_id 255 FROM initiated_outgoing_transactions 256 WHERE initiated_outgoing_batch_id IS NULL AND NOT awaiting_ack 257 ORDER BY initiation_time 258 ", 259 ) 260 .try_map(|r: PgRow| { 261 Ok(InitMetadataAck { 262 date: r.try_get_timestamp("initiation_time")?, 263 amount: r.try_get_amount("amount", currency)?, 264 subject: r.try_get("subject")?, 265 creditor: r.try_get("credit_payto")?, 266 id: r.try_get("end_to_end_id")?, 267 db_id: r.try_get_u64("initiated_outgoing_transaction_id")?, 268 }) 269 }) 270 .fetch_all(db) 271 ) 272 }