Compare commits

...

126 commits

Author SHA1 Message Date
bf17bde9f5 Bumped version to 1.0.0-Alpha-16-SNAPSHOT 2024-10-16 01:36:35 +02:00
9f3e4eff4d Bumped version to 1.0.0-Alpha-15 2024-10-16 01:33:27 +02:00
f89de94aa7 Little refactoring 2024-10-15 21:55:16 +02:00
684b3fb40e Fixed that if lastCreatedMesssage is a DialogInit message, that we don't sent it again as we just initialized a new dialog with initDialogWithStrongCustomerAuthentication() 2024-10-15 21:53:02 +02:00
df692ea222 Renamed messageLogWithoutSensitiveData to messageLog 2024-10-15 13:30:18 +02:00
636963b3d4 Added and finTsModelOrDeserialized to 2024-10-15 13:29:25 +02:00
4802493886 Saving some CPU cycles, only serializing finTsModel if required 2024-10-15 13:28:35 +02:00
ecf930fcad Fixed that quantity is a floating point number 2024-10-15 09:29:52 +02:00
ce3b1d32d7 Renamed messageLogWithoutSensitiveData to messageLog 2024-10-15 02:35:14 +02:00
c39789dfde Fixed only adding -Xdebug if debugger is attached 2024-10-15 02:28:04 +02:00
ab0b676216 Added messageWithoutSensitiveData as extra field so that user can choose between them 2024-10-15 02:05:32 +02:00
20fe60d9f6 Also pretty printing error messages 2024-10-15 02:00:23 +02:00
529caeaa87 Fixed that imageBytes is now nullable 2024-10-14 22:13:32 +02:00
3d6c68e743 Implemented serializer for BankData 2024-10-14 22:12:36 +02:00
7cdb7247c8 Implemented serializing FinTS data 2024-10-14 22:09:14 +02:00
d7d2702869 Retrieving ChangeTanMediaParameters now from supportedJobs instead of storing it a second time 2024-10-14 20:44:31 +02:00
67b58117e1 Mapping PinInfo 2024-10-14 20:20:51 +02:00
66801a1c7a Implemented parsing HICAZS 2024-10-14 20:20:03 +02:00
2410504ede Setting jobsRequiringTan now directly, ignoring PinInfo 2024-10-14 15:45:28 +02:00
2a3b962af5 Made TanMedium serializable 2024-10-10 02:31:00 +02:00
8346fb5077 Using now nullable hashCode() method 2024-10-09 19:12:24 +02:00
8dc2174081 Added mediumName to hashCode() and equals() 2024-10-09 19:08:43 +02:00
05322aface Added toString() implementation 2024-10-04 18:04:36 +02:00
dcbbe043f0 Renamed dialogType to messageType 2024-10-04 18:04:12 +02:00
65d983a5e7 Added a check to determine HHD version 2024-09-26 14:21:10 +02:00
9aad2a5101 Made parsedDataSet, mimeType and imageBytes nullable, as in case of decoding error they are not set 2024-09-26 14:19:27 +02:00
be3a2df6d9 Bumped version to 1.0.0-Alpha-15-SNAPSHOT 2024-09-19 21:15:43 +02:00
dea8be3bfa Bumped version to 1.0.0-Alpha-14 2024-09-19 21:15:00 +02:00
cba2f25335 Updated klf version 2024-09-19 05:05:01 +02:00
825217ef88 Downgraded Kotlin to version 1.9.x so that more applications can use this library 2024-09-19 05:03:28 +02:00
b3cb76e77d Added option appendFinTsMessagesToLog to easily configure if FinTS messages should be added to log by default 2024-09-18 17:33:12 +02:00
b0c2f38bd6 Bumped version to 1.0.0-Alpha-14-SNAPSHOT 2024-09-17 17:38:36 +02:00
fca1542b5c Bumped version to 1.0.0-Alpha-13 2024-09-17 17:35:09 +02:00
07672d1189 Fixed marking accounts that support CustomerSegmentId.SecuritiesAccountBalance as supported account type 2024-09-17 04:20:58 +02:00
62aa04a667 Updating lastAccountUpdateTime also when have been retrieved 2024-09-11 23:18:27 +02:00
3aa0edfb34 Renamed lastTransactionsRetrievalTime to lastAccountUpdateTime 2024-09-11 23:05:26 +02:00
e4d605531e Added hint that amount strings use ',' as decimal separator 2024-09-11 22:59:51 +02:00
a42de32260 Implemented requesting and parsing securities account balance 2024-09-11 22:48:31 +02:00
95e60b2706 Implemented Mt535Parser 2024-09-11 05:50:31 +02:00
fd9eadf45e Removed fallback of using platform specific DateFormatter 2024-09-11 01:09:50 +02:00
7ddeb88475 Decided against parsing Mt942 creationTime and smallest amount (should rarely be used and may only cause parsing errors) 2024-09-11 00:25:22 +02:00
90a7543641 Implemented Mt942Parser 2024-09-10 23:40:22 +02:00
d1de7f5eb0 Extracted Mt94xParserBase 2024-09-10 21:29:13 +02:00
ef8045fa96 Extracted MtParserTestBase 2024-09-10 20:42:51 +02:00
2031cb9e9f Made remainder nullable to signal there is no remainder 2024-09-10 18:23:05 +02:00
e260eaa535 Added test for remainder 2024-09-10 18:17:26 +02:00
891641fc6f Found rule how four digit year are calculated from two digit years 2024-09-10 18:03:20 +02:00
c158097d3a Added tanMethodsNotSupportedByApplication to filter out TAN methods that client application does not support (e.g. chipTanUsb) 2024-09-10 03:20:50 +02:00
6908f52e48 Using now NonVisualOrImageBased as default to determine user's (suggested) TAN method as it provides a good default for most users 2024-09-10 02:47:23 +02:00
61d8f2c342 Added preferredTanMethods and preferredTanMedium to JobContext 2024-09-10 02:46:35 +02:00
6bf7fdcb44 Implemented passing default bank data to FinTsClient as e.g. bank names returned from bank server are often quite bad, e.g. DB24 for Deutsche Bank 2024-09-09 23:01:06 +02:00
fbafbb62e3 Added option closeDialogs 2024-09-09 17:06:29 +02:00
9372d17313 Avoiding concurrent modification exception 2024-09-09 17:02:47 +02:00
9b1a5fa929 Fixed not continuing to next account if user cancelled process 2024-09-09 17:01:32 +02:00
42bf002626 Added tanExpiredCallback, so that UI can react to when TAN expired 2024-09-09 03:36:23 +02:00
20f06387c5 Added check if tanExpirationTime is exceeded if set 2024-09-09 02:56:34 +02:00
75320da2be Changed type of tanExpirationTime to Instant so that UI can better convert it to user's time zone 2024-09-09 02:55:37 +02:00
be2908517f Fixed that validityDateTimeForChallenge has been renamed to tanExpirationTime 2024-09-09 00:45:30 +02:00
c4f504dd0a Added tanExpirationTime to TanChallenge 2024-09-08 22:40:05 +02:00
0848586894 Added timestamp at which TanChallenge was created 2024-09-08 22:36:39 +02:00
83c2882567 Added isReversal 2024-09-08 22:20:59 +02:00
f069f9155c Adjusted names to fints4k names 2024-09-08 22:19:38 +02:00
bf5ee4890e Renamed otherPartyBankCode to otherPartyBankId 2024-09-08 22:17:16 +02:00
ed4214fd49 Fixed calling the right Instant.now() method 2024-09-08 22:03:37 +02:00
b8fe9e78e1 Renamed transactionsRetentionDays to serverTransactionsRetentionDays 2024-09-08 22:01:28 +02:00
da2bf8d469 Terminate waiting for TAN input after a timeout 2024-09-08 20:38:20 +02:00
113b817627 Extracted Instant.nowExt() 2024-09-08 20:31:12 +02:00
bd18644c0d Calling mayRetrieveAutomaticallyIfUserEnteredDecoupledTan() out of loop. Should make no difference but should sound more logic 2024-09-08 20:22:18 +02:00
b32cf94e25 Using now isEnteringTanDone 2024-09-08 20:20:31 +02:00
8cc2f3bdcd Added timestamp at which TanChallenge was created 2024-09-08 18:31:02 +02:00
59b8213163 Extracted clearUserApprovedDecoupledTanCallbacks() and clearing callbacks also when user did not enter TAN or requested to change TAN method or medium to avoid memory leaks 2024-09-08 18:14:35 +02:00
cb34c86665 Changed order of opening and closing balance 2024-09-05 23:31:02 +02:00
70c1082531 Renamed countDaysForWhichTransactionsAreKept to transactionsRetentionDays 2024-09-05 21:53:00 +02:00
30e9a57b96 Fixed setting either sepaReference - in case of structured information - or unparsedReference - in case of unstructured reference. And that reference may is null 2024-09-05 19:36:03 +02:00
bf76de4f23 Applied adjusted values from MT 940 to AccountTransaction 2024-09-05 19:16:15 +02:00
47e2b851b9 Adjusted names according to English Translation of DFÜ-Abkommen Anlage_3_Datenformate_V3.8.pdf (Appendix_3-Data_Formats_V3-8.pdf) 2024-09-05 18:20:56 +02:00
f90e280b74 Adjusted names according to English Translation of DFÜ-Abkommen Anlage_3_Datenformate_V3.8.pdf (Appendix_3-Data_Formats_V3-8.pdf) 2024-09-05 18:15:42 +02:00
9600e2f11b Converted lastTransactionsRetrievalTime to Instant 2024-09-03 22:06:36 +02:00
b2fb04372f Updating BankAccount.bookedTransactions only if we retrieved transactions and adding it to existing list instead of replacing existing bookedTransactions list 2024-09-03 21:19:24 +02:00
3b05a8b9c8 Renamed lastTransactionRetrievalTime to lastTransactionsRetrievalTime 2024-09-03 21:15:41 +02:00
d689c7663f Fixed updating BankAccount.balance and .retrievedTransactionsFrom 2024-09-03 21:14:52 +02:00
6238b5abb2 Renamed europeBerlin to EuropeBerlin 2024-09-03 20:56:07 +02:00
1f8c1d303e Extracted getRequiredDataToSendUserJobs() 2024-09-03 01:06:27 +02:00
09c2080481 Added lastTransactionRetrievalTime to BankAccount, removed retrievedTransactionsTo for it 2024-09-03 01:05:04 +02:00
e36c27c0e0 Added Decoupled response code of Sparkasse 2024-09-02 19:36:43 +02:00
6865f64880 Added callback to get notified when user approved Decoupled TAN (e.g. to close a dialog) 2024-09-02 19:35:36 +02:00
3f9921a62e Mapped Decoupled TAN methods to their own types 2024-09-02 13:23:34 +02:00
504fbaf13b Implemented Decoupled TAN process (HKTAN 7) 2024-09-02 13:15:36 +02:00
fb70bcd443 Fixed mapping multiple HITANS segments in a message 2024-09-02 03:23:19 +02:00
952fa9c13a Bumped version to 1.0.0-Alpha-13-SNAPSHOT 2024-09-01 19:58:11 +02:00
b47fcabc1c Bumped version to 1.0.0-Alpha-12 2024-09-01 19:57:29 +02:00
878d32ea16 Set jvmToolchain to 11 as new klf version requires JDK 11 2024-09-01 19:55:57 +02:00
5f7b07a601 Added from Spec that countMaxJobsPerMessage is almost useless 2024-09-01 17:40:03 +02:00
7e8d005fcb Made also remaining properties and methods open 2024-08-30 17:29:16 +02:00
21215300b3 Updated klf version 2024-08-30 17:28:32 +02:00
b826ee3c8f Added comment why dates like 30th of February may occur 2024-08-27 09:47:13 +02:00
32defa20f7 Fixed that Sparkasse returns 30th of February or 29th of February also in non leap years 2024-08-26 23:46:48 +02:00
c5432883ef Added tests for parsing date 2024-08-26 23:45:47 +02:00
dae6084ebb Selecting active TAN media first 2024-08-26 22:25:09 +02:00
c564750832 Added targetiosX64 2024-08-26 02:20:09 +02:00
d16289d824 Fixed BIC comments 2024-08-24 18:29:46 +02:00
a2ac04f424 Added clarification about Dialogsprache 2024-08-24 18:25:56 +02:00
8247584a61 Added clarification about KundensystemStatus 2024-08-24 18:25:30 +02:00
34e40aeee1 Replaced deprecated methods 2024-08-24 17:33:00 +02:00
5d7ea48a46 Removed variant with callback 2024-08-24 17:17:47 +02:00
d055b61ed9 Fixed typo from package name migration 2024-08-24 17:13:44 +02:00
87c59e63ee Added more documentation from FinTS spec 2024-08-24 17:13:22 +02:00
40b916a49f Made methods overridable; made + 3 clearer 2024-08-24 17:12:44 +02:00
eabe2c4930 Made message trace ordered 2024-08-24 17:11:42 +02:00
0f151a8f4f Extracted jobsRequiringTan 2024-08-23 23:27:58 +02:00
33a86607df Changed order to make it clearer 2024-08-23 22:08:33 +02:00
f154e60e1e Removed unused enum 2024-08-23 15:16:12 +02:00
7e5a455c24 Changed base package from net.dankito to net.codinux (except for client model) 2024-08-23 14:50:57 +02:00
d8b0c89be7 Added parsed received segments to MessageLog 2024-08-23 14:06:14 +02:00
3d385b5bdf Added alternative names 2024-08-23 13:08:00 +02:00
4f3924f2cd Deciding upfront if message ever has to be pretty printed. Avoids pretty printing it multiple times 2024-08-23 13:06:48 +02:00
c0796cfc38 Added option to disable collection messageLog 2024-08-23 13:02:10 +02:00
9857a0565d Fixed logging message 2024-08-23 12:57:37 +02:00
a3dbe8f142 By default not adding message trace to message anymore; added messageIncludingMessageTrace for that 2024-08-23 12:53:45 +02:00
88f6d53ccb Temporarily disabling iOS DateFormatter as it conflicts with nativeMain's DateFormatter. TODO: Fix this 2024-08-23 12:47:14 +02:00
850beb8421 Added messageLogAdded() to FinTsClientCallback to get instantly notified of sent, received and error messages 2024-08-23 12:42:52 +02:00
da50b72898 Added Toolchain resolver 2024-08-23 12:11:22 +02:00
237802b18d Moved createBankWithAllFeatures() and createRandomMessage() to FinTsTestBase 2024-08-22 18:40:40 +02:00
88c64f1ff1 Updated README 2024-08-22 18:11:12 +02:00
5e7d880499 Added publishAll task 2024-08-22 03:32:37 +02:00
199506547b Bumped version to 1.0.0-Alpha-12-SNAPSHOT 2024-08-22 03:28:16 +02:00
419 changed files with 6410 additions and 2998 deletions

View file

@ -4,35 +4,49 @@ fints4k is an implementation of the FinTS 3.0 online banking protocol used by mo
It's fast, easy extendable and running on multiple platforms: JVM, Android, (iOS, JavaScript, Windows, MacOS, Linux).
However it's not a full implementation of FinTS standard but implements all common use cases:
## Features
- Retrieving account information, balances and turnovers (Kontoumsätze und -saldo).
- Transfer money and real-time transfers (SEPA Überweisungen und Echtzeitüberweisung).
- Supports TAN methods chipTAN manual, Flickercode, QrCode and Photo (Matrix code), pushTAN, smsTAN and appTAN.
However, this is quite a low level implementation and in most cases not what you want to use.
In most cases you want to use a higher level abstraction like [FinTs4kBankingClient](https://git.dankito.net/codinux/BankingClient).
## Setup
Not uploaded to Maven Central yet, will do this the next few days!
Gradle:
```
repositories {
mavenCentral()
maven {
setUrl("https://maven.dankito.net/api/packages/codinux/maven")
}
}
dependencies {
compile 'net.dankito.banking:fints4k:0.1.0'
implementation("net.codinux.banking:fints4k:1.0.0-Alpha-13")
}
```
Maven:
```
// add Repository https://maven.dankito.net/api/packages/codinux/maven
<dependency>
<groupId>net.dankito.banking</groupId>
<artifactId>fints4k</artifactId>
<version>0.1.0</version>
<artifactId>fints4k-jvm</artifactId>
<version>1.0.0-Alpha-11</version>
</dependency>
```
## Usage
Quite outdated, have to update it. In most cases use [FinTs4kBankingClient](https://git.dankito.net/codinux/BankingClient).
See e.g. [JavaShowcase](fints4k/src/test/java/net/dankito/banking/fints/JavaShowcase.java) or [FinTsClientTest](fints4k/src/test/kotlin/net/dankito/banking/fints/FinTsClientTest.kt).
```java

View file

@ -7,9 +7,9 @@ import kotlinx.coroutines.withContext
import kotlinx.datetime.LocalDate
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
import net.dankito.banking.client.model.response.GetAccountDataResponse
import net.dankito.banking.fints.FinTsClient
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
import net.dankito.banking.fints.model.TanChallenge
import net.codinux.banking.fints.FinTsClient
import net.codinux.banking.fints.callback.SimpleFinTsClientCallback
import net.codinux.banking.fints.model.TanChallenge
import net.dankito.utils.multiplatform.extensions.millisSinceEpochAtSystemDefaultTimeZone
import org.slf4j.LoggerFactory
import java.math.BigDecimal

View file

@ -5,7 +5,7 @@ import net.codinux.banking.fints4k.android.Presenter
import net.codinux.banking.fints4k.android.R
import net.codinux.banking.fints4k.android.adapter.viewholder.AccountTransactionsViewHolder
import net.dankito.banking.client.model.AccountTransaction
import net.dankito.banking.fints.util.toBigDecimal
import net.codinux.banking.fints.util.toBigDecimal
import net.dankito.utils.android.extensions.setTextColorToColorResource
import net.dankito.utils.android.ui.adapter.ListRecyclerAdapter
import org.slf4j.LoggerFactory

View file

@ -15,9 +15,9 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import net.codinux.banking.fints4k.android.Presenter
import net.codinux.banking.fints4k.android.R
import net.dankito.banking.fints.model.FlickerCodeTanChallenge
import net.dankito.banking.fints.model.ImageTanChallenge
import net.dankito.banking.fints.model.TanChallenge
import net.codinux.banking.fints.model.FlickerCodeTanChallenge
import net.codinux.banking.fints.model.ImageTanChallenge
import net.codinux.banking.fints.model.TanChallenge
import net.dankito.utils.android.extensions.getSpannedFromHtml
import net.dankito.utils.android.extensions.show

View file

@ -13,12 +13,12 @@ import net.dankito.banking.client.model.CustomerAccount
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
import net.dankito.banking.client.model.parameter.RetrieveTransactions
import net.dankito.banking.client.model.parameter.TransferMoneyParameter
import net.dankito.banking.fints.FinTsClient
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
import net.dankito.banking.fints.extensions.toStringWithMinDigits
import net.dankito.banking.fints.getAccountData
import net.dankito.banking.fints.model.TanChallenge
import net.dankito.banking.fints.transferMoney
import net.codinux.banking.fints.FinTsClient
import net.codinux.banking.fints.callback.SimpleFinTsClientCallback
import net.codinux.banking.fints.extensions.toStringWithMinDigits
import net.codinux.banking.fints.getAccountData
import net.codinux.banking.fints.model.TanChallenge
import net.codinux.banking.fints.transferMoney
import util.CsvWriter
import util.OutputFormat

View file

@ -1,7 +1,7 @@
package commands
import NativeApp
import net.dankito.banking.fints.model.TanMethodType
import net.codinux.banking.fints.model.TanMethodType
data class CommonConfig(

View file

@ -5,10 +5,10 @@ import com.github.ajalt.clikt.core.requireObject
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.options.option
import net.dankito.banking.client.model.parameter.TransferMoneyParameter
import net.dankito.banking.fints.model.AccountData
import net.dankito.banking.fints.model.Amount
import net.dankito.banking.fints.model.Currency
import net.dankito.banking.fints.model.Money
import net.codinux.banking.fints.model.AccountData
import net.codinux.banking.fints.model.Amount
import net.codinux.banking.fints.model.Currency
import net.codinux.banking.fints.model.Money
class TransferMoneyCommand : CliktCommand("Transfers money from your account to a recipient", name = "transfer", printHelpOnEmptyArgs = true) {

View file

@ -12,8 +12,8 @@ import kotlinx.datetime.LocalDate
import kotlinx.datetime.minus
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
import net.dankito.banking.client.model.parameter.RetrieveTransactions
import net.dankito.banking.fints.model.TanMethodType
import net.dankito.banking.fints.extensions.todayAtEuropeBerlin
import net.codinux.banking.fints.model.TanMethodType
import net.codinux.banking.fints.extensions.todayAtEuropeBerlin
import util.OutputFormat

View file

@ -1,11 +1,11 @@
package net.dankito.banking.fints
package net.codinux.banking.fints
import kotlinx.coroutines.runBlocking
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
import net.dankito.banking.client.model.parameter.TransferMoneyParameter
import net.dankito.banking.client.model.response.GetAccountDataResponse
import net.dankito.banking.client.model.response.TransferMoneyResponse
import net.dankito.banking.fints.model.Money
import net.codinux.banking.fints.model.Money
fun FinTsClient.getAccountData(bankCode: String, loginName: String, password: String): GetAccountDataResponse {

View file

@ -41,8 +41,8 @@ open class CsvWriter {
protected open suspend fun writeToFile(stream: AsyncStream, valueSeparator: String, customer: CustomerAccount, account: BankAccount, transaction: AccountTransaction) {
val amount = if (valueSeparator == ";") transaction.amount.amount.string.replace('.', ',') else transaction.amount.amount.string.replace(',', '.')
stream.writeString(listOf(customer.bankName, account.identifier, transaction.valueDate, amount, transaction.amount.currency, ensureNotNull(transaction.bookingText), wrap(transaction.reference),
ensureNotNull(transaction.otherPartyName), ensureNotNull(transaction.otherPartyBankCode), ensureNotNull(transaction.otherPartyAccountId)).joinToString(valueSeparator))
stream.writeString(listOf(customer.bankName, account.identifier, transaction.valueDate, amount, transaction.amount.currency, ensureNotNull(transaction.postingText), wrap(transaction.reference ?: ""),
ensureNotNull(transaction.otherPartyName), ensureNotNull(transaction.otherPartyBankId), ensureNotNull(transaction.otherPartyAccountId)).joinToString(valueSeparator))
stream.writeString(NewLine)
}

View file

@ -1,7 +1,7 @@
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.dankito.banking.client.model.AccountTransaction
import net.dankito.banking.fints.model.TanChallenge
import net.codinux.banking.fints.model.TanChallenge
import react.*
import react.dom.*
import styled.styledDiv

View file

@ -2,8 +2,8 @@ import io.ktor.util.encodeBase64
import kotlinx.html.InputType
import kotlinx.html.js.onChangeFunction
import kotlinx.html.style
import net.dankito.banking.fints.model.ImageTanChallenge
import net.dankito.banking.fints.model.TanChallenge
import net.codinux.banking.fints.model.ImageTanChallenge
import net.codinux.banking.fints.model.TanChallenge
import org.w3c.dom.HTMLInputElement
import react.Props
import react.RBuilder

View file

@ -6,11 +6,11 @@ import net.dankito.banking.client.model.parameter.GetAccountDataParameter
import net.dankito.banking.client.model.parameter.TransferMoneyParameter
import net.dankito.banking.client.model.response.GetAccountDataResponse
import net.dankito.banking.client.model.response.TransferMoneyResponse
import net.dankito.banking.fints.FinTsClient
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.webclient.KtorWebClient
import net.dankito.banking.fints.webclient.ProxyingWebClient
import net.codinux.banking.fints.FinTsClient
import net.codinux.banking.fints.callback.SimpleFinTsClientCallback
import net.codinux.banking.fints.model.*
import net.codinux.banking.fints.webclient.KtorWebClient
import net.codinux.banking.fints.webclient.ProxyingWebClient
import net.dankito.utils.multiplatform.log.LoggerFactory
open class Presenter {

View file

@ -1,16 +1,16 @@
// TODO: move to versions.gradle
ext {
appVersionName = '1.0.0-Alpha-11'
appVersionName = "1.0.0-Alpha-16-SNAPSHOT"
/* Test */
assertJVersion = '3.12.2'
assertJVersion = "3.12.2"
mockitoVersion = '2.22.0'
mockitoKotlinVersion = '1.6.0'
mockitoVersion = "2.22.0"
mockitoKotlinVersion = "1.6.0"
logbackVersion = '1.2.3'
logbackVersion = "1.2.3"
}
buildscript {
@ -48,4 +48,10 @@ task publishAllToMavenLocal {
dependsOn = [
"fints4k:publishToMavenLocal",
]
}
task publishAll {
dependsOn = [
"fints4k:publish",
]
}

26
docs/Vokabular.md Normal file
View file

@ -0,0 +1,26 @@
| | |
|--------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|
| Geschäftsvorfall | Business Transaction / Job |
| Verwendungszweck | Remittance information, reference, (payment) purpose |
| Überweisung | Remittance (techn.), money transfer, bank transfer, wire transfer (Amerik.), credit transfer |
| Buchungsschlüssel | posting key |
| Buchungstext | posting text |
| | |
| Ende-zu-Ende Referenz | End to End Reference |
| Kundenreferenz | Reference of the submitting customer |
| Mandatsreferenz | mandate reference |
| Creditor Identifier | Creditor Identifier |
| Originators Identification Code | Originators Identification Code |
| Compensation Amount | Compensation Amount |
| Original Amount | Original Amount |
| Abweichender Überweisender (CT-AT08) / Abweichender Zahlungsempfänger (DD-AT38) | payers/debtors reference party (for credit transfer / payees / creditors reference party (for a direct debit) |
| Abweichender Zahlungsempfänger (CT-AT28) / Abweichender Zahlungspflichtiger (DDAT15) | payees/creditors reference party / payers/debtors reference party |
| | |
| Überweisender | Payer, debtor |
| Zahlungsempfänger | Payee, creditor |
| Zahlungseingang | Payment receipt |
| Lastschrift | direct debit |
| | |
| | |
| Primanoten-Nr. | Journal no. |
| | |

View file

@ -8,11 +8,15 @@ plugins {
kotlin {
jvmToolchain(8)
jvmToolchain(11)
compilerOptions {
// suppresses compiler warning: [EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING] 'expect'/'actual' classes (including interfaces, objects, annotations, enums, and 'actual' typealiases) are in Beta.
freeCompilerArgs.add("-Xexpect-actual-classes")
if (System.getProperty("idea.debugger.dispatch.addr") != null) {
freeCompilerArgs.add("-Xdebug")
}
}
@ -34,7 +38,7 @@ kotlin {
browser {
testTask {
useKarma {
// useChromeHeadless()
useChromeHeadless()
useFirefoxHeadless()
}
}
@ -43,10 +47,13 @@ kotlin {
nodejs()
}
// wasmJs() // ktor is not available for wasmJs yet
linuxX64()
mingwX64()
iosX64()
iosArm64()
iosSimulatorArm64()
macosX64()
@ -69,8 +76,9 @@ kotlin {
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinxSerializationVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializationVersion")
implementation("net.codinux.log:kmp-log:$klfVersion")
implementation("net.codinux.log:klf:$klfVersion")
}
}

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints
package net.codinux.banking.fints
import net.dankito.banking.client.model.parameter.FinTsClientParameter
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
@ -6,15 +6,21 @@ import net.dankito.banking.client.model.parameter.TransferMoneyParameter
import net.dankito.banking.client.model.response.ErrorCode
import net.dankito.banking.client.model.response.GetAccountDataResponse
import net.dankito.banking.client.model.response.TransferMoneyResponse
import net.dankito.banking.fints.callback.FinTsClientCallback
import net.dankito.banking.fints.config.FinTsClientConfiguration
import net.dankito.banking.fints.mapper.FinTsModelMapper
import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.response.client.FinTsClientResponse
import net.dankito.banking.fints.response.client.GetAccountInfoResponse
import net.dankito.banking.fints.response.client.GetAccountTransactionsResponse
import net.dankito.banking.fints.response.segments.AccountType
import net.dankito.banking.fints.util.BicFinder
import net.codinux.banking.fints.callback.FinTsClientCallback
import net.codinux.banking.fints.config.FinTsClientConfiguration
import net.codinux.banking.fints.mapper.FinTsModelMapper
import net.codinux.banking.fints.messages.datenelemente.implementierte.KundensystemID
import net.codinux.banking.fints.model.*
import net.codinux.banking.fints.response.client.FinTsClientResponse
import net.codinux.banking.fints.response.client.GetAccountInfoResponse
import net.codinux.banking.fints.response.client.GetAccountTransactionsResponse
import net.codinux.banking.fints.response.segments.AccountType
import net.codinux.banking.fints.response.segments.BankParameters
import net.codinux.banking.fints.util.BicFinder
import net.codinux.log.LogLevel
import net.codinux.log.LoggerFactory
import kotlin.js.JsName
import kotlin.jvm.JvmName
open class FinTsClient(
@ -35,56 +41,59 @@ open class FinTsClient(
protected open val bicFinder = BicFinder()
init {
LoggerFactory.getLogger("net.codinux.banking.fints.log.MessageLogCollector").level = if (config.options.appendFinTsMessagesToLog) {
LogLevel.Debug
} else {
null
}
}
open suspend fun getAccountDataAsync(bankCode: String, loginName: String, password: String): GetAccountDataResponse {
return getAccountDataAsync(GetAccountDataParameter(bankCode, loginName, password))
}
open suspend fun getAccountDataAsync(param: GetAccountDataParameter): GetAccountDataResponse {
val finTsServerAddress = config.finTsServerAddressFinder.findFinTsServerAddress(param.bankCode)
if (finTsServerAddress.isNullOrBlank()) {
return GetAccountDataResponse(ErrorCode.BankDoesNotSupportFinTs3, "Either bank does not support FinTS 3.0 or we don't know its FinTS server address", null, listOf())
}
val basicAccountDataResponse = getRequiredDataToSendUserJobs(param)
val bank = basicAccountDataResponse.finTsModel
val bank = mapper.mapToBankData(param, finTsServerAddress)
val accounts = param.accounts
if (accounts.isNullOrEmpty() || param.retrieveOnlyAccountInfo) { // then first retrieve customer's bank accounts
val getAccountInfoResponse = getAccountInfo(param, bank)
if (getAccountInfoResponse.successful == false || param.retrieveOnlyAccountInfo) {
return GetAccountDataResponse(mapper.mapErrorCode(getAccountInfoResponse), mapper.mapErrorMessages(getAccountInfoResponse), null,
getAccountInfoResponse.messageLog, bank)
} else {
return getAccountData(param, getAccountInfoResponse.bank, getAccountInfoResponse.bank.accounts, getAccountInfoResponse)
}
if (basicAccountDataResponse.successful == false || param.retrieveOnlyAccountInfo || bank == null) {
return GetAccountDataResponse(basicAccountDataResponse.error, basicAccountDataResponse.errorMessage, null,
basicAccountDataResponse.messageLog, bank)
} else {
return getAccountData(param, bank, accounts.map { mapper.mapToAccountData(it, param) }, null)
return getAccountData(param, bank, bank.accounts, basicAccountDataResponse.messageLog)
}
}
protected open suspend fun getAccountData(param: GetAccountDataParameter, bank: BankData, accounts: List<AccountData>, previousJobResponse: FinTsClientResponse?): GetAccountDataResponse {
protected open suspend fun getAccountData(param: GetAccountDataParameter, bank: BankData, accounts: List<AccountData>, previousJobMessageLog: List<MessageLogEntry>?): GetAccountDataResponse {
val retrievedTransactionsResponses = mutableListOf<GetAccountTransactionsResponse>()
val accountsSupportingRetrievingTransactions = accounts.filter { it.supportsRetrievingBalance || it.supportsRetrievingAccountTransactions }
if (accountsSupportingRetrievingTransactions.isEmpty()) {
val errorMessage = "None of the accounts ${accounts.map { it.productName }} supports retrieving balance or transactions" // TODO: translate
return GetAccountDataResponse(ErrorCode.NoneOfTheAccountsSupportsRetrievingData, errorMessage, mapper.map(bank), previousJobResponse?.messageLog ?: listOf(), bank)
return GetAccountDataResponse(ErrorCode.NoneOfTheAccountsSupportsRetrievingData, errorMessage, mapper.map(bank), previousJobMessageLog ?: listOf(), bank)
}
accountsSupportingRetrievingTransactions.forEach { account ->
retrievedTransactionsResponses.add(getAccountData(param, bank, account))
for (account in accountsSupportingRetrievingTransactions) {
val response = getAccountTransactions(param, bank, account)
retrievedTransactionsResponses.add(response)
if (response.tanRequiredButWeWereToldToAbortIfSo || response.userCancelledAction) { // if user cancelled action or TAN is required but we were told to abort then, then don't continue with next account
break
}
}
val unsuccessfulJob = retrievedTransactionsResponses.firstOrNull { it.successful == false }
val errorCode = unsuccessfulJob?.let { mapper.mapErrorCode(it) }
?: if (retrievedTransactionsResponses.size < accountsSupportingRetrievingTransactions.size) ErrorCode.DidNotRetrieveAllAccountData else null
return GetAccountDataResponse(errorCode, mapper.mapErrorMessages(unsuccessfulJob), mapper.map(bank, retrievedTransactionsResponses),
mapper.mergeMessageLog(previousJobResponse, *retrievedTransactionsResponses.toTypedArray()), bank)
return GetAccountDataResponse(errorCode, mapper.mapErrorMessages(unsuccessfulJob), mapper.map(bank, retrievedTransactionsResponses, param.retrieveTransactionsTo),
mapper.mergeMessageLog(previousJobMessageLog, *retrievedTransactionsResponses.map { it.messageLog }.toTypedArray()), bank)
}
protected open suspend fun getAccountData(param: GetAccountDataParameter, bank: BankData, account: AccountData): GetAccountTransactionsResponse {
val context = JobContext(JobContextType.GetTransactions, this.callback, config, bank, account)
protected open suspend fun getAccountTransactions(param: GetAccountDataParameter, bank: BankData, account: AccountData): GetAccountTransactionsResponse {
val context = JobContext(JobContextType.GetTransactions, this.callback, config, bank, account, param.preferredTanMethods, param.tanMethodsNotSupportedByApplication, param.preferredTanMedium)
return config.jobExecutor.getTransactionsAsync(context, mapper.toGetAccountTransactionsParameter(param, bank, account))
}
@ -142,7 +151,7 @@ open class FinTsClient(
accountToUse = selectedAccount
}
val context = JobContext(JobContextType.TransferMoney, this.callback, config, bank, accountToUse)
val context = JobContext(JobContextType.TransferMoney, this.callback, config, bank, accountToUse, param.preferredTanMethods, param.tanMethodsNotSupportedByApplication, param.preferredTanMedium)
val response = config.jobExecutor.transferMoneyAsync(context, BankTransferData(param.recipientName, param.recipientAccountIdentifier, recipientBankIdentifier,
param.amount, param.reference, param.instantPayment))
@ -163,25 +172,65 @@ open class FinTsClient(
return null
}
/**
* Ensures all basic data to initialize a dialog with strong customer authorization is retrieved so you can send your
* actual jobs (Geschäftsvorfälle) to your bank's FinTS server.
*
* These data include:
* - Bank communication data like FinTS server address, BIC, bank name, bank code used for FinTS.
* - BPD (BankParameterDaten): bank name, BPD version, supported languages, supported HBCI versions, supported TAN methods,
* max count jobs per message (Anzahl Geschäftsvorfallsarten) (see [BankParameters] [BankParameters](src/commonMain/kotlin/net/codinux/banking/fints/response/segmentsBankParameters) ).
* - Min and max online banking password length, min TAN length, hint for login name (for all: if available)
* - UPD (UserParameterDaten): username, UPD version.
* - Customer system ID (Kundensystem-ID, see [KundensystemID]), TAN methods available for user and may user's TAN media.
* - Which jobs the bank supports and which jobs need strong customer authorization (= require HKTAN segment).
* - Which jobs the user is allowed to use.
* - Which jobs can be called for a specific bank account.
*
* When implementing your own jobs, call this method first, then send an init dialog message and in next message your actual jobs.
*
* More or less implements everything of 02 FinTS_3.0_Formals.pdf so that you can start directly with the jobs from
* 04 FinTS_3.0_Messages_Geschaeftsvorfaelle.pdf
*/
open suspend fun getRequiredDataToSendUserJobs(param: FinTsClientParameter): net.dankito.banking.client.model.response.FinTsClientResponse {
param.finTsModelOrDeserialized?.let { finTsModel ->
return net.dankito.banking.client.model.response.FinTsClientResponse(null, null, emptyList(), finTsModel)
}
val defaultValues = (param as? GetAccountDataParameter)?.defaultBankValues
val finTsServerAddress = defaultValues?.finTs3ServerAddress ?: config.finTsServerAddressFinder.findFinTsServerAddress(param.bankCode)
if (finTsServerAddress.isNullOrBlank()) {
return net.dankito.banking.client.model.response.FinTsClientResponse(ErrorCode.BankDoesNotSupportFinTs3, "Either bank does not support FinTS 3.0 or we don't know its FinTS server address", emptyList(), null)
}
val bank = mapper.mapToBankData(param, finTsServerAddress, defaultValues)
val getAccountInfoResponse = getAccountInfo(param, bank)
return net.dankito.banking.client.model.response.FinTsClientResponse(mapper.mapErrorCode(getAccountInfoResponse), mapper.mapErrorMessages(getAccountInfoResponse),
getAccountInfoResponse.messageLog, bank)
}
protected open suspend fun getAccountInfo(param: FinTsClientParameter, bank: BankData): GetAccountInfoResponse {
param.finTsModel?.let {
param.finTsModelOrDeserialized?.let {
// TODO: implement
// return GetAccountInfoResponse(it)
}
val context = JobContext(JobContextType.GetAccountInfo, this.callback, config, bank)
val context = JobContext(JobContextType.GetAccountInfo, this.callback, config, bank, null, param.preferredTanMethods, param.tanMethodsNotSupportedByApplication, param.preferredTanMedium)
/* First dialog: Get user's basic data like BPD, customer system ID and her TAN methods */
val newUserInfoResponse = config.jobExecutor.retrieveBasicDataLikeUsersTanMethods(context, param.preferredTanMethods, param.preferredTanMedium)
val newUserInfoResponse = config.jobExecutor.retrieveBasicDataLikeUsersTanMethods(context)
/* Second dialog, executed in retrieveBasicDataLikeUsersTanMethods() if required: some banks require that in order to initialize a dialog with
strong customer authorization TAN media is required */
if (newUserInfoResponse.successful == false) { // bank parameter (FinTS server address, ...) already seem to be wrong
return GetAccountInfoResponse(context, newUserInfoResponse)
}
/* Second dialog, executed in retrieveBasicDataLikeUsersTanMethods() if required: some banks require that in order to initialize a dialog with
strong customer authorization TAN media is required */
val getAccountsResponse = config.jobExecutor.getAccounts(context)
return GetAccountInfoResponse(context, getAccountsResponse)

View file

@ -1,17 +1,14 @@
package net.dankito.banking.fints
package net.codinux.banking.fints
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.datetime.*
import net.dankito.banking.fints.callback.FinTsClientCallback
import net.dankito.banking.fints.config.FinTsClientConfiguration
import net.dankito.banking.fints.extensions.minusDays
import net.dankito.banking.fints.extensions.todayAtEuropeBerlin
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.*
import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.response.BankResponse
import net.dankito.banking.fints.response.client.*
import net.dankito.banking.fints.webclient.IWebClient
import net.codinux.banking.fints.callback.FinTsClientCallback
import net.codinux.banking.fints.config.FinTsClientConfiguration
import net.codinux.banking.fints.extensions.minusDays
import net.codinux.banking.fints.extensions.todayAtEuropeBerlin
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.*
import net.codinux.banking.fints.model.*
import net.codinux.banking.fints.response.BankResponse
import net.codinux.banking.fints.response.client.*
/**
@ -26,19 +23,6 @@ open class FinTsClientDeprecated(
constructor(callback: FinTsClientCallback) : this(FinTsClientConfiguration(), callback)
/**
* Retrieves information about bank (e.g. supported HBCI versions, FinTS server address,
* supported jobs, ...).
*
* On success [bank] parameter is updated afterwards.
*/
open fun getAnonymousBankInfoAsync(bank: BankData, callback: (FinTsClientResponse) -> Unit) {
GlobalScope.launch {
callback(getAnonymousBankInfo(bank))
}
}
/**
* Retrieves information about bank (e.g. supported HBCI versions, FinTS server address,
* supported jobs, ...).
@ -53,13 +37,13 @@ open class FinTsClientDeprecated(
}
open suspend fun addAccountAsync(parameter: AddAccountParameter): AddAccountResponse {
val bank = parameter.bank
val context = JobContext(JobContextType.AddAccount, this.callback, config, bank)
open suspend fun addAccountAsync(param: AddAccountParameter): AddAccountResponse {
val bank = param.bank
val context = JobContext(JobContextType.AddAccount, this.callback, config, bank, null, param.preferredTanMethods, param.tanMethodsNotSupportedByApplication, param.preferredTanMedium)
/* First dialog: Get user's basic data like BPD, customer system ID and her TAN methods */
val newUserInfoResponse = config.jobExecutor.retrieveBasicDataLikeUsersTanMethods(context, parameter.preferredTanMethods, parameter.preferredTanMedium)
val newUserInfoResponse = config.jobExecutor.retrieveBasicDataLikeUsersTanMethods(context)
if (newUserInfoResponse.successful == false) { // bank parameter (FinTS server address, ...) already seem to be wrong
return AddAccountResponse(context, newUserInfoResponse)
@ -68,7 +52,7 @@ open class FinTsClientDeprecated(
/* Second dialog, executed in retrieveBasicDataLikeUsersTanMethods() if required: some banks require that in order to initialize a dialog with
strong customer authorization TAN media is required */
return addAccountGetAccountsAndTransactions(context, parameter)
return addAccountGetAccountsAndTransactions(context, param)
}
protected open suspend fun addAccountGetAccountsAndTransactions(context: JobContext, parameter: AddAccountParameter): AddAccountResponse {
@ -134,11 +118,11 @@ open class FinTsClientDeprecated(
return GetAccountTransactionsParameter(bank, account, account.supportsRetrievingBalance, ninetyDaysAgo, abortIfTanIsRequired = true)
}
open suspend fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter): GetAccountTransactionsResponse {
open suspend fun getAccountTransactionsAsync(param: GetAccountTransactionsParameter): GetAccountTransactionsResponse {
val context = JobContext(JobContextType.GetTransactions, this.callback, config, parameter.bank, parameter.account)
val context = JobContext(JobContextType.GetTransactions, this.callback, config, param.bank, param.account)
return config.jobExecutor.getTransactionsAsync(context, parameter)
return config.jobExecutor.getTransactionsAsync(context, param)
}
@ -151,7 +135,7 @@ open class FinTsClientDeprecated(
}
open suspend fun changeTanMedium(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData): FinTsClientResponse {
open suspend fun changeTanMedium(newActiveTanMedium: TanMedium, bank: BankData): FinTsClientResponse {
val context = JobContext(JobContextType.ChangeTanMedium, this.callback, config, bank)
val response = config.jobExecutor.changeTanMedium(context, newActiveTanMedium)

View file

@ -1,11 +1,11 @@
package net.dankito.banking.fints
package net.codinux.banking.fints
import net.dankito.banking.fints.callback.FinTsClientCallback
import net.dankito.banking.fints.config.FinTsClientConfiguration
import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.response.client.AddAccountResponse
import net.dankito.banking.fints.response.client.FinTsClientResponse
import net.dankito.banking.fints.response.client.GetAccountTransactionsResponse
import net.codinux.banking.fints.callback.FinTsClientCallback
import net.codinux.banking.fints.config.FinTsClientConfiguration
import net.codinux.banking.fints.model.*
import net.codinux.banking.fints.response.client.AddAccountResponse
import net.codinux.banking.fints.response.client.FinTsClientResponse
import net.codinux.banking.fints.response.client.GetAccountTransactionsResponse
open class FinTsClientForCustomer(

View file

@ -1,26 +1,28 @@
package net.dankito.banking.fints
package net.codinux.banking.fints
import kotlinx.coroutines.delay
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import net.codinux.banking.fints.extensions.*
import net.codinux.log.logger
import net.dankito.banking.fints.messages.MessageBuilder
import net.dankito.banking.fints.messages.MessageBuilderResult
import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.*
import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId
import net.dankito.banking.fints.messages.segmente.id.ISegmentId
import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.model.mapper.ModelMapper
import net.dankito.banking.fints.response.BankResponse
import net.dankito.banking.fints.response.InstituteSegmentId
import net.dankito.banking.fints.response.client.*
import net.dankito.banking.fints.response.segments.*
import net.dankito.banking.fints.tan.FlickerCodeDecoder
import net.dankito.banking.fints.tan.TanImageDecoder
import net.dankito.banking.fints.util.TanMethodSelector
import net.dankito.banking.fints.extensions.minusDays
import net.dankito.banking.fints.extensions.todayAtEuropeBerlin
import net.dankito.banking.fints.extensions.todayAtSystemDefaultTimeZone
import net.codinux.banking.fints.messages.MessageBuilder
import net.codinux.banking.fints.messages.MessageBuilderResult
import net.codinux.banking.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.*
import net.codinux.banking.fints.messages.segmente.id.CustomerSegmentId
import net.codinux.banking.fints.messages.segmente.id.ISegmentId
import net.codinux.banking.fints.model.*
import net.codinux.banking.fints.model.mapper.ModelMapper
import net.codinux.banking.fints.response.BankResponse
import net.codinux.banking.fints.response.InstituteSegmentId
import net.codinux.banking.fints.response.client.*
import net.codinux.banking.fints.response.segments.*
import net.codinux.banking.fints.tan.FlickerCodeDecoder
import net.codinux.banking.fints.tan.TanImageDecoder
import net.codinux.banking.fints.util.TanMethodSelector
import net.codinux.log.Log
import kotlin.math.max
import kotlin.time.Duration.Companion.seconds
/**
@ -72,8 +74,7 @@ open class FinTsJobExecutor(
*
* Be aware this method resets BPD, UPD and selected TAN method!
*/
open suspend fun retrieveBasicDataLikeUsersTanMethods(context: JobContext, preferredTanMethods: List<TanMethodType>? = null, preferredTanMedium: String? = null,
closeDialog: Boolean = false): BankResponse {
open suspend fun retrieveBasicDataLikeUsersTanMethods(context: JobContext): BankResponse {
val bank = context.bank
// just to ensure settings are in its initial state and that bank sends us bank parameter (BPD),
@ -89,7 +90,7 @@ open class FinTsJobExecutor(
bank.resetSelectedTanMethod()
// this is the only case where Einschritt-TAN-Verfahren is accepted: to get user's TAN methods
context.startNewDialog(closeDialog, versionOfSecurityProcedure = VersionDesSicherheitsverfahrens.Version_1)
context.startNewDialog(versionOfSecurityProcedure = VersionDesSicherheitsverfahrens.Version_1)
val message = messageBuilder.createInitDialogMessage(context)
@ -102,12 +103,10 @@ open class FinTsJobExecutor(
if (bank.tanMethodsAvailableForUser.isEmpty()) { // could not retrieve supported tan methods for user
return getTanMethodsResponse
} else {
getUsersTanMethod(context, preferredTanMethods)
getUsersTanMethod(context)
if (bank.isTanMethodSelected == false) {
return getTanMethodsResponse
} else if (bank.tanMedia.isEmpty() && isJobSupported(bank, CustomerSegmentId.TanMediaList)) { // tan media not retrieved yet
getTanMediaList(context, TanMedienArtVersion.Alle, TanMediumKlasse.AlleMedien, preferredTanMedium)
if (bank.isTanMethodSelected && bank.tanMedia.isEmpty() && bank.tanMethodsAvailableForUser.any { it.nameOfTanMediumRequired } && isJobSupported(bank, CustomerSegmentId.TanMediaList)) { // tan media not retrieved yet
getTanMediaList(context, TanMedienArtVersion.Alle, TanMediumKlasse.AlleMedien, context.preferredTanMedium)
return getTanMethodsResponse // TODO: judge if bank requires selecting TAN media and if though evaluate getTanMediaListResponse
} else {
@ -146,6 +145,7 @@ open class FinTsJobExecutor(
return BankResponse(true, internalError = "Die TAN Verfahren der Bank konnten nicht ermittelt werden") // TODO: translate
} else {
bank.tanMethodsAvailableForUser = bank.tanMethodsSupportedByBank
.filterNot { context.tanMethodsNotSupportedByApplication.contains(it.type) }
val didSelectTanMethod = getUsersTanMethod(context)
@ -202,6 +202,29 @@ open class FinTsJobExecutor(
var balance: Money? = balanceResponse?.getFirstSegmentById<BalanceSegment>(InstituteSegmentId.Balance)?.let {
Money(it.balance, it.currency)
}
// TODO: for larger portfolios there can be a Aufsetzpunkt, but for balances we currently do not support sending multiple messages
val statementOfHoldings = balanceResponse?.getFirstSegmentById<SecuritiesAccountBalanceSegment>(InstituteSegmentId.SecuritiesAccountBalance)?.let {
val statementOfHoldings = it.statementOfHoldings
val statementOfHolding = statementOfHoldings.firstOrNull { it.totalBalance != null }
if (statementOfHolding != null) {
balance = Money(statementOfHolding.totalBalance!!, statementOfHolding.currency ?: Currency.DefaultCurrencyCode)
}
statementOfHoldings
} ?: emptyList()
if (parameter.account.supportsRetrievingAccountTransactions == false) {
if (balanceResponse == null) {
return GetAccountTransactionsResponse(context, BankResponse(false, "Balance could not be retrieved"), RetrievedAccountData.unsuccessful(parameter.account))
} else {
val successful = balance != null || balanceResponse.tanRequiredButWeWereToldToAbortIfSo
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, emptyList(), emptyList(), statementOfHoldings, Instant.nowExt(), null, null, balanceResponse?.internalError)
return GetAccountTransactionsResponse(context, balanceResponse, retrievedData)
}
}
val bookedTransactions = mutableSetOf<AccountTransaction>()
val unbookedTransactions = mutableSetOf<Any>()
@ -217,27 +240,30 @@ open class FinTsJobExecutor(
context.bank, parameter.account)
bookedTransactions.addAll(chunkTransaction)
remainingMt940String = remainder
remainingMt940String = remainder ?: ""
parameter.retrievedChunkListener?.invoke(bookedTransactions)
}
response.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(InstituteSegmentId.CreditCardTransactions)?.let { creditCardTransactionsSegment ->
balance = Money(creditCardTransactionsSegment.balance.amount, creditCardTransactionsSegment.balance.currency ?: "EUR")
bookedTransactions.addAll(creditCardTransactionsSegment.transactions.map { AccountTransaction(parameter.account, it.amount, it.description, it.bookingDate, it.transactionDescriptionBase ?: "", null, null, "", it.valueDate) })
bookedTransactions.addAll(creditCardTransactionsSegment.transactions.map { AccountTransaction(parameter.account, it.amount, it.description, it.bookingDate, it.valueDate, it.transactionDescriptionBase ?: "", null, null) })
}
}
val startTime = Instant.nowExt()
val response = getAndHandleResponseForMessage(context, message)
closeDialog(context)
val successful = response.tanRequiredButWeWereToldToAbortIfSo
|| (response.successful && (parameter.alsoRetrieveBalance == false || balance != null))
|| (response.successful && (parameter.alsoRetrieveBalance == false || balance != null))
|| (parameter.account.supportsRetrievingAccountTransactions == false && balance != null)
val fromDate = parameter.fromDate
?: parameter.account.countDaysForWhichTransactionsAreKept?.let { LocalDate.todayAtSystemDefaultTimeZone().minusDays(it) }
?: parameter.account.serverTransactionsRetentionDays?.let { LocalDate.todayAtSystemDefaultTimeZone().minusDays(it) }
?: bookedTransactions.minByOrNull { it.valueDate }?.valueDate
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError)
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, statementOfHoldings, startTime, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError)
return GetAccountTransactionsResponse(context, response, retrievedData,
if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null)
@ -307,6 +333,7 @@ open class FinTsJobExecutor(
bank.selectedTanMedium = preferredTanMedium?.let { bank.tanMedia.firstOrNull { it.mediumName == preferredTanMedium } }
?: bank.selectedTanMedium?.let { selected -> bank.tanMedia.firstOrNull { it.mediumName == selected.mediumName } } // try to find selectedTanMedium in new TanMedia instances
?: bank.tanMedia.firstOrNull { it.status == TanMediumStatus.Aktiv && it.mediumName != null }
?: bank.tanMedia.firstOrNull { it.mediumName != null }
}
@ -314,7 +341,7 @@ open class FinTsJobExecutor(
}
open suspend fun changeTanMedium(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium): BankResponse {
open suspend fun changeTanMedium(context: JobContext, newActiveTanMedium: TanMedium): BankResponse {
val bank = context.bank
if (bank.changeTanMediumParameters?.enteringAtcAndTanRequired == true) {
@ -331,7 +358,7 @@ open class FinTsJobExecutor(
}
}
protected open suspend fun sendChangeTanMediumMessage(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium, enteredAtc: EnterTanGeneratorAtcResult?): BankResponse {
protected open suspend fun sendChangeTanMediumMessage(context: JobContext, newActiveTanMedium: TanMedium, enteredAtc: EnterTanGeneratorAtcResult?): BankResponse {
return sendMessageInNewDialogAndHandleResponse(context, null, true) {
messageBuilder.createChangeTanMediumMessage(context, newActiveTanMedium, enteredAtc?.tan, enteredAtc?.atc)
@ -374,20 +401,36 @@ open class FinTsJobExecutor(
protected open suspend fun handleEnteringTanRequired(context: JobContext, tanResponse: TanResponse, response: BankResponse): BankResponse {
// on all platforms run on Dispatchers.Main, but on iOS skip this (or wrap in withContext(Dispatchers.IO) )
// val enteredTanResult = GlobalScope.async {
val tanChallenge = createTanChallenge(tanResponse, modelMapper.mapToActionRequiringTan(context.type), context.bank, context.account)
val tanChallenge = createTanChallenge(tanResponse, modelMapper.mapToActionRequiringTan(context.type), context.bank, context.account)
context.callback.enterTan(tanChallenge)
context.callback.enterTan(tanChallenge)
while (tanChallenge.enterTanResult == null) {
delay(250)
mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context, tanChallenge, tanResponse)
mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context, tanChallenge, tanResponse)
var invocationCount = 0 // TODO: remove again
// TODO: add a timeout of e.g. 30 min
while (tanChallenge.isEnteringTanDone == false) {
delay(500)
if (++invocationCount % 10 == 0) {
Log.info { "Waiting for TAN input invocation count: $invocationCount" }
}
val now = Instant.nowExt()
if ((tanChallenge.tanExpirationTime != null && now > tanChallenge.tanExpirationTime) ||
// most TANs a valid 5 - 15 minutes. So terminate wait process after that time
(tanChallenge.tanExpirationTime == null && now > tanChallenge.challengeCreationTimestamp.plusMinutes(15))) {
if (tanChallenge.isEnteringTanDone == false) {
Log.info { "Terminating waiting for TAN input" } // TODO: remove again
tanChallenge.tanExpired()
}
break
}
}
val enteredTanResult = tanChallenge.enterTanResult!!
// }
return handleEnterTanResult(context, enteredTanResult, tanResponse, response)
}
@ -401,27 +444,82 @@ open class FinTsJobExecutor(
return when (tanMethod.type) {
TanMethodType.ChipTanFlickercode ->
FlickerCodeTanChallenge(
FlickerCodeDecoder().decodeChallenge(challenge, tanMethod.hhdVersion ?: HHDVersion.HHD_1_4), // HHD 1.4 is currently the most used version
forAction, messageToShowToUser, challenge, tanMethod, tanResponse.tanMediaIdentifier, bank, account)
FlickerCodeDecoder().decodeChallenge(challenge, tanMethod.hhdVersion ?: getFallbackHhdVersion(challenge)),
forAction, messageToShowToUser, challenge, tanMethod, tanResponse.tanMediaIdentifier, bank, account, tanResponse.tanExpirationTime)
TanMethodType.ChipTanQrCode, TanMethodType.ChipTanPhotoTanMatrixCode,
TanMethodType.QrCode, TanMethodType.photoTan ->
ImageTanChallenge(TanImageDecoder().decodeChallenge(challenge), forAction, messageToShowToUser, challenge, tanMethod, tanResponse.tanMediaIdentifier, bank, account)
ImageTanChallenge(TanImageDecoder().decodeChallenge(challenge), forAction, messageToShowToUser, challenge, tanMethod, tanResponse.tanMediaIdentifier, bank, account, tanResponse.tanExpirationTime)
else -> TanChallenge(forAction, messageToShowToUser, challenge, tanMethod, tanResponse.tanMediaIdentifier, bank, account)
else -> TanChallenge(forAction, messageToShowToUser, challenge, tanMethod, tanResponse.tanMediaIdentifier, bank, account, tanResponse.tanExpirationTime)
}
}
protected open fun mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge, tanResponse: TanResponse) {
protected open fun getFallbackHhdVersion(challenge: String): HHDVersion {
if (challenge.length <= 35) { // is this true in all circumstances?
return HHDVersion.HHD_1_3
}
return HHDVersion.HHD_1_4 // HHD 1.4 is currently the most used version
}
protected open suspend fun mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge, tanResponse: TanResponse) {
context.bank.selectedTanMethod.decoupledParameters?.let { decoupledTanMethodParameters ->
if (tanResponse.tanProcess == TanProcess.AppTan && decoupledTanMethodParameters.periodicStateRequestsAllowed) {
automaticallyRetrieveIfUserEnteredDecoupledTan(context, tanChallenge)
if (decoupledTanMethodParameters.periodicStateRequestsAllowed) {
val responseAfterApprovingDecoupledTan =
automaticallyRetrieveIfUserEnteredDecoupledTan(context, tanChallenge, tanResponse, decoupledTanMethodParameters)
if (responseAfterApprovingDecoupledTan != null) {
tanChallenge.userApprovedDecoupledTan(responseAfterApprovingDecoupledTan)
} else {
tanChallenge.userDidNotEnterTan()
}
}
}
}
protected open fun automaticallyRetrieveIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge) {
protected open suspend fun automaticallyRetrieveIfUserEnteredDecoupledTan(context: JobContext, tanChallenge: TanChallenge, tanResponse: TanResponse, parameters: DecoupledTanMethodParameters): BankResponse? {
log.info { "automaticallyRetrieveIfUserEnteredDecoupledTan() called for $tanChallenge" }
delay(max(5, parameters.initialDelayInSecondsForStateRequest).seconds)
var iteration = 0
val minWaitTime = when {
parameters.maxNumberOfStateRequests <= 10 -> 30
parameters.maxNumberOfStateRequests <= 24 -> 10
else -> 3
}
val delayForNextStateRequest = max(minWaitTime, parameters.delayInSecondsForNextStateRequest).seconds
while (iteration < parameters.maxNumberOfStateRequests) {
try {
val message = messageBuilder.createDecoupledTanStatusMessage(context, tanResponse)
val response = getAndHandleResponseForMessage(context, message)
val tanFeedbacks = response.segmentFeedbacks.filter { it.referenceSegmentNumber == MessageBuilder.SignedMessagePayloadFirstSegmentNumber }
if (tanFeedbacks.isNotEmpty()) {
// new feedback code for Decoupled TAN: 0900 Sicherheitsfreigabe gültig
// Sparkasse responds for pushTan with: HIRMS:4:2:3+0020::Der Auftrag wurde ausgeführt.+0020::Die gebuchten Umsätze wurden übermittelt.'
val isTanApproved = tanFeedbacks.any { it.feedbacks.any { it.responseCode == 900 || it.responseCode == 20 } }
if (isTanApproved) {
return response
}
}
iteration++
// sometimes delayInSecondsForNextStateRequests is only 1 or 2 seconds, that's too fast i think
delay(delayForNextStateRequest)
} catch (e: Throwable) {
log.error(e) { "Could not check status of Decoupled TAN" }
return null
}
}
tanChallenge.tanExpired()
return null
}
protected open suspend fun handleEnterTanResult(context: JobContext, enteredTanResult: EnterTanResult, tanResponse: TanResponse,
@ -429,19 +527,18 @@ open class FinTsJobExecutor(
if (enteredTanResult.changeTanMethodTo != null) {
return handleUserAsksToChangeTanMethodAndResendLastMessage(context, enteredTanResult.changeTanMethodTo)
}
else if (enteredTanResult.changeTanMediumTo is TanGeneratorTanMedium) {
} else if (enteredTanResult.changeTanMediumTo != null) {
return handleUserAsksToChangeTanMediumAndResendLastMessage(context, enteredTanResult.changeTanMediumTo,
enteredTanResult.changeTanMediumResultCallback)
}
else if (enteredTanResult.enteredTan == null) {
} else if (enteredTanResult.userApprovedDecoupledTan == true && enteredTanResult.responseAfterApprovingDecoupledTan != null) {
return enteredTanResult.responseAfterApprovingDecoupledTan
} else if (enteredTanResult.enteredTan == null) {
// i tried to send a HKTAN with cancelJob = true but then i saw there are no tan methods that support cancellation (at least not at my bank)
// but it's not required anyway, tan times out after some time. Simply don't respond anything and close dialog
response.tanRequiredButUserDidNotEnterOne = true
return response
}
else {
} else {
return sendTanToBank(context, enteredTanResult.enteredTan, tanResponse)
}
}
@ -465,7 +562,7 @@ open class FinTsJobExecutor(
return resendMessageInNewDialog(context, lastCreatedMessage)
}
protected open suspend fun handleUserAsksToChangeTanMediumAndResendLastMessage(context: JobContext, changeTanMediumTo: TanGeneratorTanMedium,
protected open suspend fun handleUserAsksToChangeTanMediumAndResendLastMessage(context: JobContext, changeTanMediumTo: TanMedium,
changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?): BankResponse {
val lastCreatedMessage = context.dialog.currentMessage
@ -493,7 +590,8 @@ open class FinTsJobExecutor(
val initDialogResponse = initDialogWithStrongCustomerAuthentication(context)
if (initDialogResponse.successful == false) {
// if lastCreatedMessage was a dialog init message, there's no need to send this message again, we just initialized a new dialog in initDialogWithStrongCustomerAuthentication()
if (initDialogResponse.successful == false || lastCreatedMessage.isDialogInitMessage()) {
return initDialogResponse
} else {
val newMessage = messageBuilder.rebuildMessage(context, lastCreatedMessage)
@ -566,7 +664,7 @@ open class FinTsJobExecutor(
protected open suspend fun initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context: JobContext): BankResponse {
context.startNewDialog(false) // don't know if it's ok for all invocations of this method to set closeDialog to false (was actually only set in getAccounts())
context.startNewDialog() // don't know if it's ok for all invocations of this method to set closeDialog to false (was actually only set in getAccounts())
val message = messageBuilder.createInitDialogMessage(context)
@ -642,7 +740,7 @@ open class FinTsJobExecutor(
return BankResponse(true, noTanMethodSelected = noTanMethodSelected, internalError = errorMessage)
}
open suspend fun getUsersTanMethod(context: JobContext, preferredTanMethods: List<TanMethodType>? = null): Boolean {
open suspend fun getUsersTanMethod(context: JobContext): Boolean {
val bank = context.bank
if (bank.tanMethodsAvailableForUser.size == 1) { // user has only one TAN method -> set it and we're done
@ -650,13 +748,13 @@ open class FinTsJobExecutor(
return true
}
else {
tanMethodSelector.findPreferredTanMethod(bank.tanMethodsAvailableForUser, preferredTanMethods)?.let {
tanMethodSelector.findPreferredTanMethod(bank.tanMethodsAvailableForUser, context.preferredTanMethods, context.tanMethodsNotSupportedByApplication)?.let {
bank.selectedTanMethod = it
return true
}
// we know user's supported tan methods, now ask user which one to select
val suggestedTanMethod = tanMethodSelector.getSuggestedTanMethod(bank.tanMethodsAvailableForUser)
val suggestedTanMethod = tanMethodSelector.getSuggestedTanMethod(bank.tanMethodsAvailableForUser, context.tanMethodsNotSupportedByApplication)
val selectedTanMethod = context.callback.askUserForTanMethod(bank.tanMethodsAvailableForUser, suggestedTanMethod)
@ -677,14 +775,14 @@ open class FinTsJobExecutor(
protected open fun updateBankAndCustomerDataIfResponseSuccessful(context: JobContext, response: BankResponse) {
if (response.successful) {
updateBankAndCustomerData(context.bank, response)
updateBankAndCustomerData(context.bank, response, context)
}
}
protected open fun updateBankAndCustomerData(bank: BankData, response: BankResponse) {
protected open fun updateBankAndCustomerData(bank: BankData, response: BankResponse, context: JobContext) {
updateBankData(bank, response)
modelMapper.updateCustomerData(bank, response)
modelMapper.updateCustomerData(bank, response, context)
}

View file

@ -1,17 +1,18 @@
package net.dankito.banking.fints
package net.codinux.banking.fints
import net.codinux.log.logger
import net.dankito.banking.fints.messages.MessageBuilder
import net.dankito.banking.fints.messages.MessageBuilderResult
import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.response.BankResponse
import net.dankito.banking.fints.response.segments.TanResponse
import net.dankito.banking.fints.util.IBase64Service
import net.dankito.banking.fints.util.PureKotlinBase64Service
import net.dankito.banking.fints.webclient.IWebClient
import net.dankito.banking.fints.webclient.KtorWebClient
import net.dankito.banking.fints.webclient.WebClientResponse
import net.dankito.banking.fints.extensions.getAllExceptionMessagesJoined
import net.codinux.banking.fints.messages.MessageBuilder
import net.codinux.banking.fints.messages.MessageBuilderResult
import net.codinux.banking.fints.model.*
import net.codinux.banking.fints.response.BankResponse
import net.codinux.banking.fints.response.segments.TanResponse
import net.codinux.banking.fints.util.IBase64Service
import net.codinux.banking.fints.util.PureKotlinBase64Service
import net.codinux.banking.fints.webclient.IWebClient
import net.codinux.banking.fints.webclient.KtorWebClient
import net.codinux.banking.fints.webclient.WebClientResponse
import net.codinux.banking.fints.extensions.getAllExceptionMessagesJoined
import net.codinux.banking.fints.response.segments.ReceivedSegment
open class RequestExecutor(
@ -104,9 +105,11 @@ open class RequestExecutor(
try {
val decodedResponse = decodeBase64Response(responseBody)
addMessageLog(context, MessageLogEntryType.Received, decodedResponse)
val parsedResponse = context.responseParser.parse(decodedResponse)
return context.responseParser.parse(decodedResponse)
addMessageLog(context, MessageLogEntryType.Received, decodedResponse, parsedResponse.receivedSegments)
return parsedResponse
} catch (e: Exception) {
logError(context, "Could not decode responseBody:\r\n'$responseBody'", e)
@ -164,8 +167,8 @@ open class RequestExecutor(
}
protected open fun addMessageLog(context: JobContext, type: MessageLogEntryType, message: String) {
context.addMessageLog(type, message)
protected open fun addMessageLog(context: JobContext, type: MessageLogEntryType, message: String, parsedSegments: List<ReceivedSegment> = emptyList()) {
context.addMessageLog(type, message, parsedSegments)
}
protected open fun logError(context: JobContext, message: String, e: Exception?) {

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.callback
package net.codinux.banking.fints.callback
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.banking.fints.model.*
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
import net.codinux.banking.fints.model.*
interface FinTsClientCallback {
@ -25,6 +25,13 @@ interface FinTsClientCallback {
*
* If you do not support entering TAN generator ATC, return [EnterTanGeneratorAtcResult.userDidNotEnterAtc]
*/
suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult
suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanMedium): EnterTanGeneratorAtcResult
/**
* Gets fired when a FinTS message get sent to bank server, a FinTS message is received from bank server or an error occurred.
*
* Be aware, in order that this message gets fired [net.codinux.banking.fints.config.FinTsClientOptions.fireCallbackOnMessageLogs] has to be set to true.
*/
fun messageLogAdded(messageLogEntry: MessageLogEntry)
}

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.callback
package net.codinux.banking.fints.callback
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.banking.fints.model.*
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
import net.codinux.banking.fints.model.*
open class NoOpFinTsClientCallback : FinTsClientCallback {
@ -14,8 +14,12 @@ open class NoOpFinTsClientCallback : FinTsClientCallback {
return tanChallenge.userDidNotEnterTan()
}
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanMedium): EnterTanGeneratorAtcResult {
return EnterTanGeneratorAtcResult.userDidNotEnterAtc()
}
override fun messageLogAdded(messageLogEntry: MessageLogEntry) {
}
}

View file

@ -1,18 +1,19 @@
package net.dankito.banking.fints.callback
package net.codinux.banking.fints.callback
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.banking.fints.model.*
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
import net.codinux.banking.fints.model.*
open class SimpleFinTsClientCallback(
protected open val enterTan: ((tanChallenge: TanChallenge) -> Unit)? = null,
protected open val enterTanGeneratorAtc: ((bank: BankData, tanMedium: TanGeneratorTanMedium) -> EnterTanGeneratorAtcResult)? = null,
protected open val askUserForTanMethod: ((supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?) -> TanMethod?)? = null
protected open val askUserForTanMethod: ((supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?) -> TanMethod?)? = null,
protected open val messageLogAdded: ((MessageLogEntry) -> Unit)? = null,
protected open val enterTanGeneratorAtc: ((bank: BankData, tanMedium: TanMedium) -> EnterTanGeneratorAtcResult)? = null,
protected open val enterTan: ((tanChallenge: TanChallenge) -> Unit)? = null
) : FinTsClientCallback {
constructor() : this(null) // Swift does not support default parameter values -> create constructor overloads
constructor() : this(null as ((tanChallenge: TanChallenge) -> Unit)?) // Swift does not support default parameter values -> create constructor overloads
constructor(enterTan: ((tanChallenge: TanChallenge) -> Unit)?) : this(enterTan, null)
constructor(enterTan: ((tanChallenge: TanChallenge) -> Unit)?) : this(null, null, null, enterTan)
override suspend fun askUserForTanMethod(supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?): TanMethod? {
@ -24,8 +25,12 @@ open class SimpleFinTsClientCallback(
enterTan?.invoke(tanChallenge) ?: run { tanChallenge.userDidNotEnterTan() }
}
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanMedium): EnterTanGeneratorAtcResult {
return enterTanGeneratorAtc?.invoke(bank, tanMedium) ?: EnterTanGeneratorAtcResult.userDidNotEnterAtc()
}
override fun messageLogAdded(messageLogEntry: MessageLogEntry) {
messageLogAdded?.invoke(messageLogEntry)
}
}

View file

@ -1,15 +1,15 @@
package net.dankito.banking.fints.config
package net.codinux.banking.fints.config
import net.dankito.banking.fints.FinTsJobExecutor
import net.dankito.banking.fints.RequestExecutor
import net.dankito.banking.fints.messages.MessageBuilder
import net.dankito.banking.fints.model.mapper.ModelMapper
import net.dankito.banking.fints.util.FinTsServerAddressFinder
import net.dankito.banking.fints.util.IBase64Service
import net.dankito.banking.fints.util.PureKotlinBase64Service
import net.dankito.banking.fints.util.TanMethodSelector
import net.dankito.banking.fints.webclient.IWebClient
import net.dankito.banking.fints.webclient.KtorWebClient
import net.codinux.banking.fints.FinTsJobExecutor
import net.codinux.banking.fints.RequestExecutor
import net.codinux.banking.fints.messages.MessageBuilder
import net.codinux.banking.fints.model.mapper.ModelMapper
import net.codinux.banking.fints.util.FinTsServerAddressFinder
import net.codinux.banking.fints.util.IBase64Service
import net.codinux.banking.fints.util.PureKotlinBase64Service
import net.codinux.banking.fints.util.TanMethodSelector
import net.codinux.banking.fints.webclient.IWebClient
import net.codinux.banking.fints.webclient.KtorWebClient
class FinTsClientConfiguration(
var options: FinTsClientOptions = FinTsClientOptions(),

View file

@ -0,0 +1,40 @@
package net.codinux.banking.fints.config
import net.codinux.banking.fints.model.ProductData
data class FinTsClientOptions(
/**
* If FinTS messages sent to and received from bank servers and errors should be collected. They are then accessible
* via [net.codinux.banking.fints.response.client.FinTsClientResponse.messageLog].
*
* Set to false by default.
*/
val collectMessageLog: Boolean = false,
/**
* If set to true then [net.codinux.banking.fints.callback.FinTsClientCallback.messageLogAdded] get fired when a
* FinTS message get sent to bank server, a FinTS message is received from bank server or an error occurred.
*
* Defaults to false.
*/
val fireCallbackOnMessageLogs: Boolean = false,
/**
* If sensitive data like user name, password, login name should be removed from FinTS messages before being logged.
*
* Defaults to true.
*/
val removeSensitiveDataFromMessageLog: Boolean = true,
val appendFinTsMessagesToLog: Boolean = false,
val closeDialogs: Boolean = true,
val version: String = "1.0.0", // TODO: get version dynamically
val productName: String = "15E53C26816138699C7B6A3E8" // TODO: extract constant // TODO: get product number for fints4k and Bankmeister (if we stick with that name)
) {
val product: ProductData by lazy { ProductData(productName, version) }
}

View file

@ -0,0 +1,12 @@
package net.codinux.banking.fints.extensions
import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.Instant
import kotlinx.datetime.plus
// should actually be named `now()`, but that name is already shadowed by deprecated Instant.Companion.now() method
fun Instant.Companion.nowExt(): Instant = Clock.System.now()
fun Instant.plusMinutes(minutes: Int) = this.plus(minutes, DateTimeUnit.MINUTE)

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.extensions
package net.codinux.banking.fints.extensions
import kotlinx.datetime.*
import kotlin.js.JsName
@ -12,7 +12,7 @@ fun LocalDate.Companion.todayAtSystemDefaultTimeZone(): LocalDate {
}
fun LocalDate.Companion.todayAtEuropeBerlin(): LocalDate {
return nowAt(TimeZone.europeBerlin)
return nowAt(TimeZone.EuropeBerlin)
}
@JsName("nowAtForDate")

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.extensions
package net.codinux.banking.fints.extensions
import kotlinx.datetime.*
@ -9,9 +9,9 @@ fun LocalDateTime.Companion.nowAtUtc(): LocalDateTime {
}
fun LocalDateTime.Companion.nowAtEuropeBerlin(): LocalDateTime {
return nowAt(TimeZone.europeBerlin)
return nowAt(TimeZone.EuropeBerlin)
}
fun LocalDateTime.Companion.nowAt(timeZone: TimeZone): LocalDateTime {
return Clock.System.now().toLocalDateTime(timeZone)
return Instant.nowExt().toLocalDateTime(timeZone)
}

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.extensions
package net.codinux.banking.fints.extensions
fun Int.toStringWithMinDigits(minimumCountDigits: Int, fillerString: Char = '0'): String {

View file

@ -0,0 +1,11 @@
package net.codinux.banking.fints.extensions
import kotlinx.datetime.Instant
import kotlin.random.Random
fun randomWithSeed(): Random = Random(randomSeed())
fun randomSeed(): Long {
return Instant.nowExt().nanosecondsOfSecond.toLong() + Instant.nowExt().toEpochMilliseconds()
}

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.extensions
package net.codinux.banking.fints.extensions
/**

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.extensions
package net.codinux.banking.fints.extensions
fun Throwable.getAllExceptionMessagesJoined(maxDepth: Int = 5): String {

View file

@ -0,0 +1,7 @@
package net.codinux.banking.fints.extensions
import kotlinx.datetime.TimeZone
val TimeZone.Companion.EuropeBerlin: TimeZone
get() = TimeZone.of("Europe/Berlin")

View file

@ -0,0 +1,10 @@
package net.codinux.banking.fints.log
import kotlin.reflect.KClass
interface IMessageLogAppender {
fun logError(loggingClass: KClass<*>, message: String, e: Throwable? = null)
}

View file

@ -0,0 +1,19 @@
package net.codinux.banking.fints.log
import net.codinux.banking.fints.model.AccountData
import net.codinux.banking.fints.model.BankData
import net.codinux.banking.fints.model.MessageType
import net.codinux.banking.fints.model.JobContextType
class MessageContext(
val jobType: JobContextType,
val messageType: MessageType,
val jobNumber: Int,
val dialogNumber: Int,
val messageNumber: Int,
val bank: BankData,
val account: AccountData?
) {
override fun toString() = "${jobNumber}_${dialogNumber}_$messageNumber ${bank.bankCode} $jobType $messageType"
}

View file

@ -1,19 +1,22 @@
package net.dankito.banking.fints.log
package net.codinux.banking.fints.log
import net.codinux.log.LoggerFactory
import net.codinux.log.logger
import net.dankito.banking.fints.config.FinTsClientOptions
import net.dankito.banking.fints.model.BankData
import net.dankito.banking.fints.model.MessageLogEntry
import net.dankito.banking.fints.model.MessageLogEntryType
import net.dankito.banking.fints.extensions.getInnerException
import net.dankito.banking.fints.extensions.nthIndexOf
import net.dankito.banking.fints.extensions.toStringWithMinDigits
import net.dankito.banking.fints.util.FinTsUtils
import net.codinux.banking.fints.callback.FinTsClientCallback
import net.codinux.banking.fints.config.FinTsClientOptions
import net.codinux.banking.fints.model.BankData
import net.codinux.banking.fints.model.MessageLogEntry
import net.codinux.banking.fints.model.MessageLogEntryType
import net.codinux.banking.fints.extensions.getInnerException
import net.codinux.banking.fints.extensions.nthIndexOf
import net.codinux.banking.fints.extensions.toStringWithMinDigits
import net.codinux.banking.fints.response.segments.ReceivedSegment
import net.codinux.banking.fints.util.FinTsUtils
import kotlin.reflect.KClass
open class MessageLogCollector(
private val callback: FinTsClientCallback,
private val options: FinTsClientOptions = FinTsClientOptions(),
private val finTsUtils: FinTsUtils = FinTsUtils()
) {
@ -34,43 +37,57 @@ open class MessageLogCollector(
// 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 BankData
open val messageLog: List<MessageLogEntry>
// safe CPU cycles by only formatting and removing sensitive data if messageLog is really requested
get() = _messageLog.map { MessageLogEntry(it.type, it.context, it.messageTrace, createMessageForLog(it), it.error, it.time) }
// safe CPU cycles by only removing sensitive data if messageLog is really requested
get() = _messageLog.map {
val message = createMessageForLog(it)
val messageWithoutSensitiveData = if (options.removeSensitiveDataFromMessageLog) {
safelyRemoveSensitiveDataFromMessage(message, it.context.bank)
} else {
message
}
private fun createMessageForLog(logEntry: MessageLogEntry): String {
val message = if (logEntry.type == MessageLogEntryType.Error) {
logEntry.messageTrace + logEntry.message + (if (logEntry.error != null) NewLine + getStackTrace(logEntry.error!!) else "")
} else {
logEntry.messageTrace + "\n" + prettyPrintFinTsMessage(logEntry.message)
MessageLogEntry(it.type, it.context, it.messageTrace, message, messageWithoutSensitiveData, it.error, it.parsedSegments, it.time)
}
return if (options.removeSensitiveDataFromMessageLog) {
safelyRemoveSensitiveDataFromMessage(message, logEntry.context.bank)
private fun createMessageForLog(logEntry: MessageLogEntry): String =
if (logEntry.type == MessageLogEntryType.Error) {
logEntry.message + (if (logEntry.error != null) NewLine + getStackTrace(logEntry.error!!) else "")
} else {
message
logEntry.message
}
}
open fun addMessageLog(type: MessageLogEntryType, message: String, context: MessageContext) {
open fun addMessageLog(type: MessageLogEntryType, message: String, context: MessageContext, parsedSegments: List<ReceivedSegment> = emptyList()) {
val messageTrace = createMessageTraceString(type, context)
val prettyPrintMessage = prettyPrintMessageIfRequired(message)
addMessageLogEntry(type, context, messageTrace, message)
log.debug { "$messageTrace\n$prettyPrintMessage" }
log.debug { "$messageTrace\n${prettyPrintFinTsMessage(message)}" }
addMessageLogEntry(type, context, messageTrace, prettyPrintMessage, null, parsedSegments)
}
open fun logError(loggingClass: KClass<*>, message: String, context: MessageContext, e: Exception? = null) {
open fun logError(loggingClass: KClass<*>, message: String, context: MessageContext, e: Throwable? = null) {
val type = MessageLogEntryType.Error
val messageTrace = createMessageTraceString(type, context)
val prettyPrintMessage = prettyPrintFinTsMessage(message) // error messages almost always get logged / displayed -> pretty print
LoggerFactory.getLogger(loggingClass).error(e) { messageTrace + messageTrace }
LoggerFactory.getLogger(loggingClass).error(e) { "$messageTrace\n$prettyPrintMessage" }
addMessageLogEntry(type, context, messageTrace, message, e)
addMessageLogEntry(type, context, messageTrace, prettyPrintMessage, e)
}
protected open fun addMessageLogEntry(type: MessageLogEntryType, context: MessageContext, messageTrace: String, message: String, error: Throwable? = null) {
_messageLog.add(MessageLogEntry(type, context, messageTrace, message, error))
protected open fun addMessageLogEntry(type: MessageLogEntryType, context: MessageContext, messageTrace: String, message: String, error: Throwable? = null, parsedSegments: List<ReceivedSegment> = emptyList()) {
if (options.collectMessageLog || options.fireCallbackOnMessageLogs) {
val newEntry = MessageLogEntry(type, context, messageTrace, message, null, error, parsedSegments)
if (options.collectMessageLog) {
_messageLog.add(newEntry)
}
if (options.fireCallbackOnMessageLogs) {
callback.messageLogAdded(newEntry)
}
}
}
@ -78,7 +95,7 @@ open class MessageLogCollector(
return "${twoDigits(context.jobNumber)}_${twoDigits(context.dialogNumber)}_${twoDigits(context.messageNumber)}_" +
"${context.bank.bankCode}_${context.bank.customerId}" +
"${ context.account?.let { "_${it.accountIdentifier}" } ?: "" }_" +
"${context.jobType.name}_${context.dialogType.name} " +
"${context.jobType.name}_${context.messageType.name} " +
"${getMessageTypeString(type)}:"
}
@ -88,12 +105,19 @@ open class MessageLogCollector(
protected open fun getMessageTypeString(type: MessageLogEntryType): String {
return when (type) {
MessageLogEntryType.Sent -> "Sending message"
MessageLogEntryType.Received -> "Received message"
MessageLogEntryType.Error -> "Error"
MessageLogEntryType.Sent -> "01 Sending message"
MessageLogEntryType.Received -> "02 Received message"
MessageLogEntryType.Error -> "03 Error"
}
}
protected open fun prettyPrintMessageIfRequired(message: String): String =
if (options.collectMessageLog || options.fireCallbackOnMessageLogs || log.isDebugEnabled) { // only use CPU cycles if message will ever be used / displayed
prettyPrintFinTsMessage(message)
} else {
message
}
protected open fun prettyPrintFinTsMessage(message: String): String =
finTsUtils.prettyPrintFinTsMessage(message)

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.mapper
package net.codinux.banking.fints.mapper
import kotlinx.datetime.LocalDate
import net.dankito.banking.client.model.*
@ -7,14 +7,14 @@ import net.dankito.banking.client.model.parameter.FinTsClientParameter
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.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen
import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.response.client.FinTsClientResponse
import net.dankito.banking.fints.response.client.GetAccountTransactionsResponse
import net.dankito.banking.fints.response.segments.AccountType
import net.dankito.banking.fints.util.BicFinder
import net.dankito.banking.fints.extensions.minusDays
import net.dankito.banking.fints.extensions.todayAtEuropeBerlin
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen
import net.codinux.banking.fints.model.*
import net.codinux.banking.fints.response.client.FinTsClientResponse
import net.codinux.banking.fints.response.client.GetAccountTransactionsResponse
import net.codinux.banking.fints.response.segments.AccountType
import net.codinux.banking.fints.util.BicFinder
import net.codinux.banking.fints.extensions.minusDays
import net.codinux.banking.fints.extensions.todayAtEuropeBerlin
open class FinTsModelMapper {
@ -22,8 +22,11 @@ open class FinTsModelMapper {
protected open val bicFinder = BicFinder()
open fun mapToBankData(param: FinTsClientParameter, finTsServerAddress: String): BankData {
return BankData(param.bankCode, param.loginName, param.password, finTsServerAddress, bicFinder.findBic(param.bankCode) ?: "")
open fun mapToBankData(param: FinTsClientParameter, finTsServerAddress: String, defaultValues: BankData? = null): BankData {
return BankData(
param.bankCode, param.loginName, param.password, finTsServerAddress,
defaultValues?.bic ?: bicFinder.findBic(param.bankCode) ?: "", defaultValues?.bankName ?: ""
)
}
open fun mapToAccountData(credentials: BankAccountIdentifier, param: FinTsClientParameter): AccountData {
@ -51,7 +54,7 @@ open class FinTsModelMapper {
open fun map(account: AccountData): BankAccount {
return BankAccount(account.accountIdentifier, account.subAccountAttribute, account.iban, account.accountHolderName, map(account.accountType), account.productName,
account.currency ?: Currency.DefaultCurrencyCode, account.accountLimit, account.countDaysForWhichTransactionsAreKept, account.isAccountTypeSupportedByApplication,
account.currency ?: Currency.DefaultCurrencyCode, account.accountLimit, account.serverTransactionsRetentionDays, account.isAccountTypeSupportedByApplication,
account.supportsRetrievingAccountTransactions, account.supportsRetrievingBalance, account.supportsTransferringMoney, account.supportsRealTimeTransfer)
}
@ -70,16 +73,34 @@ open class FinTsModelMapper {
}
}
open fun map(bank: BankData, retrievedTransactionsResponses: List<GetAccountTransactionsResponse>): CustomerAccount {
open fun map(bank: BankData, retrievedTransactionsResponses: List<GetAccountTransactionsResponse>, retrieveTransactionsTo: LocalDate? = null): CustomerAccount {
val customerAccount = map(bank)
val retrievedData = retrievedTransactionsResponses.mapNotNull { it.retrievedData }
customerAccount.accounts.forEach { bankAccount ->
retrievedData.firstOrNull { it.account.accountIdentifier == bankAccount.identifier }?.let { accountTransactionsResponse ->
bankAccount.balance = accountTransactionsResponse.balance ?: Money.Zero
bankAccount.retrievedTransactionsFrom = accountTransactionsResponse.retrievedTransactionsFrom
bankAccount.retrievedTransactionsTo = accountTransactionsResponse.retrievedTransactionsTo
bankAccount.bookedTransactions = map(accountTransactionsResponse)
accountTransactionsResponse.balance?.let { balance ->
bankAccount.balance = balance
}
if (accountTransactionsResponse.retrievedTransactionsFrom != null && (bankAccount.retrievedTransactionsFrom == null ||
accountTransactionsResponse.retrievedTransactionsFrom!! < bankAccount.retrievedTransactionsFrom!!)) {
bankAccount.retrievedTransactionsFrom = accountTransactionsResponse.retrievedTransactionsFrom
}
val retrievalTime = accountTransactionsResponse.retrievalTime
if (retrieveTransactionsTo == null && (bankAccount.lastAccountUpdateTime == null || bankAccount.lastAccountUpdateTime!! <= retrievalTime || // if retrieveTransactionsTo is set, then we don't retrieve all current transactions -> don't set lastAccountUpdateTime
(bankAccount.supportsRetrievingTransactions == false && accountTransactionsResponse.statementOfHoldings.isNotEmpty()))) { // TODO: really check for supportsRetrievingTransactions == false if statementOfHoldings are set? Are there really accounts that support HKWPD and HKKAZ?
bankAccount.lastAccountUpdateTime = retrievalTime
}
if (accountTransactionsResponse.bookedTransactions.isNotEmpty()) {
bankAccount.bookedTransactions = bankAccount.bookedTransactions.toMutableList().apply {
addAll(map(accountTransactionsResponse))
}
}
bankAccount.statementOfHoldings = accountTransactionsResponse.statementOfHoldings
}
}
@ -90,15 +111,29 @@ open class FinTsModelMapper {
return data.bookedTransactions.map { map(it) }
}
open fun map(transaction: net.dankito.banking.fints.model.AccountTransaction): AccountTransaction {
return AccountTransaction(transaction.amount, transaction.unparsedReference, transaction.bookingDate,
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId, transaction.bookingText, transaction.valueDate,
transaction.statementNumber, transaction.sequenceNumber, transaction.openingBalance, transaction.closingBalance,
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)
open fun map(transaction: net.codinux.banking.fints.model.AccountTransaction): AccountTransaction {
return AccountTransaction(
transaction.amount, transaction.reference,
transaction.bookingDate, transaction.valueDate,
transaction.otherPartyName, transaction.otherPartyBankId, transaction.otherPartyAccountId,
transaction.postingText,
transaction.openingBalance, transaction.closingBalance,
transaction.statementNumber, transaction.sheetNumber,
transaction.customerReference, transaction.bankReference, transaction.furtherInformation,
transaction.endToEndReference, transaction.mandateReference, transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
transaction.compensationAmount, transaction.originalAmount, transaction.deviantOriginator, transaction.deviantRecipient,
transaction.referenceWithNoSpecialType,
transaction.journalNumber, transaction.textKeyAddition,
transaction.orderReferenceNumber, transaction.referenceNumber,
transaction.isReversal
)
}
@ -157,6 +192,10 @@ open class FinTsModelMapper {
else errorMessages.joinToString("\r\n")
}
open fun mergeMessageLog(vararg messageLogs: List<MessageLogEntry>?): List<MessageLogEntry> {
return messageLogs.filterNotNull().flatten()
}
open fun mergeMessageLog(vararg responses: FinTsClientResponse?): List<MessageLogEntry> {
return responses.filterNotNull().flatMap { it.messageLog }
}

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.messages
package net.codinux.banking.fints.messages
enum class Existenzstatus {

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.messages
package net.codinux.banking.fints.messages
import io.ktor.utils.io.charsets.Charsets

View file

@ -1,26 +1,24 @@
package net.dankito.banking.fints.messages
package net.codinux.banking.fints.messages
import net.dankito.banking.fints.extensions.randomWithSeed
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.Synchronisierungsmodus
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMedienArtVersion
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanProcess
import net.dankito.banking.fints.messages.segmente.Segment
import net.dankito.banking.fints.messages.segmente.Synchronisierung
import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId
import net.dankito.banking.fints.messages.segmente.id.ISegmentId
import net.dankito.banking.fints.messages.segmente.implementierte.*
import net.dankito.banking.fints.messages.segmente.implementierte.sepa.SepaBankTransferBase
import net.dankito.banking.fints.messages.segmente.implementierte.tan.TanGeneratorListeAnzeigen
import net.dankito.banking.fints.messages.segmente.implementierte.tan.TanGeneratorTanMediumAnOderUmmelden
import net.dankito.banking.fints.messages.segmente.implementierte.umsaetze.*
import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.response.InstituteSegmentId
import net.dankito.banking.fints.response.segments.*
import net.dankito.banking.fints.util.FinTsUtils
import net.codinux.banking.fints.extensions.randomWithSeed
import net.codinux.banking.fints.messages.datenelemente.implementierte.Aufsetzpunkt
import net.codinux.banking.fints.messages.datenelemente.implementierte.KundensystemID
import net.codinux.banking.fints.messages.datenelemente.implementierte.Synchronisierungsmodus
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.*
import net.codinux.banking.fints.messages.segmente.Segment
import net.codinux.banking.fints.messages.segmente.Synchronisierung
import net.codinux.banking.fints.messages.segmente.id.CustomerSegmentId
import net.codinux.banking.fints.messages.segmente.id.ISegmentId
import net.codinux.banking.fints.messages.segmente.implementierte.*
import net.codinux.banking.fints.messages.segmente.implementierte.depot.Depotaufstellung
import net.codinux.banking.fints.messages.segmente.implementierte.sepa.SepaBankTransferBase
import net.codinux.banking.fints.messages.segmente.implementierte.tan.TanGeneratorListeAnzeigen
import net.codinux.banking.fints.messages.segmente.implementierte.tan.TanGeneratorTanMediumAnOderUmmelden
import net.codinux.banking.fints.messages.segmente.implementierte.umsaetze.*
import net.codinux.banking.fints.model.*
import net.codinux.banking.fints.response.InstituteSegmentId
import net.codinux.banking.fints.response.segments.*
import net.codinux.banking.fints.util.FinTsUtils
import kotlin.math.absoluteValue
@ -41,7 +39,7 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
private const val SignatureHeaderSegmentNumber = MessageHeaderSegmentNumber + 1
private const val SignedMessagePayloadFirstSegmentNumber = SignatureHeaderSegmentNumber + 1
const val SignedMessagePayloadFirstSegmentNumber = SignatureHeaderSegmentNumber + 1
}
@ -133,7 +131,7 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
}
if (context.bank.customerSystemId == KundensystemID.Anonymous) {
segments.add(Synchronisierung(segments.size + 3, Synchronisierungsmodus.NeueKundensystemIdZurueckmelden))
segments.add(Synchronisierung(SignedMessagePayloadFirstSegmentNumber + segments.size, Synchronisierungsmodus.NeueKundensystemIdZurueckmelden))
}
return createSignedMessageBuilderResult(context, MessageType.DialogInit, segments)
@ -242,17 +240,38 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
return createSignedMessageBuilderResult(context, MessageType.GetBalance, segments)
}
val securitiesAccountResult = supportsGetSecuritiesAccountBalance(account)
if (securitiesAccountResult.isJobVersionSupported) {
return createGetSecuritiesAccountBalanceMessage(context, result, account)
}
return result
}
protected open fun createGetSecuritiesAccountBalanceMessage(context: JobContext, result: MessageBuilderResult,
account: AccountData): MessageBuilderResult {
val segments = mutableListOf<Segment>(Depotaufstellung(SignedMessagePayloadFirstSegmentNumber, account))
addTanSegmentIfRequired(context, CustomerSegmentId.SecuritiesAccountBalance, segments, SignedMessagePayloadFirstSegmentNumber + 1)
return createSignedMessageBuilderResult(context, MessageType.GetSecuritiesAccountBalance, segments)
}
open fun supportsGetBalance(account: AccountData): Boolean {
return supportsGetBalanceMessage(account).isJobVersionSupported
|| supportsGetSecuritiesAccountBalance(account).isJobVersionSupported
}
protected open fun supportsGetBalanceMessage(account: AccountData): MessageBuilderResult {
return getSupportedVersionsOfJobForAccount(CustomerSegmentId.Balance, account, listOf(5, 6, 7, 8))
}
protected open fun supportsGetSecuritiesAccountBalance(account: AccountData): MessageBuilderResult {
return getSupportedVersionsOfJobForAccount(CustomerSegmentId.SecuritiesAccountBalance, account, listOf(6))
}
open fun createGetTanMediaListMessage(context: JobContext,
tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle,
@ -272,7 +291,7 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
}
// TODO: no HKTAN needed?
open fun createChangeTanMediumMessage(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium,
open fun createChangeTanMediumMessage(context: JobContext, newActiveTanMedium: TanMedium,
tan: String? = null, atc: Int? = null): MessageBuilderResult {
val result = getSupportedVersionsOfJobForBank(CustomerSegmentId.ChangeTanMedium, context.bank, listOf(1, 2, 3))
@ -295,12 +314,22 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
val segments = listOf(
ZweiSchrittTanEinreichung(SignedMessagePayloadFirstSegmentNumber, tanProcess, null,
tanResponse.jobHashValue, tanResponse.jobReference, false, null, tanResponse.tanMediaIdentifier)
tanResponse.jobHashValue, tanResponse.jobReference, false, null, tanResponse.tanMediaIdentifier, tanResponse.segmentVersion)
)
return createSignedMessageBuilderResult(context, MessageType.Tan, createSignedMessage(context, enteredTan, segments), segments)
}
open fun createDecoupledTanStatusMessage(context: JobContext, tanResponse: TanResponse): MessageBuilderResult {
val segments = listOf(
ZweiSchrittTanEinreichung(SignedMessagePayloadFirstSegmentNumber, TanProcess.AppTan,
jobReference = tanResponse.jobReference, furtherTanFollows = false, segmentVersion = 7, tanMediaIdentifier = tanResponse.tanMediaIdentifier)
)
return createSignedMessageBuilderResult(context, MessageType.CheckDecoupledTanStatus, createSignedMessage(context, null, segments), segments)
}
open fun createBankTransferMessage(context: JobContext, data: BankTransferData, account: AccountData): MessageBuilderResult {
@ -394,7 +423,7 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
return createSignedMessage(context, null, payloadSegments)
}
open fun createSignedMessage(context: JobContext, tan: String? = null, payloadSegments: List<Segment>): String {
protected open fun createSignedMessage(context: JobContext, tan: String? = null, payloadSegments: List<Segment>): String {
val date = utils.formatDateTodayAsInt()
val time = utils.formatTimeNowAsInt()
@ -406,7 +435,7 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
return createMessage(context, encryptedPayload, payloadSegments.size)
}
open fun createMessage(context: JobContext, payloadSegments: List<Segment>, countWrappedSegments: Int = 0): String {
protected open fun createMessage(context: JobContext, payloadSegments: List<Segment>, countWrappedSegments: Int = 0): String {
val dialog = context.dialog
dialog.increaseMessageNumber()
@ -521,8 +550,10 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
}
protected open fun createTwoStepTanSegment(context: JobContext, segmentId: CustomerSegmentId, segmentNumber: Int): ZweiSchrittTanEinreichung {
val bank = context.bank
return ZweiSchrittTanEinreichung(segmentNumber, TanProcess.TanProcess4, segmentId,
tanMediaIdentifier = getTanMediaIdentifierIfRequired(context))
tanMediaIdentifier = getTanMediaIdentifierIfRequired(context), segmentVersion = bank.selectedTanMethod.hktanVersion)
}
protected open fun getTanMediaIdentifierIfRequired(context: JobContext): String? {
@ -536,8 +567,7 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
}
protected open fun isTanRequiredForJob(context: JobContext, segmentId: CustomerSegmentId): Boolean {
return context.bank.pinInfo?.jobTanConfiguration?.first { it.segmentId == segmentId.id }?.tanRequired
?: false // TODO: actually in this case it's not allowed to execute job via PIN/TAN at all
return context.bank.doesJobRequireTan(segmentId)
}
protected open fun getSepaUrnFor(segmentId: CustomerSegmentId, bank: BankData, sepaDataFormat: String): String? {

View file

@ -1,7 +1,9 @@
package net.dankito.banking.fints.messages
package net.codinux.banking.fints.messages
import net.dankito.banking.fints.messages.segmente.Segment
import net.dankito.banking.fints.messages.segmente.implementierte.ZweiSchrittTanEinreichung
import net.codinux.banking.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.codinux.banking.fints.messages.segmente.Segment
import net.codinux.banking.fints.messages.segmente.implementierte.Verarbeitungsvorbereitung
import net.codinux.banking.fints.messages.segmente.implementierte.ZweiSchrittTanEinreichung
open class MessageBuilderResult(
@ -32,4 +34,10 @@ open class MessageBuilderResult(
&& messageBodySegments.first() is ZweiSchrittTanEinreichung
}
open fun isDialogInitMessage(): Boolean =
messageBodySegments.any { it is Verarbeitungsvorbereitung }
override fun toString() = "${messageBodySegments.joinToString { (it.dataElementsAndGroups.firstOrNull() as? Segmentkopf)?.let { "${it.identifier}:${it.segmentVersion}" } ?: "<No Segment header>" } }}"
}

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages
package net.codinux.banking.fints.messages
import net.dankito.banking.fints.util.MessageUtils
import net.codinux.banking.fints.util.MessageUtils
abstract class Nachrichtenteil(protected val messageUtils: MessageUtils = MessageUtils()) {

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.messages
package net.codinux.banking.fints.messages
class Separators {

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente
package net.codinux.banking.fints.messages.datenelemente
import net.dankito.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.Existenzstatus
abstract class Datenelement(existenzstatus: Existenzstatus): DatenelementBase(existenzstatus) {

View file

@ -0,0 +1,7 @@
package net.codinux.banking.fints.messages.datenelemente
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.Nachrichtenteil
abstract class DatenelementBase(val existenzstatus: Existenzstatus) : Nachrichtenteil()

View file

@ -1,8 +1,8 @@
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
package net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.dankito.banking.fints.messages.datenelemente.implementierte.ICodeEnum
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.codinux.banking.fints.messages.datenelemente.implementierte.ICodeEnum
/**

View file

@ -1,10 +1,10 @@
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
package net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate
import kotlinx.datetime.LocalDate
import net.codinux.log.logger
import net.dankito.banking.fints.extensions.toStringWithMinDigits
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.codinux.banking.fints.extensions.toStringWithMinDigits
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
package net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
package net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
package net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.ZiffernDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.ZiffernDatenelement
/**

View file

@ -1,10 +1,10 @@
package net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate
package net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate
import kotlinx.datetime.LocalTime
import net.codinux.log.logger
import net.dankito.banking.fints.extensions.toStringWithMinDigits
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.ZiffernDatenelement
import net.codinux.banking.fints.extensions.toStringWithMinDigits
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.ZiffernDatenelement
/**

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.basisformate
package net.codinux.banking.fints.messages.datenelemente.basisformate
import net.dankito.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.Existenzstatus
/**

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.basisformate
package net.codinux.banking.fints.messages.datenelemente.basisformate
import net.dankito.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.Existenzstatus
/**

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.basisformate
package net.codinux.banking.fints.messages.datenelemente.basisformate
import net.dankito.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.Existenzstatus
/**

View file

@ -1,11 +1,11 @@
package net.dankito.banking.fints.messages.datenelemente.basisformate
package net.codinux.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.HbciCharset
import net.dankito.banking.fints.messages.Separators
import net.dankito.banking.fints.messages.datenelemente.Datenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.HbciCharset
import net.codinux.banking.fints.messages.Separators
import net.codinux.banking.fints.messages.datenelemente.Datenelement
/**

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.basisformate
package net.codinux.banking.fints.messages.datenelemente.basisformate
import net.dankito.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.Existenzstatus
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.Existenzstatus
open class BezugssegmentInKreditinstitutsnachricht(segmentNumber: Int) : Bezugssegment(segmentNumber, Existenzstatus.Optional)

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.Existenzstatus
open class BezugssegmentInKundennachricht(segmentNumber: Int) : Bezugssegment(segmentNumber, Existenzstatus.NotAllowed)

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**

View file

@ -0,0 +1,21 @@
package net.codinux.banking.fints.messages.datenelemente.implementierte
enum class Dialogsprache(override val code: String) : ICodeEnum {
/**
* Der Kunde darf lediglich ein Sprachkennzeichen einstellen, das im Rahmen
* der BPD vom Kreditinstitut an das Kundensystem übermittelt wurde.
* Wenn noch keine BPD vorliegen, sollte das Kundenprodukt mit Hilfe eines
* anonymen Dialogs die aktuelle BPD des Instituts ermitteln und die Standardsprache des Instituts einstellen, die in den Bankparameterdaten mitgeteilt
* wird. Falls die BPD nicht abgerufen werden kann, ist der Wert 0 einzustellen. Das Kreditinstitut antwortet in diesem Fall in seiner Standardsprache.
*/
Default("0"),
German("1"),
English("2"),
French("3")
}

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Code
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Code
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.TextDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.TextDatenelement
/**

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
enum class HbciVersion(val versionNumber: Int, override val code: String) : ICodeEnum {

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
inline fun <reified T : Enum<T>> allCodes(): List<String> {

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Code
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Code
/**

View file

@ -0,0 +1,21 @@
package net.codinux.banking.fints.messages.datenelemente.implementierte
/**
* Information darüber, ob die Kundensystem-ID erforderlich ist.
*/
enum class KundensystemStatusWerte(override val code: String) : ICodeEnum {
/**
* Kundensystem-ID wird nicht benötigt (HBCI DDV-Verfahren und
* chipkartenbasierte Verfahren ab Sicherheitsprofil-Version 3)
* und PinTan bis über HKSYN eine Kundensystem-ID vom Bankserver abgerufen wurde.
*/
NichtBenoetigt("0"),
/**
* Kundensystem-ID wird benötigt (sonstige HBCI RAH- /
* RDH- und PIN/TAN-Verfahren)
*/
Benoetigt("1")
}

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.ZiffernDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.ZiffernDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**

View file

@ -0,0 +1,7 @@
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.TextDatenelement
open class NotAllowedDatenelement : TextDatenelement("", Existenzstatus.NotAllowed)

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**

View file

@ -1,4 +1,4 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
enum class Synchronisierungsmodus(override val code: String) : ICodeEnum {

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Code
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Code
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte
package net.codinux.banking.fints.messages.datenelemente.implementierte
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.account
package net.codinux.banking.fints.messages.datenelemente.implementierte.account
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.JaNein
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.JaNein
/**

View file

@ -0,0 +1,7 @@
package net.codinux.banking.fints.messages.datenelemente.implementierte.account
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
open class BIC(bic: String, existenzstatus: Existenzstatus) : AlphanumerischesDatenelement(bic, existenzstatus, 11)

View file

@ -0,0 +1,7 @@
package net.codinux.banking.fints.messages.datenelemente.implementierte.account
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
open class IBAN(iban: String, existenzstatus: Existenzstatus) : AlphanumerischesDatenelement(iban, existenzstatus, 34)

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.account
package net.codinux.banking.fints.messages.datenelemente.implementierte.account
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**

View file

@ -1,8 +1,8 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.account
package net.codinux.banking.fints.messages.datenelemente.implementierte.account
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.dankito.banking.fints.model.GetAccountTransactionsParameter
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.codinux.banking.fints.model.GetAccountTransactionsParameter
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.account
package net.codinux.banking.fints.messages.datenelemente.implementierte.account
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.encryption
package net.codinux.banking.fints.messages.datenelemente.implementierte.encryption
import net.dankito.banking.fints.messages.datenelemente.implementierte.ICodeEnum
import net.codinux.banking.fints.messages.datenelemente.implementierte.ICodeEnum
enum class BezeichnerFuerAlgorithmusparameterIV(override val code: String) : ICodeEnum {

View file

@ -1,8 +1,8 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.encryption
package net.codinux.banking.fints.messages.datenelemente.implementierte.encryption
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.banking.fints.messages.datenelemente.implementierte.allCodes
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Code
import net.codinux.banking.fints.messages.datenelemente.implementierte.allCodes
/**

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.encryption
package net.codinux.banking.fints.messages.datenelemente.implementierte.encryption
import net.dankito.banking.fints.messages.datenelemente.implementierte.ICodeEnum
import net.codinux.banking.fints.messages.datenelemente.implementierte.ICodeEnum
enum class BezeichnerFuerAlgorithmusparameterSchluessel(override val code: String) : ICodeEnum {

View file

@ -1,8 +1,8 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.encryption
package net.codinux.banking.fints.messages.datenelemente.implementierte.encryption
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.banking.fints.messages.datenelemente.implementierte.allCodes
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Code
import net.codinux.banking.fints.messages.datenelemente.implementierte.allCodes
/**

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.encryption
package net.codinux.banking.fints.messages.datenelemente.implementierte.encryption
import net.dankito.banking.fints.messages.datenelemente.implementierte.ICodeEnum
import net.codinux.banking.fints.messages.datenelemente.implementierte.ICodeEnum
enum class Komprimierungsfunktion(val abbreviation: String, override val code: String) : ICodeEnum {

View file

@ -1,8 +1,8 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.encryption
package net.codinux.banking.fints.messages.datenelemente.implementierte.encryption
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Code
import net.dankito.banking.fints.messages.datenelemente.implementierte.allCodes
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.abgeleiteteformate.Code
import net.codinux.banking.fints.messages.datenelemente.implementierte.allCodes
/**

View file

@ -1,7 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.encryption
package net.codinux.banking.fints.messages.datenelemente.implementierte.encryption
import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.BinaerDatenelement
import net.codinux.banking.fints.messages.Existenzstatus
import net.codinux.banking.fints.messages.datenelemente.basisformate.BinaerDatenelement
/**

View file

@ -1,6 +1,6 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.encryption
package net.codinux.banking.fints.messages.datenelemente.implementierte.encryption
import net.dankito.banking.fints.messages.datenelemente.implementierte.ICodeEnum
import net.codinux.banking.fints.messages.datenelemente.implementierte.ICodeEnum
enum class Verschluesselungsalgorithmus(override val code: String) : ICodeEnum {

Some files were not shown because too many files have changed in this diff Show more