Compare commits
30 Commits
9600e2f11b
...
c158097d3a
Author | SHA1 | Date |
---|---|---|
dankito | c158097d3a | |
dankito | 6908f52e48 | |
dankito | 61d8f2c342 | |
dankito | 6bf7fdcb44 | |
dankito | fbafbb62e3 | |
dankito | 9372d17313 | |
dankito | 9b1a5fa929 | |
dankito | 42bf002626 | |
dankito | 20f06387c5 | |
dankito | 75320da2be | |
dankito | be2908517f | |
dankito | c4f504dd0a | |
dankito | 0848586894 | |
dankito | 83c2882567 | |
dankito | f069f9155c | |
dankito | bf5ee4890e | |
dankito | ed4214fd49 | |
dankito | b8fe9e78e1 | |
dankito | da2bf8d469 | |
dankito | 113b817627 | |
dankito | bd18644c0d | |
dankito | b32cf94e25 | |
dankito | 8cc2f3bdcd | |
dankito | 59b8213163 | |
dankito | cb34c86665 | |
dankito | 70c1082531 | |
dankito | 30e9a57b96 | |
dankito | bf76de4f23 | |
dankito | 47e2b851b9 | |
dankito | f90e280b74 |
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) | payer’s/debtor’s reference party (for credit transfer / payee’s / creditor’s reference party (for a direct debit) |
|
||||
| Abweichender Zahlungsempfänger (CT-AT28) / Abweichender Zahlungspflichtiger (DDAT15) | payee’s/creditor’s reference party / payer’s/debtor’s reference party |
|
||||
| | |
|
||||
| Überweisender | Payer, debtor |
|
||||
| Zahlungsempfänger | Payee, creditor |
|
||||
| Zahlungseingang | Payment receipt |
|
||||
| Lastschrift | direct debit |
|
||||
| | |
|
||||
| | |
|
||||
| Primanoten-Nr. | Journal no. |
|
||||
| | |
|
|
@ -63,8 +63,13 @@ open class FinTsClient(
|
|||
return GetAccountDataResponse(ErrorCode.NoneOfTheAccountsSupportsRetrievingData, errorMessage, mapper.map(bank), previousJobMessageLog ?: listOf(), bank)
|
||||
}
|
||||
|
||||
accountsSupportingRetrievingTransactions.forEach { account ->
|
||||
retrievedTransactionsResponses.add(getAccountTransactions(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 }
|
||||
|
@ -75,7 +80,7 @@ open class FinTsClient(
|
|||
}
|
||||
|
||||
protected open suspend fun getAccountTransactions(param: GetAccountDataParameter, bank: BankData, account: AccountData): GetAccountTransactionsResponse {
|
||||
val context = JobContext(JobContextType.GetTransactions, this.callback, config, bank, account)
|
||||
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))
|
||||
}
|
||||
|
@ -133,7 +138,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))
|
||||
|
@ -179,12 +184,14 @@ open class FinTsClient(
|
|||
return net.dankito.banking.client.model.response.FinTsClientResponse(null, null, emptyList(), param.finTsModel)
|
||||
}
|
||||
|
||||
val finTsServerAddress = config.finTsServerAddressFinder.findFinTsServerAddress(param.bankCode)
|
||||
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)
|
||||
val bank = mapper.mapToBankData(param, finTsServerAddress, defaultValues)
|
||||
|
||||
val getAccountInfoResponse = getAccountInfo(param, bank)
|
||||
|
||||
|
@ -198,11 +205,11 @@ open class FinTsClient(
|
|||
// 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 */
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package net.codinux.banking.fints
|
||||
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.*
|
||||
import net.codinux.banking.fints.callback.FinTsClientCallback
|
||||
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
||||
|
@ -39,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)
|
||||
|
@ -54,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 {
|
||||
|
@ -120,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)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package net.codinux.banking.fints
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.fints.extensions.*
|
||||
import net.codinux.log.logger
|
||||
import net.codinux.banking.fints.messages.MessageBuilder
|
||||
import net.codinux.banking.fints.messages.MessageBuilderResult
|
||||
|
@ -19,9 +20,7 @@ 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.banking.fints.extensions.minusDays
|
||||
import net.codinux.banking.fints.extensions.todayAtEuropeBerlin
|
||||
import net.codinux.banking.fints.extensions.todayAtSystemDefaultTimeZone
|
||||
import net.codinux.log.Log
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
@ -75,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),
|
||||
|
@ -92,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)
|
||||
|
||||
|
@ -105,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 {
|
||||
|
@ -149,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)
|
||||
|
||||
|
@ -227,11 +224,11 @@ open class FinTsJobExecutor(
|
|||
|
||||
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 = Clock.System.now()
|
||||
val startTime = Instant.nowExt()
|
||||
|
||||
val response = getAndHandleResponseForMessage(context, message)
|
||||
|
||||
|
@ -240,7 +237,7 @@ open class FinTsJobExecutor(
|
|||
val successful = response.tanRequiredButWeWereToldToAbortIfSo
|
||||
|| (response.successful && (parameter.alsoRetrieveBalance == 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, startTime, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError)
|
||||
|
||||
|
@ -380,20 +377,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)
|
||||
}
|
||||
|
@ -408,13 +421,13 @@ open class FinTsJobExecutor(
|
|||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,6 +485,8 @@ open class FinTsJobExecutor(
|
|||
}
|
||||
}
|
||||
|
||||
tanChallenge.tanExpired()
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -616,7 +631,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)
|
||||
|
||||
|
@ -692,7 +707,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
|
||||
|
@ -700,13 +715,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)
|
||||
|
||||
|
@ -727,14 +742,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)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ data class FinTsClientOptions(
|
|||
* Defaults to true.
|
||||
*/
|
||||
val removeSensitiveDataFromMessageLog: Boolean = true,
|
||||
|
||||
val closeDialogs: Boolean = true,
|
||||
|
||||
val version: String = "1.0.0", // TODO: get version dynamically
|
||||
val productName: String = "15E53C26816138699C7B6A3E8"
|
||||
) {
|
||||
|
|
|
@ -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)
|
|
@ -13,5 +13,5 @@ fun LocalDateTime.Companion.nowAtEuropeBerlin(): LocalDateTime {
|
|||
}
|
||||
|
||||
fun LocalDateTime.Companion.nowAt(timeZone: TimeZone): LocalDateTime {
|
||||
return Clock.System.now().toLocalDateTime(timeZone)
|
||||
return Instant.nowExt().toLocalDateTime(timeZone)
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
package net.codinux.banking.fints.extensions
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
fun randomWithSeed(): Random = Random(randomSeed())
|
||||
|
||||
fun randomSeed(): Long {
|
||||
return Clock.System.now().nanosecondsOfSecond.toLong() + Clock.System.now().toEpochMilliseconds()
|
||||
return Instant.nowExt().nanosecondsOfSecond.toLong() + Instant.nowExt().toEpochMilliseconds()
|
||||
}
|
|
@ -26,8 +26,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 {
|
||||
|
@ -55,7 +58,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)
|
||||
}
|
||||
|
||||
|
@ -111,14 +114,28 @@ open class FinTsModelMapper {
|
|||
}
|
||||
|
||||
open fun map(transaction: net.codinux.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)
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -32,7 +32,11 @@ open class AccountData(
|
|||
|| allowedJobNames.contains(CustomerSegmentId.AccountTransactionsMt940.id)
|
||||
|
||||
|
||||
open var countDaysForWhichTransactionsAreKept: Int? = null
|
||||
|
||||
/**
|
||||
* Count days for which transactions are stored on bank server (if available).
|
||||
*/
|
||||
open var serverTransactionsRetentionDays: Int? = null
|
||||
|
||||
|
||||
protected open val _supportedFeatures = mutableSetOf<AccountFeature>()
|
||||
|
|
|
@ -7,59 +7,124 @@ import net.codinux.banking.fints.extensions.UnixEpochStart
|
|||
open class AccountTransaction(
|
||||
val account: AccountData,
|
||||
val amount: Money,
|
||||
val isReversal: Boolean,
|
||||
val unparsedReference: String,
|
||||
val reference: String?, // that was also new to me that reference may is null
|
||||
|
||||
val bookingDate: LocalDate,
|
||||
val otherPartyName: String?,
|
||||
val otherPartyBankCode: String?,
|
||||
val otherPartyAccountId: String?,
|
||||
val bookingText: String?,
|
||||
val valueDate: LocalDate,
|
||||
val statementNumber: Int,
|
||||
val sequenceNumber: Int?,
|
||||
|
||||
/**
|
||||
* Name des Überweisenden oder Zahlungsempfängers
|
||||
*/
|
||||
val otherPartyName: String?,
|
||||
/**
|
||||
* BIC des Überweisenden / Zahlungsempfängers
|
||||
*/
|
||||
val otherPartyBankId: String?,
|
||||
/**
|
||||
* IBAN des Überweisenden oder Zahlungsempfängers
|
||||
*/
|
||||
val otherPartyAccountId: String?,
|
||||
|
||||
/**
|
||||
* Buchungstext, z. B. DAUERAUFTRAG, BARGELDAUSZAHLUNG, ONLINE-UEBERWEISUNG, FOLGELASTSCHRIFT, ...
|
||||
*/
|
||||
val postingText: String?,
|
||||
|
||||
val openingBalance: Money?,
|
||||
val closingBalance: Money?,
|
||||
|
||||
val endToEndReference: String?,
|
||||
/**
|
||||
* Auszugsnummer
|
||||
*/
|
||||
val statementNumber: Int,
|
||||
/**
|
||||
* Blattnummer
|
||||
*/
|
||||
val sheetNumber: Int?,
|
||||
|
||||
/**
|
||||
* Kundenreferenz.
|
||||
*/
|
||||
val customerReference: String?,
|
||||
/**
|
||||
* Bankreferenz
|
||||
*/
|
||||
val bankReference: String?,
|
||||
/**
|
||||
* Währungsart und Umsatzbetrag in Ursprungswährung
|
||||
*/
|
||||
val furtherInformation: String?,
|
||||
|
||||
|
||||
/* Remittance information */
|
||||
|
||||
val endToEndReference: String?,
|
||||
val mandateReference: String?,
|
||||
val creditorIdentifier: String?,
|
||||
val originatorsIdentificationCode: String?,
|
||||
|
||||
/**
|
||||
* Summe aus Auslagenersatz und Bearbeitungsprovision bei einer nationalen Rücklastschrift
|
||||
* sowie optionalem Zinsausgleich.
|
||||
*/
|
||||
val compensationAmount: String?,
|
||||
/**
|
||||
* Betrag der ursprünglichen Lastschrift
|
||||
*/
|
||||
val originalAmount: String?,
|
||||
val sepaReference: String?,
|
||||
/**
|
||||
* Abweichender Überweisender oder Zahlungsempfänger
|
||||
*/
|
||||
val deviantOriginator: String?,
|
||||
/**
|
||||
* Abweichender Zahlungsempfänger oder Zahlungspflichtiger
|
||||
*/
|
||||
val deviantRecipient: String?,
|
||||
val referenceWithNoSpecialType: String?,
|
||||
val primaNotaNumber: String?,
|
||||
val textKeySupplement: String?,
|
||||
|
||||
val currencyType: String?,
|
||||
val bookingKey: String,
|
||||
val referenceForTheAccountOwner: String,
|
||||
val referenceOfTheAccountServicingInstitution: String?,
|
||||
val supplementaryDetails: String?,
|
||||
/**
|
||||
* Primanoten-Nr.
|
||||
*/
|
||||
val journalNumber: String?,
|
||||
/**
|
||||
* Bei R-Transaktionen siehe Tabelle der
|
||||
* SEPA-Rückgabecodes, bei SEPALastschriften siehe optionale Belegung
|
||||
* bei GVC 104 und GVC 105 (GVC = Geschäftsvorfallcode)
|
||||
*/
|
||||
val textKeyAddition: String?,
|
||||
|
||||
val transactionReferenceNumber: String,
|
||||
val relatedReferenceNumber: String?
|
||||
/**
|
||||
* Referenznummer, die vom Sender als eindeutige Kennung für die Nachricht vergeben wurde
|
||||
* (z.B. als Referenz auf stornierte Nachrichten).
|
||||
*/
|
||||
val orderReferenceNumber: String?,
|
||||
/**
|
||||
* Bezugsreferenz
|
||||
*/
|
||||
val referenceNumber: String?,
|
||||
|
||||
/**
|
||||
* Storno, ob die Buchung storniert wurde(?).
|
||||
* Aus:
|
||||
* „RC“ = Storno Haben
|
||||
* „RD“ = Storno Soll
|
||||
*/
|
||||
val isReversal: Boolean
|
||||
) {
|
||||
|
||||
// for object deserializers
|
||||
internal constructor() : this(AccountData(), Money(Amount.Zero, ""), "", UnixEpochStart, null, null, null, null, UnixEpochStart)
|
||||
internal constructor() : this(AccountData(), Money(Amount.Zero, ""), "", UnixEpochStart, UnixEpochStart, null, null, null, null)
|
||||
|
||||
constructor(account: AccountData, amount: Money, unparsedReference: String, bookingDate: LocalDate, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, bookingText: String?, valueDate: LocalDate)
|
||||
: this(account, amount, false, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
|
||||
0, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, "", "", null, null, "", null)
|
||||
constructor(account: AccountData, amount: Money, unparsedReference: String, bookingDate: LocalDate, valueDate: LocalDate, otherPartyName: String?, otherPartyBankId: String?, otherPartyAccountId: String?, postingText: String? = null)
|
||||
: this(account, amount, unparsedReference, bookingDate, valueDate, otherPartyName, otherPartyBankId, otherPartyAccountId, postingText,
|
||||
null, null, 0, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null,
|
||||
"", null, null, "", null, false)
|
||||
|
||||
|
||||
open val showOtherPartyName: Boolean
|
||||
get() = otherPartyName.isNullOrBlank() == false /* && type != "ENTGELTABSCHLUSS" && type != "AUSZAHLUNG" */ // TODO
|
||||
|
||||
val reference: String
|
||||
get() = sepaReference ?: unparsedReference
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
|
@ -67,12 +132,12 @@ open class AccountTransaction(
|
|||
|
||||
if (account != other.account) return false
|
||||
if (amount != other.amount) return false
|
||||
if (unparsedReference != other.unparsedReference) return false
|
||||
if (reference != other.reference) return false
|
||||
if (bookingDate != other.bookingDate) return false
|
||||
if (otherPartyName != other.otherPartyName) return false
|
||||
if (otherPartyBankCode != other.otherPartyBankCode) return false
|
||||
if (otherPartyBankId != other.otherPartyBankId) return false
|
||||
if (otherPartyAccountId != other.otherPartyAccountId) return false
|
||||
if (bookingText != other.bookingText) return false
|
||||
if (postingText != other.postingText) return false
|
||||
if (valueDate != other.valueDate) return false
|
||||
|
||||
return true
|
||||
|
@ -81,19 +146,19 @@ open class AccountTransaction(
|
|||
override fun hashCode(): Int {
|
||||
var result = account.hashCode()
|
||||
result = 31 * result + amount.hashCode()
|
||||
result = 31 * result + unparsedReference.hashCode()
|
||||
result = 31 * result + reference.hashCode()
|
||||
result = 31 * result + bookingDate.hashCode()
|
||||
result = 31 * result + (otherPartyName?.hashCode() ?: 0)
|
||||
result = 31 * result + (otherPartyBankCode?.hashCode() ?: 0)
|
||||
result = 31 * result + (otherPartyBankId?.hashCode() ?: 0)
|
||||
result = 31 * result + (otherPartyAccountId?.hashCode() ?: 0)
|
||||
result = 31 * result + (bookingText?.hashCode() ?: 0)
|
||||
result = 31 * result + (postingText?.hashCode() ?: 0)
|
||||
result = 31 * result + valueDate.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$valueDate $amount $otherPartyName: $unparsedReference"
|
||||
return "$valueDate $amount $otherPartyName: $reference"
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,7 @@ open class AddAccountParameter @JvmOverloads constructor(
|
|||
open val bank: BankData,
|
||||
open val fetchBalanceAndTransactions: Boolean = true,
|
||||
open val preferredTanMethods: List<TanMethodType>? = null,
|
||||
open val tanMethodsNotSupportedByApplication: List<TanMethodType>? = null,
|
||||
open val preferredTanMedium: String? = null
|
||||
) {
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package net.codinux.banking.fints.model
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import net.codinux.banking.fints.tan.FlickerCode
|
||||
|
||||
|
||||
|
@ -11,8 +12,9 @@ open class FlickerCodeTanChallenge(
|
|||
tanMethod: TanMethod,
|
||||
tanMediaIdentifier: String?,
|
||||
bank: BankData,
|
||||
account: AccountData? = null
|
||||
) : TanChallenge(forAction, messageToShowToUser, challenge, tanMethod, tanMediaIdentifier, bank, account) {
|
||||
account: AccountData? = null,
|
||||
tanExpirationTime: Instant? = null
|
||||
) : TanChallenge(forAction, messageToShowToUser, challenge, tanMethod, tanMediaIdentifier, bank, account, tanExpirationTime) {
|
||||
|
||||
override fun toString(): String {
|
||||
return "$tanMethod (medium: $tanMediaIdentifier) $flickerCode: $messageToShowToUser"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package net.codinux.banking.fints.model
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import net.codinux.banking.fints.tan.TanImage
|
||||
|
||||
|
||||
|
@ -11,8 +12,9 @@ open class ImageTanChallenge(
|
|||
tanMethod: TanMethod,
|
||||
tanMediaIdentifier: String?,
|
||||
bank: BankData,
|
||||
account: AccountData? = null
|
||||
) : TanChallenge(forAction, messageToShowToUser, challenge, tanMethod, tanMediaIdentifier, bank, account) {
|
||||
account: AccountData? = null,
|
||||
tanExpirationTime: Instant? = null
|
||||
) : TanChallenge(forAction, messageToShowToUser, challenge, tanMethod, tanMediaIdentifier, bank, account, tanExpirationTime) {
|
||||
|
||||
override fun toString(): String {
|
||||
return "$tanMethod (medium: $tanMediaIdentifier) $image: $messageToShowToUser"
|
||||
|
|
|
@ -25,6 +25,9 @@ open class JobContext(
|
|||
* Only set if the current context is for a specific account (like get account's transactions).
|
||||
*/
|
||||
open val account: AccountData? = null,
|
||||
open val preferredTanMethods: List<TanMethodType>? = null,
|
||||
tanMethodsNotSupportedByApplication: List<TanMethodType>? = null,
|
||||
open val preferredTanMedium: String? = null,
|
||||
protected open val messageLogCollector: MessageLogCollector = MessageLogCollector(callback, config.options)
|
||||
) : MessageBaseData(bank, config.options.product), IMessageLogAppender {
|
||||
|
||||
|
@ -35,6 +38,8 @@ open class JobContext(
|
|||
|
||||
protected open val _dialogs = mutableListOf<DialogContext>()
|
||||
|
||||
open val tanMethodsNotSupportedByApplication: List<TanMethodType> = tanMethodsNotSupportedByApplication ?: emptyList()
|
||||
|
||||
open val mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(Mt940Parser(this), this)
|
||||
|
||||
open val responseParser: ResponseParser = ResponseParser(logAppender = this)
|
||||
|
@ -55,7 +60,7 @@ open class JobContext(
|
|||
protected open var dialogNumber: Int = 0
|
||||
|
||||
|
||||
open fun startNewDialog(closeDialog: Boolean = true, dialogId: String = DialogContext.InitialDialogId,
|
||||
open fun startNewDialog(closeDialog: Boolean = config.options.closeDialogs, dialogId: String = DialogContext.InitialDialogId,
|
||||
versionOfSecurityProcedure: VersionDesSicherheitsverfahrens = VersionDesSicherheitsverfahrens.Version_2,
|
||||
chunkedResponseHandler: ((BankResponse) -> Unit)? = dialog.chunkedResponseHandler) : DialogContext {
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package net.codinux.banking.fints.model
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import net.codinux.banking.fints.extensions.nowExt
|
||||
import net.codinux.banking.fints.log.MessageContext
|
||||
import net.codinux.banking.fints.response.segments.ReceivedSegment
|
||||
|
||||
|
@ -18,7 +18,7 @@ open class MessageLogEntry(
|
|||
* Is only set if [type] is set to [MessageLogEntryType.Received] and response parsing was successful.
|
||||
*/
|
||||
open val parsedSegments: List<ReceivedSegment> = emptyList(),
|
||||
open val time: Instant = Clock.System.now()
|
||||
open val time: Instant = Instant.nowExt()
|
||||
) {
|
||||
|
||||
val messageIncludingMessageTrace: String
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package net.codinux.banking.fints.model
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import net.codinux.banking.fints.extensions.nowExt
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
|
||||
import net.codinux.banking.fints.response.BankResponse
|
||||
import net.codinux.banking.fints.response.client.FinTsClientResponse
|
||||
import net.codinux.log.Log
|
||||
|
||||
|
||||
open class TanChallenge(
|
||||
|
@ -12,7 +15,14 @@ open class TanChallenge(
|
|||
val tanMethod: TanMethod,
|
||||
val tanMediaIdentifier: String?,
|
||||
val bank: BankData,
|
||||
val account: AccountData? = null
|
||||
val account: AccountData? = null,
|
||||
/**
|
||||
* Datum und Uhrzeit, bis zu welchem Zeitpunkt eine TAN auf Basis der gesendeten Challenge gültig ist. Nach Ablauf der Gültigkeitsdauer wird die entsprechende TAN entwertet.
|
||||
*
|
||||
* In server's time zone, that is Europe/Berlin.
|
||||
*/
|
||||
val tanExpirationTime: Instant? = null,
|
||||
val challengeCreationTimestamp: Instant = Instant.nowExt()
|
||||
) {
|
||||
|
||||
var enterTanResult: EnterTanResult? = null
|
||||
|
@ -21,6 +31,8 @@ open class TanChallenge(
|
|||
open val isEnteringTanDone: Boolean
|
||||
get() = enterTanResult != null
|
||||
|
||||
private val tanExpiredCallbacks = mutableListOf<() -> Unit>()
|
||||
|
||||
private val userApprovedDecoupledTanCallbacks = mutableListOf<() -> Unit>()
|
||||
|
||||
|
||||
|
@ -31,23 +43,59 @@ open class TanChallenge(
|
|||
internal fun userApprovedDecoupledTan(responseAfterApprovingDecoupledTan: BankResponse) {
|
||||
this.enterTanResult = EnterTanResult(null, true, responseAfterApprovingDecoupledTan)
|
||||
|
||||
userApprovedDecoupledTanCallbacks.forEach { it.invoke() }
|
||||
userApprovedDecoupledTanCallbacks.clear()
|
||||
userApprovedDecoupledTanCallbacks.toTypedArray().forEach { // copy to avoid ConcurrentModificationException
|
||||
try {
|
||||
it.invoke()
|
||||
} catch (e: Throwable) {
|
||||
Log.error(e) { "Could not call userApprovedDecoupledTanCallback" }
|
||||
}
|
||||
}
|
||||
clearUserApprovedDecoupledTanCallbacks()
|
||||
}
|
||||
|
||||
fun userDidNotEnterTan() {
|
||||
clearUserApprovedDecoupledTanCallbacks()
|
||||
|
||||
this.enterTanResult = EnterTanResult(null)
|
||||
}
|
||||
|
||||
internal fun tanExpired() {
|
||||
tanExpiredCallbacks.toTypedArray().forEach {
|
||||
try {
|
||||
it.invoke()
|
||||
} catch (e: Throwable) {
|
||||
Log.error(e) { "Could not call tanExpiredCallback" }
|
||||
}
|
||||
}
|
||||
|
||||
clearTanExpiredCallbacks()
|
||||
|
||||
userDidNotEnterTan()
|
||||
}
|
||||
|
||||
fun userAsksToChangeTanMethod(changeTanMethodTo: TanMethod) {
|
||||
clearUserApprovedDecoupledTanCallbacks()
|
||||
|
||||
this.enterTanResult = EnterTanResult(null, changeTanMethodTo = changeTanMethodTo)
|
||||
}
|
||||
|
||||
fun userAsksToChangeTanMedium(changeTanMediumTo: TanMedium, changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?) {
|
||||
clearUserApprovedDecoupledTanCallbacks()
|
||||
|
||||
this.enterTanResult = EnterTanResult(null, changeTanMediumTo = changeTanMediumTo, changeTanMediumResultCallback = changeTanMediumResultCallback)
|
||||
}
|
||||
|
||||
|
||||
fun addTanExpiredCallback(callback: () -> Unit) {
|
||||
if (isEnteringTanDone == false) {
|
||||
this.tanExpiredCallbacks.add(callback)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun clearTanExpiredCallbacks() {
|
||||
tanExpiredCallbacks.clear()
|
||||
}
|
||||
|
||||
fun addUserApprovedDecoupledTanCallback(callback: () -> Unit) {
|
||||
if (isEnteringTanDone == false) {
|
||||
this.userApprovedDecoupledTanCallbacks.add(callback)
|
||||
|
@ -56,6 +104,10 @@ open class TanChallenge(
|
|||
}
|
||||
}
|
||||
|
||||
protected open fun clearUserApprovedDecoupledTanCallbacks() {
|
||||
userApprovedDecoupledTanCallbacks.clear()
|
||||
}
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$tanMethod (medium: $tanMediaIdentifier): $messageToShowToUser"
|
||||
|
|
|
@ -63,7 +63,7 @@ open class ModelMapper(
|
|||
}
|
||||
}
|
||||
|
||||
open fun updateCustomerData(bank: BankData, response: BankResponse) {
|
||||
open fun updateCustomerData(bank: BankData, response: BankResponse, context: JobContext) {
|
||||
response.getFirstSegmentById<BankParameters>(InstituteSegmentId.BankParameters)?.let { bankParameters ->
|
||||
// TODO: ask user if there is more than one supported language? But it seems that almost all banks only support German.
|
||||
if (bank.selectedLanguage == Dialogsprache.Default && bankParameters.supportedLanguages.isNotEmpty()) {
|
||||
|
@ -102,7 +102,7 @@ open class ModelMapper(
|
|||
accountInfo.accountLimit, accountInfo.allowedJobNames)
|
||||
|
||||
bank.supportedJobs.filterIsInstance<RetrieveAccountTransactionsParameters>().sortedByDescending { it.segmentVersion }.firstOrNull { newAccount.allowedJobNames.contains(it.jobName) }?.let { transactionsParameters ->
|
||||
newAccount.countDaysForWhichTransactionsAreKept = transactionsParameters.countDaysForWhichTransactionsAreKept
|
||||
newAccount.serverTransactionsRetentionDays = transactionsParameters.serverTransactionsRetentionDays
|
||||
}
|
||||
|
||||
bank.addAccount(newAccount)
|
||||
|
@ -146,6 +146,7 @@ open class ModelMapper(
|
|||
|
||||
if (response.supportedTanMethodsForUser.isNotEmpty()) {
|
||||
bank.tanMethodsAvailableForUser = response.supportedTanMethodsForUser.mapNotNull { findTanMethod(it, bank) }
|
||||
.filterNot { context.tanMethodsNotSupportedByApplication.contains(it.type) }
|
||||
|
||||
if (bank.tanMethodsAvailableForUser.firstOrNull { it.securityFunction == bank.selectedTanMethod.securityFunction } == null) { // supportedTanMethods don't contain selectedTanMethod anymore
|
||||
bank.resetSelectedTanMethod()
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package net.codinux.banking.fints.response
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.LocalTime
|
||||
import kotlinx.datetime.atTime
|
||||
import kotlinx.datetime.*
|
||||
import net.codinux.banking.fints.extensions.EuropeBerlin
|
||||
import net.codinux.log.logger
|
||||
import net.codinux.banking.fints.log.IMessageLogAppender
|
||||
import net.codinux.banking.fints.messages.Separators
|
||||
|
@ -577,7 +575,7 @@ open class ResponseParser(
|
|||
if (dataElementGroups.size > 3) parseStringToNullIfEmpty(dataElementGroups[3]) else null,
|
||||
if (dataElementGroups.size > 4) parseStringToNullIfEmpty(dataElementGroups[4]) else null,
|
||||
binaryChallengeHHD_UC?.let { extractBinaryData(it) },
|
||||
if (dataElementGroups.size > 6) parseNullableDateTime(dataElementGroups[6]) else null,
|
||||
if (dataElementGroups.size > 6) parseNullableDateTime(dataElementGroups[6])?.toInstant(TimeZone.EuropeBerlin) else null,
|
||||
if (dataElementGroups.size > 7) parseStringToNullIfEmpty(dataElementGroups[7]) else null,
|
||||
segment
|
||||
)
|
||||
|
@ -748,11 +746,11 @@ open class ResponseParser(
|
|||
val transactionsParameterIndex = if (jobParameters.segmentVersion >= 6) 4 else 3
|
||||
val dataElements = getDataElements(dataElementGroups[transactionsParameterIndex])
|
||||
|
||||
val countDaysForWhichTransactionsAreKept = parseInt(dataElements[0])
|
||||
val serverTransactionsRetentionDays = parseInt(dataElements[0])
|
||||
val settingCountEntriesAllowed = parseBoolean(dataElements[1])
|
||||
val settingAllAccountAllowed = if (dataElements.size > 2) parseBoolean(dataElements[2]) else false
|
||||
|
||||
return RetrieveAccountTransactionsParameters(jobParameters, countDaysForWhichTransactionsAreKept, settingCountEntriesAllowed, settingAllAccountAllowed)
|
||||
return RetrieveAccountTransactionsParameters(jobParameters, serverTransactionsRetentionDays, settingCountEntriesAllowed, settingAllAccountAllowed)
|
||||
}
|
||||
|
||||
|
||||
|
@ -805,11 +803,11 @@ open class ResponseParser(
|
|||
val transactionsParameterIndex = if (jobParameters.segmentVersion >= 2) 4 else 3 // TODO: check if at segment version 1 the transactions parameter are the third data elements group
|
||||
val dataElements = getDataElements(dataElementGroups[transactionsParameterIndex])
|
||||
|
||||
val countDaysForWhichTransactionsAreKept = parseInt(dataElements[0])
|
||||
val serverTransactionsRetentionDays = parseInt(dataElements[0])
|
||||
val settingCountEntriesAllowed = parseBoolean(dataElements[1])
|
||||
val settingAllAccountAllowed = if (dataElements.size > 2) parseBoolean(dataElements[2]) else false
|
||||
|
||||
return RetrieveAccountTransactionsParameters(jobParameters, countDaysForWhichTransactionsAreKept, settingCountEntriesAllowed, settingAllAccountAllowed)
|
||||
return RetrieveAccountTransactionsParameters(jobParameters, serverTransactionsRetentionDays, settingCountEntriesAllowed, settingAllAccountAllowed)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package net.codinux.banking.fints.response.segments
|
|||
|
||||
open class RetrieveAccountTransactionsParameters(
|
||||
parameters: JobParameters,
|
||||
open val countDaysForWhichTransactionsAreKept: Int,
|
||||
open val serverTransactionsRetentionDays: Int,
|
||||
open val settingCountEntriesAllowed: Boolean,
|
||||
open val settingAllAccountAllowed: Boolean
|
||||
) : JobParameters(parameters) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package net.codinux.banking.fints.response.segments
|
||||
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.Instant
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanProcess
|
||||
|
||||
|
||||
|
@ -31,7 +31,13 @@ open class TanResponse(
|
|||
val challenge: String?, // M: bei TAN-Prozess=1, 3, 4. O: bei TAN-Prozess=2
|
||||
|
||||
val challengeHHD_UC: String?,
|
||||
val validityDateTimeForChallenge: LocalDateTime?,
|
||||
|
||||
/**
|
||||
* Datum und Uhrzeit, bis zu welchem Zeitpunkt eine TAN auf Basis der gesendeten Challenge gültig ist. Nach Ablauf der Gültigkeitsdauer wird die entsprechende TAN entwertet.
|
||||
*
|
||||
* In server's time zone, that is Europe/Berlin.
|
||||
*/
|
||||
val tanExpirationTime: Instant?,
|
||||
val tanMediaIdentifier: String? = null, // M: bei TAN-Prozess=1, 3, 4 und „Anzahl unterstützter aktiver TAN-Medien“ nicht vorhanden. O: sonst
|
||||
|
||||
segmentString: String
|
||||
|
|
|
@ -44,44 +44,50 @@ open class Mt940AccountTransactionsParser(
|
|||
protected open fun mapToAccountTransaction(statement: AccountStatement, transaction: Transaction, account: AccountData): AccountTransaction {
|
||||
val currency = statement.closingBalance.currency
|
||||
|
||||
// may parse postingKey to postingText (Buchungstext) according to table in 8.2.3 Buchungsschlüssel (Feld 61), S. 654 ff.
|
||||
|
||||
return AccountTransaction(
|
||||
account,
|
||||
Money(mapAmount(transaction.statementLine), currency),
|
||||
transaction.statementLine.isReversal,
|
||||
transaction.information?.unparsedReference ?: "",
|
||||
// either field :86: contains structured information, then sepaReference is a mandatory field, or :86: is unstructured, then the whole field value is the reference
|
||||
transaction.information?.sepaReference ?: transaction.information?.unparsedReference ?: "",
|
||||
|
||||
transaction.statementLine.bookingDate ?: statement.closingBalance.bookingDate,
|
||||
transaction.information?.otherPartyName,
|
||||
transaction.information?.otherPartyBankCode,
|
||||
transaction.information?.otherPartyAccountId,
|
||||
transaction.information?.bookingText,
|
||||
transaction.statementLine.valueDate,
|
||||
|
||||
transaction.information?.otherPartyName,
|
||||
transaction.information?.otherPartyBankId,
|
||||
transaction.information?.otherPartyAccountId,
|
||||
|
||||
transaction.information?.postingText,
|
||||
|
||||
Money(mapAmount(statement.openingBalance), currency),
|
||||
Money(mapAmount(statement.closingBalance), currency),
|
||||
|
||||
statement.statementNumber,
|
||||
statement.sequenceNumber,
|
||||
Money(mapAmount(statement.openingBalance), currency), // TODO: that's not true, these are the opening and closing balance of
|
||||
Money(mapAmount(statement.closingBalance), currency), // all transactions of this day, not this specific transaction's ones
|
||||
statement.sheetNumber,
|
||||
|
||||
// :60: customer reference: Wenn „KREF+“ eingestellt ist, dann erfolgt die Angabe der Referenznummer in Tag :86: .
|
||||
transaction.information?.customerReference ?: transaction.statementLine.customerReference,
|
||||
transaction.statementLine.bankReference,
|
||||
transaction.statementLine.furtherInformationOriginalAmountAndCharges,
|
||||
|
||||
transaction.information?.endToEndReference,
|
||||
transaction.information?.customerReference,
|
||||
transaction.information?.mandateReference,
|
||||
transaction.information?.creditorIdentifier,
|
||||
transaction.information?.originatorsIdentificationCode,
|
||||
transaction.information?.compensationAmount,
|
||||
transaction.information?.originalAmount,
|
||||
transaction.information?.sepaReference,
|
||||
transaction.information?.deviantOriginator,
|
||||
transaction.information?.deviantRecipient,
|
||||
transaction.information?.referenceWithNoSpecialType,
|
||||
transaction.information?.primaNotaNumber,
|
||||
transaction.information?.textKeySupplement,
|
||||
transaction.information?.journalNumber,
|
||||
transaction.information?.textKeyAddition,
|
||||
|
||||
transaction.statementLine.currencyType,
|
||||
transaction.statementLine.bookingKey,
|
||||
transaction.statementLine.referenceForTheAccountOwner,
|
||||
transaction.statementLine.referenceOfTheAccountServicingInstitution,
|
||||
transaction.statementLine.supplementaryDetails,
|
||||
statement.orderReferenceNumber,
|
||||
statement.referenceNumber,
|
||||
|
||||
statement.transactionReferenceNumber,
|
||||
statement.relatedReferenceNumber
|
||||
transaction.statementLine.isReversal,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -34,9 +34,9 @@ open class Mt940Parser(
|
|||
val AccountStatementFieldSeparatorRegex = Regex("(?<!T\\d\\d(:\\d\\d)?):\\d\\d\\w?:")
|
||||
|
||||
|
||||
const val TransactionReferenceNumberCode = "20"
|
||||
const val OrderReferenceNumberCode = "20"
|
||||
|
||||
const val RelatedReferenceNumberCode = "21"
|
||||
const val ReferenceNumberCode = "21"
|
||||
|
||||
const val AccountIdentificationCode = "25"
|
||||
|
||||
|
@ -46,7 +46,7 @@ open class Mt940Parser(
|
|||
|
||||
const val StatementLineCode = "61"
|
||||
|
||||
const val InformationToAccountOwnerCode = "86"
|
||||
const val RemittanceInformationFieldCode = "86"
|
||||
|
||||
const val ClosingBalanceCode = "62"
|
||||
|
||||
|
@ -61,7 +61,7 @@ open class Mt940Parser(
|
|||
|
||||
val ReferenceTypeRegex = Regex("[A-Z]{4}\\+")
|
||||
|
||||
val InformationToAccountOwnerSubFieldRegex = Regex("\\?\\d\\d")
|
||||
val RemittanceInformationSubFieldRegex = Regex("\\?\\d\\d")
|
||||
|
||||
|
||||
const val EndToEndReferenceKey = "EREF+"
|
||||
|
@ -169,8 +169,8 @@ open class Mt940Parser(
|
|||
val closingBalancePair = fieldsByCode.first { it.first.startsWith(ClosingBalanceCode) }
|
||||
|
||||
return AccountStatement(
|
||||
getFieldValue(fieldsByCode, TransactionReferenceNumberCode),
|
||||
getOptionalFieldValue(fieldsByCode, RelatedReferenceNumberCode),
|
||||
getFieldValue(fieldsByCode, OrderReferenceNumberCode),
|
||||
getOptionalFieldValue(fieldsByCode, ReferenceNumberCode),
|
||||
accountIdentification[0],
|
||||
if (accountIdentification.size > 1) accountIdentification[1] else null,
|
||||
statementAndMaySequenceNumber[0].toInt(),
|
||||
|
@ -210,7 +210,7 @@ open class Mt940Parser(
|
|||
val statementLine = parseStatementLine(pair.second)
|
||||
|
||||
val nextPair = if (index < fieldsByCode.size - 1) fieldsByCode.get(index + 1) else null
|
||||
val information = if (nextPair?.first == InformationToAccountOwnerCode) parseNullableInformationToAccountOwner(nextPair.second) else null
|
||||
val information = if (nextPair?.first == RemittanceInformationFieldCode) parseNullableRemittanceInformationField(nextPair.second) else null
|
||||
|
||||
transactions.add(Transaction(statementLine, information))
|
||||
}
|
||||
|
@ -269,11 +269,11 @@ open class Mt940Parser(
|
|||
*/
|
||||
val transactionType = fieldValue.substring(amountEndIndex, amountEndIndex + 1) // transaction type is 'N', 'S' or 'F'
|
||||
|
||||
val bookingKeyStart = amountEndIndex + 1
|
||||
val bookingKey = fieldValue.substring(bookingKeyStart, bookingKeyStart + 3) // TODO: parse codes, p. 178
|
||||
val postingKeyStart = amountEndIndex + 1
|
||||
val postingKey = fieldValue.substring(postingKeyStart, postingKeyStart + 3) // TODO: parse codes, p. 178
|
||||
|
||||
val customerAndBankReference = fieldValue.substring(bookingKeyStart + 3).split("//")
|
||||
val customerReference = customerAndBankReference[0]
|
||||
val customerAndBankReference = fieldValue.substring(postingKeyStart + 3).split("//")
|
||||
val customerReference = customerAndBankReference[0].takeIf { it != "NONREF" }
|
||||
|
||||
/**
|
||||
* The content of this subfield is the account servicing institution's own reference for the transaction.
|
||||
|
@ -281,58 +281,58 @@ open class Mt940Parser(
|
|||
* reference may be identical to subfield 7, Reference for the Account Owner. If this is
|
||||
* the case, Reference of the Account Servicing Institution, subfield 8 may be omitted.
|
||||
*/
|
||||
var bankReference = if (customerAndBankReference.size > 1) customerAndBankReference[1] else customerReference // TODO: or use null?
|
||||
var supplementaryDetails: String? = null
|
||||
var bankReference = if (customerAndBankReference.size > 1) customerAndBankReference[1] else null
|
||||
var furtherInformation: String? = null
|
||||
|
||||
val bankReferenceAndSupplementaryDetails = bankReference.split("\n")
|
||||
if (bankReferenceAndSupplementaryDetails.size > 1) {
|
||||
bankReference = bankReferenceAndSupplementaryDetails[0].trim()
|
||||
if (bankReference != null && bankReference.contains('\n')) {
|
||||
val bankReferenceAndFurtherInformation = bankReference.split("\n")
|
||||
bankReference = bankReferenceAndFurtherInformation[0].trim()
|
||||
// TODO: parse /OCMT/ and /CHGS/, see page 518
|
||||
supplementaryDetails = bankReferenceAndSupplementaryDetails[1].trim()
|
||||
furtherInformation = bankReferenceAndFurtherInformation[1].trim()
|
||||
}
|
||||
|
||||
return StatementLine(!!!isDebit, isCancellation, valueDate, bookingDate, null, amount, bookingKey,
|
||||
customerReference, bankReference, supplementaryDetails)
|
||||
return StatementLine(!!!isDebit, isCancellation, valueDate, bookingDate, null, amount, postingKey,
|
||||
customerReference, bankReference, furtherInformation)
|
||||
}
|
||||
|
||||
protected open fun parseNullableInformationToAccountOwner(informationToAccountOwnerString: String): InformationToAccountOwner? {
|
||||
protected open fun parseNullableRemittanceInformationField(remittanceInformationFieldString: String): RemittanceInformationField? {
|
||||
try {
|
||||
val information = parseInformationToAccountOwner(informationToAccountOwnerString)
|
||||
val information = parseRemittanceInformationField(remittanceInformationFieldString)
|
||||
|
||||
mapReference(information)
|
||||
|
||||
return information
|
||||
} catch (e: Exception) {
|
||||
logError("Could not parse InformationToAccountOwner from field value '$informationToAccountOwnerString'", e)
|
||||
logError("Could not parse RemittanceInformationField from field value '$remittanceInformationFieldString'", e)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
protected open fun parseInformationToAccountOwner(informationToAccountOwnerString: String): InformationToAccountOwner {
|
||||
protected open fun parseRemittanceInformationField(remittanceInformationFieldString: String): RemittanceInformationField {
|
||||
// e. g. starts with 0 -> Inlandszahlungsverkehr, starts with '3' -> Wertpapiergeschäft
|
||||
// see Finanzdatenformate p. 209 - 215
|
||||
val geschaeftsvorfallCode = informationToAccountOwnerString.substring(0, 2) // TODO: may map
|
||||
val geschaeftsvorfallCode = remittanceInformationFieldString.substring(0, 2) // TODO: may map
|
||||
|
||||
val referenceParts = mutableListOf<String>()
|
||||
val otherPartyName = StringBuilder()
|
||||
var otherPartyBankCode: String? = null
|
||||
var otherPartyBankId: String? = null
|
||||
var otherPartyAccountId: String? = null
|
||||
var bookingText: String? = null
|
||||
var primaNotaNumber: String? = null
|
||||
var textKeySupplement: String? = null
|
||||
|
||||
val subFieldMatches = InformationToAccountOwnerSubFieldRegex.findAll(informationToAccountOwnerString).toList()
|
||||
val subFieldMatches = RemittanceInformationSubFieldRegex.findAll(remittanceInformationFieldString).toList()
|
||||
subFieldMatches.forEachIndexed { index, matchResult ->
|
||||
val fieldCode = matchResult.value.substring(1, 3).toInt()
|
||||
val endIndex = if (index + 1 < subFieldMatches.size) subFieldMatches[index + 1].range.start else informationToAccountOwnerString.length
|
||||
val fieldValue = informationToAccountOwnerString.substring(matchResult.range.last + 1, endIndex)
|
||||
val endIndex = if (index + 1 < subFieldMatches.size) subFieldMatches[index + 1].range.start else remittanceInformationFieldString.length
|
||||
val fieldValue = remittanceInformationFieldString.substring(matchResult.range.last + 1, endIndex)
|
||||
|
||||
when (fieldCode) {
|
||||
0 -> bookingText = fieldValue
|
||||
10 -> primaNotaNumber = fieldValue
|
||||
in 20..29 -> referenceParts.add(fieldValue)
|
||||
30 -> otherPartyBankCode = fieldValue
|
||||
30 -> otherPartyBankId = fieldValue
|
||||
31 -> otherPartyAccountId = fieldValue
|
||||
32, 33 -> otherPartyName.append(fieldValue)
|
||||
34 -> textKeySupplement = fieldValue
|
||||
|
@ -345,8 +345,8 @@ open class Mt940Parser(
|
|||
|
||||
val otherPartyNameString = if (otherPartyName.isBlank()) null else otherPartyName.toString()
|
||||
|
||||
return InformationToAccountOwner(
|
||||
reference, otherPartyNameString, otherPartyBankCode, otherPartyAccountId,
|
||||
return RemittanceInformationField(
|
||||
reference, otherPartyNameString, otherPartyBankId, otherPartyAccountId,
|
||||
bookingText, primaNotaNumber, textKeySupplement
|
||||
)
|
||||
}
|
||||
|
@ -396,7 +396,7 @@ open class Mt940Parser(
|
|||
*
|
||||
* Weitere 4 Verwendungszwecke können zu den Feldschlüsseln 60 bis 63 eingestellt werden.
|
||||
*/
|
||||
protected open fun mapReference(information: InformationToAccountOwner) {
|
||||
protected open fun mapReference(information: RemittanceInformationField) {
|
||||
val referenceParts = getReferenceParts(information.unparsedReference)
|
||||
|
||||
referenceParts.forEach { entry ->
|
||||
|
@ -431,7 +431,7 @@ open class Mt940Parser(
|
|||
}
|
||||
|
||||
// TODO: there are more. See .pdf from Deutsche Bank
|
||||
protected open fun setReferenceLineValue(information: InformationToAccountOwner, referenceType: String, typeValue: String) {
|
||||
protected open fun setReferenceLineValue(information: RemittanceInformationField, referenceType: String, typeValue: String) {
|
||||
when (referenceType) {
|
||||
EndToEndReferenceKey -> information.endToEndReference = typeValue
|
||||
CustomerReferenceKey -> information.customerReference = typeValue
|
||||
|
|
|
@ -11,7 +11,7 @@ open class AccountStatement(
|
|||
*
|
||||
* Max length = 16
|
||||
*/
|
||||
val transactionReferenceNumber: String,
|
||||
val orderReferenceNumber: String,
|
||||
|
||||
/**
|
||||
* Bezugsreferenz oder „NONREF“.
|
||||
|
@ -20,7 +20,7 @@ open class AccountStatement(
|
|||
*
|
||||
* Max length = 16
|
||||
*/
|
||||
val relatedReferenceNumber: String?,
|
||||
val referenceNumber: String?,
|
||||
|
||||
/**
|
||||
* xxxxxxxxxxx/Konto-Nr. oder yyyyyyyy/Konto-Nr.
|
||||
|
@ -50,7 +50,7 @@ open class AccountStatement(
|
|||
*
|
||||
* Max length = 5
|
||||
*/
|
||||
val sequenceNumber: Int?,
|
||||
val sheetNumber: Int?,
|
||||
|
||||
val openingBalance: Balance,
|
||||
|
||||
|
@ -72,7 +72,7 @@ open class AccountStatement(
|
|||
*
|
||||
* Max length = 65
|
||||
*/
|
||||
val multipurposeField: String? = null
|
||||
val remittanceInformationField: String? = null
|
||||
|
||||
) {
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ open class Balance(
|
|||
val isCredit: Boolean,
|
||||
|
||||
/**
|
||||
* JJMMTT = Buchungsdatum des Saldos oder '0' beim ersten Auszug
|
||||
* JJMMTT = Buchungsdatum des Saldos oder '000000' beim ersten Auszug
|
||||
*
|
||||
* Max length = 6
|
||||
*/
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
package net.codinux.banking.fints.transactions.mt940.model
|
||||
|
||||
|
||||
open class InformationToAccountOwner(
|
||||
val unparsedReference: String,
|
||||
val otherPartyName: String?,
|
||||
val otherPartyBankCode: String?,
|
||||
val otherPartyAccountId: String?,
|
||||
val bookingText: String?,
|
||||
val primaNotaNumber: String?,
|
||||
val textKeySupplement: String?
|
||||
) {
|
||||
|
||||
var endToEndReference: String? = null
|
||||
|
||||
var customerReference: String? = null
|
||||
|
||||
var mandateReference: String? = null
|
||||
|
||||
var creditorIdentifier: String? = null
|
||||
|
||||
var originatorsIdentificationCode: String? = null
|
||||
|
||||
var compensationAmount: String? = null
|
||||
|
||||
var originalAmount: String? = null
|
||||
|
||||
var sepaReference: String? = null
|
||||
|
||||
var deviantOriginator: String? = null
|
||||
|
||||
var deviantRecipient: String? = null
|
||||
|
||||
var referenceWithNoSpecialType: String? = null
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$otherPartyName $unparsedReference"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package net.codinux.banking.fints.transactions.mt940.model
|
||||
|
||||
open class RemittanceInformationField(
|
||||
val unparsedReference: String,
|
||||
/**
|
||||
* AT 02 Name des Überweisenden
|
||||
* AT 03 Name des Zahlungsempfängers (bei mehr als 54 Zeichen wird der Name gekürzt)
|
||||
*/
|
||||
val otherPartyName: String?,
|
||||
/**
|
||||
* BLZ Überweisender / Zahlungsempfänger
|
||||
* Bei SEPA-Zahlungen BIC des Überweisenden / Zahlungsempfängers.
|
||||
*/
|
||||
val otherPartyBankId: String?,
|
||||
/**
|
||||
* AT 01 IBAN des Überweisenden (Zahlungseingang Überweisung)
|
||||
* AT 04 IBAN des Zahlungsempfängers (Eingang Lastschrift)
|
||||
*/
|
||||
val otherPartyAccountId: String?,
|
||||
/**
|
||||
* Buchungstext, z. B. DAUERAUFTRAG, BARGELDAUSZAHLUNG, ONLINE-UEBERWEISUNG, FOLGELASTSCHRIFT, ...
|
||||
*/
|
||||
val postingText: String?,
|
||||
/**
|
||||
* Primanoten-Nr.
|
||||
*/
|
||||
val journalNumber: String?,
|
||||
/**
|
||||
* Bei R-Transaktionen siehe Tabelle der
|
||||
* SEPA-Rückgabecodes, bei SEPALastschriften siehe optionale Belegung
|
||||
* bei GVC 104 und GVC 105 (GVC = Geschäftsvorfallcode)
|
||||
*/
|
||||
val textKeyAddition: String?
|
||||
) {
|
||||
|
||||
/**
|
||||
* (DDAT10; CT-AT41 - Angabe verpflichtend)
|
||||
* (NOTPROVIDED wird nicht eingestellt.
|
||||
* Im Falle von Schecks wird hinter EREF+ die Konstante „SCHECK-NR. “, gefolgt von der Schecknummer angegeben (erst
|
||||
* nach Migration Scheckvordruck auf ISO 20022; November 2016, entspricht dem Inhalt der EndToEndId des
|
||||
* entsprechenden Scheckumsatzes).
|
||||
*/
|
||||
var endToEndReference: String? = null
|
||||
|
||||
var customerReference: String? = null
|
||||
|
||||
/**
|
||||
* (DD-AT01 - Angabe verpflichtend)
|
||||
*/
|
||||
var mandateReference: String? = null
|
||||
|
||||
/**
|
||||
* (DD-AT02 - Angabe verpflichtend bei SEPALastschriften, nicht jedoch bei SEPARücklastschriften)
|
||||
*/
|
||||
var creditorIdentifier: String? = null
|
||||
|
||||
/**
|
||||
* (CT-AT10- Angabe verpflichtend,)
|
||||
* Entweder CRED oder DEBT
|
||||
*/
|
||||
var originatorsIdentificationCode: String? = null
|
||||
|
||||
/**
|
||||
* Summe aus Auslagenersatz und Bearbeitungsprovision bei einer nationalen Rücklastschrift
|
||||
* sowie optionalem Zinsausgleich.
|
||||
*/
|
||||
var compensationAmount: String? = null
|
||||
|
||||
/**
|
||||
* Betrag der ursprünglichen Lastschrift
|
||||
*/
|
||||
var originalAmount: String? = null
|
||||
|
||||
/**
|
||||
* (DD-AT22; CT-AT05 -Angabe verpflichtend, nicht jedoch bei RTransaktionen52)
|
||||
*/
|
||||
var sepaReference: String? = null
|
||||
|
||||
/**
|
||||
* Abweichender Überweisender (CT-AT08) / Abweichender Zahlungsempfänger (DD-AT38)
|
||||
* (optional)53
|
||||
*/
|
||||
var deviantOriginator: String? = null
|
||||
|
||||
/**
|
||||
* Abweichender Zahlungsempfänger (CT-AT28) /
|
||||
* Abweichender Zahlungspflichtiger ((DDAT15)
|
||||
* (optional)53
|
||||
*/
|
||||
var deviantRecipient: String? = null
|
||||
|
||||
var referenceWithNoSpecialType: String? = null
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$otherPartyName $unparsedReference"
|
||||
}
|
||||
|
||||
}
|
|
@ -42,26 +42,57 @@ open class StatementLine(
|
|||
val currencyType: String?,
|
||||
|
||||
/**
|
||||
* Codes see p. 177 bottom - 179
|
||||
*
|
||||
* After constant „N“
|
||||
* in Kontowährung
|
||||
*
|
||||
* Max length = 15
|
||||
*/
|
||||
val amount: Amount,
|
||||
|
||||
/**
|
||||
* in Kontowährung
|
||||
* Codes see p. 177 bottom - 179
|
||||
*
|
||||
* After constant „N“
|
||||
*
|
||||
* Length = 3
|
||||
*/
|
||||
val bookingKey: String,
|
||||
val postingKey: String,
|
||||
|
||||
val referenceForTheAccountOwner: String,
|
||||
/**
|
||||
* Kundenreferenz.
|
||||
* Bei Nichtbelegung wird „NONREF“ eingestellt, zum Beispiel bei Schecknummer
|
||||
* Wenn „KREF+“ eingestellt ist, dann erfolgt die Angabe der Referenznummer in Tag :86: .
|
||||
*/
|
||||
val customerReference: String?,
|
||||
|
||||
val referenceOfTheAccountServicingInstitution: String?,
|
||||
/**
|
||||
* Bankreferenz
|
||||
*/
|
||||
val bankReference: String?,
|
||||
|
||||
val supplementaryDetails: String? = null
|
||||
/**
|
||||
* Währungsart und Umsatzbetrag in Ursprungswährung (original currency
|
||||
* amount) in folgendem
|
||||
* Format:
|
||||
* /OCMT/3a..15d/
|
||||
* sowie Währungsart und
|
||||
* Gebührenbetrag
|
||||
* (charges) in folgendem
|
||||
* Format:
|
||||
* /CHGS/3a..15d/
|
||||
* 3a = 3-stelliger
|
||||
* Währungscode gemäß
|
||||
* ISO 4217
|
||||
* ..15d = Betrag mit Komma
|
||||
* als Dezimalzeichen (gemäß SWIFT-Konvention).
|
||||
* Im Falle von SEPALastschriftrückgaben ist
|
||||
* das Feld /OCMT/ mit dem
|
||||
* Originalbetrag und das
|
||||
* Feld /CHGS/ mit der
|
||||
* Summe aus Entgelten
|
||||
* sowie Zinsausgleich zu
|
||||
* belegen.
|
||||
*/
|
||||
val furtherInformationOriginalAmountAndCharges: String? = null
|
||||
|
||||
) {
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ package net.codinux.banking.fints.transactions.mt940.model
|
|||
open class Transaction(
|
||||
|
||||
val statementLine: StatementLine,
|
||||
val information: InformationToAccountOwner? = null
|
||||
val information: RemittanceInformationField? = null
|
||||
|
||||
) {
|
||||
|
||||
|
|
|
@ -12,64 +12,36 @@ open class TanMethodSelector {
|
|||
|
||||
val ImageBased = listOf(TanMethodType.QrCode, TanMethodType.ChipTanQrCode, TanMethodType.photoTan, TanMethodType.ChipTanPhotoTanMatrixCode)
|
||||
|
||||
/**
|
||||
* NonVisualOrImageBased is a good default for most users as it lists the most simplistic ones (which also work with
|
||||
* the command line) first and then continues with image based TAN methods, which for UI applications are easily to display.
|
||||
*/
|
||||
val NonVisualOrImageBased = buildList {
|
||||
// decoupled TAN method is the most simplistic TAN method, user only has to confirm the action in her TAN app, no manual TAN entering required
|
||||
// AppTan is the second most simplistic TAN method: user has to confirm action in her TAN app and then enter the displayed TAN
|
||||
addAll(listOf(TanMethodType.DecoupledTan, TanMethodType.DecoupledPushTan, TanMethodType.AppTan, TanMethodType.SmsTan, TanMethodType.EnterTan))
|
||||
addAll(ImageBased)
|
||||
addAll(listOf(TanMethodType.ChipTanManuell)) // this is quite inconvenient for user, so i added it as last
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
open fun getSuggestedTanMethod(tanMethods: List<TanMethod>): TanMethod? {
|
||||
return tanMethods.firstOrNull { it.type == TanMethodType.DecoupledPushTan || it.type == TanMethodType.DecoupledTan } // decoupled TAN method is the most simplistic TAN method, user only has to confirm the action in her TAN app, no manual TAN entering required
|
||||
?: tanMethods.firstOrNull { it.type == TanMethodType.AppTan } // that's the second most simplistic TAN method: user has to confirm action in her TAN app and then enter the displayed TAN
|
||||
?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb && it.type != TanMethodType.SmsTan && it.type != TanMethodType.ChipTanManuell }
|
||||
?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb && it.type != TanMethodType.SmsTan }
|
||||
?: tanMethods.firstOrNull { it.type != TanMethodType.ChipTanUsb }
|
||||
?: first(tanMethods)
|
||||
open fun getSuggestedTanMethod(tanMethods: List<TanMethod>, tanMethodsNotSupportedByApplication: List<TanMethodType> = emptyList()): TanMethod? {
|
||||
return findPreferredTanMethod(tanMethods, NonVisualOrImageBased, tanMethodsNotSupportedByApplication) // we use NonVisualOrImageBased as it provides a good default for most users
|
||||
?: tanMethods.firstOrNull { it.type !in tanMethodsNotSupportedByApplication }
|
||||
}
|
||||
|
||||
open fun findPreferredTanMethod(tanMethods: List<TanMethod>, preferredTanMethods: List<TanMethodType>?): TanMethod? {
|
||||
open fun findPreferredTanMethod(tanMethods: List<TanMethod>, preferredTanMethods: List<TanMethodType>?, tanMethodsNotSupportedByApplication: List<TanMethodType> = emptyList()): TanMethod? {
|
||||
preferredTanMethods?.forEach { preferredTanMethodType ->
|
||||
tanMethods.firstOrNull { it.type == preferredTanMethodType }?.let {
|
||||
return it
|
||||
if (preferredTanMethodType !in tanMethodsNotSupportedByApplication) {
|
||||
tanMethods.firstOrNull { it.type == preferredTanMethodType }?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
open fun nonVisual(tanMethods: List<TanMethod>): TanMethod? {
|
||||
return findPreferredTanMethod(tanMethods, NonVisual)
|
||||
?: tanMethods.firstOrNull { it.displayName.contains("manuell", true) }
|
||||
}
|
||||
|
||||
open fun nonVisualOrFirst(tanMethods: List<TanMethod>): TanMethod? {
|
||||
return nonVisual(tanMethods)
|
||||
?: first(tanMethods)
|
||||
}
|
||||
|
||||
|
||||
open fun imageBased(tanMethods: List<TanMethod>): TanMethod? {
|
||||
return findPreferredTanMethod(tanMethods, ImageBased)
|
||||
}
|
||||
|
||||
open fun imageBasedOrFirst(tanMethods: List<TanMethod>): TanMethod? {
|
||||
return imageBased(tanMethods)
|
||||
?: first(tanMethods)
|
||||
}
|
||||
|
||||
|
||||
open fun nonVisualOrImageBased(tanMethods: List<TanMethod>): TanMethod? {
|
||||
return nonVisual(tanMethods)
|
||||
?: imageBased(tanMethods)
|
||||
}
|
||||
|
||||
open fun nonVisualOrImageBasedOrFirst(tanMethods: List<TanMethod>): TanMethod? {
|
||||
return nonVisual(tanMethods)
|
||||
?: imageBased(tanMethods)
|
||||
?: first(tanMethods)
|
||||
}
|
||||
|
||||
|
||||
open fun first(tanMethods: List<TanMethod>): TanMethod? {
|
||||
return tanMethods.firstOrNull()
|
||||
}
|
||||
|
||||
}
|
|
@ -10,70 +10,72 @@ import net.codinux.banking.fints.extensions.UnixEpochStart
|
|||
@Serializable
|
||||
open class AccountTransaction(
|
||||
val amount: Money, // TODO: if we decide to stick with Money, create own type, don't use that one from fints.model (or move over from)
|
||||
val unparsedReference: String, // alternative names: purpose, reason
|
||||
val reference: String?, // alternative names: purpose, reason
|
||||
|
||||
val bookingDate: LocalDate,
|
||||
val otherPartyName: String?,
|
||||
val otherPartyBankCode: String?,
|
||||
val otherPartyAccountId: String?,
|
||||
val bookingText: String?,
|
||||
val valueDate: LocalDate,
|
||||
val statementNumber: Int,
|
||||
val sequenceNumber: Int?,
|
||||
|
||||
val otherPartyName: String?,
|
||||
val otherPartyBankId: String?,
|
||||
val otherPartyAccountId: String?,
|
||||
|
||||
val postingText: String?,
|
||||
|
||||
val openingBalance: Money?,
|
||||
val closingBalance: Money?,
|
||||
|
||||
val endToEndReference: String?,
|
||||
val statementNumber: Int,
|
||||
val sheetNumber: Int?,
|
||||
|
||||
val customerReference: String?,
|
||||
val bankReference: String?,
|
||||
val furtherInformation: String?,
|
||||
|
||||
val endToEndReference: String?,
|
||||
val mandateReference: String?,
|
||||
val creditorIdentifier: String?,
|
||||
val originatorsIdentificationCode: String?,
|
||||
|
||||
val compensationAmount: String?,
|
||||
val originalAmount: String?,
|
||||
val sepaReference: String?,
|
||||
val deviantOriginator: String?,
|
||||
val deviantRecipient: String?,
|
||||
val referenceWithNoSpecialType: String?,
|
||||
val primaNotaNumber: String?,
|
||||
val textKeySupplement: String?,
|
||||
|
||||
val currencyType: String?,
|
||||
val bookingKey: String,
|
||||
val referenceForTheAccountOwner: String,
|
||||
val referenceOfTheAccountServicingInstitution: String?,
|
||||
val supplementaryDetails: String?,
|
||||
val journalNumber: String?,
|
||||
val textKeyAddition: String?,
|
||||
|
||||
val transactionReferenceNumber: String,
|
||||
val relatedReferenceNumber: String?
|
||||
val orderReferenceNumber: String?,
|
||||
val referenceNumber: String?,
|
||||
|
||||
val isReversal: Boolean
|
||||
) {
|
||||
|
||||
// for object deserializers
|
||||
internal constructor() : this(Money(Amount.Zero, ""), "", UnixEpochStart, null, null, null, null, UnixEpochStart)
|
||||
internal constructor() : this(Money(Amount.Zero, ""), "", UnixEpochStart, UnixEpochStart, null, null, null, null)
|
||||
|
||||
constructor(amount: Money, unparsedReference: String, bookingDate: LocalDate, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, bookingText: String?, valueDate: LocalDate)
|
||||
: this(amount, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
|
||||
0, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, "", "", null, null, "", null)
|
||||
constructor(amount: Money, unparsedReference: String, bookingDate: LocalDate, valueDate: LocalDate, otherPartyName: String?, otherPartyBankId: String?, otherPartyAccountId: String?, postingText: String?)
|
||||
: this(amount, unparsedReference, bookingDate, valueDate, otherPartyName, otherPartyBankId, otherPartyAccountId, postingText,
|
||||
null, null, 0, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, false)
|
||||
|
||||
|
||||
open val showOtherPartyName: Boolean
|
||||
get() = otherPartyName.isNullOrBlank() == false /* && type != "ENTGELTABSCHLUSS" && type != "AUSZAHLUNG" */ // TODO
|
||||
|
||||
val reference: String
|
||||
get() = sepaReference ?: unparsedReference
|
||||
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is AccountTransaction) return false
|
||||
|
||||
if (amount != other.amount) return false
|
||||
if (unparsedReference != other.unparsedReference) return false
|
||||
if (reference != other.reference) return false
|
||||
if (bookingDate != other.bookingDate) return false
|
||||
if (otherPartyName != other.otherPartyName) return false
|
||||
if (otherPartyBankCode != other.otherPartyBankCode) return false
|
||||
if (otherPartyBankId != other.otherPartyBankId) return false
|
||||
if (otherPartyAccountId != other.otherPartyAccountId) return false
|
||||
if (bookingText != other.bookingText) return false
|
||||
if (postingText != other.postingText) return false
|
||||
if (valueDate != other.valueDate) return false
|
||||
|
||||
return true
|
||||
|
@ -81,19 +83,19 @@ open class AccountTransaction(
|
|||
|
||||
override fun hashCode(): Int {
|
||||
var result = amount.hashCode()
|
||||
result = 31 * result + unparsedReference.hashCode()
|
||||
result = 31 * result + reference.hashCode()
|
||||
result = 31 * result + bookingDate.hashCode()
|
||||
result = 31 * result + (otherPartyName?.hashCode() ?: 0)
|
||||
result = 31 * result + (otherPartyBankCode?.hashCode() ?: 0)
|
||||
result = 31 * result + (otherPartyBankId?.hashCode() ?: 0)
|
||||
result = 31 * result + (otherPartyAccountId?.hashCode() ?: 0)
|
||||
result = 31 * result + (bookingText?.hashCode() ?: 0)
|
||||
result = 31 * result + (postingText?.hashCode() ?: 0)
|
||||
result = 31 * result + valueDate.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$valueDate $amount $otherPartyName: $unparsedReference"
|
||||
return "$valueDate $amount $otherPartyName: $reference"
|
||||
}
|
||||
|
||||
}
|
|
@ -18,7 +18,7 @@ open class BankAccount(
|
|||
open val currency: String = Currency.DefaultCurrencyCode, // TODO: may parse to a value object
|
||||
open val accountLimit: String? = null,
|
||||
|
||||
open val countDaysForWhichTransactionsAreKept: Int? = null,
|
||||
open val serverTransactionsRetentionDays: Int? = null,
|
||||
open val isAccountTypeSupportedByApplication: Boolean = false,
|
||||
// TODO: create an enum AccountCapabilities [ RetrieveBalance, RetrieveTransactions, TransferMoney / MoneyTransfer(?), InstantPayment ]
|
||||
open val supportsRetrievingTransactions: Boolean = false,
|
||||
|
|
|
@ -12,6 +12,7 @@ open class FinTsClientParameter(
|
|||
password: String,
|
||||
|
||||
open val preferredTanMethods: List<TanMethodType>? = null,
|
||||
open val tanMethodsNotSupportedByApplication: List<TanMethodType>? = null,
|
||||
open val preferredTanMedium: String? = null, // the ID of the medium
|
||||
open val abortIfTanIsRequired: Boolean = false,
|
||||
open val finTsModel: BankData? = null
|
||||
|
|
|
@ -21,10 +21,12 @@ open class GetAccountDataParameter(
|
|||
open val retrieveTransactionsTo: LocalDate? = null,
|
||||
|
||||
preferredTanMethods: List<TanMethodType>? = null,
|
||||
tanMethodsNotSupportedByApplication: List<TanMethodType>? = null,
|
||||
preferredTanMedium: String? = null,
|
||||
abortIfTanIsRequired: Boolean = false,
|
||||
finTsModel: BankData? = null
|
||||
) : FinTsClientParameter(bankCode, loginName, password, preferredTanMethods, preferredTanMedium, abortIfTanIsRequired, finTsModel) {
|
||||
finTsModel: BankData? = null,
|
||||
open val defaultBankValues: BankData? = null
|
||||
) : FinTsClientParameter(bankCode, loginName, password, preferredTanMethods, tanMethodsNotSupportedByApplication, preferredTanMedium, abortIfTanIsRequired, finTsModel) {
|
||||
|
||||
open val retrieveOnlyAccountInfo: Boolean
|
||||
get() = retrieveBalance == false && retrieveTransactions == RetrieveTransactions.No
|
||||
|
|
|
@ -34,10 +34,11 @@ open class TransferMoneyParameter(
|
|||
open val instantPayment: Boolean = false,
|
||||
|
||||
preferredTanMethods: List<TanMethodType>? = null,
|
||||
tanMethodsNotSupportedByApplication: List<TanMethodType>? = null,
|
||||
preferredTanMedium: String? = null,
|
||||
abortIfTanIsRequired: Boolean = false,
|
||||
finTsModel: BankData? = null,
|
||||
|
||||
open val selectAccountToUseForTransfer: ((List<AccountData>) -> AccountData?)? = null // TODO: use BankAccount instead of AccountData
|
||||
|
||||
) : FinTsClientParameter(bankCode, loginName, password, preferredTanMethods, preferredTanMedium, abortIfTanIsRequired, finTsModel)
|
||||
) : FinTsClientParameter(bankCode, loginName, password, preferredTanMethods, tanMethodsNotSupportedByApplication, preferredTanMedium, abortIfTanIsRequired, finTsModel)
|
|
@ -966,7 +966,7 @@ class ResponseParserTest : FinTsTestBase() {
|
|||
assertEquals(TanResponse.NoJobReferenceResponse, segment.jobReference)
|
||||
assertEquals(TanResponse.NoChallengeResponse, segment.challenge)
|
||||
assertNull(segment.challengeHHD_UC)
|
||||
assertEquals(null, segment.validityDateTimeForChallenge)
|
||||
assertEquals(null, segment.tanExpirationTime)
|
||||
assertEquals(null, segment.tanMediaIdentifier)
|
||||
}
|
||||
?: run { fail("No segment of type TanResponse found in ${result.receivedSegments}") }
|
||||
|
@ -995,7 +995,7 @@ class ResponseParserTest : FinTsTestBase() {
|
|||
assertEquals(jobReference, segment.jobReference)
|
||||
assertEquals(unmaskString(challenge), segment.challenge)
|
||||
assertEquals(challengeHHD_UC, segment.challengeHHD_UC)
|
||||
assertEquals(null, segment.validityDateTimeForChallenge)
|
||||
assertEquals(null, segment.tanExpirationTime)
|
||||
assertEquals(tanMediaIdentifier, segment.tanMediaIdentifier)
|
||||
}
|
||||
?: run { fail("No segment of type TanResponse found in ${result.receivedSegments}") }
|
||||
|
@ -1189,16 +1189,16 @@ class ResponseParserTest : FinTsTestBase() {
|
|||
fun parseAccountTransactionsMt940Parameters_Version4() {
|
||||
|
||||
// given
|
||||
val countDaysForWhichTransactionsAreKept = 90
|
||||
val serverTransactionsRetentionDays = 90
|
||||
|
||||
// when
|
||||
val result = underTest.parse("HIKAZS:21:4:4+20+1+$countDaysForWhichTransactionsAreKept:N'")
|
||||
val result = underTest.parse("HIKAZS:21:4:4+20+1+$serverTransactionsRetentionDays:N'")
|
||||
|
||||
// then
|
||||
assertSuccessfullyParsedSegment(result, InstituteSegmentId.AccountTransactionsMt940Parameters, 21, 4, 4)
|
||||
|
||||
result.getFirstSegmentById<RetrieveAccountTransactionsParameters>(InstituteSegmentId.AccountTransactionsMt940Parameters)?.let { segment ->
|
||||
assertEquals(countDaysForWhichTransactionsAreKept, segment.countDaysForWhichTransactionsAreKept)
|
||||
assertEquals(serverTransactionsRetentionDays, segment.serverTransactionsRetentionDays)
|
||||
assertFalse(segment.settingCountEntriesAllowed)
|
||||
assertFalse(segment.settingAllAccountAllowed)
|
||||
}
|
||||
|
@ -1209,16 +1209,16 @@ class ResponseParserTest : FinTsTestBase() {
|
|||
fun parseAccountTransactionsMt940Parameters_Version6() {
|
||||
|
||||
// given
|
||||
val countDaysForWhichTransactionsAreKept = 90
|
||||
val serverTransactionsRetentionDays = 90
|
||||
|
||||
// when
|
||||
val result = underTest.parse("HIKAZS:23:6:4+20+1+1+$countDaysForWhichTransactionsAreKept:N:N'")
|
||||
val result = underTest.parse("HIKAZS:23:6:4+20+1+1+$serverTransactionsRetentionDays:N:N'")
|
||||
|
||||
// then
|
||||
assertSuccessfullyParsedSegment(result, InstituteSegmentId.AccountTransactionsMt940Parameters, 23, 6, 4)
|
||||
|
||||
result.getFirstSegmentById<RetrieveAccountTransactionsParameters>(InstituteSegmentId.AccountTransactionsMt940Parameters)?.let { segment ->
|
||||
assertEquals(countDaysForWhichTransactionsAreKept, segment.countDaysForWhichTransactionsAreKept)
|
||||
assertEquals(serverTransactionsRetentionDays, segment.serverTransactionsRetentionDays)
|
||||
assertFalse(segment.settingCountEntriesAllowed)
|
||||
assertFalse(segment.settingAllAccountAllowed)
|
||||
}
|
||||
|
@ -1290,16 +1290,16 @@ class ResponseParserTest : FinTsTestBase() {
|
|||
fun parseCreditCardAccountTransactionsParameters() {
|
||||
|
||||
// given
|
||||
val countDaysForWhichTransactionsAreKept = 9999
|
||||
val serverTransactionsRetentionDays = 9999
|
||||
|
||||
// when
|
||||
val result = underTest.parse("DIKKUS:15:2:4+999+1+0+$countDaysForWhichTransactionsAreKept:J:J'")
|
||||
val result = underTest.parse("DIKKUS:15:2:4+999+1+0+$serverTransactionsRetentionDays:J:J'")
|
||||
|
||||
// then
|
||||
assertSuccessfullyParsedSegment(result, InstituteSegmentId.CreditCardTransactionsParameters, 15, 2, 4)
|
||||
|
||||
result.getFirstSegmentById<RetrieveAccountTransactionsParameters>(InstituteSegmentId.CreditCardTransactionsParameters)?.let { segment ->
|
||||
assertEquals(countDaysForWhichTransactionsAreKept, segment.countDaysForWhichTransactionsAreKept)
|
||||
assertEquals(serverTransactionsRetentionDays, segment.serverTransactionsRetentionDays)
|
||||
assertTrue(segment.settingCountEntriesAllowed)
|
||||
assertTrue(segment.settingAllAccountAllowed)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package net.codinux.banking.fints.transactions
|
|||
import net.codinux.banking.fints.FinTsTestBase
|
||||
import net.codinux.banking.fints.transactions.mt940.Mt940Parser
|
||||
import net.codinux.banking.fints.transactions.mt940.model.Balance
|
||||
import net.codinux.banking.fints.transactions.mt940.model.InformationToAccountOwner
|
||||
import net.codinux.banking.fints.transactions.mt940.model.RemittanceInformationField
|
||||
import net.codinux.banking.fints.transactions.mt940.model.StatementLine
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.fints.extensions.*
|
||||
|
@ -26,12 +26,12 @@ class Mt940ParserTest : FinTsTestBase() {
|
|||
|
||||
val AccountStatement1Transaction1Amount = Amount("1234,56")
|
||||
val AccountStatement1Transaction1OtherPartyName = "Sender1"
|
||||
val AccountStatement1Transaction1OtherPartyBankCode = "AAAADE12"
|
||||
val AccountStatement1Transaction1OtherPartyBankId = "AAAADE12"
|
||||
val AccountStatement1Transaction1OtherPartyAccountId = "DE99876543210987654321"
|
||||
|
||||
val AccountStatement1Transaction2Amount = Amount("432,10")
|
||||
val AccountStatement1Transaction2OtherPartyName = "Receiver2"
|
||||
val AccountStatement1Transaction2OtherPartyBankCode = "BBBBDE56"
|
||||
val AccountStatement1Transaction2OtherPartyBankId = "BBBBDE56"
|
||||
val AccountStatement1Transaction2OtherPartyAccountId = "DE77987654321234567890"
|
||||
|
||||
val AccountStatement1ClosingBalanceAmount = Amount("13580,23")
|
||||
|
@ -67,7 +67,7 @@ class Mt940ParserTest : FinTsTestBase() {
|
|||
val transaction = statement.transactions.first()
|
||||
assertTurnover(transaction.statementLine, AccountStatement1BookingDate, AccountStatement1Transaction1Amount)
|
||||
assertTransactionDetails(transaction.information, AccountStatement1Transaction1OtherPartyName,
|
||||
AccountStatement1Transaction1OtherPartyBankCode, AccountStatement1Transaction1OtherPartyAccountId)
|
||||
AccountStatement1Transaction1OtherPartyBankId, AccountStatement1Transaction1OtherPartyAccountId)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -90,7 +90,7 @@ class Mt940ParserTest : FinTsTestBase() {
|
|||
assertEquals(BankCode, statement.bankCodeBicOrIban)
|
||||
assertEquals(CustomerId, statement.accountIdentifier)
|
||||
assertEquals(0, statement.statementNumber)
|
||||
assertNull(statement.sequenceNumber)
|
||||
assertNull(statement.sheetNumber)
|
||||
|
||||
assertBalance(statement.openingBalance, true, bookingDate, Amount("0,00"))
|
||||
assertBalance(statement.closingBalance, isCredit, bookingDate, amount)
|
||||
|
@ -124,12 +124,12 @@ class Mt940ParserTest : FinTsTestBase() {
|
|||
val firstTransaction = statement.transactions.first()
|
||||
assertTurnover(firstTransaction.statementLine, AccountStatement1BookingDate, AccountStatement1Transaction1Amount)
|
||||
assertTransactionDetails(firstTransaction.information, AccountStatement1Transaction1OtherPartyName,
|
||||
AccountStatement1Transaction1OtherPartyBankCode, AccountStatement1Transaction1OtherPartyAccountId)
|
||||
AccountStatement1Transaction1OtherPartyBankId, AccountStatement1Transaction1OtherPartyAccountId)
|
||||
|
||||
val secondTransaction = statement.transactions[1]
|
||||
assertTurnover(secondTransaction.statementLine, AccountStatement1BookingDate, AccountStatement1Transaction2Amount, false)
|
||||
assertTransactionDetails(secondTransaction.information, AccountStatement1Transaction2OtherPartyName,
|
||||
AccountStatement1Transaction2OtherPartyBankCode, AccountStatement1Transaction2OtherPartyAccountId)
|
||||
AccountStatement1Transaction2OtherPartyBankId, AccountStatement1Transaction2OtherPartyAccountId)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -306,8 +306,8 @@ class Mt940ParserTest : FinTsTestBase() {
|
|||
assertSize(1, result.first().transactions)
|
||||
|
||||
result.first().transactions[0].information?.apply {
|
||||
assertEquals("BASISLASTSCHRIFT", bookingText)
|
||||
assertEquals("TUBDDEDD", otherPartyBankCode)
|
||||
assertEquals("BASISLASTSCHRIFT", postingText)
|
||||
assertEquals("TUBDDEDD", otherPartyBankId)
|
||||
assertEquals("DE87300308801234567890", otherPartyAccountId)
|
||||
assertEquals("6MKL2OT30QENNLIU", endToEndReference)
|
||||
assertEquals("?,3SQNdUbxm9z7dB)+gKYDJAKzCM0G", mandateReference)
|
||||
|
@ -362,13 +362,13 @@ class Mt940ParserTest : FinTsTestBase() {
|
|||
assertEquals(amount, statementLine.amount)
|
||||
}
|
||||
|
||||
private fun assertTransactionDetails(details: InformationToAccountOwner?, otherPartyName: String,
|
||||
otherPartyBankCode: String, otherPartyAccountId: String) {
|
||||
private fun assertTransactionDetails(details: RemittanceInformationField?, otherPartyName: String,
|
||||
otherPartyBankId: String, otherPartyAccountId: String) {
|
||||
|
||||
assertNotNull(details)
|
||||
|
||||
assertEquals(otherPartyName, details.otherPartyName)
|
||||
assertEquals(otherPartyBankCode, details.otherPartyBankCode)
|
||||
assertEquals(otherPartyBankId, details.otherPartyBankId)
|
||||
assertEquals(otherPartyAccountId, details.otherPartyAccountId)
|
||||
}
|
||||
|
||||
|
@ -380,7 +380,7 @@ class Mt940ParserTest : FinTsTestBase() {
|
|||
:60F:C${convertMt940Date(AccountStatement1PreviousStatementBookingDate)}EUR$AccountStatement1OpeningBalanceAmount
|
||||
:61:${convertMt940Date(AccountStatement1BookingDate)}${convertToShortBookingDate(AccountStatement1BookingDate)}CR${AccountStatement1Transaction1Amount}N062NONREF
|
||||
:86:166?00GUTSCHR. UEBERWEISUNG?109249?20EREF+674?21SVWZ+1908301/
|
||||
EUR ${AccountStatement1Transaction1Amount}/20?2219-10-02/...?30$AccountStatement1Transaction1OtherPartyBankCode?31$AccountStatement1Transaction1OtherPartyAccountId
|
||||
EUR ${AccountStatement1Transaction1Amount}/20?2219-10-02/...?30$AccountStatement1Transaction1OtherPartyBankId?31$AccountStatement1Transaction1OtherPartyAccountId
|
||||
?32$AccountStatement1Transaction1OtherPartyName
|
||||
:62F:C${convertMt940Date(AccountStatement1BookingDate)}EUR$AccountStatement1ClosingBalanceAmount
|
||||
-
|
||||
|
@ -393,11 +393,11 @@ class Mt940ParserTest : FinTsTestBase() {
|
|||
:60F:C${convertMt940Date(AccountStatement1PreviousStatementBookingDate)}EUR$AccountStatement1OpeningBalanceAmount
|
||||
:61:${convertMt940Date(AccountStatement1BookingDate)}${convertToShortBookingDate(AccountStatement1BookingDate)}CR${AccountStatement1Transaction1Amount}N062NONREF
|
||||
:86:166?00GUTSCHR. UEBERWEISUNG?109249?20EREF+674?21SVWZ+1908301/
|
||||
EUR ${AccountStatement1Transaction1Amount}/20?2219-10-02/...?30$AccountStatement1Transaction1OtherPartyBankCode?31$AccountStatement1Transaction1OtherPartyAccountId
|
||||
EUR ${AccountStatement1Transaction1Amount}/20?2219-10-02/...?30$AccountStatement1Transaction1OtherPartyBankId?31$AccountStatement1Transaction1OtherPartyAccountId
|
||||
?32$AccountStatement1Transaction1OtherPartyName
|
||||
:61:${convertMt940Date(AccountStatement1BookingDate)}${convertToShortBookingDate(AccountStatement1BookingDate)}DR${AccountStatement1Transaction2Amount}N062NONREF
|
||||
:86:166?00ONLINE-UEBERWEISUNG?109249?20EREF+674?21SVWZ+1908301/
|
||||
EUR ${AccountStatement1Transaction2Amount}/20?2219-10-02/...?30$AccountStatement1Transaction2OtherPartyBankCode?31$AccountStatement1Transaction2OtherPartyAccountId
|
||||
EUR ${AccountStatement1Transaction2Amount}/20?2219-10-02/...?30$AccountStatement1Transaction2OtherPartyBankId?31$AccountStatement1Transaction2OtherPartyAccountId
|
||||
?32$AccountStatement1Transaction2OtherPartyName
|
||||
:62F:C${convertMt940Date(AccountStatement1BookingDate)}EUR${AccountStatement1With2TransactionsClosingBalanceAmount}
|
||||
-
|
||||
|
|
Loading…
Reference in New Issue