# 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: ``` plugins { kotlin("jvm") version "2.0.10" // or kotlin("multiplatform"), depending on your requirements } repositories { mavenCentral() maven { setUrl("https://maven.dankito.net/api/packages/codinux/maven") } } dependencies { implementation("net.codinux.banking.client:fints4k-banking-client:0.5.0") } ``` ## 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.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` } } } ```