libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

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 }