# Banking Client [![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.codinux.banking.client/banking-client/badge.svg?style=plastic)](https://maven-badges.herokuapp.com/maven-central/net.codinux.banking.client/banking-client) Library to abstract over different banking client implementations like [fints4k](https://git.dankito.net/codinux/fints4k). It's primary purpose is to abstract away the different implementation details and to create a common model that can be used in all projects directly or indirectly referencing it - Web Service, Middleware, Native Apps, HTML Apps - so that not each project has the implement to model again. ## Setup ### Gradle: ``` repositories { // other repositories like mavenCentral(), ... maven { setUrl("https://maven.dankito.net/api/packages/codinux/maven") } } dependencies { implementation("net.codinux.banking.client:fints4k-banking-client:0.7.2") } ``` ## Usage For the full example source code see [ShowUsage](SampleApp/src/main/kotlin/net/codinux/banking/client/fints4k/example/ShowUsage.kt). ### Get AccountData Retrieves data like accounts, balance and booked transactions (Konten, Saldo und Kontoumsätze). Simple example: ```kotlin class ShowUsage { private val bankCode = "" // Bankleitzahl deiner Bank private val loginName = "" // Online-Banking Login Name mit dem du dich beim Online-Banking deiner Bank anmeldest private val password = "" // Online-Banking Password mit dem du dich beim Online-Banking deiner Bank anmeldest fun getAccountData() { val client = FinTs4kBankingClient(SimpleBankingClientCallback()) val response = client.getAccountData(bankCode, loginName, password) // that's all printReceivedData(response) // now print retrieved data (save it to database, display it in UI, ...) } private fun printReceivedData(response: Response) { response.data?.let { data -> val user = data.user println("Kunde: ${user.customerName} ${user.accounts.size} Konten @ ${user.bic} ${user.bankName}") println() println("Konten:") user.accounts.sortedBy { it.type }.forEach { account -> println("${account.identifier} ${account.productName} ${account.balance} ${account.currency}") } println() println("Umsätze:") data.bookedTransactions.forEach { transaction -> println("${transaction.valueDate} ${transaction.amount} ${transaction.currency} ${transaction.otherPartyName ?: ""} - ${transaction.reference}") } } } } ``` This fetches the booked account transactions of the last 90 days. In most cases no TAN is required for this. #### GetAccountData parameter You can also specify options e.g. which transactions you would like to retrieve: ```kotlin val options = GetAccountDataOptions( retrieveBalance = true, // retrieve balance (Saldo / Kontostand) yes or no retrieveTransactions = RetrieveTransactions.AccordingToRetrieveFromAndTo, // so that fromDate and toDate below determine of which time period transactions (Umsätze) should be retrieved; defaults to OfLast90Days which in most cases doesn't require a TAN retrieveTransactionsFrom = LocalDate(2023, 1, 1), retrieveTransactionsTo = LocalDate(2023, 12, 31), abortIfTanIsRequired = false // e.g. for command line application when entering TAN is either not possible or a TAN procedure is used that cannot be handled via a break point (e.g. showing a TAN image or flicker code) ) val response = client.getAccountData(options) ``` #### TAN handling Retrieving transactions older than 90 days or sometimes even log in requires a TAN, so add TAN handling in Client Callback: ```kotlin val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback -> val tan: String? = null // if a TAN is required, read TAN from command line, add a UI, ... callback.invoke(EnterTanResult(tan)) // ... set a break point here, get TAN e.g. from your TAN app, set tan variable in debugger view and resume debugger }) ``` E.g. TAN handling on the command line: ```kotlin println("Enter password for $bankCode:") val password = readln() // as an alternative for hard coding password; of course can also be done for bankCode and login name val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback -> println("A TAN is required for ${tanChallenge.forAction}. Selected TAN method is '${tanChallenge.selectedTanMethod.displayName}'. Messsage of your credit institute:") println(tanChallenge.messageToShowToUser) println("Get TAN from your TAN app etc., enter it and press Enter (or press Enter without an input to abort process):") val tan: String? = readlnOrNull().takeUnless { it.isNullOrBlank() } // map empty input to null to abort process callback.invoke(EnterTanResult(tan)) }) ``` #### Error handling Add some error handling by checking `response.error`: ```kotlin response.error?.let{ error -> println("Could not fetch account data: ${error.type} ${error.internalError ?: error.errorMessagesFromBank.joinToString()}") } ``` ### Update Account Transactions The data model saves when it retrieved account transactions the last time (in `BankAccount.lastAccountUpdateTime`). So you only need to call `FinTs4kBankingClient.updateAccountTransactions()` to retrieve all transactions starting from `BankAccount.lastAccountUpdateTime`. But as we can only specify from which day on account transactions should be retrieved, response may contain some transactions from the day of `lastAccountUpdateTime` that we already have locally. To filter out these you can use `BankingModelService().findNewTransactions(retrieveTransactions, existingTransactions)`: ```kotlin fun updateAccountTransactions() { val client = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback()) // simulate account transactions we retrieved last time val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date val lastCallToBankServer = client.getAccountData(GetAccountDataOptions(RetrieveTransactions.AccordingToRetrieveFromAndTo, retrieveTransactionsFrom = today.minusDays(90), retrieveTransactionsTo = today.minusDays(30))) if (lastCallToBankServer.data != null) { // now update account transactions val existingTransactions = lastCallToBankServer.data!!.bookedTransactions val updateTransactionsResponse = client.updateAccountTransactions() if (updateTransactionsResponse.data != null) { val retrievedTransactions = updateTransactionsResponse.data!!.flatMap { it.bookedTransactions } val newTransactions = BankingModelService().findNewTransactions(retrievedTransactions, existingTransactions) // `retrievedTransactions` may contain transactions we already have locally, `newTransactions` only // transactions that are not in `existingTransactions` } } } ``` ## Logging BankingClient and fints4k both use [klf](https://github.com/codinux-gmbh/klf), a logging facade for Kotlin (Multiplatform) with appenders for all supported KMP platforms. So logging works on all platforms out of the box. On JVM, if slf4j is on the classpath, logging can be configured with any slf4j compatible logging backend (logback, log4j, JBoss Logging, ...). If you want to see all sent and received FinTS messages, set the log level of `net.codinux.banking.fints.log.MessageLogCollector` to `DEBUG`, either via: - your logging framework (e.g. logback) - klf: `net.codinux.log.LoggerFactory.getLogger("net.codinux.banking.fints.log.MessageLogCollector").level = LogLevel.Debug` - `appendFinTsMessagesToLog` option: ```kotlin val client = FinTs4kBankingClient(FinTsClientOptions(appendFinTsMessagesToLog = true), SimpleBankingClientCallback()) ``` But be aware, in latter case if you create multiple FinTs4kBankingClient instances, the latest value of `appendFinTsMessagesToLog` overrides the value of all previous FinTs4kBankingClient instances. As with all other options, this configures the logger's level globally, so the latest configured log level value wins.