dankito 736ed22a51 | ||
---|---|---|
BankingClient | ||
BankingClientModel | ||
FinTs4jBankingClient | ||
SampleApp | ||
docs/Vokabular | ||
gradle | ||
kotlin-js-store | ||
.gitignore | ||
.gitmodules | ||
README.md | ||
build.gradle.kts | ||
gradle.properties | ||
gradlew | ||
gradlew.bat | ||
settings.gradle.kts |
README.md
Banking Client
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.