Go to file
dankito 736ed22a51 Updated version in README 2024-10-20 11:07:29 +02:00
BankingClient Implemented setting preferredTanMethods for updateAccountTransactionsAsync() and transferMoneyAsync() 2024-10-16 20:04:59 +02:00
BankingClientModel Added preferredTanMethods to constructor overload 2024-10-16 15:34:54 +02:00
FinTs4jBankingClient Fixed mapping new TanMethodType names 2024-10-19 21:37:39 +02:00
SampleApp Added errorType to error output 2024-09-17 17:26:35 +02:00
docs/Vokabular Renamed bankCode to domesticBankCode 2024-09-16 17:22:46 +02:00
gradle Simplified publishing artifacts to codinux repo 2024-10-16 20:03:32 +02:00
kotlin-js-store Updated to new fints4k data model, that TanGeneratorTanMedium and MobilePhoneTanMedium are now derived anymore from TanMedium 2024-10-10 18:54:54 +02:00
.gitignore Added basic BankingClient model 2024-08-16 16:53:49 +02:00
.gitmodules Made banking-client-model publishable 2024-08-16 16:55:33 +02:00
README.md Updated version in README 2024-10-20 11:07:29 +02:00
build.gradle.kts Bumped verion to 0.7.2 2024-10-20 11:05:38 +02:00
gradle.properties Configured parallel Gradle build, but disabled by default as releasing with this option won't work 2024-09-19 21:30:06 +02:00
gradlew Initial commit 2024-08-14 19:41:11 +02:00
gradlew.bat Initial commit 2024-08-14 19:41:11 +02:00
settings.gradle.kts Added SampleApp to show usage 2024-08-22 17:00:34 +02:00

README.md

Banking Client

Maven Central

Library to abstract over different banking client implementations like 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.

Get AccountData

Retrieves data like accounts, balance and booked transactions (Konten, Saldo und Kontoumsätze).

Simple example:

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<GetAccountDataResponse>) {
        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:

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:

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:

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:

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):

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, 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:
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.