147 lines
5.9 KiB
Markdown
147 lines
5.9 KiB
Markdown
# 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<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:
|
|
|
|
```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)
|
|
```
|
|
|
|
Retrieving transactions older than 90 days 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, 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`:
|
|
|
|
```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`
|
|
}
|
|
}
|
|
}
|
|
``` |