Compare commits

...

6 Commits

12 changed files with 107 additions and 46 deletions

View File

@ -53,6 +53,7 @@ kotlin {
linuxX64()
mingwX64()
iosX64()
iosArm64()
iosSimulatorArm64()
macosX64()

View File

@ -1,5 +1,6 @@
package net.codinux.banking.client
import net.codinux.banking.client.model.MessageLogEntry
import net.codinux.banking.client.model.tan.EnterTanResult
import net.codinux.banking.client.model.tan.TanChallenge
@ -7,4 +8,11 @@ interface BankingClientCallback {
fun enterTan(tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit)
/**
* An optional method, for sure not available for all client implementations (currently only for FinTs4kBankingClient).
*
* Gets fired when a FinTS message get sent to bank server, a FinTS message is received from bank server or an error occurred.
*/
fun messageLogAdded(messageLogEntry: MessageLogEntry)
}

View File

@ -1,9 +1,11 @@
package net.codinux.banking.client
import net.codinux.banking.client.model.MessageLogEntry
import net.codinux.banking.client.model.tan.EnterTanResult
import net.codinux.banking.client.model.tan.TanChallenge
open class SimpleBankingClientCallback(
protected val messageLogAdded: ((MessageLogEntry) -> Unit)? = null,
protected val enterTan: ((tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) -> Unit)? = null
) : BankingClientCallback {
@ -15,4 +17,8 @@ open class SimpleBankingClientCallback(
}
}
override fun messageLogAdded(messageLogEntry: MessageLogEntry) {
messageLogAdded?.invoke(messageLogEntry)
}
}

View File

@ -63,6 +63,7 @@ kotlin {
linuxX64()
mingwX64()
iosX64()
iosArm64()
iosSimulatorArm64()
macosX64()

View File

@ -0,0 +1,12 @@
package net.codinux.banking.client.model
import net.codinux.banking.client.model.config.NoArgConstructor
@NoArgConstructor
open class BankAccountIdentifier(
val identifier: String,
val subAccountNumber: String?,
val iban: String?
) {
override fun toString() = "$identifier, $iban"
}

View File

@ -1,6 +1,7 @@
package net.codinux.banking.client.model.options
import kotlinx.datetime.LocalDate
import net.codinux.banking.client.model.BankAccountIdentifier
import net.codinux.banking.client.model.config.NoArgConstructor
@NoArgConstructor
@ -9,7 +10,9 @@ open class GetAccountDataOptions(
val retrieveTransactions: RetrieveTransactions = RetrieveTransactions.OfLast90Days,
val retrieveTransactionsFrom: LocalDate? = null,
val retrieveTransactionsTo: LocalDate? = null,
val abortIfTanIsRequired: Boolean = false
val abortIfTanIsRequired: Boolean = false,
// account(s) may should get excluded from data retrieval, so add option to set for which accounts data should be retrieved
val accounts: List<BankAccountIdentifier> = emptyList()
) {
override fun toString(): String {
return "retrieveBalance=$retrieveBalance, retrieveTransactions=$retrieveTransactions, abortIfTanIsRequired=$abortIfTanIsRequired"

View File

@ -9,11 +9,6 @@ plugins {
}
repositories {
mavenLocal()
}
kotlin {
jvmToolchain(8)
@ -54,12 +49,13 @@ kotlin {
}
}
// wasmJs()
// wasmJs() // ktor is not available for wasmJs yet
linuxX64()
mingwX64()
iosX64()
iosArm64()
iosSimulatorArm64()
macosX64()
@ -81,7 +77,7 @@ kotlin {
dependencies {
api(project(":BankingClient"))
api("net.codinux.banking:fints4k:1.0.0-Alpha-11")
api("net.codinux.banking:fints4k:1.0.0-Alpha-12-SNAPSHOT")
api("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinxDateTimeVersion")
}

View File

@ -1,11 +1,13 @@
package net.codinux.banking.client.fints4k
import net.codinux.banking.client.BankingClientCallback
import net.dankito.banking.fints.callback.FinTsClientCallback
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.banking.fints.model.BankData
import net.dankito.banking.fints.model.EnterTanGeneratorAtcResult
import net.dankito.banking.fints.model.TanMethod
import net.codinux.banking.client.model.MessageLogEntryType
import net.codinux.banking.fints.callback.FinTsClientCallback
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
import net.codinux.banking.fints.model.TanMethod
open class BridgeFintTsToBankingClientCallback(
protected val bankingClientCallback: BankingClientCallback,
@ -16,7 +18,7 @@ open class BridgeFintTsToBankingClientCallback(
return suggestedTanMethod
}
override suspend fun enterTan(tanChallenge: net.dankito.banking.fints.model.TanChallenge) {
override suspend fun enterTan(tanChallenge: net.codinux.banking.fints.model.TanChallenge) {
bankingClientCallback.enterTan(mapper.mapTanChallenge(tanChallenge)) { enterTanResult ->
if (enterTanResult.enteredTan != null) {
tanChallenge.userEnteredTan(enterTanResult.enteredTan!!)
@ -29,4 +31,14 @@ open class BridgeFintTsToBankingClientCallback(
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
return EnterTanGeneratorAtcResult.userDidNotEnterAtc()
}
override fun messageLogAdded(messageLogEntry: 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

@ -6,15 +6,20 @@ import net.codinux.banking.client.model.options.GetAccountDataOptions
import net.codinux.banking.client.model.request.GetAccountDataRequest
import net.codinux.banking.client.model.response.GetAccountDataResponse
import net.codinux.banking.client.model.response.Response
import net.dankito.banking.fints.FinTsClient
import net.codinux.banking.fints.FinTsClient
import net.codinux.banking.fints.config.FinTsClientConfiguration
open class FinTs4kBankingClient(
config: FinTsClientConfiguration = FinTsClientConfiguration(),
callback: BankingClientCallback
) : BankingClient {
private val mapper = FinTs4kMapper()
constructor(callback: BankingClientCallback) : this(FinTsClientConfiguration(), callback)
private val client = FinTsClient(BridgeFintTsToBankingClientCallback(callback, mapper))
protected val mapper = FinTs4kMapper()
protected val client = FinTsClient(config, BridgeFintTsToBankingClientCallback(callback, mapper))
override suspend fun getAccountDataAsync(request: GetAccountDataRequest): Response<GetAccountDataResponse> {

View File

@ -3,11 +3,15 @@ package net.codinux.banking.client.fints4k
import net.codinux.banking.client.BankingClientCallback
import net.codinux.banking.client.BankingClientForCustomerBase
import net.codinux.banking.client.model.AccountCredentials
import net.codinux.banking.fints.config.FinTsClientConfiguration
open class FinTs4kBankingClientForCustomer(credentials: AccountCredentials, callback: BankingClientCallback)
: BankingClientForCustomerBase(credentials, FinTs4kBankingClient(callback)) {
open class FinTs4kBankingClientForCustomer(credentials: AccountCredentials, config: FinTsClientConfiguration = FinTsClientConfiguration(), callback: BankingClientCallback)
: BankingClientForCustomerBase(credentials, FinTs4kBankingClient(config, callback)) {
constructor(bankCode: String, loginName: String, password: String, callback: BankingClientCallback)
: this(AccountCredentials(bankCode, loginName, password), callback)
: this(bankCode, loginName, password, FinTsClientConfiguration(), callback)
constructor(bankCode: String, loginName: String, password: String, config: FinTsClientConfiguration = FinTsClientConfiguration(), callback: BankingClientCallback)
: this(AccountCredentials(bankCode, loginName, password), config, callback)
}

View File

@ -15,17 +15,17 @@ 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.fints.mapper.FinTsModelMapper
import net.dankito.banking.fints.model.*
import net.codinux.banking.fints.mapper.FinTsModelMapper
import net.codinux.banking.fints.model.*
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
open class FinTs4kMapper {
private val fintsModelMapper = FinTsModelMapper()
protected val fintsModelMapper = FinTsModelMapper()
fun mapToGetAccountDataParameter(credentials: AccountCredentials, options: GetAccountDataOptions) = GetAccountDataParameter(
open fun mapToGetAccountDataParameter(credentials: AccountCredentials, options: GetAccountDataOptions) = GetAccountDataParameter(
credentials.bankCode, credentials.loginName, credentials.password,
options.accounts.map { BankAccountIdentifierImpl(it.identifier, it.subAccountNumber, it.iban) },
options.retrieveBalance,
@ -34,7 +34,7 @@ open class FinTs4kMapper {
)
fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<GetAccountDataResponse> {
open fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<GetAccountDataResponse> {
return if (response.successful && response.customerAccount != null) {
Response.success(GetAccountDataResponse(mapCustomer(response.customerAccount!!)))
} else {
@ -43,25 +43,25 @@ open class FinTs4kMapper {
}
fun mapToCustomerAccountViewInfo(bank: BankData): CustomerAccountViewInfo = CustomerAccountViewInfo(
open fun mapToCustomerAccountViewInfo(bank: BankData): CustomerAccountViewInfo = CustomerAccountViewInfo(
bank.bankCode, bank.customerId, bank.bankName
)
fun mapToBankAccountViewInfo(account: AccountData): BankAccountViewInfo = BankAccountViewInfo(
open fun mapToBankAccountViewInfo(account: AccountData): BankAccountViewInfo = BankAccountViewInfo(
account.accountIdentifier, account.subAccountAttribute,
mapAccountType(fintsModelMapper.map(account.accountType)),
account.iban, account.productName
)
private fun mapCustomer(customer: net.dankito.banking.client.model.CustomerAccount): CustomerAccount = CustomerAccount(
protected open fun mapCustomer(customer: net.dankito.banking.client.model.CustomerAccount): CustomerAccount = CustomerAccount(
customer.bankCode, customer.loginName, customer.password,
customer.bankName, customer.bic, customer.customerName, customer.userId,
customer.accounts.map { mapAccount(it) }
)
private fun mapAccount(account: net.dankito.banking.client.model.BankAccount): BankAccount = BankAccount(
protected open fun mapAccount(account: net.dankito.banking.client.model.BankAccount): BankAccount = BankAccount(
account.identifier, account.accountHolderName, mapAccountType(account.type), account.iban, account.subAccountNumber,
account.productName, account.currency, account.accountLimit, account.isAccountTypeSupportedByApplication,
mapFeatures(account),
@ -71,10 +71,10 @@ open class FinTs4kMapper {
bookedTransactions = account.bookedTransactions.map { mapTransaction(it) }.toMutableList()
)
private fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
protected open fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
BankAccountType.valueOf(type.name)
private fun mapFeatures(account: net.dankito.banking.client.model.BankAccount): Set<BankAccountFeatures> = buildSet {
protected open fun mapFeatures(account: net.dankito.banking.client.model.BankAccount): Set<BankAccountFeatures> = buildSet {
if (account.supportsRetrievingBalance) {
add(BankAccountFeatures.RetrieveBalance)
}
@ -90,22 +90,34 @@ open class FinTs4kMapper {
}
private fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
protected open fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.unparsedReference,
transaction.bookingDate, transaction.valueDate,
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId,
transaction.bookingText, null,
transaction.statementNumber, transaction.sequenceNumber,
mapNullableAmount(transaction.openingBalance), mapNullableAmount(transaction.closingBalance),
// TODO: map other properties
transaction.endToEndReference, transaction.customerReference, transaction.mandateReference,
transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
transaction.compensationAmount, transaction.originalAmount,
transaction.sepaReference,
transaction.deviantOriginator, transaction.deviantRecipient,
transaction.referenceWithNoSpecialType, transaction.primaNotaNumber, transaction.textKeySupplement,
transaction.currencyType, transaction.bookingKey,
transaction.referenceForTheAccountOwner, transaction.referenceOfTheAccountServicingInstitution,
transaction.supplementaryDetails,
transaction.transactionReferenceNumber, transaction.relatedReferenceNumber
)
private fun mapNullableAmount(amount: Money?) = amount?.let { mapAmount(it) }
protected open fun mapNullableAmount(amount: Money?) = amount?.let { mapAmount(it) }
private fun mapAmount(amount: Money) = Amount.fromString(amount.amount.string.replace(',', '.'))
protected open fun mapAmount(amount: Money) = Amount.fromString(amount.amount.string.replace(',', '.'))
fun mapTanChallenge(challenge: net.dankito.banking.fints.model.TanChallenge): TanChallenge {
open fun mapTanChallenge(challenge: net.codinux.banking.fints.model.TanChallenge): TanChallenge {
val type = mapTanChallengeType(challenge)
val action = mapActionRequiringTan(challenge.forAction)
val tanMethod = mapTanMethod(challenge.tanMethod)
@ -118,38 +130,38 @@ open class FinTs4kMapper {
return TanChallenge(type, action, challenge.messageToShowToUser, tanMethod, tanImage, flickerCode, customer, account)
}
private fun mapTanChallengeType(challenge: net.dankito.banking.fints.model.TanChallenge): TanChallengeType = when {
protected open fun mapTanChallengeType(challenge: net.codinux.banking.fints.model.TanChallenge): TanChallengeType = when {
challenge is ImageTanChallenge -> TanChallengeType.Image
challenge is FlickerCodeTanChallenge -> TanChallengeType.Flickercode
else -> TanChallengeType.EnterTan
}
private fun mapActionRequiringTan(action: net.dankito.banking.fints.model.ActionRequiringTan): ActionRequiringTan =
protected open fun mapActionRequiringTan(action: net.codinux.banking.fints.model.ActionRequiringTan): ActionRequiringTan =
ActionRequiringTan.valueOf(action.name)
private fun mapTanMethod(method: net.dankito.banking.fints.model.TanMethod): TanMethod = TanMethod(
protected open fun mapTanMethod(method: net.codinux.banking.fints.model.TanMethod): TanMethod = TanMethod(
method.displayName, mapTanMethodType(method.type), method.securityFunction.code, method.maxTanInputLength, mapAllowedTanFormat(method.allowedTanFormat)
)
private fun mapTanMethodType(type: net.dankito.banking.fints.model.TanMethodType): TanMethodType =
protected open fun mapTanMethodType(type: net.codinux.banking.fints.model.TanMethodType): TanMethodType =
TanMethodType.valueOf(type.name)
private fun mapAllowedTanFormat(allowedTanFormat: net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat?): AllowedTanFormat =
protected open fun mapAllowedTanFormat(allowedTanFormat: net.codinux.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat?): AllowedTanFormat =
allowedTanFormat?.let { AllowedTanFormat.valueOf(it.name) } ?: AllowedTanFormat.Alphanumeric
private fun mapTanImage(image: net.dankito.banking.fints.tan.TanImage): TanImage =
protected open fun mapTanImage(image: net.codinux.banking.fints.tan.TanImage): TanImage =
TanImage(image.mimeType, mapToBase64(image.imageBytes), mapException(image.decodingError))
@OptIn(ExperimentalEncodingApi::class)
private fun mapToBase64(bytes: ByteArray): String {
protected open fun mapToBase64(bytes: ByteArray): String {
return Base64.Default.encode(bytes)
}
private fun mapFlickerCode(flickerCode: net.dankito.banking.fints.tan.FlickerCode): FlickerCode =
protected open fun mapFlickerCode(flickerCode: net.codinux.banking.fints.tan.FlickerCode): FlickerCode =
FlickerCode(flickerCode.challengeHHD_UC, flickerCode.parsedDataSet, mapException(flickerCode.decodingError))
private fun <T> mapError(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<T> {
protected open fun <T> mapError(response: net.dankito.banking.client.model.response.GetAccountDataResponse): 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())
@ -158,7 +170,7 @@ open class FinTs4kMapper {
}
}
private fun mapException(exception: Exception?): String? =
protected open fun mapException(exception: Exception?): String? =
exception?.stackTraceToString()
}

View File

@ -16,6 +16,7 @@ allprojects {
repositories {
mavenCentral()
mavenLocal()
maven {
setUrl("https://maven.dankito.net/api/packages/codinux/maven")
}