Added FinTs4kBankingClient

This commit is contained in:
dankito 2024-08-17 03:29:17 +02:00
parent c581a9ebb1
commit 3e4dfee615
7 changed files with 310 additions and 2 deletions

View File

@ -0,0 +1,125 @@
@file:OptIn(ExperimentalWasmDsl::class)
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
plugins {
kotlin("multiplatform")
}
repositories {
mavenLocal()
// TODO: remove again as soon as kmp-web-client is released
maven {
setUrl("https://oss.sonatype.org/content/repositories/snapshots/")
}
}
kotlin {
jvmToolchain(8)
jvm {
withJava()
testRuns["test"].executionTask.configure {
useJUnitPlatform()
testLogging {
showExceptions = true
showStandardStreams = true
events("passed", "skipped", "failed")
// exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
}
}
// js {
// moduleName = "fints4k-banking-client"
// binaries.executable()
//
// browser {
// testTask {
// useKarma {
// useChromeHeadless()
// useFirefoxHeadless()
// }
// }
// }
//
// nodejs {
// testTask {
// useMocha {
// timeout = "20s" // Mocha times out after 2 s, which is too short for bufferExceeded() test
// }
// }
// }
// }
//
// wasmJs()
//
//
// linuxX64()
// mingwX64()
//
// iosArm64()
// iosSimulatorArm64()
// macosX64()
// macosArm64()
// watchosArm64()
// watchosSimulatorArm64()
// tvosArm64()
// tvosSimulatorArm64()
applyDefaultHierarchyTemplate()
val coroutinesVersion: String by project
sourceSets {
commonMain {
dependencies {
api(project(":BankingClient"))
api("net.codinux.banking:fints4k:1.0.0-Alpha-10")
// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
}
}
commonTest {
dependencies {
implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
}
}
jvmMain {
dependencies {
// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:$coroutinesVersion")
}
}
jvmTest {
dependencies {
implementation(kotlin("test"))
}
}
// jsMain {
// dependencies {
//// implementation(npm("@js-joda/timezone", "2.3.0"))
// }
// }
// jsTest { }
//
// nativeMain { }
// nativeTest { }
}
}
ext["customArtifactId"] = "fints4k-banking-client"
apply(from = "../gradle/scripts/publish-codinux.gradle.kts")

View File

@ -0,0 +1,26 @@
package net.codinux.banking.client.fints4k
import net.codinux.banking.client.BankingClient
import net.codinux.banking.client.model.AccountCredentials
import net.codinux.banking.client.model.options.GetAccountDataOptions
import net.codinux.banking.client.model.response.GetAccountDataResponse
import net.codinux.banking.client.model.response.Response
import net.dankito.banking.fints.FinTsClient
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
class FinTs4kBankingClient : BankingClient {
private val mapper = FinTs4kMapper()
private val client = FinTsClient(SimpleFinTsClientCallback { tanChallenge ->
// callback.enterTan()
})
override suspend fun getAccountDataAsync(credentials: AccountCredentials, options: GetAccountDataOptions): Response<GetAccountDataResponse> {
val response = client.getAccountDataAsync(mapper.mapToGetAccountDataParameter(credentials, options))
return mapper.map(response)
}
}

View File

@ -0,0 +1,12 @@
package net.codinux.banking.client.fints4k
import net.codinux.banking.client.BankingClientForCustomerBase
import net.codinux.banking.client.model.AccountCredentials
class FinTs4kBankingClientForCustomer(credentials: AccountCredentials)
: BankingClientForCustomerBase(credentials, FinTs4kBankingClient()) {
constructor(bankCode: String, loginName: String, password: String)
: this(AccountCredentials(bankCode, loginName, password))
}

View File

@ -0,0 +1,106 @@
package net.codinux.banking.client.fints4k
import net.codinux.banking.client.model.*
import net.codinux.banking.client.model.options.GetAccountDataOptions
import net.codinux.banking.client.model.response.*
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.model.Money
class FinTs4kMapper {
fun mapToGetAccountDataParameter(credentials: AccountCredentials, options: GetAccountDataOptions) = GetAccountDataParameter(
credentials.bankCode, credentials.loginName, credentials.password,
null,
options.retrieveBalance,
RetrieveTransactions.valueOf(options.retrieveTransactions.name), options.retrieveTransactionsFrom, options.retrieveTransactionsTo,
abortIfTanIsRequired = options.abortIfTanIsRequired
)
fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<GetAccountDataResponse> {
return if (response.successful && response.customerAccount != null) {
Response(ResponseType.Success, mapCustomer(response.customerAccount!!))
} else {
mapError(response)
}
}
private fun mapCustomer(customer: net.dankito.banking.client.model.CustomerAccount): GetAccountDataResponse {
val mapped = CustomerAccount(
customer.bankCode, customer.loginName, customer.password,
customer.bankName, customer.bic, customer.customerName, customer.userId,
customer.accounts.map { mapAccount(it) }
)
return GetAccountDataResponse(mapped)
}
private 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, isAccountTypeSupported(account),
mapFeatures(account),
mapAmount(account.balance), account.retrievedTransactionsFrom, account.retrievedTransactionsTo,
// TODO: map haveAllTransactionsBeenRetrieved and countDaysForWhichTransactionsAreKept
bookedTransactions = account.bookedTransactions.map { mapTransaction(it) }.toMutableList()
)
private 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 {
if (account.supportsRetrievingBalance) {
add(BankAccountFeatures.RetrieveBalance)
}
if (account.supportsRetrievingTransactions) {
add(BankAccountFeatures.RetrieveTransactions)
}
if (account.supportsTransferringMoney) {
add(BankAccountFeatures.TransferMoney)
}
if (account.supportsInstantPayment) {
add(BankAccountFeatures.InstantPayment)
}
}
private fun isAccountTypeSupported(account: net.dankito.banking.client.model.BankAccount): Boolean {
// TODO:
// open val isAccountTypeSupportedByApplication: Boolean
// get() = FinTsClient.SupportedAccountTypes.contains(accountType)
// || allowedJobNames.contains(CustomerSegmentId.Balance.id)
// || allowedJobNames.contains(CustomerSegmentId.AccountTransactionsMt940.id)
return true
}
private 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
)
private fun mapNullableAmount(amount: Money?) = amount?.let { mapAmount(it) }
private fun mapAmount(amount: Money) = Amount.fromString(amount.amount.string.replace(',', '.'))
private fun <T> mapError(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<T> {
val error = if (response.error != null) {
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())
} else {
Error(ErrorType.UnknownError, response.errorMessage)
}
return Response(ResponseType.Error, null, error)
}
}

View File

@ -0,0 +1,33 @@
package net.codinux.banking.client.fints4k
import kotlinx.coroutines.test.runTest
import net.codinux.banking.client.model.response.ResponseType
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class FinTs4kBankingClientTest {
companion object {
// set your credentials here:
private const val bankCode = ""
private const val loginName = ""
private const val password = ""
}
private val underTest = FinTs4kBankingClientForCustomer(bankCode, loginName, password)
@Test
fun getAccountDataAsync() = runTest {
val result = underTest.getAccountDataAsync()
assertEquals(ResponseType.Success, result.type)
assertNotNull(result.data)
assertTrue(result.data!!.bookedTransactions.isNotEmpty())
}
}

View File

@ -28,13 +28,17 @@ allprojects {
tasks.register("publishAllToMavenLocal") {
dependsOn(
":BankingClientModel:publishToMavenLocal",
":BankingClient:publishToMavenLocal"
":BankingClient:publishToMavenLocal",
":FinTs4jBankingClient:publishToMavenLocal"
)
}
tasks.register("publishAll") {
dependsOn(
":BankingClientModel:publish",
":BankingClient:publish"
":BankingClient:publish",
":FinTs4jBankingClient:publish"
)
}

View File

@ -26,3 +26,5 @@ rootProject.name = "BankingClientProject"
include("BankingClientModel")
include("BankingClient")
include("FinTs4jBankingClient")