BankingClient/README.md

170 lines
6.9 KiB
Markdown
Raw Normal View History

2024-08-22 15:07:02 +00:00
# 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).
2024-08-22 15:07:02 +00:00
### Get AccountData
Retrieves data like accounts, balance and booked transactions (Konten, Saldo und Kontoumsätze).
Simple example:
2024-08-22 15:07:02 +00:00
```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())
2024-08-22 15:07:02 +00:00
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>) {
2024-08-22 15:07:02 +00:00
response.data?.let { data ->
val user = data.user
println("Kunde: ${user.customerName} ${user.accounts.size} Konten @ ${user.bic} ${user.bankName}")
2024-08-22 15:07:02 +00:00
println()
println("Konten:")
user.accounts.sortedBy { it.type }.forEach { account ->
2024-08-22 15:07:02 +00:00
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:
```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)
```
### TAN handling
Retrieving transactions older than 90 days or sometimes even log in 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, 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:
```kotlin
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
2024-08-22 15:07:02 +00:00
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`
}
}
}
2024-08-22 15:07:02 +00:00
```