Compare commits

..

No commits in common. "main" and "v0.6.0" have entirely different histories.
main ... v0.6.0

32 changed files with 565 additions and 535 deletions

View file

@ -8,7 +8,6 @@ import net.codinux.banking.client.model.response.GetAccountDataResponse
import net.codinux.banking.client.model.response.GetTransactionsResponse
import net.codinux.banking.client.model.response.Response
import net.codinux.banking.client.model.response.TransferMoneyResponse
import net.codinux.banking.client.model.tan.TanMethodType
interface BankingClient {
@ -42,10 +41,7 @@ interface BankingClient {
*
* Optionally specify which [accounts] should be updated. If not specified all accounts will be updated.
*/
suspend fun updateAccountTransactionsAsync(
bank: BankAccess, accounts: List<BankAccount>? = null,
preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications
): Response<List<GetTransactionsResponse>>
suspend fun updateAccountTransactionsAsync(bank: BankAccess, accounts: List<BankAccount>? = null): Response<List<GetTransactionsResponse>>
suspend fun transferMoneyAsync(bankCode: String, loginName: String, password: String, recipientName: String,

View file

@ -9,7 +9,6 @@ import net.codinux.banking.client.model.response.GetAccountDataResponse
import net.codinux.banking.client.model.response.GetTransactionsResponse
import net.codinux.banking.client.model.response.Response
import net.codinux.banking.client.model.response.TransferMoneyResponse
import net.codinux.banking.client.model.tan.TanMethodType
interface BankingClientForUser {
@ -41,10 +40,7 @@ interface BankingClientForUser {
* Updates account's transactions beginning from [BankAccount.lastAccountUpdateTime].
* This may requires TAN if [BankAccount.lastAccountUpdateTime] is older than 90 days.
*/
suspend fun updateAccountTransactionsAsync(
accounts: List<BankAccount>? = null,
preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications
): Response<List<GetTransactionsResponse>>
suspend fun updateAccountTransactionsAsync(accounts: List<BankAccount>? = null): Response<List<GetTransactionsResponse>>
suspend fun transferMoneyAsync(recipientName: String, recipientAccountIdentifier: String, amount: Amount, paymentReference: String? = null): Response<TransferMoneyResponse>

View file

@ -10,7 +10,6 @@ import net.codinux.banking.client.model.request.TransferMoneyRequest
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
import net.codinux.banking.client.model.response.GetTransactionsResponse
import net.codinux.banking.client.model.response.Response
import net.codinux.banking.client.model.tan.TanMethodType
abstract class BankingClientForUserBase(
protected val credentials: AccountCredentials,
@ -26,7 +25,7 @@ abstract class BankingClientForUserBase(
}
}
override suspend fun updateAccountTransactionsAsync(accounts: List<BankAccount>?, preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>?): Response<List<GetTransactionsResponse>> =
override suspend fun updateAccountTransactionsAsync(accounts: List<BankAccount>?): Response<List<GetTransactionsResponse>> =
client.updateAccountTransactionsAsync(bank, accounts)

View file

@ -8,7 +8,6 @@ import net.codinux.banking.client.model.options.GetAccountDataOptions
import net.codinux.banking.client.model.request.GetAccountDataRequest
import net.codinux.banking.client.model.request.TransferMoneyRequest
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
import net.codinux.banking.client.model.tan.TanMethodType
/* BankingClient */
@ -20,8 +19,8 @@ fun BankingClient.getAccountData(request: GetAccountDataRequest) = runBlocking {
getAccountDataAsync(request)
}
fun BankingClient.updateAccountTransactions(bank: BankAccess, accounts: List<BankAccount>? = null, preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications) = runBlocking {
updateAccountTransactionsAsync(bank, accounts, preferredTanMethodsIfSelectedTanMethodIsNotAvailable)
fun BankingClient.updateAccountTransactions(bank: BankAccess, accounts: List<BankAccount>? = null) = runBlocking {
updateAccountTransactionsAsync(bank, accounts)
}
@ -45,8 +44,8 @@ fun BankingClientForUser.getAccountData(options: GetAccountDataOptions) = runBlo
getAccountDataAsync(options)
}
fun BankingClientForUser.updateAccountTransactions(accounts: List<BankAccount>? = null, preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications) = runBlocking {
updateAccountTransactionsAsync(accounts, preferredTanMethodsIfSelectedTanMethodIsNotAvailable)
fun BankingClientForUser.updateAccountTransactions() = runBlocking {
updateAccountTransactionsAsync()
}

View file

@ -1,10 +1,8 @@
package net.codinux.banking.client.model
import kotlinx.datetime.LocalDate
import net.codinux.banking.client.model.config.JsonIgnore
import net.codinux.banking.client.model.config.NoArgConstructor
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
@NoArgConstructor
open class AccountTransaction(
val amount: Amount = Amount.Zero, // TODO: a string is really bad in UI, find a better solution
@ -136,18 +134,5 @@ open class AccountTransaction(
"$amount $currency $bookingDate $valueDate $reference $otherPartyName $otherPartyBankId $otherPartyAccountId"
}
@get:JsonIgnore
open val displayedReference: String?
get() = userSetReference ?: referenceNumber
@get:JsonIgnore
open val displayedOtherPartyName: String?
get() = userSetOtherPartyName ?: otherPartyName
@get:JsonIgnore
open val displayedOtherPartyNameOrPostingText: String?
get() = displayedOtherPartyName ?: postingText
override fun toString() = "${valueDate.dayOfMonth}.${valueDate.monthNumber}.${valueDate.year} ${amount.toString().padStart(4, ' ')} ${if (currency == "EUR") "€" else currency} ${otherPartyName ?: ""} - $reference"
}

View file

@ -70,49 +70,12 @@ open class BankAccess(
var wrongCredentialsEntered: Boolean = false
/**
* BankingClient specific data of this account that the client needs to fulfill its job.
*
* You should treat it as opaque data, that only makes sense to the BankingClient, and pass it back to the client if set.
*
* For fints4k e.g. contains the FinTS jobs the bank supports, FinTS specific data like KundensystemID and so on.
*
* The deserialized in-memory only value of [serializedClientData] so that we don't have to deserialize [serializedClientData] each time.
*/
var clientData: Any? = null
/**
* Serialized version of [clientData].
*
* The same as with [clientData] you should treat this value as opaque that only makes sense to the client implementation.
*
* [clientData] is the deserialized in-memory model of this value, so that we don't have to serialize this value each time.
* serializedClientData is the serialized version of clientData so that you can store (and restore) it e.g. to your
* database, so that on next application start client implementation doesn't have to fetch all these data again.
* Speeds up e.g. getting account transactions and transferring money with fints4k as FinTS requires quite a lot of
* data before account transactions can be retrieved.
*/
var serializedClientData: String? = null
@get:JsonIgnore
open val displayName: String
get() = userSetDisplayName ?: bankName
@get:JsonIgnore
open val accountsSorted: List<out BankAccount>
get() = accounts.sortedBy { it.displayIndex }
@get:JsonIgnore
open val tanMethodsSorted: List<out TanMethod>
get() = tanMethods.sortedBy { it.identifier }
@get:JsonIgnore
open val tanMediaSorted: List<out TanMedium>
get() = tanMedia.sortedBy { it.status }
@get:JsonIgnore
val selectedTanMethod: TanMethod
get() = tanMethods.first { it.identifier == selectedTanMethodIdentifier }

View file

@ -53,11 +53,11 @@ open class BankAccount(
}
@get:JsonIgnore
open val supportsBalanceRetrieval: Boolean
open val supportsTransactionRetrieval: Boolean
get() = supportsAnyFeature(BankAccountFeatures.RetrieveBalance)
@get:JsonIgnore
open val supportsTransactionRetrieval: Boolean
open val supportsBalanceRetrieval: Boolean
get() = supportsAnyFeature(BankAccountFeatures.RetrieveTransactions)
@get:JsonIgnore

View file

@ -6,18 +6,7 @@ import kotlinx.datetime.Instant
open class MessageLogEntry(
open val type: MessageLogEntryType,
open val message: String,
open val messageWithoutSensitiveData: String? = null,
open val messageTrace: String? = null,
open val error: Throwable? = null,
open val time: Instant = Clock.System.now(),
val messageNumberString: String? = null,
val messageNumber: Int? = null,
val jobType: String? = null,
val messageCategory: String? = null,
val bank: BankAccess? = null, // TODO: make non-null
val account: BankAccount? = null
) {
override fun toString() = "$messageNumberString $jobType $messageCategory $type $message"
}
open val time: Instant = Clock.System.now()
)

View file

@ -27,14 +27,14 @@ open class GetAccountDataOptions(
* likes to use a different one, she can select another one in EnterTanDialog.
*
* By default we prefer non visual TanMethods (like AppTan and SMS) over image based TanMethods (like QR-code and
* photoTan) and exclude ChipTanUsb, which is not supported by application, and FlickerCode, which is hard to
* photoTan) and exclude ChipTanUsb, which is not supported by application, and Flickercode, which is hard to
* implement and therefore most applications have not implemented.
*
* Console apps can only handle non visual TanMethods.
* But also graphical applications prefer non visual TanMethods as then they only have to display a text field to input
* TAN, and then image based TanMethods as then they additionally only have to display an image.
*/
val preferredTanMethods: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications,
val preferredTanMethods: List<TanMethodType>? = TanMethodType.NonVisualOrImageBased,
val tanMethodsNotSupportedByApplication: List<TanMethodType> = TanMethodType.TanMethodsNotSupportedByMostApplications,

View file

@ -64,19 +64,16 @@ open class TransferMoneyRequest(
* likes to use a different one, she can select another one in EnterTanDialog.
*
* By default we prefer non visual TanMethods (like AppTan and SMS) over image based TanMethods (like QR-code and
* photoTan) and exclude ChipTanUsb, which is not supported by application, and FlickerCode, which is hard to
* photoTan) and exclude ChipTanUsb, which is not supported by application, and Flickercode, which is hard to
* implement and therefore most applications have not implemented.
*
* Console apps can only handle non visual TanMethods.
* But also graphical applications prefer non visual TanMethods as then they only have to display a text field to input
* TAN, and then image based TanMethods as then they additionally only have to display an image.
*/
val preferredTanMethods: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications,
val preferredTanMethods: List<TanMethodType>? = TanMethodType.NonVisualOrImageBased,
val tanMethodsNotSupportedByApplication: List<TanMethodType> = TanMethodType.TanMethodsNotSupportedByMostApplications,
val clientData: Any? = null,
var serializedClientData: String? = null
val tanMethodsNotSupportedByApplication: List<TanMethodType> = TanMethodType.TanMethodsNotSupportedByMostApplications
) {
override fun toString() = "$amount to $recipientName - $paymentReference"
}

View file

@ -1,6 +1,8 @@
package net.codinux.banking.client.model.request
import net.codinux.banking.client.model.*
import net.codinux.banking.client.model.Amount
import net.codinux.banking.client.model.BankAccountIdentifier
import net.codinux.banking.client.model.DefaultValues
import net.codinux.banking.client.model.config.NoArgConstructor
import net.codinux.banking.client.model.tan.TanMethodType
@ -35,36 +37,13 @@ open class TransferMoneyRequestForUser(
instantTransfer: Boolean = false,
preferredTanMethods: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications,
preferredTanMethods: List<TanMethodType>? = TanMethodType.NonVisualOrImageBased,
tanMethodsNotSupportedByApplication: List<TanMethodType> = TanMethodType.TanMethodsNotSupportedByMostApplications,
clientData: Any? = null,
serializedClientData: String? = null
) : TransferMoneyRequest(senderAccount, recipientName, recipientAccountIdentifier, recipientBankIdentifier, amount, currency, paymentReference, instantTransfer, preferredTanMethods, tanMethodsNotSupportedByApplication, clientData, serializedClientData) {
) : TransferMoneyRequest(senderAccount, recipientName, recipientAccountIdentifier, recipientBankIdentifier, amount, currency, paymentReference, instantTransfer, preferredTanMethods, tanMethodsNotSupportedByApplication) {
constructor(bankCode: String, loginName: String, password: String, request: TransferMoneyRequest)
: this(bankCode, loginName, password, request.senderAccount, request.recipientName, request.recipientAccountIdentifier, request.recipientBankIdentifier,
request.amount, request.currency, request.paymentReference, request.instantTransfer, request.preferredTanMethods, request.tanMethodsNotSupportedByApplication)
constructor(
bank: BankAccess, account: BankAccount?,
recipientName: String, recipientAccountIdentifier: String, recipientBankIdentifier: String? = null,
amount: Amount, currency: String = DefaultValues.DefaultCurrency, paymentReference: String? = null, instantTransfer: Boolean = false,
preferredTanMethods: List<TanMethodType>? = TanMethodType.TanMethodsPreferredByMostApplications
) : this(bank.domesticBankCode, bank.loginName, bank.password!!, account?.let { BankAccountIdentifier(it.identifier, it.subAccountNumber, it.iban) },
recipientName, recipientAccountIdentifier, recipientBankIdentifier, amount, currency, paymentReference, instantTransfer,
listOf(bank.selectedTanMethod.type) + (preferredTanMethods ?: emptyList()), TanMethodType.TanMethodsNotSupportedByMostApplications,
bank.clientData, bank.serializedClientData
) {
this.bank = bank
this.account = account
}
open var bank: BankAccess? = null
protected set
open var account: BankAccount? = null
protected set
override fun toString() = "$bankCode $loginName ${super.toString()}"
}

View file

@ -2,7 +2,6 @@ package net.codinux.banking.client.model.response
import net.codinux.banking.client.model.AccountTransaction
import net.codinux.banking.client.model.BankAccess
import net.codinux.banking.client.model.MessageLogEntry
import net.codinux.banking.client.model.config.JsonIgnore
import net.codinux.banking.client.model.config.NoArgConstructor

View file

@ -1,6 +1,5 @@
package net.codinux.banking.client.model.response
import net.codinux.banking.client.model.MessageLogEntry
import net.codinux.banking.client.model.config.NoArgConstructor
// TODO: may differentiate between ClientResponse, which is either Success or Error, and RestResponse, which can be Success, Error and TanRequired
@ -9,22 +8,21 @@ open class Response<T> protected constructor(
val type: ResponseType,
val data: T? = null,
val error: Error? = null,
val tanRequired: TanRequired? = null,
val messageLog: List<MessageLogEntry> = emptyList()
val tanRequired: TanRequired? = null
) {
companion object {
fun <T> success(data: T, messageLog: List<MessageLogEntry> = emptyList()): Response<T> =
Response(ResponseType.Success, data, messageLog = messageLog)
fun <T> success(data: T): Response<T> =
Response(ResponseType.Success, data)
fun <T> error(errorType: ErrorType, internalError: String? = null, errorMessagesFromBank: List<String> = emptyList(), messageLog: List<MessageLogEntry> = emptyList()): Response<T> =
Response(ResponseType.Error, null, Error(errorType, internalError, errorMessagesFromBank), messageLog = messageLog)
fun <T> error(errorType: ErrorType, internalError: String? = null, errorMessagesFromBank: List<String> = emptyList()): Response<T> =
Response(ResponseType.Error, null, Error(errorType, internalError, errorMessagesFromBank))
fun <T> tanRequired(tanRequired: TanRequired, messageLog: List<MessageLogEntry> = emptyList()): Response<T> =
Response(ResponseType.TanRequired, null, null, tanRequired, messageLog)
fun <T> tanRequired(tanRequired: TanRequired): Response<T> =
Response(ResponseType.TanRequired, null, null, tanRequired)
fun <T> bankReturnedError(errorMessagesFromBank: List<String>, messageLog: List<MessageLogEntry> = emptyList()): Response<T> =
Response.error(ErrorType.BankReturnedError, null, errorMessagesFromBank, messageLog)
fun <T> bankReturnedError(errorMessagesFromBank: List<String>): Response<T> =
Response.error(ErrorType.BankReturnedError, null, errorMessagesFromBank)
}

View file

@ -1,6 +1,5 @@
package net.codinux.banking.client.model.response
import net.codinux.banking.client.model.MessageLogEntry
import net.codinux.banking.client.model.config.NoArgConstructor
/**

View file

@ -12,7 +12,7 @@ open class Holding(
open var isin: String? = null,
open var wkn: String? = null,
open var quantity: Double? = null,
open var quantity: Int? = null,
open var currency: String? = null,
/**

View file

@ -7,13 +7,13 @@ import net.codinux.banking.client.model.config.NoArgConstructor
@NoArgConstructor
open class FlickerCode(
val challengeHHD_UC: String,
val parsedDataSet: String? = null,
val parsedDataSet: String,
val decodingError: String? = null
) {
@get:JsonIgnore
val decodingSuccessful: Boolean
get() = parsedDataSet != null
get() = decodingError == null
override fun toString(): String {

View file

@ -88,7 +88,7 @@ open class TanChallenge(
return "$selectedTanMethod $forAction: $messageToShowToUser" + when (type) {
TanChallengeType.EnterTan -> ""
TanChallengeType.Image -> ", Image: $tanImage"
TanChallengeType.FlickerCode -> ", FlickerCode: $flickerCode"
TanChallengeType.Flickercode -> ", FlickerCode: $flickerCode"
}
}

View file

@ -3,7 +3,7 @@ package net.codinux.banking.client.model.tan
enum class TanChallengeType {
Image,
FlickerCode,
Flickercode,
EnterTan
}

View file

@ -6,22 +6,22 @@ import net.codinux.banking.client.model.config.NoArgConstructor
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
@NoArgConstructor
open class TanImage(
val mimeType: String? = null,
val imageBytesBase64: String? = null,
val mimeType: String,
val imageBytesBase64: String,
val decodingError: String? = null
) {
@get:JsonIgnore
val decodingSuccessful: Boolean
get() = mimeType != null && imageBytesBase64 != null
get() = decodingError == null
override fun toString(): String {
mimeType?.let {
return mimeType
if (decodingSuccessful == false) {
return "Decoding error: $decodingError"
}
return "Decoding error: $decodingError"
return mimeType
}
}

View file

@ -3,9 +3,9 @@ package net.codinux.banking.client.model.tan
enum class TanMethodType {
EnterTan,
ChipTanManual,
ChipTanManuell,
ChipTanFlickerCode,
ChipTanFlickercode,
ChipTanUsb,
@ -33,34 +33,16 @@ enum class TanMethodType {
companion object {
val NonVisual = listOf(TanMethodType.DecoupledTan, TanMethodType.DecoupledPushTan, TanMethodType.AppTan, TanMethodType.SmsTan, TanMethodType.ChipTanManual, TanMethodType.EnterTan)
val NonVisual = listOf(TanMethodType.DecoupledTan, TanMethodType.DecoupledPushTan, TanMethodType.AppTan, TanMethodType.SmsTan, TanMethodType.ChipTanManuell, TanMethodType.EnterTan)
val NonVisualWithoutChipTanManual = NonVisual.toMutableList().apply { remove(TanMethodType.ChipTanManual) }.toList()
val ImageBased = listOf(
TanMethodType.QrCode, TanMethodType.photoTan, // non ChipTan
TanMethodType.ChipTanQrCode, TanMethodType.ChipTanPhotoTanMatrixCode // ChipTan; QrCode (e.g. used by Sparkassen) is faster than MatrixCode (e.g. used by Volksbanken)
)
val ImageBased = listOf(TanMethodType.QrCode, TanMethodType.ChipTanQrCode, TanMethodType.photoTan, TanMethodType.ChipTanPhotoTanMatrixCode)
val NonVisualOrImageBased = buildList {
addAll(NonVisualWithoutChipTanManual)
addAll(listOf(TanMethodType.DecoupledTan, TanMethodType.DecoupledPushTan, TanMethodType.AppTan, TanMethodType.SmsTan, TanMethodType.EnterTan))
addAll(ImageBased)
addAll(listOf(TanMethodType.ChipTanManual)) // this is quite inconvenient for user, so i added it as last
addAll(listOf(TanMethodType.ChipTanManuell)) // this is quite inconvenient for user, so i added it as last
}
/**
* The same as [NonVisualOrImageBased] but including [ChipTanFlickerCode] - for applications supporting it - as
* FlickerCode is still the most used ChipTan procedure.
*/
val NonVisualOrImageBasedOrFlickerCode = NonVisualOrImageBased.toMutableList().apply {
val index = this.indexOf(ChipTanQrCode)
this.add(index, ChipTanFlickerCode)
}.toList()
val TanMethodsPreferredByMostApplications = NonVisualOrImageBased
val TanMethodsNotSupportedByMostApplications = listOf(TanMethodType.ChipTanUsb)
}

View file

@ -4,6 +4,8 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
plugins {
kotlin("multiplatform")
id("maven-publish")
}
@ -32,7 +34,7 @@ kotlin {
browser {
testTask {
useKarma {
// useChromeHeadless()
useChromeHeadless()
useFirefoxHeadless()
}
}
@ -75,7 +77,7 @@ kotlin {
dependencies {
api(project(":BankingClient"))
implementation("net.codinux.banking:fints4k:1.0.0-Alpha-15")
implementation("net.codinux.banking:fints4k:1.0.0-Alpha-13")
api("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinxDateTimeVersion")
}
@ -113,6 +115,21 @@ kotlin {
ext["customArtifactId"] = "fints4k-banking-client"
//ext["customArtifactId"] = "fints4k-banking-client"
//
//apply(from = "../gradle/scripts/publish-codinux.gradle.kts")
apply(from = "../gradle/scripts/publish-codinux-repo.gradle.kts")
publishing {
repositories {
maven {
name = "codinux"
url = uri("https://maven.dankito.net/api/packages/codinux/maven")
credentials(PasswordCredentials::class.java) {
username = project.property("codinuxRegistryWriterUsername") as String
password = project.property("codinuxRegistryWriterPassword") as String
}
}
}
}

View file

@ -3,7 +3,7 @@ package net.codinux.banking.client.fints4k
import net.codinux.banking.client.BankingClientCallback
import net.codinux.banking.client.model.MessageLogEntryType
import net.codinux.banking.fints.callback.FinTsClientCallback
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.codinux.banking.fints.model.BankData
import net.codinux.banking.fints.model.EnterTanGeneratorAtcResult
import net.codinux.banking.fints.model.MessageLogEntry
@ -23,20 +23,23 @@ open class BridgeFintTsToBankingClientCallback(
if (enterTanResult.enteredTan != null) {
tanChallenge.userEnteredTan(enterTanResult.enteredTan!!)
} else if (enterTanResult.changeTanMethodTo != null) {
val fintsTanMethod = tanChallenge.bank.tanMethodsAvailableForUser.first { it.securityFunction.code == enterTanResult.changeTanMethodTo!!.identifier }
tanChallenge.userAsksToChangeTanMethod(fintsTanMethod)
tanChallenge.userAsksToChangeTanMethod(mapper.mapTanMethod(enterTanResult.changeTanMethodTo!!))
} else {
tanChallenge.userDidNotEnterTan()
}
}
}
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanMedium): EnterTanGeneratorAtcResult {
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
return EnterTanGeneratorAtcResult.userDidNotEnterAtc()
}
override fun messageLogAdded(messageLogEntry: MessageLogEntry) {
val mapped = mapper.mapMessageLogEntry(messageLogEntry)
val mapped = net.codinux.banking.client.model.MessageLogEntry(
MessageLogEntryType.valueOf(messageLogEntry.type.name),
messageLogEntry.message, messageLogEntry.messageTrace,
messageLogEntry.error, messageLogEntry.time
)
bankingClientCallback.messageLogAdded(mapped)
}

View file

@ -9,9 +9,9 @@ import net.codinux.banking.client.model.options.GetAccountDataOptions
import net.codinux.banking.client.model.request.GetAccountDataRequest
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
import net.codinux.banking.client.model.response.*
import net.codinux.banking.client.model.tan.TanMethodType
import net.codinux.banking.fints.FinTsClient
import net.codinux.banking.fints.config.FinTsClientConfiguration
import net.codinux.banking.fints.model.BankData
open class FinTs4kBankingClient(
config: FinTsClientConfiguration = FinTsClientConfiguration(),
@ -21,7 +21,7 @@ open class FinTs4kBankingClient(
constructor(callback: BankingClientCallback) : this(FinTsClientConfiguration(), callback)
constructor(options: FinTsClientOptions, callback: BankingClientCallback)
: this(FinTsClientConfiguration(net.codinux.banking.fints.config.FinTsClientOptions(options.collectMessageLog, false, options.removeSensitiveDataFromMessageLog, options.appendFinTsMessagesToLog, options.closeDialogs, options.version, options.productName)), callback)
: this(FinTsClientConfiguration(net.codinux.banking.fints.config.FinTsClientOptions(options.collectMessageLog, false, options.removeSensitiveDataFromMessageLog, options.closeDialogs, options.version, options.productName)), callback)
protected open val mapper = FinTs4kMapper()
@ -35,33 +35,35 @@ open class FinTs4kBankingClient(
return mapper.map(response, request.bankInfo)
}
override suspend fun updateAccountTransactionsAsync(bank: BankAccess, accounts: List<BankAccount>?, preferredTanMethodsIfSelectedTanMethodIsNotAvailable: List<TanMethodType>?): Response<List<GetTransactionsResponse>> {
val accountsToRequest = (accounts ?: bank.accounts).filter { it.supportsAnyFeature(BankAccountFeatures.RetrieveBalance, BankAccountFeatures.RetrieveTransactions) }
override suspend fun updateAccountTransactionsAsync(bank: BankAccess, accounts: List<BankAccount>?): Response<List<GetTransactionsResponse>> {
val accountsToRequest = (accounts ?: bank.accounts).filter { it.supportsAnyFeature(BankAccountFeatures.RetrieveBalance, BankAccountFeatures.RetrieveBalance) }
if (accountsToRequest.isNotEmpty()) {
val responses = accountsToRequest.map { account ->
val preferredTanMethods = listOf(bank.selectedTanMethod.type) + (preferredTanMethodsIfSelectedTanMethodIsNotAvailable ?: emptyList())
var finTsModel: BankData? = null
val parameter = mapper.mapToUpdateAccountTransactionsParameter(bank, account, preferredTanMethods)
val responses = accountsToRequest.map { account ->
val parameter = mapper.mapToUpdateAccountTransactionsParameter(bank, account, finTsModel)
val response = client.getAccountDataAsync(parameter)
mapper.mapCommonResponseData(bank, response) // so that basic account data doesn't have to be retrieved another time if user has multiple accounts
if (response.finTsModel != null) {
finTsModel = response.finTsModel // so that basic account data doesn't have to be retrieved another time if user has multiple accounts
}
Triple(account, parameter, response)
}
return mapper.map(bank, responses)
return mapper.map(responses)
}
return Response.error(ErrorType.NoneOfTheAccountsSupportsRetrievingData, "Keines der Konten unterstützt das Abholen der Umsätze oder des Kontostands") // TODO: translate
return Response.error(ErrorType.NoneOfTheAccountsSupportsRetrievingData, "Keiner der Konten unterstützt das Abholen der Umsätze oder des Kontostands") // TODO: translate
}
override suspend fun transferMoneyAsync(request: TransferMoneyRequestForUser): Response<TransferMoneyResponse> {
val response = client.transferMoneyAsync(mapper.mapToTransferMoneyParameter(request))
return mapper.mapTransferMoneyResponse(response, request.bank, request.account)
return mapper.mapTransferMoneyResponse(response)
}
}

View file

@ -7,8 +7,6 @@ import kotlinx.datetime.toLocalDateTime
import net.codinux.banking.client.model.*
import net.codinux.banking.client.model.AccountTransaction
import net.codinux.banking.client.model.Amount
import net.codinux.banking.client.model.MessageLogEntry
import net.codinux.banking.client.model.MessageLogEntryType
import net.codinux.banking.client.model.extensions.EuropeBerlin
import net.codinux.banking.client.model.tan.*
import net.codinux.banking.client.model.options.GetAccountDataOptions
@ -16,7 +14,6 @@ import net.codinux.banking.client.model.request.GetAccountDataRequest
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
import net.codinux.banking.client.model.response.*
import net.codinux.banking.client.model.tan.ActionRequiringTan
import net.codinux.banking.client.model.tan.AllowedTanFormat
import net.codinux.banking.client.model.tan.TanChallenge
import net.codinux.banking.client.model.tan.TanImage
import net.codinux.banking.client.model.tan.TanMethod
@ -25,15 +22,14 @@ import net.dankito.banking.client.model.BankAccountIdentifierImpl
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
import net.dankito.banking.client.model.parameter.RetrieveTransactions
import net.dankito.banking.client.model.response.ErrorCode
import net.dankito.banking.client.model.response.FinTsClientResponse
import net.codinux.banking.fints.mapper.FinTsModelMapper
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.*
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.MobilePhoneTanMedium
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMediumStatus
import net.codinux.banking.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
import net.codinux.banking.fints.model.*
import net.codinux.banking.fints.transactions.swift.model.Holding
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.MobilePhoneTanMedium
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMediumStatus
import net.dankito.banking.banklistcreator.prettifier.BankingGroupMapper
import net.dankito.banking.client.model.parameter.TransferMoneyParameter
import kotlin.io.encoding.Base64
@ -41,16 +37,6 @@ import kotlin.io.encoding.ExperimentalEncodingApi
open class FinTs4kMapper {
companion object {
val TanMethodTypesToMigrate = mapOf(
net.codinux.banking.fints.model.TanMethodType.ChipTanManuell.name to TanMethodType.ChipTanManual.name,
net.codinux.banking.fints.model.TanMethodType.ChipTanFlickercode.name to TanMethodType.ChipTanFlickerCode.name
)
val TanMethodTypesToMigrateReverse = TanMethodTypesToMigrate.map { it.value to it.key }.toMap()
}
protected val fintsModelMapper = FinTsModelMapper()
protected val bankingGroupMapper = BankingGroupMapper()
@ -72,76 +58,72 @@ open class FinTs4kMapper {
bank.serverAddress, bank.bic, bank.name
)
open fun mapToUpdateAccountTransactionsParameter(bank: BankAccess, account: BankAccount, preferredTanMethods: List<TanMethodType>? = null): GetAccountDataParameter {
open fun mapToUpdateAccountTransactionsParameter(bank: BankAccess, account: BankAccount, finTsModel: BankData?): GetAccountDataParameter {
val defaults = GetAccountDataOptions()
val accountIdentifier = BankAccountIdentifierImpl(account.identifier, account.subAccountNumber, account.iban)
val from = account.lastAccountUpdateTime?.toLocalDateTime(TimeZone.EuropeBerlin)?.date // TODO: in case lastTransactionsUpdateTime is not set, this would retrieve all transactions (and require a TAN im most cases)
val retrieveTransactions = if (from != null) RetrieveTransactions.AccordingToRetrieveFromAndTo else RetrieveTransactions.valueOf(defaults.retrieveTransactions.name)
// val preferredTanMethods = listOf(mapTanMethodType(bank.selectedTanMethod.type)) // TODO: currently we aren't saving TanMethods in database, re-enable as soon as TanMethods get saved
val preferredTanMethods = defaults.preferredTanMethods?.map { mapTanMethodType(it) }
return GetAccountDataParameter(bank.domesticBankCode, bank.loginName, bank.password!!, listOf(accountIdentifier), true,
retrieveTransactions, from,
preferredTanMethods = preferredTanMethods?.map { mapTanMethodType(it) },
preferredTanMethods = preferredTanMethods,
preferredTanMedium = bank.selectedTanMediumIdentifier,
finTsModel = bank.clientData as? BankData,
serializedFinTsModel = bank.serializedClientData
finTsModel = finTsModel
)
}
open fun mapBankAccountIdentifier(account: BankAccountIdentifier): BankAccountIdentifierImpl =
BankAccountIdentifierImpl(account.identifier, account.subAccountNumber, account.iban)
open fun mapTanMethod(method: TanMethod) = net.codinux.banking.fints.model.TanMethod( // TODO: get instance from FinTsData, don't create manually
method.displayName, Sicherheitsfunktion.entries.first { it.code == method.identifier }, mapTanMethodType(method.type), null, method.maxTanInputLength, mapAllowedTanFormat(method.allowedTanFormat)
)
protected open fun mapTanMethodType(type: TanMethodType): net.codinux.banking.fints.model.TanMethodType =
net.codinux.banking.fints.model.TanMethodType.valueOf(TanMethodTypesToMigrateReverse[type.name] ?: type.name)
net.codinux.banking.fints.model.TanMethodType.valueOf(type.name)
protected open fun mapAllowedTanFormat(allowedTanFormat: AllowedTanFormat?): net.codinux.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat =
allowedTanFormat?.let { net.codinux.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat.valueOf(it.name) } ?: net.codinux.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat.Alphanumeric
open fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse, bankInfo: BankInfo? = null): Response<GetAccountDataResponse> =
open fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse, bank: BankInfo? = null): Response<GetAccountDataResponse> =
if (response.successful && response.customerAccount != null) {
val bank = mapBank(response.customerAccount!!, bankInfo, response)
Response.success(GetAccountDataResponse(bank), mapMessageLog(response, bank))
Response.success(GetAccountDataResponse(mapBank(response.customerAccount!!, bank)))
} else {
mapError(response, mapMessageLog(response))
mapError(response)
}
open fun map(bank: BankAccess, responses: List<Triple<BankAccount, GetAccountDataParameter, net.dankito.banking.client.model.response.GetAccountDataResponse>>): Response<List<GetTransactionsResponse>> {
open fun map(responses: List<Triple<BankAccount, GetAccountDataParameter, net.dankito.banking.client.model.response.GetAccountDataResponse>>): Response<List<GetTransactionsResponse>> {
val type = if (responses.all { it.third.successful }) ResponseType.Success else ResponseType.Error
// TODO: update BankAccess and BankAccount objects according to retrieved data
val mappedResponses = responses.map { (account, param, getAccountDataResponse) ->
val fintsBank = getAccountDataResponse.customerAccount
val finTsBankAccount = fintsBank?.accounts?.firstOrNull { it.identifier == account.identifier && it.subAccountNumber == account.subAccountNumber }
val bank = getAccountDataResponse.customerAccount
val finTsBankAccount = bank?.accounts?.firstOrNull { it.identifier == account.identifier && it.subAccountNumber == account.subAccountNumber }
val messageLog = mapMessageLog(getAccountDataResponse, bank, account)
if (getAccountDataResponse.successful && fintsBank != null && finTsBankAccount != null) {
if (getAccountDataResponse.successful && bank != null && finTsBankAccount != null) {
if (finTsBankAccount.lastAccountUpdateTime != null) {
account.lastAccountUpdateTime = finTsBankAccount.lastAccountUpdateTime
}
if (account.retrievedTransactionsFrom == null || (finTsBankAccount.retrievedTransactionsFrom != null
&& finTsBankAccount.retrievedTransactionsFrom!! < account.retrievedTransactionsFrom!!)) {
&& account.retrievedTransactionsFrom!! < finTsBankAccount.retrievedTransactionsFrom!!)) {
account.retrievedTransactionsFrom = finTsBankAccount.retrievedTransactionsFrom
}
val balance = mapMoney(finTsBankAccount.balance)
account.balance = balance
mapCommonResponseData(bank, getAccountDataResponse)
Response.success(GetTransactionsResponse(account, balance, mapBookedTransactions(finTsBankAccount), emptyList(),
Response.success(GetTransactionsResponse(account, mapMoney(finTsBankAccount.balance), mapBookedTransactions(finTsBankAccount), emptyList(),
mapHoldings(finTsBankAccount.statementOfHoldings, finTsBankAccount.currency, finTsBankAccount.lastAccountUpdateTime),
finTsBankAccount.lastAccountUpdateTime ?: Clock.System.now(), param.retrieveTransactionsFrom, param.retrieveTransactionsTo),
messageLog)
finTsBankAccount.lastAccountUpdateTime ?: Clock.System.now(), param.retrieveTransactionsFrom, param.retrieveTransactionsTo))
} else {
mapError(getAccountDataResponse, messageLog)
mapError(getAccountDataResponse)
}
}
val data = mappedResponses.filter { it.type == ResponseType.Success }.mapNotNull { it.data }
return (object : Response<List<GetTransactionsResponse>>(type, data, mappedResponses.firstNotNullOfOrNull { it.error }, messageLog = mappedResponses.flatMap { it.messageLog }) { })
return (object : Response<List<GetTransactionsResponse>>(type, data, mappedResponses.firstNotNullOfOrNull { it.error }) { })
}
@ -156,7 +138,7 @@ open class FinTs4kMapper {
)
protected open fun mapBank(bank: net.dankito.banking.client.model.CustomerAccount, info: BankInfo? = null, response: FinTsClientResponse? = null) = BankAccess(
protected open fun mapBank(bank: net.dankito.banking.client.model.CustomerAccount, info: BankInfo? = null) = BankAccess(
bank.bankCode, bank.loginName, bank.password,
info?.name ?: bank.bankName, bank.bic, bank.customerName, bank.userId,
bank.accounts.map { mapAccount(it) }.sortedBy { it.type }
@ -168,9 +150,7 @@ open class FinTs4kMapper {
info?.bankingGroup ?: getBankingGroup(bank.bankName, bank.bic),
bank.finTsServerAddress,
"de"
).apply {
response?.let { mapCommonResponseData(this, it) }
}
)
protected open fun getBankingGroup(bankName: String, bic: String): BankingGroup? =
bankingGroupMapper.getBankingGroup(bankName, bic)
@ -337,7 +317,7 @@ open class FinTs4kMapper {
protected open fun mapTanChallengeType(challenge: net.codinux.banking.fints.model.TanChallenge): TanChallengeType = when {
challenge is ImageTanChallenge -> TanChallengeType.Image
challenge is FlickerCodeTanChallenge -> TanChallengeType.FlickerCode
challenge is FlickerCodeTanChallenge -> TanChallengeType.Flickercode
else -> TanChallengeType.EnterTan
}
@ -349,7 +329,7 @@ open class FinTs4kMapper {
)
protected open fun mapTanMethodType(type: net.codinux.banking.fints.model.TanMethodType): TanMethodType =
TanMethodType.valueOf(TanMethodTypesToMigrate[type.name] ?: type.name)
TanMethodType.valueOf(type.name)
protected open fun mapAllowedTanFormat(allowedTanFormat: net.codinux.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat?): AllowedTanFormat =
allowedTanFormat?.let { AllowedTanFormat.valueOf(it.name) } ?: AllowedTanFormat.Alphanumeric
@ -358,18 +338,14 @@ open class FinTs4kMapper {
TanImage(image.mimeType, mapToBase64(image.imageBytes), mapException(image.decodingError))
@OptIn(ExperimentalEncodingApi::class)
protected open fun mapToBase64(bytes: ByteArray?): String? {
if (bytes == null || bytes.isEmpty()) {
return null
}
protected open fun mapToBase64(bytes: ByteArray): String {
return Base64.Default.encode(bytes)
}
protected open fun mapTanMedium(tanMedium: TanMedium) = net.codinux.banking.client.model.tan.TanMedium(
mapTanMediumType(tanMedium), tanMedium.mediumName, mapTanMediumStatus(tanMedium.status),
tanMedium.tanGenerator?.let { mapTanGeneratorTanMedium(it) },
tanMedium.mobilePhone?.let { mapMobilePhoneTanMedium(it) }
(tanMedium as? TanGeneratorTanMedium)?.let { mapTanGeneratorTanMedium(it) },
(tanMedium as? MobilePhoneTanMedium)?.let { mapMobilePhoneTanMedium(it) }
)
protected open fun mapTanMediumStatus(status: TanMediumStatus): net.codinux.banking.client.model.tan.TanMediumStatus = when (status) {
@ -388,9 +364,9 @@ open class FinTs4kMapper {
tanMedium.validFrom, tanMedium.validTo
)
protected open fun mapTanMediumType(tanMedium: TanMedium): TanMediumType = when (tanMedium.mediumClass) {
TanMediumKlasse.MobiltelefonMitMobileTan -> TanMediumType.MobilePhone
TanMediumKlasse.TanGenerator -> TanMediumType.TanGenerator
protected open fun mapTanMediumType(tanMedium: TanMedium): TanMediumType = when {
tanMedium is MobilePhoneTanMedium -> TanMediumType.MobilePhone
tanMedium is TanGeneratorTanMedium -> TanMediumType.TanGenerator
else -> TanMediumType.Generic
}
@ -405,74 +381,29 @@ open class FinTs4kMapper {
request.recipientName, request.recipientAccountIdentifier, request.recipientBankIdentifier,
mapToMoney(request.amount, request.currency), request.paymentReference, request.instantTransfer,
request.preferredTanMethods?.map { mapTanMethodType(it) },
request.tanMethodsNotSupportedByApplication.map { mapTanMethodType(it) },
finTsModel = request.clientData as? BankData,
serializedFinTsModel = request.serializedClientData
request.tanMethodsNotSupportedByApplication.map { mapTanMethodType(it) }
)
open fun mapTransferMoneyResponse(response: net.dankito.banking.client.model.response.TransferMoneyResponse, bank: BankAccess? = null, account: BankAccount? = null): Response<TransferMoneyResponse> =
open fun mapTransferMoneyResponse(response: net.dankito.banking.client.model.response.TransferMoneyResponse): Response<TransferMoneyResponse> =
if (response.successful) {
bank?.let { mapCommonResponseData(it, response) }
Response.success(TransferMoneyResponse(), mapMessageLog(response, bank, account))
Response.success(TransferMoneyResponse())
} else {
mapError(response, mapMessageLog(response, bank, account))
mapError(response)
}
open fun mapToMoney(amount: Amount, currency: String): Money = Money(amount.toString(), currency)
open fun mapMessageLog(response: FinTsClientResponse, bank: BankAccess? = null, account: BankAccount? = null) =
mapMessageLog(response.messageLog, bank, account)
open fun mapMessageLog(messageLog: List<net.codinux.banking.fints.model.MessageLogEntry>, bank: BankAccess? = null, account: BankAccount? = null) =
messageLog.map { mapMessageLogEntry(it, bank, account) }
open fun mapMessageLogEntry(messageLogEntry: net.codinux.banking.fints.model.MessageLogEntry, bank: BankAccess? = null, account: BankAccount? = null): MessageLogEntry {
// TODO: may map messageLogEntry.context.BankData to BankAccess
val context = messageLogEntry.context
val fintsAccount = context.account
val effectiveAccount = account ?: bank?.accounts?.firstOrNull { it.identifier == fintsAccount?.accountIdentifier && it.subAccountNumber == fintsAccount.subAccountAttribute }
val messageNumberString = "${context.jobNumber.toString().padStart(2, '0')}_${context.dialogNumber.toString().padStart(2, '0')}_${context.messageNumber.toString().padStart(2, '0')}"
return MessageLogEntry(
MessageLogEntryType.valueOf(messageLogEntry.type.name),
messageLogEntry.message, messageLogEntry.messageWithoutSensitiveData,
messageLogEntry.error, messageLogEntry.time,
messageNumberString,
messageNumberString.replace("_", "").toIntOrNull(),
context.jobType.toString(),
context.messageType.toString(),
bank,
effectiveAccount
)
}
protected open fun <T> mapError(response: FinTsClientResponse, messageLog: List<MessageLogEntry>): Response<T> =
if (response.error != null) {
protected open fun <T> mapError(response: net.dankito.banking.client.model.response.FinTsClientResponse): Response<T> {
return if (response.error != null) {
Response.error(ErrorType.valueOf(response.error!!.name), if (response.error == ErrorCode.BankReturnedError) null else response.errorMessage,
if (response.error == ErrorCode.BankReturnedError && response.errorMessage !== null) listOf(response.errorMessage!!) else emptyList(), messageLog)
if (response.error == ErrorCode.BankReturnedError && response.errorMessage !== null) listOf(response.errorMessage!!) else emptyList())
} else {
Response.error(ErrorType.UnknownError, response.errorMessage, messageLog = messageLog)
Response.error(ErrorType.UnknownError, response.errorMessage)
}
}
protected open fun mapException(exception: Exception?): String? =
exception?.stackTraceToString()
open fun mapCommonResponseData(bank: BankAccess, response: FinTsClientResponse) {
response.finTsModel?.let {
bank.clientData = it
}
response.serializedFinTsModel?.let {
bank.serializedClientData = it
}
}
}

View file

@ -24,8 +24,6 @@ data class FinTsClientOptions(
*/
val removeSensitiveDataFromMessageLog: Boolean = true,
val appendFinTsMessagesToLog: Boolean = false,
val closeDialogs: Boolean = true,
val version: String = "1.0.0", // TODO: get version dynamically

View file

@ -3,7 +3,6 @@ package net.codinux.banking.client.fints4k
import kotlinx.coroutines.test.runTest
import net.codinux.banking.client.SimpleBankingClientCallback
import net.codinux.banking.client.model.response.ResponseType
import net.codinux.banking.client.model.tan.EnterTanResult
import kotlin.test.*
@Ignore
@ -19,10 +18,7 @@ class FinTs4kBankingClientTest {
private val underTest = FinTs4kBankingClientForUser(bankCode, loginName, password, SimpleBankingClientCallback { tanChallenge, callback ->
println("WARN: TAN is required to execute test: ${tanChallenge.messageToShowToUser}")
val enteredTan: String? = null
callback(EnterTanResult(enteredTan))
})

View file

@ -12,7 +12,7 @@ class FinTs4kMapperTest {
@Test
fun getTotalBalance_TotalBalanceIsNull_CalculateByQuantityAndMarketValue() {
val holding = Holding("", null, null, null, 4.0, null, null, null, fints4kAmount("13.33"))
val holding = Holding("", null, null, null, 4, null, null, null, fints4kAmount("13.33"))
val result = underTest.getTotalBalance(holding)
@ -21,7 +21,7 @@ class FinTs4kMapperTest {
@Test
fun getTotalCostPrice_TotalCostPriceIsNull_CalculateByQuantityAndAverageCostPrice() {
val holding = Holding("", null, null, null, 47.0, fints4kAmount("16.828"), null)
val holding = Holding("", null, null, null, 47, fints4kAmount("16.828"), null)
val result = underTest.getTotalCostPrice(holding)

View file

@ -1,5 +1,4 @@
# Banking Client
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.codinux.banking.client/banking-client/badge.svg?style=plastic)](https://maven-badges.herokuapp.com/maven-central/net.codinux.banking.client/banking-client)
Library to abstract over different banking client implementations like [fints4k](https://git.dankito.net/codinux/fints4k).
@ -12,8 +11,13 @@ not each project has the implement to model again.
### Gradle:
```
plugins {
kotlin("jvm") version "2.0.10" // or kotlin("multiplatform"), depending on your requirements
}
repositories {
// other repositories like mavenCentral(), ...
mavenCentral()
maven {
setUrl("https://maven.dankito.net/api/packages/codinux/maven")
}
@ -21,7 +25,7 @@ repositories {
dependencies {
implementation("net.codinux.banking.client:fints4k-banking-client:0.7.2")
implementation("net.codinux.banking.client:fints4k-banking-client:0.6.0")
}
```
@ -164,21 +168,3 @@ fun updateAccountTransactions() {
}
}
```
## Logging
BankingClient and fints4k both use [klf](https://github.com/codinux-gmbh/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:
```kotlin
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.

View file

@ -12,7 +12,7 @@ buildscript {
allprojects {
group = "net.codinux.banking.client"
version = "0.7.2"
version = "0.6.0"
repositories {
mavenCentral()
@ -23,7 +23,7 @@ allprojects {
}
ext["sourceCodeRepositoryBaseUrl"] = "git.dankito.net/codinux/BankingClient"
ext["sourceCodeRepositoryBaseUrl"] = "github.com/codinux/BankingClient"
ext["projectDescription"] = "Model and base definitions for Banking Client, a common abstraction for different implements of banking libraries"
}

View file

@ -1,13 +1,10 @@
kotlin.code.style=official
#org.gradle.parallel=true
kotlinVersion=1.9.25
kotlinVersion=2.0.10
kotlinxDateTimeVersion=0.5.0
jsJodaTimeZoneVersion=2.3.0
# Coroutines 1.9 (currently RC) requires Kotlin 2.0
coroutinesVersion=1.8.1
# 0.3.10 uses Kotlin 2.0.0

@ -1 +1 @@
Subproject commit 88f1b01167e6a34b5b91f8797845bca0b7e4d3ab
Subproject commit bdf8b14738c06016a48e1fc9781ad4d999e1219f

File diff suppressed because it is too large Load diff