Go to file
dankito 62482fb5a3 Renamed bankCode to domesticBankCode 2024-09-16 17:22:46 +02:00
BankingClient Renamed bankCode to domesticBankCode 2024-09-16 17:22:46 +02:00
BankingClientModel Renamed bankCode to domesticBankCode 2024-09-16 17:22:46 +02:00
FinTs4jBankingClient Renamed bankCode to domesticBankCode 2024-09-16 17:22:46 +02:00
SampleApp Renamed User to BankAccess 2024-09-16 16:33:08 +02:00
docs/Vokabular Renamed bankCode to domesticBankCode 2024-09-16 17:22:46 +02:00
gradle Added documentation 2024-08-17 03:26:11 +02:00
kotlin-js-store Added arithmetic operations to Amount 2024-09-12 03:30:00 +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 Renamed lastTransactionsRetrievalTime to lastAccountUpdateTime 2024-09-11 23:21:06 +02:00
build.gradle.kts Bumped version to 0.5.2-SNAPSHOT 2024-09-01 20:20:25 +02:00
gradle.properties Added arithmetic operations to Amount 2024-09-12 03:30:00 +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

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