Converted fints4k to a Kotlin multi platform project
This commit is contained in:
parent
d1bb7d81c3
commit
e44a68addc
33
build.gradle
33
build.gradle
|
@ -4,9 +4,24 @@ ext {
|
||||||
|
|
||||||
appVersionCode = 3
|
appVersionCode = 3
|
||||||
|
|
||||||
|
|
||||||
|
/* MPP / basic dependencies */
|
||||||
|
|
||||||
kotlinVersion = '1.3.72'
|
kotlinVersion = '1.3.72'
|
||||||
kotlinCoroutinesVersion = "1.3.5"
|
kotlinCoroutinesVersion = "1.3.5"
|
||||||
|
|
||||||
|
serializationVersion = "0.20.0"
|
||||||
|
|
||||||
|
|
||||||
|
ktorVersion = "1.3.1"
|
||||||
|
|
||||||
|
klockVersion = "1.8.4"
|
||||||
|
|
||||||
|
bigNumVersion = "0.1.5"
|
||||||
|
|
||||||
|
uuidVersion = "0.1.0"
|
||||||
|
|
||||||
|
|
||||||
javaUtilsVersion = '1.0.16'
|
javaUtilsVersion = '1.0.16'
|
||||||
|
|
||||||
luceneUtilsVersion = "0.5.1-SNAPSHOT"
|
luceneUtilsVersion = "0.5.1-SNAPSHOT"
|
||||||
|
@ -17,6 +32,15 @@ ext {
|
||||||
|
|
||||||
/* Android */
|
/* Android */
|
||||||
|
|
||||||
|
androidCompileSdkVersion = 28
|
||||||
|
|
||||||
|
androidBuildToolsVersion = "29.0.3"
|
||||||
|
|
||||||
|
androidMinSdkVersion = 16
|
||||||
|
|
||||||
|
androidTargetSdkVersion = 28
|
||||||
|
|
||||||
|
|
||||||
androidUtilsVersion = '1.1.1-SNAPSHOT'
|
androidUtilsVersion = '1.1.1-SNAPSHOT'
|
||||||
|
|
||||||
materialDrawerVersion = "8.0.1"
|
materialDrawerVersion = "8.0.1"
|
||||||
|
@ -44,6 +68,10 @@ ext {
|
||||||
/* Test */
|
/* Test */
|
||||||
|
|
||||||
junitVersion = '4.12'
|
junitVersion = '4.12'
|
||||||
|
junit5Version = '5.5.2'
|
||||||
|
|
||||||
|
atriumVersion = "0.12.0"
|
||||||
|
|
||||||
assertJVersion = '3.12.2'
|
assertJVersion = '3.12.2'
|
||||||
|
|
||||||
mockitoVersion = '2.22.0'
|
mockitoVersion = '2.22.0'
|
||||||
|
@ -54,15 +82,16 @@ ext {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.61'
|
ext.kotlin_version = '1.3.72'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.1'
|
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
|
|
||||||
// Nexus staging plugin has to be downgraded to 0.10.0 to be applicable to sub projects, see https://github.com/UweTrottmann/SeriesGuide/commit/ca33e8ad2fa6cc5c426450c8aef3417ba073ca7f
|
// Nexus staging plugin has to be downgraded to 0.10.0 to be applicable to sub projects, see https://github.com/UweTrottmann/SeriesGuide/commit/ca33e8ad2fa6cc5c426450c8aef3417ba073ca7f
|
||||||
classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.10.0"
|
classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.10.0"
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
apply plugin: 'kotlin'
|
||||||
|
|
||||||
|
sourceCompatibility = "8"
|
||||||
|
targetCompatibility = "8"
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api project(":fints4k")
|
||||||
|
}
|
|
@ -1,36 +1,144 @@
|
||||||
apply plugin: 'java-library'
|
|
||||||
apply plugin: 'kotlin'
|
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceCompatibility = "1.7"
|
plugins {
|
||||||
targetCompatibility = "1.7"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
compileKotlin.kotlinOptions.jvmTarget = "1.6"
|
id "com.android.library"
|
||||||
|
}
|
||||||
compileTestKotlin.kotlinOptions.jvmTarget = "1.8"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
kotlin {
|
||||||
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
jvm("jvm6") {
|
||||||
|
compilations.main.kotlinOptions {
|
||||||
api "net.dankito.utils:java-utils:$javaUtilsVersion"
|
jvmTarget = "1.6"
|
||||||
|
}
|
||||||
implementation "net.dankito.search:lucene-4-utils:$luceneUtilsVersion"
|
}
|
||||||
|
|
||||||
|
android()
|
||||||
testImplementation "junit:junit:$junitVersion"
|
|
||||||
testImplementation "org.assertj:assertj-core:$assertJVersion"
|
|
||||||
|
sourceSets {
|
||||||
testImplementation "org.mockito:mockito-core:$mockitoVersion"
|
commonMain {
|
||||||
testImplementation "com.nhaarman:mockito-kotlin:$mockitoKotlinVersion" // so that Mockito.any() doesn't return null which null-safe Kotlin parameter don't like
|
dependencies {
|
||||||
// for how to enable mocking final class (which is standard in Kotlin) with Mockito see https://github.com/mockito/mockito/wiki/What's-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods
|
implementation kotlin("stdlib-common")
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$kotlinCoroutinesVersion"
|
||||||
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
|
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serializationVersion"
|
||||||
testImplementation("net.dankito.utils:java-fx-utils:$javaFxUtilsVersion") {
|
|
||||||
exclude group: "org.controlsfx"
|
implementation "io.ktor:ktor-client-core:$ktorVersion"
|
||||||
|
//
|
||||||
|
// implementation("io.ktor:ktor-client-serialization:$ktor_version")
|
||||||
|
|
||||||
|
api "com.soywiz.korlibs.klock:klock:$klockVersion"
|
||||||
|
|
||||||
|
api("com.ionspin.kotlin:bignum:$bigNumVersion")
|
||||||
|
|
||||||
|
implementation "com.benasher44:uuid:$uuidVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commonTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin("test-common")
|
||||||
|
implementation kotlin("test-annotations-common")
|
||||||
|
|
||||||
|
|
||||||
|
implementation project(":BankingUiCommon")
|
||||||
|
implementation project(":BankFinder")
|
||||||
|
implementation project(":fints4kBankingClient")
|
||||||
|
|
||||||
|
implementation "ch.tutteli.atrium:atrium-fluent-en_GB:$atriumVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
jvm6Main {
|
||||||
|
dependencies {
|
||||||
|
// implementation "io.ktor:ktor-client-cio:$ktorVersion"
|
||||||
|
implementation "io.ktor:ktor-client-okhttp:$ktorVersion"
|
||||||
|
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion"
|
||||||
|
|
||||||
|
api "com.soywiz.korlibs.klock:klock-jvm:$klockVersion"
|
||||||
|
|
||||||
|
implementation "org.slf4j:slf4j-api:$slf4jVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jvm6Test {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin("test-junit")
|
||||||
|
|
||||||
|
implementation "org.junit.jupiter:junit-jupiter:$junit5Version"
|
||||||
|
runtimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version"
|
||||||
|
|
||||||
|
implementation "org.assertj:assertj-core:$assertJVersion"
|
||||||
|
implementation "org.mockito:mockito-core:$mockitoVersion"
|
||||||
|
|
||||||
|
|
||||||
|
implementation "org.apache.commons:commons-csv:1.8"
|
||||||
|
|
||||||
|
implementation "org.slf4j:slf4j-simple:$slf4jVersion"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
androidMain {
|
||||||
|
dependsOn jvm6Main
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "io.ktor:ktor-client-android:$ktorVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion androidCompileSdkVersion
|
||||||
|
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion androidMinSdkVersion
|
||||||
|
targetSdkVersion androidTargetSdkVersion
|
||||||
|
|
||||||
|
versionName version
|
||||||
|
versionCode appVersionCode
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
pickFirst 'META-INF/ktor-http.kotlin_module'
|
||||||
|
pickFirst 'META-INF/kotlinx-io.kotlin_module'
|
||||||
|
pickFirst 'META-INF/atomicfu.kotlin_module'
|
||||||
|
pickFirst 'META-INF/ktor-utils.kotlin_module'
|
||||||
|
pickFirst 'META-INF/kotlinx-coroutines-io.kotlin_module'
|
||||||
|
pickFirst 'META-INF/ktor-client-core.kotlin_module'
|
||||||
|
pickFirst 'META-INF/DEPENDENCIES'
|
||||||
|
pickFirst 'META-INF/NOTICE'
|
||||||
|
pickFirst 'META-INF/LICENSE'
|
||||||
|
pickFirst 'META-INF/LICENSE.txt'
|
||||||
|
pickFirst 'META-INF/NOTICE.txt'
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
}
|
}
|
||||||
|
|
||||||
testImplementation "org.apache.commons:commons-csv:1.8"
|
|
||||||
}
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
package net.dankito.banking.fints
|
package net.dankito.banking.fints
|
||||||
|
|
||||||
|
import com.soywiz.klock.DateTime
|
||||||
|
import com.soywiz.klock.DateTimeSpan
|
||||||
|
import com.soywiz.klock.DateTimeTz
|
||||||
import net.dankito.banking.fints.callback.FinTsClientCallback
|
import net.dankito.banking.fints.callback.FinTsClientCallback
|
||||||
import net.dankito.banking.fints.messages.MessageBuilder
|
import net.dankito.banking.fints.messages.MessageBuilder
|
||||||
import net.dankito.banking.fints.messages.MessageBuilderResult
|
import net.dankito.banking.fints.messages.MessageBuilderResult
|
||||||
|
@ -25,43 +28,37 @@ import net.dankito.banking.fints.tan.TanImageDecoder
|
||||||
import net.dankito.banking.fints.transactions.IAccountTransactionsParser
|
import net.dankito.banking.fints.transactions.IAccountTransactionsParser
|
||||||
import net.dankito.banking.fints.transactions.Mt940AccountTransactionsParser
|
import net.dankito.banking.fints.transactions.Mt940AccountTransactionsParser
|
||||||
import net.dankito.banking.fints.util.IBase64Service
|
import net.dankito.banking.fints.util.IBase64Service
|
||||||
import net.dankito.utils.IThreadPool
|
import net.dankito.banking.fints.util.IThreadPool
|
||||||
import net.dankito.utils.ThreadPool
|
import net.dankito.banking.fints.util.PureKotlinBase64Service
|
||||||
import net.dankito.utils.web.client.IWebClient
|
import net.dankito.banking.fints.util.log.LoggerFactory
|
||||||
import net.dankito.utils.web.client.OkHttpWebClient
|
import net.dankito.banking.fints.webclient.IWebClient
|
||||||
import net.dankito.utils.web.client.RequestParameters
|
import net.dankito.banking.fints.webclient.KtorWebClient
|
||||||
import net.dankito.utils.web.client.WebClientResponse
|
import net.dankito.banking.fints.webclient.WebClientResponse
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
|
||||||
|
|
||||||
|
|
||||||
open class FinTsClient @JvmOverloads constructor(
|
open class FinTsClient(
|
||||||
protected val callback: FinTsClientCallback,
|
protected val callback: FinTsClientCallback,
|
||||||
protected val base64Service: IBase64Service,
|
protected val webClient: IWebClient = KtorWebClient(),
|
||||||
protected val webClient: IWebClient = OkHttpWebClient(),
|
protected val base64Service: IBase64Service = PureKotlinBase64Service(),
|
||||||
|
protected val threadPool: IThreadPool,
|
||||||
protected val messageBuilder: MessageBuilder = MessageBuilder(),
|
protected val messageBuilder: MessageBuilder = MessageBuilder(),
|
||||||
protected val responseParser: ResponseParser = ResponseParser(),
|
protected val responseParser: ResponseParser = ResponseParser(),
|
||||||
protected val mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
protected val mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
||||||
protected val threadPool: IThreadPool = ThreadPool(),
|
|
||||||
protected val product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically
|
protected val product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val NinetyDaysAgoMilliseconds = 90 * 24 * 60 * 60 * 1000L
|
|
||||||
|
|
||||||
val FindAccountTransactionsStartRegex = Regex("^HIKAZ:\\d:\\d:\\d\\+@\\d+@", RegexOption.MULTILINE)
|
val FindAccountTransactionsStartRegex = Regex("^HIKAZ:\\d:\\d:\\d\\+@\\d+@", RegexOption.MULTILINE)
|
||||||
val FindAccountTransactionsEndRegex = Regex("^-'", RegexOption.MULTILINE)
|
val FindAccountTransactionsEndRegex = Regex("^-'", RegexOption.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
private val log = LoggerFactory.getLogger(FinTsClient::class.java)
|
private val log = LoggerFactory.getLogger(FinTsClient::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
open var areWeThatGentleToCloseDialogs: Boolean = true
|
open var areWeThatGentleToCloseDialogs: Boolean = true
|
||||||
|
|
||||||
protected val messageLogField = CopyOnWriteArrayList<MessageLogEntry>()
|
protected val messageLogField = ArrayList<MessageLogEntry>() // TODO: make thread safe like with CopyOnWriteArrayList
|
||||||
|
|
||||||
// in either case remove sensitive data after response is parsed as otherwise some information like account holder name and accounts may is not set yet on CustomerData
|
// in either case remove sensitive data after response is parsed as otherwise some information like account holder name and accounts may is not set yet on CustomerData
|
||||||
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
||||||
|
@ -215,7 +212,7 @@ open class FinTsClient @JvmOverloads constructor(
|
||||||
|
|
||||||
// also check if retrieving account transactions of last 90 days without tan is supported (and thereby may retrieve first account transactions)
|
// also check if retrieving account transactions of last 90 days without tan is supported (and thereby may retrieve first account transactions)
|
||||||
val transactionsOfLast90DaysResponses = mutableListOf<GetTransactionsResponse>()
|
val transactionsOfLast90DaysResponses = mutableListOf<GetTransactionsResponse>()
|
||||||
val balances = mutableMapOf<AccountData, BigDecimal>()
|
val balances = mutableMapOf<AccountData, Money>()
|
||||||
customer.accounts.forEach { account ->
|
customer.accounts.forEach { account ->
|
||||||
if (account.supportsFeature(AccountFeature.RetrieveAccountTransactions)) {
|
if (account.supportsFeature(AccountFeature.RetrieveAccountTransactions)) {
|
||||||
val response = tryGetTransactionsOfLast90DaysWithoutTan(bank, customer, account, false)
|
val response = tryGetTransactionsOfLast90DaysWithoutTan(bank, customer, account, false)
|
||||||
|
@ -252,10 +249,10 @@ open class FinTsClient @JvmOverloads constructor(
|
||||||
protected open fun tryGetTransactionsOfLast90DaysWithoutTan(bank: BankData, customer: CustomerData, account: AccountData,
|
protected open fun tryGetTransactionsOfLast90DaysWithoutTan(bank: BankData, customer: CustomerData, account: AccountData,
|
||||||
hasRetrievedTransactionsWithTanJustBefore: Boolean): GetTransactionsResponse {
|
hasRetrievedTransactionsWithTanJustBefore: Boolean): GetTransactionsResponse {
|
||||||
|
|
||||||
val now = Date()
|
val now = DateTimeTz.nowLocal()
|
||||||
val ninetyDaysAgo = Date(now.time - NinetyDaysAgoMilliseconds - now.timezoneOffset * 60 * 1000) // map to UTC
|
val ninetyDaysAgo = now.minus(DateTimeSpan(days = 90))
|
||||||
|
|
||||||
val response = getTransactions(GetTransactionsParameter(account.supportsFeature(AccountFeature.RetrieveBalance), ninetyDaysAgo, abortIfTanIsRequired = true), bank, customer, account)
|
val response = getTransactions(GetTransactionsParameter(account.supportsFeature(AccountFeature.RetrieveBalance), ninetyDaysAgo.local.date, abortIfTanIsRequired = true), bank, customer, account)
|
||||||
|
|
||||||
|
|
||||||
account.triedToRetrieveTransactionsOfLast90DaysWithoutTan = true
|
account.triedToRetrieveTransactionsOfLast90DaysWithoutTan = true
|
||||||
|
@ -290,7 +287,7 @@ open class FinTsClient @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var balance: BigDecimal? = null
|
var balance: Money? = null
|
||||||
|
|
||||||
if (parameter.alsoRetrieveBalance && account.supportsFeature(AccountFeature.RetrieveBalance)) {
|
if (parameter.alsoRetrieveBalance && account.supportsFeature(AccountFeature.RetrieveBalance)) {
|
||||||
val balanceResponse = getBalanceAfterDialogInit(account, dialogContext)
|
val balanceResponse = getBalanceAfterDialogInit(account, dialogContext)
|
||||||
|
@ -301,7 +298,7 @@ open class FinTsClient @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
balanceResponse.getFirstSegmentById<BalanceSegment>(InstituteSegmentId.Balance)?.let {
|
balanceResponse.getFirstSegmentById<BalanceSegment>(InstituteSegmentId.Balance)?.let {
|
||||||
balance = it.balance
|
balance = Money(it.balance, it.currency)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,9 +573,7 @@ open class FinTsClient @JvmOverloads constructor(
|
||||||
protected open fun getResponseForMessage(requestBody: String, finTs3ServerAddress: String): WebClientResponse {
|
protected open fun getResponseForMessage(requestBody: String, finTs3ServerAddress: String): WebClientResponse {
|
||||||
val encodedRequestBody = base64Service.encode(requestBody)
|
val encodedRequestBody = base64Service.encode(requestBody)
|
||||||
|
|
||||||
return webClient.post(
|
return webClient.post(finTs3ServerAddress, encodedRequestBody, "application/octet-stream")
|
||||||
RequestParameters(finTs3ServerAddress, encodedRequestBody, "application/octet-stream")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun fireAndForgetMessage(message: MessageBuilderResult, dialogContext: DialogContext) {
|
protected open fun fireAndForgetMessage(message: MessageBuilderResult, dialogContext: DialogContext) {
|
||||||
|
@ -594,7 +589,7 @@ open class FinTsClient @JvmOverloads constructor(
|
||||||
protected open fun handleResponse(webResponse: WebClientResponse, dialogContext: DialogContext): Response {
|
protected open fun handleResponse(webResponse: WebClientResponse, dialogContext: DialogContext): Response {
|
||||||
val responseBody = webResponse.body
|
val responseBody = webResponse.body
|
||||||
|
|
||||||
if (webResponse.isSuccessful && responseBody != null) {
|
if (webResponse.successful && responseBody != null) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val decodedResponse = decodeBase64Response(responseBody)
|
val decodedResponse = decodeBase64Response(responseBody)
|
||||||
|
@ -603,14 +598,14 @@ open class FinTsClient @JvmOverloads constructor(
|
||||||
|
|
||||||
return responseParser.parse(decodedResponse)
|
return responseParser.parse(decodedResponse)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error("Could not decode responseBody:\r\n'$responseBody'", e)
|
log.error(e) { "Could not decode responseBody:\r\n'$responseBody'" }
|
||||||
|
|
||||||
return Response(false, exception = e)
|
return Response(false, exception = e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val bank = dialogContext.bank
|
val bank = dialogContext.bank
|
||||||
log.error("Request to $bank (${bank.finTs3ServerAddress}) failed", webResponse.error)
|
log.error { "Request to $bank (${bank.finTs3ServerAddress}) failed" } // TODO: add webResponse.error
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response(false, exception = webResponse.error)
|
return Response(false, exception = webResponse.error)
|
||||||
|
@ -633,14 +628,12 @@ open class FinTsClient @JvmOverloads constructor(
|
||||||
|
|
||||||
|
|
||||||
protected open fun addMessageLog(message: String, type: MessageLogEntryType, dialogContext: DialogContext) {
|
protected open fun addMessageLog(message: String, type: MessageLogEntryType, dialogContext: DialogContext) {
|
||||||
val timeStamp = Date()
|
val timeStamp = DateTime.now()
|
||||||
val messagePrefix = "${if (type == MessageLogEntryType.Sent) "Sending" else "Received"} message:\r\n" // currently no need to translate
|
val messagePrefix = "${if (type == MessageLogEntryType.Sent) "Sending" else "Received"} message:\r\n" // currently no need to translate
|
||||||
val prettyPrintMessage = prettyPrintHbciMessage(message)
|
val prettyPrintMessage = prettyPrintHbciMessage(message)
|
||||||
val prettyPrintMessageWithPrefix = "$messagePrefix$prettyPrintMessage"
|
val prettyPrintMessageWithPrefix = "$messagePrefix$prettyPrintMessage"
|
||||||
|
|
||||||
if (log.isDebugEnabled) {
|
log.debug { prettyPrintMessageWithPrefix }
|
||||||
log.debug(prettyPrintMessageWithPrefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
messageLogField.add(MessageLogEntry(prettyPrintMessageWithPrefix, timeStamp, dialogContext.customer))
|
messageLogField.add(MessageLogEntry(prettyPrintMessageWithPrefix, timeStamp, dialogContext.customer))
|
||||||
}
|
}
|
|
@ -10,26 +10,26 @@ import net.dankito.banking.fints.response.client.GetTransactionsResponse
|
||||||
import net.dankito.banking.fints.transactions.IAccountTransactionsParser
|
import net.dankito.banking.fints.transactions.IAccountTransactionsParser
|
||||||
import net.dankito.banking.fints.transactions.Mt940AccountTransactionsParser
|
import net.dankito.banking.fints.transactions.Mt940AccountTransactionsParser
|
||||||
import net.dankito.banking.fints.util.IBase64Service
|
import net.dankito.banking.fints.util.IBase64Service
|
||||||
import net.dankito.utils.IThreadPool
|
import net.dankito.banking.fints.util.IThreadPool
|
||||||
import net.dankito.utils.ThreadPool
|
import net.dankito.banking.fints.util.PureKotlinBase64Service
|
||||||
import net.dankito.utils.web.client.IWebClient
|
import net.dankito.banking.fints.webclient.IWebClient
|
||||||
import net.dankito.utils.web.client.OkHttpWebClient
|
import net.dankito.banking.fints.webclient.KtorWebClient
|
||||||
|
|
||||||
|
|
||||||
open class FinTsClientForCustomer @JvmOverloads constructor(
|
open class FinTsClientForCustomer(
|
||||||
val bank: BankData,
|
val bank: BankData,
|
||||||
val customer: CustomerData,
|
val customer: CustomerData,
|
||||||
webClient: IWebClient = OkHttpWebClient(),
|
|
||||||
base64Service: IBase64Service,
|
|
||||||
threadPool: IThreadPool = ThreadPool(),
|
|
||||||
callback: FinTsClientCallback,
|
callback: FinTsClientCallback,
|
||||||
|
webClient: IWebClient = KtorWebClient(),
|
||||||
|
base64Service: IBase64Service = PureKotlinBase64Service(),
|
||||||
|
threadPool: IThreadPool,
|
||||||
messageBuilder: MessageBuilder = MessageBuilder(),
|
messageBuilder: MessageBuilder = MessageBuilder(),
|
||||||
responseParser: ResponseParser = ResponseParser(),
|
responseParser: ResponseParser = ResponseParser(),
|
||||||
mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
||||||
product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically
|
product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically
|
||||||
) {
|
) {
|
||||||
|
|
||||||
protected val client = FinTsClient(callback, base64Service, webClient, messageBuilder, responseParser, mt940Parser, threadPool, product)
|
protected val client = FinTsClient(callback, webClient, base64Service, threadPool, messageBuilder, responseParser, mt940Parser, product)
|
||||||
|
|
||||||
|
|
||||||
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
|
@ -1,5 +1,7 @@
|
||||||
package net.dankito.banking.fints.messages
|
package net.dankito.banking.fints.messages
|
||||||
|
|
||||||
|
import io.ktor.utils.io.charsets.Charsets
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Der HBCI-Basiszeichensatz baut auf dem international normierten Zeichensatz ISO 8859 auf.
|
* Der HBCI-Basiszeichensatz baut auf dem international normierten Zeichensatz ISO 8859 auf.
|
|
@ -1,5 +1,6 @@
|
||||||
package net.dankito.banking.fints.messages
|
package net.dankito.banking.fints.messages
|
||||||
|
|
||||||
|
import com.soywiz.klock.DateTime
|
||||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.Aufsetzpunkt
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.Aufsetzpunkt
|
||||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.KundensystemID
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.KundensystemID
|
||||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.Synchronisierungsmodus
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.Synchronisierungsmodus
|
||||||
|
@ -22,7 +23,7 @@ import net.dankito.banking.fints.response.segments.JobParameters
|
||||||
import net.dankito.banking.fints.response.segments.SepaAccountInfoParameters
|
import net.dankito.banking.fints.response.segments.SepaAccountInfoParameters
|
||||||
import net.dankito.banking.fints.response.segments.TanResponse
|
import net.dankito.banking.fints.response.segments.TanResponse
|
||||||
import net.dankito.banking.fints.util.FinTsUtils
|
import net.dankito.banking.fints.util.FinTsUtils
|
||||||
import net.dankito.utils.extensions.containsAny
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
|
@ -356,7 +357,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun createControlReference(): String {
|
protected open fun createControlReference(): String {
|
||||||
return Math.abs(Random(System.nanoTime()).nextInt()).toString()
|
return Random(DateTime.nowUnixLong()).nextInt().absoluteValue.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -442,4 +443,16 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: move to a library
|
||||||
|
fun <T> Collection<T>.containsAny(otherCollection: Collection<T>): Boolean {
|
||||||
|
for (otherItem in otherCollection) {
|
||||||
|
if (this.contains(otherItem)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
|
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
|
||||||
|
|
||||||
|
import com.soywiz.klock.Date
|
||||||
|
import com.soywiz.klock.DateFormat
|
||||||
|
import com.soywiz.klock.parse
|
||||||
import net.dankito.banking.fints.messages.Existenzstatus
|
import net.dankito.banking.fints.messages.Existenzstatus
|
||||||
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
|
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,11 +17,20 @@ open class Datum(date: Int?, existenzstatus: Existenzstatus) : NumerischesDatene
|
||||||
companion object {
|
companion object {
|
||||||
const val HbciDateFormatString = "yyyyMMdd"
|
const val HbciDateFormatString = "yyyyMMdd"
|
||||||
|
|
||||||
val HbciDateFormat = SimpleDateFormat(HbciDateFormatString)
|
val HbciDateFormat = DateFormat(HbciDateFormatString)
|
||||||
|
|
||||||
|
|
||||||
|
fun format(date: Date): String {
|
||||||
|
return HbciDateFormat.format(date.dateTimeDayStart.localUnadjusted) // TODO: is this correct?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parse(dateString: String): Date {
|
||||||
|
return HbciDateFormat.parse(dateString).utc.date // TODO: really use UTC?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
constructor(date: Date?, existenzstatus: Existenzstatus)
|
constructor(date: Date?, existenzstatus: Existenzstatus)
|
||||||
: this(date?.let { HbciDateFormat.format(it).toInt() }, existenzstatus)
|
: this(date?.let { format(it).toInt() }, existenzstatus)
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
|
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
|
||||||
|
|
||||||
|
import com.soywiz.klock.*
|
||||||
import net.dankito.banking.fints.messages.Existenzstatus
|
import net.dankito.banking.fints.messages.Existenzstatus
|
||||||
import net.dankito.banking.fints.messages.datenelemente.basisformate.ZiffernDatenelement
|
import net.dankito.banking.fints.messages.datenelemente.basisformate.ZiffernDatenelement
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,11 +16,20 @@ open class Uhrzeit(time: Int?, existenzstatus: Existenzstatus) : ZiffernDatenele
|
||||||
companion object {
|
companion object {
|
||||||
const val HbciTimeFormatString = "HHmmss"
|
const val HbciTimeFormatString = "HHmmss"
|
||||||
|
|
||||||
val HbciTimeFormat = SimpleDateFormat(HbciTimeFormatString)
|
val HbciTimeFormat = DateFormat(HbciTimeFormatString)
|
||||||
|
|
||||||
|
|
||||||
|
fun format(time: Time): String {
|
||||||
|
return HbciTimeFormat.format(DateTimeTz.Companion.fromUnixLocal(time.encoded.milliseconds)) // TODO: is this correct?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parse(dateString: String): Time {
|
||||||
|
return HbciTimeFormat.parse(dateString).utc.time // TODO: is this correct?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
constructor(time: Date?, existenzstatus: Existenzstatus)
|
constructor(time: Time?, existenzstatus: Existenzstatus)
|
||||||
: this(time?.let { HbciTimeFormat.format(it).toInt() }, existenzstatus)
|
: this(time?.let { format(time).toInt() }, existenzstatus)
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package net.dankito.banking.fints.messages.datenelemente.basisformate
|
package net.dankito.banking.fints.messages.datenelemente.basisformate
|
||||||
|
|
||||||
|
import io.ktor.utils.io.charsets.encode
|
||||||
|
import io.ktor.utils.io.charsets.name
|
||||||
import net.dankito.banking.fints.messages.Existenzstatus
|
import net.dankito.banking.fints.messages.Existenzstatus
|
||||||
import net.dankito.banking.fints.messages.HbciCharset
|
import net.dankito.banking.fints.messages.HbciCharset
|
||||||
import net.dankito.banking.fints.messages.Separators
|
import net.dankito.banking.fints.messages.Separators
|
||||||
|
@ -35,9 +37,11 @@ abstract class TextDatenelement(var value: String?, existenzstatus: Existenzstat
|
||||||
checkIfMandatoryValueIsSet()
|
checkIfMandatoryValueIsSet()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (HbciCharset.DefaultCharset.newEncoder().canEncode(value) == false) {
|
value?.let { // at this time value is != null otherwise checkIfMandatoryValueIsSet() would fail
|
||||||
|
if (HbciCharset.DefaultCharset.newEncoder().encode(it).canRead() == false) {
|
||||||
throwInvalidCharacterException()
|
throwInvalidCharacterException()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throwInvalidCharacterException()
|
throwInvalidCharacterException()
|
||||||
}
|
}
|
||||||
|
@ -46,14 +50,14 @@ abstract class TextDatenelement(var value: String?, existenzstatus: Existenzstat
|
||||||
|
|
||||||
protected open fun checkIfMandatoryValueIsSet() {
|
protected open fun checkIfMandatoryValueIsSet() {
|
||||||
if (existenzstatus == Existenzstatus.Mandatory && value == null) {
|
if (existenzstatus == Existenzstatus.Mandatory && value == null) {
|
||||||
throwValidationException("Wert ist auf dem Pflichtfeld ${javaClass.simpleName} not set")
|
throwValidationException("Wert ist auf dem Pflichtfeld ${this::class.simpleName} not set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun throwInvalidCharacterException() {
|
protected open fun throwInvalidCharacterException() {
|
||||||
throwValidationException(
|
throwValidationException(
|
||||||
"Wert '$value' enthält Zeichen die gemäß des Zeichensatzes " +
|
"Wert '$value' enthält Zeichen die gemäß des Zeichensatzes " +
|
||||||
"${HbciCharset.DefaultCharset.displayName()} nicht erlaubt sind."
|
"${HbciCharset.DefaultCharset.name} nicht erlaubt sind."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,15 @@ package net.dankito.banking.fints.messages.datenelemente.implementierte.sepa
|
||||||
import net.dankito.banking.fints.messages.Existenzstatus
|
import net.dankito.banking.fints.messages.Existenzstatus
|
||||||
import net.dankito.banking.fints.messages.datenelemente.basisformate.BinaerDatenelement
|
import net.dankito.banking.fints.messages.datenelemente.basisformate.BinaerDatenelement
|
||||||
import net.dankito.banking.fints.messages.segmente.implementierte.sepa.ISepaMessageCreator
|
import net.dankito.banking.fints.messages.segmente.implementierte.sepa.ISepaMessageCreator
|
||||||
|
import net.dankito.banking.fints.messages.segmente.implementierte.sepa.PaymentInformationMessages
|
||||||
|
|
||||||
|
|
||||||
open class SepaMessage(
|
open class SepaMessage(
|
||||||
filename: String,
|
messageTemplate: PaymentInformationMessages,
|
||||||
replacementStrings: Map<String, String>,
|
replacementStrings: Map<String, String>,
|
||||||
messageCreator: ISepaMessageCreator
|
messageCreator: ISepaMessageCreator
|
||||||
)
|
)
|
||||||
: BinaerDatenelement(messageCreator.createXmlFile(filename, replacementStrings), Existenzstatus.Mandatory) {
|
: BinaerDatenelement(messageCreator.createXmlFile(messageTemplate, replacementStrings), Existenzstatus.Mandatory) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue