BankingClient/README.md

5.9 KiB

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:

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.

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.

You can also specify options e.g. which transactions should be retrieved:

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)

Retrieving transactions older than 90 days 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, add a UI or ...
    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
})

Add some error handling by checking response.error:

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

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`
        }
    }
}