libeufin

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

Config.kt (7313B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2024, 2025, 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 package tech.libeufin.nexus
     21 
     22 import tech.libeufin.common.*
     23 import tech.libeufin.common.db.DatabaseConfig
     24 import tech.libeufin.nexus.db.Database
     25 import tech.libeufin.ebics.EbicsKeysConfig
     26 import tech.libeufin.ebics.EbicsSetupConfig
     27 import tech.libeufin.ebics.EbicsHostConfig
     28 import java.nio.file.Path
     29 import java.time.Instant
     30 import org.slf4j.Logger
     31 import org.slf4j.LoggerFactory
     32 
     33 private val logger: Logger = LoggerFactory.getLogger("libeufin-config")
     34 
     35 val NEXUS_CONFIG_SOURCE = ConfigSource("libeufin", "libeufin-nexus", "libeufin-nexus")
     36 
     37 data class NexusIngestConfig(
     38     val accountType: AccountType,
     39     val ignoreTransactionsBefore: Instant,
     40     val ignoreBouncesBefore: Instant,
     41     val restrictionPaytoRegex: Regex?,
     42     val bounceDeduceFee: Boolean,
     43     val bounceFee: TalerAmount
     44 ) {
     45     companion object {
     46         fun default(accountType: AccountType, currency: String = "KUDOS") 
     47             = NexusIngestConfig(accountType, Instant.MIN, Instant.MIN, null, false, TalerAmount.zero(currency))
     48     }
     49 }
     50 
     51 class NexusFetchConfig(config: TalerConfig, currency: String) {
     52     private val section = config.section("nexus-fetch")
     53     val frequency = section.duration("frequency").require()
     54     val frequencyRaw = section.string("frequency").require()
     55     val checkpointTime = section.time("checkpoint_time_of_day").require()
     56     val ignoreTransactionsBefore = section.date("ignore_transactions_before").default(Instant.MIN)
     57     val ignoreBouncesBefore = section.date("ignore_bounces_before").default(Instant.MIN)
     58     val restrictionPaytoRegex = section.regex("restriction_payto_regex").orNull()
     59     val bounceDeduceFee = section.boolean("bounce_deduce_fee").default(false)
     60     val bounceFee = section.amount("bounce_fee", currency).default(TalerAmount.zero(currency))
     61 }
     62 
     63 class NexusSubmitConfig(config: TalerConfig) {
     64     private val section = config.section("nexus-submit")
     65     val frequency = section.duration("frequency").require()
     66     val frequencyRaw = section.string("frequency").require()
     67     val requireAck = section.boolean("manual_ack").default(false)
     68 }
     69 
     70 class NexusSetupConfig(config: TalerConfig): EbicsSetupConfig {
     71     private val section = config.section("nexus-setup")
     72     override val bankAuthPubKey = section.hex("bank_authentication_pub_key_hash").orNull()
     73     override val bankEncPubKey = section.hex("bank_encryption_pub_key_hash").orNull()
     74 }
     75 
     76 class NexusHostConfig(sect: TalerConfigSection): EbicsHostConfig {
     77     /** The bank base URL */
     78     override val baseUrl = sect.string("host_base_url").require()
     79     /** The bank EBICS host ID */
     80     override val hostId = sect.string("host_id").require()
     81     /** EBICS user ID */
     82     override val userId = sect.string("user_id").require()
     83     /** EBICS partner ID */
     84     override val partnerId = sect.string("partner_id").require()
     85 }
     86 
     87 class NexusEbicsConfig( 
     88     sect: TalerConfigSection,
     89 ): EbicsKeysConfig {
     90     val host by lazy { NexusHostConfig(sect) }
     91     /** Bank account metadata */
     92     val account = IbanAccountMetadata(
     93         iban = sect.iban("iban").require(),
     94         bic = sect.string("bic").require(),
     95         name = sect.string("name").require()
     96     )
     97     val qrIban = sect.iban("qr_iban").orNull() 
     98     /** Bank account payto */
     99     val payto = IbanPayto.build(account.iban.toString(), account.bic, account.name)
    100 
    101     val dialect = sect.map("bank_dialect", "bank dialect", mapOf(
    102         "postfinance" to Dialect.postfinance,
    103         "gls" to Dialect.gls,
    104         "maerki_baumann" to Dialect.maerki_baumann,
    105         "valiant" to Dialect.valiant,
    106         "raiffeisen" to Dialect.raiffeisen,
    107     )).require()
    108 
    109     /** Path where we store the bank public keys */
    110     override val bankPublicKeysPath = sect.path("bank_public_keys_file").require()
    111     /** Path where we store our private keys */
    112     override val clientPrivateKeysPath = sect.path("client_private_keys_file").require()
    113 }
    114 
    115 class ApiConfig(section: TalerConfigSection) {
    116     val authMethod = section.requireAuthMethod()
    117 }
    118 
    119 /** Configuration for libeufin-nexus */
    120 class NexusConfig internal constructor (val cfg: TalerConfig) {
    121     private val sect = cfg.section("nexus-ebics")
    122 
    123     val dbCfg by lazy { cfg.dbConfig() }
    124     val serverCfg by lazy {
    125         cfg.loadServerConfig("nexus-httpd")
    126     }
    127 
    128     /** The bank's currency */
    129     val currency = sect.string("currency").require()
    130 
    131     val accountType = sect.map("account_type", "account type", mapOf(
    132         "normal" to AccountType.normal,
    133         "exchange" to AccountType.exchange
    134     )).require()
    135 
    136     val fetch by lazy { NexusFetchConfig(cfg, currency) }
    137     val submit by lazy { NexusSubmitConfig(cfg) }
    138     val ebics by lazy { NexusEbicsConfig(sect) }
    139     val setup by lazy { NexusSetupConfig(cfg) }
    140 
    141     val ingest get() = NexusIngestConfig(
    142         accountType,
    143         fetch.ignoreTransactionsBefore,
    144         fetch.ignoreBouncesBefore,
    145         fetch.restrictionPaytoRegex,
    146         fetch.bounceDeduceFee,
    147         fetch.bounceFee
    148     )
    149    
    150     val wireGatewayApiCfg = cfg.section("nexus-httpd-wire-gateway-api").apiConf()
    151     val revenueApiCfg = cfg.section("nexus-httpd-revenue-api").apiConf()
    152     val observabilityApiCfg = cfg.section("nexus-httpd-observability-api").apiConf()
    153 }
    154 
    155 fun NexusConfig.checkCurrency(amount: TalerAmount) {
    156     if (amount.currency != currency) throw badRequest(
    157         "Wrong currency: expected $currency got ${amount.currency}",
    158         TalerErrorCode.GENERIC_CURRENCY_MISMATCH
    159     )
    160 }
    161 
    162 private fun TalerConfigSection.apiConf(): ApiConfig? {
    163     val enabled = boolean("enabled").require()
    164     return if (enabled) {
    165         return ApiConfig(this)
    166     } else {
    167         null
    168     }
    169 }
    170 
    171 enum class AccountType {
    172     normal,
    173     exchange
    174 }
    175 
    176 private fun TalerConfig.dbConfig(): DatabaseConfig {
    177     val sect = section("libeufin-nexusdb-postgres")
    178     val configOption = sect.string("config")
    179     return DatabaseConfig(
    180         dbConnStr = configOption.orNull() ?: section("nexus-postgres").string("config").orNull() ?: configOption.require(),
    181         sqlDir = sect.path("sql_dir").require()
    182     )
    183 }
    184 
    185 /** Load nexus config at [configPath] */
    186 fun nexusConfig(configPath: Path?): NexusConfig {
    187     val config = NEXUS_CONFIG_SOURCE.fromFile(configPath)
    188     return NexusConfig(config)
    189 }
    190 
    191 /** Load nexus db config at [configPath] */
    192 fun dbConfig(configPath: Path?): DatabaseConfig =
    193     NEXUS_CONFIG_SOURCE.fromFile(configPath).dbConfig()
    194 
    195 /** Run [lambda] with access to a database conn pool */
    196 suspend fun NexusConfig.withDb(lambda: suspend (Database, NexusConfig) -> Unit) {
    197     Database(dbCfg, currency).use { lambda(it, this) }
    198 }