administrative.rs (7454B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2026 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 use std::fmt::Display; 21 22 use compact_str::CompactString; 23 use taler_common::types::{ 24 amount::Currency, 25 iban::{BIC, IBAN}, 26 }; 27 use taler_macros::EnumMeta; 28 29 use crate::{ 30 config::EbicsHostCfg, 31 ebics::{ 32 EbicsResponse, 33 ebics_code::EbicsReturnCode, 34 order::{BTF, Order}, 35 }, 36 xml, 37 xml::{Xml, XmlAccess as _}, 38 }; 39 40 #[derive(Debug, Clone, PartialEq, Eq)] 41 pub struct VersionNumber { 42 pub number: CompactString, 43 pub schema: CompactString, 44 } 45 46 impl Display for VersionNumber { 47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 48 let Self { number, schema } = self; 49 write!(f, "{number}:{schema}") 50 } 51 } 52 53 pub struct HKD { 54 pub partner: PartnerInfo, 55 pub users: Box<[UserInfo]>, 56 } 57 pub struct PartnerInfo { 58 pub name: Option<CompactString>, 59 pub accounts: Box<[AccountInfo]>, 60 pub orders: Vec<OrderInfo>, 61 } 62 pub struct OrderInfo { 63 pub order: Order, 64 pub description: String, 65 } 66 pub struct AccountInfo { 67 pub currency: Currency, 68 pub iban: IBAN, 69 pub bic: BIC, 70 } 71 pub struct UserInfo { 72 pub id: CompactString, 73 pub status: UserStatus, 74 pub permissions: Box<[Order]>, 75 } 76 77 pub struct HAA { 78 pub orders: Vec<Order>, 79 } 80 81 #[derive(Debug, Clone, PartialEq, Eq, EnumMeta)] 82 #[enum_meta(Description)] 83 pub enum UserStatus { 84 /// "Subscriber is permitted access" 85 Ready, 86 /// "Subscriber is established, pending access permission" 87 New, 88 /// "Subscriber has sent INI file, but no HIA file yet" 89 INI, 90 /// "Subscriber has sent HIA order, but no INI file yet" 91 HIA, 92 /// "Subscriber has sent both HIA order and INI file" 93 Initialised, 94 /// "Suspended after several failed attempts, new initialisation via INI and HIA possible" 95 SuspendedFailedAttempts, 96 /// "Suspended after SPR order, new initialisation via INI and HIA possible" 97 SuspendedSPR, 98 /// "Suspended by bank, new initialisation via INI and HIA is not possible, suspension can only be revoked by the bank" 99 SuspendedBank, 100 } 101 102 pub fn hev_msg(cfg: &EbicsHostCfg) -> String { 103 xml!( 104 "ebicsHEVRequest" "xmlns"="http://www.ebics.org/H000" { 105 "HostID": &cfg.host_id 106 } 107 ) 108 } 109 110 pub fn parse_hev(xml: &[u8]) -> xml::Result<EbicsResponse<Box<[VersionNumber]>>> { 111 Xml::parse(xml, "ebicsHEVResponse", |root| { 112 let s = root.one("SystemReturnCode")?; 113 Ok(EbicsResponse { 114 technical_code: s.one("ReturnCode").parse()?, 115 technical_text: s.one("ReportText").parse()?, 116 bank_code: EbicsReturnCode::EBICS_OK, 117 content: Some( 118 root.many("VersionNumber") 119 .map(|n| { 120 Ok(VersionNumber { 121 number: n.parse()?, 122 schema: n.attr("ProtocolVersion")?.into(), 123 }) 124 }) 125 .collect::<xml::Result<_>>()?, 126 ), 127 }) 128 }) 129 } 130 131 fn service(n: Xml) -> xml::Result<BTF> { 132 let msg = n.one("MsgName")?; 133 Ok(BTF { 134 service: n.one("ServiceName").parse()?, 135 scope: n.opt("Scope").parse()?, 136 option: n.opt("ServiceOption").parse()?, 137 container: n.opt("Container").parse_attr("containerType")?, 138 msg: msg.parse()?, 139 version: msg.parse_opt_attr("version")?, 140 }) 141 } 142 143 pub fn parse_hkd(xml: &[u8]) -> xml::Result<HKD> { 144 fn order(n: Xml) -> xml::Result<Order> { 145 let ty = n.one("AdminOrderType")?.text(); 146 Order::from_parts(ty, n.opt("Service")?.map(service).transpose()?) 147 .ok_or_else(|| n.parse_err(format_args!("Unknown order type {ty}"))) 148 } 149 Xml::parse(xml, "HKDResponseOrderData", |root| { 150 let partner = root.one("PartnerInfo")?; 151 152 Ok(HKD { 153 partner: PartnerInfo { 154 name: partner.one("AddressInfo").opt("Name").parse()?, 155 accounts: partner 156 .many("AccountInfo") 157 .map(|account| { 158 let currency = account.parse_attr("Currency")?; 159 let iban = account 160 .many("AccountNumber") 161 .find(|nb| nb.opt_attr("international") == Some("true")) 162 .unwrap() 163 .parse()?; 164 let bic = account 165 .many("BankCode") 166 .find(|nb| nb.opt_attr("international") == Some("true")) 167 .unwrap() 168 .parse()?; 169 Ok(AccountInfo { 170 currency, 171 iban, 172 bic, 173 }) 174 }) 175 .collect::<xml::Result<_>>()?, 176 orders: partner 177 .many("OrderInfo") 178 .map(|n| { 179 Ok(OrderInfo { 180 order: order(n)?, 181 description: n.one("Description").parse()?, 182 }) 183 }) 184 .collect::<xml::Result<_>>()?, 185 }, 186 users: root 187 .many("UserInfo") 188 .map(|n| { 189 let id = n.one("UserID")?; 190 Ok(UserInfo { 191 id: id.parse()?, 192 status: match id.attr("Status")? { 193 "1" => UserStatus::Ready, 194 "2" => UserStatus::New, 195 "3" => UserStatus::INI, 196 "4" => UserStatus::HIA, 197 "5" => UserStatus::Initialised, 198 "6" => UserStatus::SuspendedFailedAttempts, 199 // 7 is not applicable per spec 200 "8" => UserStatus::SuspendedSPR, 201 "9" => UserStatus::SuspendedBank, 202 s => return Err(id.parse_err(format_args!("Unknown user status {s}"))), 203 }, 204 permissions: n 205 .many("Permission") 206 .map(|p| order(p)) 207 .collect::<xml::Result<_>>()?, 208 }) 209 }) 210 .collect::<xml::Result<_>>()?, 211 }) 212 }) 213 } 214 215 pub fn parse_haa(xml: &[u8]) -> xml::Result<HAA> { 216 Xml::parse(xml, "HAAResponseOrderData", |root| { 217 Ok(HAA { 218 orders: root 219 .many("Service") 220 .map(|n| Ok(Order::BTD(service(n)?))) 221 .collect::<xml::Result<_>>()?, 222 }) 223 }) 224 }