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) {
|
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(',', '.')
|
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),
|
stream.writeString(listOf(customer.bankName, account.identifier, transaction.valueDate, amount, transaction.amount.currency, ensureNotNull(transaction.postingText), wrap(transaction.reference ?: ""),
|
||||||
ensureNotNull(transaction.otherPartyName), ensureNotNull(transaction.otherPartyBankCode), ensureNotNull(transaction.otherPartyAccountId)).joinToString(valueSeparator))
|
ensureNotNull(transaction.otherPartyName), ensureNotNull(transaction.otherPartyBankId), ensureNotNull(transaction.otherPartyAccountId)).joinToString(valueSeparator))
|
||||||
|
|
||||||
stream.writeString(NewLine)
|
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)
|
return GetAccountDataResponse(ErrorCode.NoneOfTheAccountsSupportsRetrievingData, errorMessage, mapper.map(bank), previousJobMessageLog ?: listOf(), bank)
|
||||||
}
|
}
|
||||||
|
|
||||||
accountsSupportingRetrievingTransactions.forEach { account ->
|
for (account in accountsSupportingRetrievingTransactions) {
|
||||||
retrievedTransactionsResponses.add(getAccountTransactions(param, bank, account))
|
val response = getAccountTransactions(param, bank, account)
|
||||||
|
retrievedTransactionsResponses.add(response)
|
||||||
|
|
||||||
|
if (response.tanRequiredButWeWereToldToAbortIfSo || response.userCancelledAction) { // if user cancelled action or TAN is required but we were told to abort then, then don't continue with next account
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val unsuccessfulJob = retrievedTransactionsResponses.firstOrNull { it.successful == false }
|
val 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 {
|
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))
|
return config.jobExecutor.getTransactionsAsync(context, mapper.toGetAccountTransactionsParameter(param, bank, account))
|
||||||
}
|
}
|
||||||
|
@ -133,7 +138,7 @@ open class FinTsClient(
|
||||||
accountToUse = selectedAccount
|
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,
|
val response = config.jobExecutor.transferMoneyAsync(context, BankTransferData(param.recipientName, param.recipientAccountIdentifier, recipientBankIdentifier,
|
||||||
param.amount, param.reference, param.instantPayment))
|
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)
|
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()) {
|
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)
|
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)
|
val getAccountInfoResponse = getAccountInfo(param, bank)
|
||||||
|
|
||||||
|
@ -198,11 +205,11 @@ open class FinTsClient(
|
||||||
// return GetAccountInfoResponse(it)
|
// 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 */
|
/* 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
|
/* 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 */
|
strong customer authorization TAN media is required */
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package net.codinux.banking.fints
|
package net.codinux.banking.fints
|
||||||
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.datetime.*
|
import kotlinx.datetime.*
|
||||||
import net.codinux.banking.fints.callback.FinTsClientCallback
|
import net.codinux.banking.fints.callback.FinTsClientCallback
|
||||||
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
||||||
|
@ -39,13 +37,13 @@ open class FinTsClientDeprecated(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
open suspend fun addAccountAsync(parameter: AddAccountParameter): AddAccountResponse {
|
open suspend fun addAccountAsync(param: AddAccountParameter): AddAccountResponse {
|
||||||
val bank = parameter.bank
|
val bank = param.bank
|
||||||
val context = JobContext(JobContextType.AddAccount, this.callback, config, 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 */
|
/* 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
|
if (newUserInfoResponse.successful == false) { // bank parameter (FinTS server address, ...) already seem to be wrong
|
||||||
return AddAccountResponse(context, newUserInfoResponse)
|
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
|
/* 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 */
|
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 {
|
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)
|
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
|
package net.codinux.banking.fints
|
||||||
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Instant
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
|
import net.codinux.banking.fints.extensions.*
|
||||||
import net.codinux.log.logger
|
import net.codinux.log.logger
|
||||||
import net.codinux.banking.fints.messages.MessageBuilder
|
import net.codinux.banking.fints.messages.MessageBuilder
|
||||||
import net.codinux.banking.fints.messages.MessageBuilderResult
|
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.FlickerCodeDecoder
|
||||||
import net.codinux.banking.fints.tan.TanImageDecoder
|
import net.codinux.banking.fints.tan.TanImageDecoder
|
||||||
import net.codinux.banking.fints.util.TanMethodSelector
|
import net.codinux.banking.fints.util.TanMethodSelector
|
||||||
import net.codinux.banking.fints.extensions.minusDays
|
import net.codinux.log.Log
|
||||||
import net.codinux.banking.fints.extensions.todayAtEuropeBerlin
|
|
||||||
import net.codinux.banking.fints.extensions.todayAtSystemDefaultTimeZone
|
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
@ -75,8 +74,7 @@ open class FinTsJobExecutor(
|
||||||
*
|
*
|
||||||
* Be aware this method resets BPD, UPD and selected TAN method!
|
* Be aware this method resets BPD, UPD and selected TAN method!
|
||||||
*/
|
*/
|
||||||
open suspend fun retrieveBasicDataLikeUsersTanMethods(context: JobContext, preferredTanMethods: List<TanMethodType>? = null, preferredTanMedium: String? = null,
|
open suspend fun retrieveBasicDataLikeUsersTanMethods(context: JobContext): BankResponse {
|
||||||
closeDialog: Boolean = false): BankResponse {
|
|
||||||
val bank = context.bank
|
val bank = context.bank
|
||||||
|
|
||||||
// just to ensure settings are in its initial state and that bank sends us bank parameter (BPD),
|
// 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()
|
bank.resetSelectedTanMethod()
|
||||||
|
|
||||||
// this is the only case where Einschritt-TAN-Verfahren is accepted: to get user's TAN methods
|
// 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)
|
val message = messageBuilder.createInitDialogMessage(context)
|
||||||
|
|
||||||
|
@ -105,12 +103,10 @@ open class FinTsJobExecutor(
|
||||||
if (bank.tanMethodsAvailableForUser.isEmpty()) { // could not retrieve supported tan methods for user
|
if (bank.tanMethodsAvailableForUser.isEmpty()) { // could not retrieve supported tan methods for user
|
||||||
return getTanMethodsResponse
|
return getTanMethodsResponse
|
||||||
} else {
|
} else {
|
||||||
getUsersTanMethod(context, preferredTanMethods)
|
getUsersTanMethod(context)
|
||||||
|
|
||||||
if (bank.isTanMethodSelected == false) {
|
if (bank.isTanMethodSelected && bank.tanMedia.isEmpty() && bank.tanMethodsAvailableForUser.any { it.nameOfTanMediumRequired } && isJobSupported(bank, CustomerSegmentId.TanMediaList)) { // tan media not retrieved yet
|
||||||
return getTanMethodsResponse
|
getTanMediaList(context, TanMedienArtVersion.Alle, TanMediumKlasse.AlleMedien, context.preferredTanMedium)
|
||||||
} else if (bank.tanMedia.isEmpty() && isJobSupported(bank, CustomerSegmentId.TanMediaList)) { // tan media not retrieved yet
|
|
||||||
getTanMediaList(context, TanMedienArtVersion.Alle, TanMediumKlasse.AlleMedien, preferredTanMedium)
|
|
||||||
|
|
||||||
return getTanMethodsResponse // TODO: judge if bank requires selecting TAN media and if though evaluate getTanMediaListResponse
|
return getTanMethodsResponse // TODO: judge if bank requires selecting TAN media and if though evaluate getTanMediaListResponse
|
||||||
} else {
|
} else {
|
||||||
|
@ -149,6 +145,7 @@ open class FinTsJobExecutor(
|
||||||
return BankResponse(true, internalError = "Die TAN Verfahren der Bank konnten nicht ermittelt werden") // TODO: translate
|
return BankResponse(true, internalError = "Die TAN Verfahren der Bank konnten nicht ermittelt werden") // TODO: translate
|
||||||
} else {
|
} else {
|
||||||
bank.tanMethodsAvailableForUser = bank.tanMethodsSupportedByBank
|
bank.tanMethodsAvailableForUser = bank.tanMethodsSupportedByBank
|
||||||
|
.filterNot { context.tanMethodsNotSupportedByApplication.contains(it.type) }
|
||||||
|
|
||||||
val didSelectTanMethod = getUsersTanMethod(context)
|
val didSelectTanMethod = getUsersTanMethod(context)
|
||||||
|
|
||||||
|
@ -227,11 +224,11 @@ open class FinTsJobExecutor(
|
||||||
|
|
||||||
response.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(InstituteSegmentId.CreditCardTransactions)?.let { creditCardTransactionsSegment ->
|
response.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(InstituteSegmentId.CreditCardTransactions)?.let { creditCardTransactionsSegment ->
|
||||||
balance = Money(creditCardTransactionsSegment.balance.amount, creditCardTransactionsSegment.balance.currency ?: "EUR")
|
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)
|
val response = getAndHandleResponseForMessage(context, message)
|
||||||
|
|
||||||
|
@ -240,7 +237,7 @@ open class FinTsJobExecutor(
|
||||||
val successful = response.tanRequiredButWeWereToldToAbortIfSo
|
val successful = response.tanRequiredButWeWereToldToAbortIfSo
|
||||||
|| (response.successful && (parameter.alsoRetrieveBalance == false || balance != null))
|
|| (response.successful && (parameter.alsoRetrieveBalance == false || balance != null))
|
||||||
val fromDate = parameter.fromDate
|
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
|
?: bookedTransactions.minByOrNull { it.valueDate }?.valueDate
|
||||||
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, startTime, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError)
|
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 {
|
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) )
|
// on all platforms run on Dispatchers.Main, but on iOS skip this (or wrap in withContext(Dispatchers.IO) )
|
||||||
// val enteredTanResult = GlobalScope.async {
|
// 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) {
|
mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context, tanChallenge, tanResponse)
|
||||||
delay(250)
|
|
||||||
|
|
||||||
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!!
|
val enteredTanResult = tanChallenge.enterTanResult!!
|
||||||
// }
|
|
||||||
|
|
||||||
return handleEnterTanResult(context, enteredTanResult, tanResponse, response)
|
return handleEnterTanResult(context, enteredTanResult, tanResponse, response)
|
||||||
}
|
}
|
||||||
|
@ -408,13 +421,13 @@ open class FinTsJobExecutor(
|
||||||
TanMethodType.ChipTanFlickercode ->
|
TanMethodType.ChipTanFlickercode ->
|
||||||
FlickerCodeTanChallenge(
|
FlickerCodeTanChallenge(
|
||||||
FlickerCodeDecoder().decodeChallenge(challenge, tanMethod.hhdVersion ?: HHDVersion.HHD_1_4), // HHD 1.4 is currently the most used version
|
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.ChipTanQrCode, TanMethodType.ChipTanPhotoTanMatrixCode,
|
||||||
TanMethodType.QrCode, TanMethodType.photoTan ->
|
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
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,7 +631,7 @@ open class FinTsJobExecutor(
|
||||||
|
|
||||||
protected open suspend fun initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context: JobContext): BankResponse {
|
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)
|
val message = messageBuilder.createInitDialogMessage(context)
|
||||||
|
|
||||||
|
@ -692,7 +707,7 @@ open class FinTsJobExecutor(
|
||||||
return BankResponse(true, noTanMethodSelected = noTanMethodSelected, internalError = errorMessage)
|
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
|
val bank = context.bank
|
||||||
|
|
||||||
if (bank.tanMethodsAvailableForUser.size == 1) { // user has only one TAN method -> set it and we're done
|
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
|
return true
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tanMethodSelector.findPreferredTanMethod(bank.tanMethodsAvailableForUser, preferredTanMethods)?.let {
|
tanMethodSelector.findPreferredTanMethod(bank.tanMethodsAvailableForUser, context.preferredTanMethods, context.tanMethodsNotSupportedByApplication)?.let {
|
||||||
bank.selectedTanMethod = it
|
bank.selectedTanMethod = it
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// we know user's supported tan methods, now ask user which one to select
|
// 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)
|
val selectedTanMethod = context.callback.askUserForTanMethod(bank.tanMethodsAvailableForUser, suggestedTanMethod)
|
||||||
|
|
||||||
|
@ -727,14 +742,14 @@ open class FinTsJobExecutor(
|
||||||
|
|
||||||
protected open fun updateBankAndCustomerDataIfResponseSuccessful(context: JobContext, response: BankResponse) {
|
protected open fun updateBankAndCustomerDataIfResponseSuccessful(context: JobContext, response: BankResponse) {
|
||||||
if (response.successful) {
|
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)
|
updateBankData(bank, response)
|
||||||
|
|
||||||
modelMapper.updateCustomerData(bank, response)
|
modelMapper.updateCustomerData(bank, response, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,9 @@ data class FinTsClientOptions(
|
||||||
* Defaults to true.
|
* Defaults to true.
|
||||||
*/
|
*/
|
||||||
val removeSensitiveDataFromMessageLog: Boolean = true,
|
val removeSensitiveDataFromMessageLog: Boolean = true,
|
||||||
|
|
||||||
|
val closeDialogs: Boolean = true,
|
||||||
|
|
||||||
val version: String = "1.0.0", // TODO: get version dynamically
|
val version: String = "1.0.0", // TODO: get version dynamically
|
||||||
val productName: String = "15E53C26816138699C7B6A3E8"
|
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 {
|
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
|
package net.codinux.banking.fints.extensions
|
||||||
|
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Instant
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
fun randomWithSeed(): Random = Random(randomSeed())
|
fun randomWithSeed(): Random = Random(randomSeed())
|
||||||
|
|
||||||
fun randomSeed(): Long {
|
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()
|
protected open val bicFinder = BicFinder()
|
||||||
|
|
||||||
|
|
||||||
open fun mapToBankData(param: FinTsClientParameter, finTsServerAddress: String): BankData {
|
open fun mapToBankData(param: FinTsClientParameter, finTsServerAddress: String, defaultValues: BankData? = null): BankData {
|
||||||
return BankData(param.bankCode, param.loginName, param.password, finTsServerAddress, bicFinder.findBic(param.bankCode) ?: "")
|
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 {
|
open fun mapToAccountData(credentials: BankAccountIdentifier, param: FinTsClientParameter): AccountData {
|
||||||
|
@ -55,7 +58,7 @@ open class FinTsModelMapper {
|
||||||
|
|
||||||
open fun map(account: AccountData): BankAccount {
|
open fun map(account: AccountData): BankAccount {
|
||||||
return BankAccount(account.accountIdentifier, account.subAccountAttribute, account.iban, account.accountHolderName, map(account.accountType), account.productName,
|
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)
|
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 {
|
open fun map(transaction: net.codinux.banking.fints.model.AccountTransaction): AccountTransaction {
|
||||||
return AccountTransaction(transaction.amount, transaction.unparsedReference, transaction.bookingDate,
|
return AccountTransaction(
|
||||||
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId, transaction.bookingText, transaction.valueDate,
|
transaction.amount, transaction.reference,
|
||||||
transaction.statementNumber, transaction.sequenceNumber, transaction.openingBalance, transaction.closingBalance,
|
transaction.bookingDate, transaction.valueDate,
|
||||||
transaction.endToEndReference, transaction.customerReference, transaction.mandateReference, transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
|
transaction.otherPartyName, transaction.otherPartyBankId, transaction.otherPartyAccountId,
|
||||||
transaction.compensationAmount, transaction.originalAmount, transaction.sepaReference, transaction.deviantOriginator, transaction.deviantRecipient,
|
|
||||||
transaction.referenceWithNoSpecialType, transaction.primaNotaNumber, transaction.textKeySupplement,
|
transaction.postingText,
|
||||||
transaction.currencyType, transaction.bookingKey, transaction.referenceForTheAccountOwner, transaction.referenceOfTheAccountServicingInstitution, transaction.supplementaryDetails,
|
|
||||||
transaction.transactionReferenceNumber, transaction.relatedReferenceNumber)
|
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)
|
|| 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>()
|
protected open val _supportedFeatures = mutableSetOf<AccountFeature>()
|
||||||
|
|
|
@ -7,59 +7,124 @@ import net.codinux.banking.fints.extensions.UnixEpochStart
|
||||||
open class AccountTransaction(
|
open class AccountTransaction(
|
||||||
val account: AccountData,
|
val account: AccountData,
|
||||||
val amount: Money,
|
val amount: Money,
|
||||||
val isReversal: Boolean,
|
val reference: String?, // that was also new to me that reference may is null
|
||||||
val unparsedReference: String,
|
|
||||||
val bookingDate: LocalDate,
|
val bookingDate: LocalDate,
|
||||||
val otherPartyName: String?,
|
|
||||||
val otherPartyBankCode: String?,
|
|
||||||
val otherPartyAccountId: String?,
|
|
||||||
val bookingText: String?,
|
|
||||||
val valueDate: LocalDate,
|
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 openingBalance: Money?,
|
||||||
val closingBalance: Money?,
|
val closingBalance: Money?,
|
||||||
|
|
||||||
val endToEndReference: String?,
|
/**
|
||||||
|
* Auszugsnummer
|
||||||
|
*/
|
||||||
|
val statementNumber: Int,
|
||||||
|
/**
|
||||||
|
* Blattnummer
|
||||||
|
*/
|
||||||
|
val sheetNumber: Int?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kundenreferenz.
|
||||||
|
*/
|
||||||
val customerReference: String?,
|
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 mandateReference: String?,
|
||||||
val creditorIdentifier: String?,
|
val creditorIdentifier: String?,
|
||||||
val originatorsIdentificationCode: String?,
|
val originatorsIdentificationCode: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summe aus Auslagenersatz und Bearbeitungsprovision bei einer nationalen Rücklastschrift
|
||||||
|
* sowie optionalem Zinsausgleich.
|
||||||
|
*/
|
||||||
val compensationAmount: String?,
|
val compensationAmount: String?,
|
||||||
|
/**
|
||||||
|
* Betrag der ursprünglichen Lastschrift
|
||||||
|
*/
|
||||||
val originalAmount: String?,
|
val originalAmount: String?,
|
||||||
val sepaReference: String?,
|
/**
|
||||||
|
* Abweichender Überweisender oder Zahlungsempfänger
|
||||||
|
*/
|
||||||
val deviantOriginator: String?,
|
val deviantOriginator: String?,
|
||||||
|
/**
|
||||||
|
* Abweichender Zahlungsempfänger oder Zahlungspflichtiger
|
||||||
|
*/
|
||||||
val deviantRecipient: String?,
|
val deviantRecipient: String?,
|
||||||
val referenceWithNoSpecialType: String?,
|
val referenceWithNoSpecialType: String?,
|
||||||
val primaNotaNumber: String?,
|
|
||||||
val textKeySupplement: String?,
|
|
||||||
|
|
||||||
val currencyType: String?,
|
/**
|
||||||
val bookingKey: String,
|
* Primanoten-Nr.
|
||||||
val referenceForTheAccountOwner: String,
|
*/
|
||||||
val referenceOfTheAccountServicingInstitution: String?,
|
val journalNumber: String?,
|
||||||
val supplementaryDetails: 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
|
// 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)
|
constructor(account: AccountData, amount: Money, unparsedReference: String, bookingDate: LocalDate, valueDate: LocalDate, otherPartyName: String?, otherPartyBankId: String?, otherPartyAccountId: String?, postingText: String? = null)
|
||||||
: this(account, amount, false, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
|
: this(account, amount, unparsedReference, bookingDate, valueDate, otherPartyName, otherPartyBankId, otherPartyAccountId, postingText,
|
||||||
0, null, null, null,
|
null, null, 0, null,
|
||||||
null, null, null, null, null, null, null, null, null, null, null, null, null,
|
null, null, null, null, null, null, null, null, null, null, null,
|
||||||
null, "", "", null, null, "", null)
|
"", null, null, "", null, false)
|
||||||
|
|
||||||
|
|
||||||
open val showOtherPartyName: Boolean
|
open val showOtherPartyName: Boolean
|
||||||
get() = otherPartyName.isNullOrBlank() == false /* && type != "ENTGELTABSCHLUSS" && type != "AUSZAHLUNG" */ // TODO
|
get() = otherPartyName.isNullOrBlank() == false /* && type != "ENTGELTABSCHLUSS" && type != "AUSZAHLUNG" */ // TODO
|
||||||
|
|
||||||
val reference: String
|
|
||||||
get() = sepaReference ?: unparsedReference
|
|
||||||
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
|
@ -67,12 +132,12 @@ open class AccountTransaction(
|
||||||
|
|
||||||
if (account != other.account) return false
|
if (account != other.account) return false
|
||||||
if (amount != other.amount) 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 (bookingDate != other.bookingDate) return false
|
||||||
if (otherPartyName != other.otherPartyName) 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 (otherPartyAccountId != other.otherPartyAccountId) return false
|
||||||
if (bookingText != other.bookingText) return false
|
if (postingText != other.postingText) return false
|
||||||
if (valueDate != other.valueDate) return false
|
if (valueDate != other.valueDate) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -81,19 +146,19 @@ open class AccountTransaction(
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = account.hashCode()
|
var result = account.hashCode()
|
||||||
result = 31 * result + amount.hashCode()
|
result = 31 * result + amount.hashCode()
|
||||||
result = 31 * result + unparsedReference.hashCode()
|
result = 31 * result + reference.hashCode()
|
||||||
result = 31 * result + bookingDate.hashCode()
|
result = 31 * result + bookingDate.hashCode()
|
||||||
result = 31 * result + (otherPartyName?.hashCode() ?: 0)
|
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 + (otherPartyAccountId?.hashCode() ?: 0)
|
||||||
result = 31 * result + (bookingText?.hashCode() ?: 0)
|
result = 31 * result + (postingText?.hashCode() ?: 0)
|
||||||
result = 31 * result + valueDate.hashCode()
|
result = 31 * result + valueDate.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun toString(): String {
|
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 bank: BankData,
|
||||||
open val fetchBalanceAndTransactions: Boolean = true,
|
open val fetchBalanceAndTransactions: Boolean = true,
|
||||||
open val preferredTanMethods: List<TanMethodType>? = null,
|
open val preferredTanMethods: List<TanMethodType>? = null,
|
||||||
|
open val tanMethodsNotSupportedByApplication: List<TanMethodType>? = null,
|
||||||
open val preferredTanMedium: String? = null
|
open val preferredTanMedium: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package net.codinux.banking.fints.model
|
package net.codinux.banking.fints.model
|
||||||
|
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
import net.codinux.banking.fints.tan.FlickerCode
|
import net.codinux.banking.fints.tan.FlickerCode
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,8 +12,9 @@ open class FlickerCodeTanChallenge(
|
||||||
tanMethod: TanMethod,
|
tanMethod: TanMethod,
|
||||||
tanMediaIdentifier: String?,
|
tanMediaIdentifier: String?,
|
||||||
bank: BankData,
|
bank: BankData,
|
||||||
account: AccountData? = null
|
account: AccountData? = null,
|
||||||
) : TanChallenge(forAction, messageToShowToUser, challenge, tanMethod, tanMediaIdentifier, bank, account) {
|
tanExpirationTime: Instant? = null
|
||||||
|
) : TanChallenge(forAction, messageToShowToUser, challenge, tanMethod, tanMediaIdentifier, bank, account, tanExpirationTime) {
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "$tanMethod (medium: $tanMediaIdentifier) $flickerCode: $messageToShowToUser"
|
return "$tanMethod (medium: $tanMediaIdentifier) $flickerCode: $messageToShowToUser"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package net.codinux.banking.fints.model
|
package net.codinux.banking.fints.model
|
||||||
|
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
import net.codinux.banking.fints.tan.TanImage
|
import net.codinux.banking.fints.tan.TanImage
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,8 +12,9 @@ open class ImageTanChallenge(
|
||||||
tanMethod: TanMethod,
|
tanMethod: TanMethod,
|
||||||
tanMediaIdentifier: String?,
|
tanMediaIdentifier: String?,
|
||||||
bank: BankData,
|
bank: BankData,
|
||||||
account: AccountData? = null
|
account: AccountData? = null,
|
||||||
) : TanChallenge(forAction, messageToShowToUser, challenge, tanMethod, tanMediaIdentifier, bank, account) {
|
tanExpirationTime: Instant? = null
|
||||||
|
) : TanChallenge(forAction, messageToShowToUser, challenge, tanMethod, tanMediaIdentifier, bank, account, tanExpirationTime) {
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "$tanMethod (medium: $tanMediaIdentifier) $image: $messageToShowToUser"
|
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).
|
* Only set if the current context is for a specific account (like get account's transactions).
|
||||||
*/
|
*/
|
||||||
open val account: AccountData? = null,
|
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)
|
protected open val messageLogCollector: MessageLogCollector = MessageLogCollector(callback, config.options)
|
||||||
) : MessageBaseData(bank, config.options.product), IMessageLogAppender {
|
) : MessageBaseData(bank, config.options.product), IMessageLogAppender {
|
||||||
|
|
||||||
|
@ -35,6 +38,8 @@ open class JobContext(
|
||||||
|
|
||||||
protected open val _dialogs = mutableListOf<DialogContext>()
|
protected open val _dialogs = mutableListOf<DialogContext>()
|
||||||
|
|
||||||
|
open val tanMethodsNotSupportedByApplication: List<TanMethodType> = tanMethodsNotSupportedByApplication ?: emptyList()
|
||||||
|
|
||||||
open val mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(Mt940Parser(this), this)
|
open val mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(Mt940Parser(this), this)
|
||||||
|
|
||||||
open val responseParser: ResponseParser = ResponseParser(logAppender = this)
|
open val responseParser: ResponseParser = ResponseParser(logAppender = this)
|
||||||
|
@ -55,7 +60,7 @@ open class JobContext(
|
||||||
protected open var dialogNumber: Int = 0
|
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,
|
versionOfSecurityProcedure: VersionDesSicherheitsverfahrens = VersionDesSicherheitsverfahrens.Version_2,
|
||||||
chunkedResponseHandler: ((BankResponse) -> Unit)? = dialog.chunkedResponseHandler) : DialogContext {
|
chunkedResponseHandler: ((BankResponse) -> Unit)? = dialog.chunkedResponseHandler) : DialogContext {
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package net.codinux.banking.fints.model
|
package net.codinux.banking.fints.model
|
||||||
|
|
||||||
import kotlinx.datetime.Clock
|
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
|
import net.codinux.banking.fints.extensions.nowExt
|
||||||
import net.codinux.banking.fints.log.MessageContext
|
import net.codinux.banking.fints.log.MessageContext
|
||||||
import net.codinux.banking.fints.response.segments.ReceivedSegment
|
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.
|
* Is only set if [type] is set to [MessageLogEntryType.Received] and response parsing was successful.
|
||||||
*/
|
*/
|
||||||
open val parsedSegments: List<ReceivedSegment> = emptyList(),
|
open val parsedSegments: List<ReceivedSegment> = emptyList(),
|
||||||
open val time: Instant = Clock.System.now()
|
open val time: Instant = Instant.nowExt()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val messageIncludingMessageTrace: String
|
val messageIncludingMessageTrace: String
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package net.codinux.banking.fints.model
|
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.messages.datenelemente.implementierte.tan.TanMedium
|
||||||
import net.codinux.banking.fints.response.BankResponse
|
import net.codinux.banking.fints.response.BankResponse
|
||||||
import net.codinux.banking.fints.response.client.FinTsClientResponse
|
import net.codinux.banking.fints.response.client.FinTsClientResponse
|
||||||
|
import net.codinux.log.Log
|
||||||
|
|
||||||
|
|
||||||
open class TanChallenge(
|
open class TanChallenge(
|
||||||
|
@ -12,7 +15,14 @@ open class TanChallenge(
|
||||||
val tanMethod: TanMethod,
|
val tanMethod: TanMethod,
|
||||||
val tanMediaIdentifier: String?,
|
val tanMediaIdentifier: String?,
|
||||||
val bank: BankData,
|
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
|
var enterTanResult: EnterTanResult? = null
|
||||||
|
@ -21,6 +31,8 @@ open class TanChallenge(
|
||||||
open val isEnteringTanDone: Boolean
|
open val isEnteringTanDone: Boolean
|
||||||
get() = enterTanResult != null
|
get() = enterTanResult != null
|
||||||
|
|
||||||
|
private val tanExpiredCallbacks = mutableListOf<() -> Unit>()
|
||||||
|
|
||||||
private val userApprovedDecoupledTanCallbacks = mutableListOf<() -> Unit>()
|
private val userApprovedDecoupledTanCallbacks = mutableListOf<() -> Unit>()
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,23 +43,59 @@ open class TanChallenge(
|
||||||
internal fun userApprovedDecoupledTan(responseAfterApprovingDecoupledTan: BankResponse) {
|
internal fun userApprovedDecoupledTan(responseAfterApprovingDecoupledTan: BankResponse) {
|
||||||
this.enterTanResult = EnterTanResult(null, true, responseAfterApprovingDecoupledTan)
|
this.enterTanResult = EnterTanResult(null, true, responseAfterApprovingDecoupledTan)
|
||||||
|
|
||||||
userApprovedDecoupledTanCallbacks.forEach { it.invoke() }
|
userApprovedDecoupledTanCallbacks.toTypedArray().forEach { // copy to avoid ConcurrentModificationException
|
||||||
userApprovedDecoupledTanCallbacks.clear()
|
try {
|
||||||
|
it.invoke()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.error(e) { "Could not call userApprovedDecoupledTanCallback" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clearUserApprovedDecoupledTanCallbacks()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun userDidNotEnterTan() {
|
fun userDidNotEnterTan() {
|
||||||
|
clearUserApprovedDecoupledTanCallbacks()
|
||||||
|
|
||||||
this.enterTanResult = EnterTanResult(null)
|
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) {
|
fun userAsksToChangeTanMethod(changeTanMethodTo: TanMethod) {
|
||||||
|
clearUserApprovedDecoupledTanCallbacks()
|
||||||
|
|
||||||
this.enterTanResult = EnterTanResult(null, changeTanMethodTo = changeTanMethodTo)
|
this.enterTanResult = EnterTanResult(null, changeTanMethodTo = changeTanMethodTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun userAsksToChangeTanMedium(changeTanMediumTo: TanMedium, changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?) {
|
fun userAsksToChangeTanMedium(changeTanMediumTo: TanMedium, changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?) {
|
||||||
|
clearUserApprovedDecoupledTanCallbacks()
|
||||||
|
|
||||||
this.enterTanResult = EnterTanResult(null, changeTanMediumTo = changeTanMediumTo, changeTanMediumResultCallback = changeTanMediumResultCallback)
|
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) {
|
fun addUserApprovedDecoupledTanCallback(callback: () -> Unit) {
|
||||||
if (isEnteringTanDone == false) {
|
if (isEnteringTanDone == false) {
|
||||||
this.userApprovedDecoupledTanCallbacks.add(callback)
|
this.userApprovedDecoupledTanCallbacks.add(callback)
|
||||||
|
@ -56,6 +104,10 @@ open class TanChallenge(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun clearUserApprovedDecoupledTanCallbacks() {
|
||||||
|
userApprovedDecoupledTanCallbacks.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "$tanMethod (medium: $tanMediaIdentifier): $messageToShowToUser"
|
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 ->
|
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.
|
// 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()) {
|
if (bank.selectedLanguage == Dialogsprache.Default && bankParameters.supportedLanguages.isNotEmpty()) {
|
||||||
|
@ -102,7 +102,7 @@ open class ModelMapper(
|
||||||
accountInfo.accountLimit, accountInfo.allowedJobNames)
|
accountInfo.accountLimit, accountInfo.allowedJobNames)
|
||||||
|
|
||||||
bank.supportedJobs.filterIsInstance<RetrieveAccountTransactionsParameters>().sortedByDescending { it.segmentVersion }.firstOrNull { newAccount.allowedJobNames.contains(it.jobName) }?.let { transactionsParameters ->
|
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)
|
bank.addAccount(newAccount)
|
||||||
|
@ -146,6 +146,7 @@ open class ModelMapper(
|
||||||
|
|
||||||
if (response.supportedTanMethodsForUser.isNotEmpty()) {
|
if (response.supportedTanMethodsForUser.isNotEmpty()) {
|
||||||
bank.tanMethodsAvailableForUser = response.supportedTanMethodsForUser.mapNotNull { findTanMethod(it, bank) }
|
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
|
if (bank.tanMethodsAvailableForUser.firstOrNull { it.securityFunction == bank.selectedTanMethod.securityFunction } == null) { // supportedTanMethods don't contain selectedTanMethod anymore
|
||||||
bank.resetSelectedTanMethod()
|
bank.resetSelectedTanMethod()
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package net.codinux.banking.fints.response
|
package net.codinux.banking.fints.response
|
||||||
|
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.*
|
||||||
import kotlinx.datetime.LocalDateTime
|
import net.codinux.banking.fints.extensions.EuropeBerlin
|
||||||
import kotlinx.datetime.LocalTime
|
|
||||||
import kotlinx.datetime.atTime
|
|
||||||
import net.codinux.log.logger
|
import net.codinux.log.logger
|
||||||
import net.codinux.banking.fints.log.IMessageLogAppender
|
import net.codinux.banking.fints.log.IMessageLogAppender
|
||||||
import net.codinux.banking.fints.messages.Separators
|
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 > 3) parseStringToNullIfEmpty(dataElementGroups[3]) else null,
|
||||||
if (dataElementGroups.size > 4) parseStringToNullIfEmpty(dataElementGroups[4]) else null,
|
if (dataElementGroups.size > 4) parseStringToNullIfEmpty(dataElementGroups[4]) else null,
|
||||||
binaryChallengeHHD_UC?.let { extractBinaryData(it) },
|
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,
|
if (dataElementGroups.size > 7) parseStringToNullIfEmpty(dataElementGroups[7]) else null,
|
||||||
segment
|
segment
|
||||||
)
|
)
|
||||||
|
@ -748,11 +746,11 @@ open class ResponseParser(
|
||||||
val transactionsParameterIndex = if (jobParameters.segmentVersion >= 6) 4 else 3
|
val transactionsParameterIndex = if (jobParameters.segmentVersion >= 6) 4 else 3
|
||||||
val dataElements = getDataElements(dataElementGroups[transactionsParameterIndex])
|
val dataElements = getDataElements(dataElementGroups[transactionsParameterIndex])
|
||||||
|
|
||||||
val countDaysForWhichTransactionsAreKept = parseInt(dataElements[0])
|
val serverTransactionsRetentionDays = parseInt(dataElements[0])
|
||||||
val settingCountEntriesAllowed = parseBoolean(dataElements[1])
|
val settingCountEntriesAllowed = parseBoolean(dataElements[1])
|
||||||
val settingAllAccountAllowed = if (dataElements.size > 2) parseBoolean(dataElements[2]) else false
|
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 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 dataElements = getDataElements(dataElementGroups[transactionsParameterIndex])
|
||||||
|
|
||||||
val countDaysForWhichTransactionsAreKept = parseInt(dataElements[0])
|
val serverTransactionsRetentionDays = parseInt(dataElements[0])
|
||||||
val settingCountEntriesAllowed = parseBoolean(dataElements[1])
|
val settingCountEntriesAllowed = parseBoolean(dataElements[1])
|
||||||
val settingAllAccountAllowed = if (dataElements.size > 2) parseBoolean(dataElements[2]) else false
|
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(
|
open class RetrieveAccountTransactionsParameters(
|
||||||
parameters: JobParameters,
|
parameters: JobParameters,
|
||||||
open val countDaysForWhichTransactionsAreKept: Int,
|
open val serverTransactionsRetentionDays: Int,
|
||||||
open val settingCountEntriesAllowed: Boolean,
|
open val settingCountEntriesAllowed: Boolean,
|
||||||
open val settingAllAccountAllowed: Boolean
|
open val settingAllAccountAllowed: Boolean
|
||||||
) : JobParameters(parameters) {
|
) : JobParameters(parameters) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package net.codinux.banking.fints.response.segments
|
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
|
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 challenge: String?, // M: bei TAN-Prozess=1, 3, 4. O: bei TAN-Prozess=2
|
||||||
|
|
||||||
val challengeHHD_UC: String?,
|
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
|
val tanMediaIdentifier: String? = null, // M: bei TAN-Prozess=1, 3, 4 und „Anzahl unterstützter aktiver TAN-Medien“ nicht vorhanden. O: sonst
|
||||||
|
|
||||||
segmentString: String
|
segmentString: String
|
||||||
|
|
|
@ -44,44 +44,50 @@ open class Mt940AccountTransactionsParser(
|
||||||
protected open fun mapToAccountTransaction(statement: AccountStatement, transaction: Transaction, account: AccountData): AccountTransaction {
|
protected open fun mapToAccountTransaction(statement: AccountStatement, transaction: Transaction, account: AccountData): AccountTransaction {
|
||||||
val currency = statement.closingBalance.currency
|
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(
|
return AccountTransaction(
|
||||||
account,
|
account,
|
||||||
Money(mapAmount(transaction.statementLine), currency),
|
Money(mapAmount(transaction.statementLine), currency),
|
||||||
transaction.statementLine.isReversal,
|
// 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?.unparsedReference ?: "",
|
transaction.information?.sepaReference ?: transaction.information?.unparsedReference ?: "",
|
||||||
|
|
||||||
transaction.statementLine.bookingDate ?: statement.closingBalance.bookingDate,
|
transaction.statementLine.bookingDate ?: statement.closingBalance.bookingDate,
|
||||||
transaction.information?.otherPartyName,
|
|
||||||
transaction.information?.otherPartyBankCode,
|
|
||||||
transaction.information?.otherPartyAccountId,
|
|
||||||
transaction.information?.bookingText,
|
|
||||||
transaction.statementLine.valueDate,
|
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.statementNumber,
|
||||||
statement.sequenceNumber,
|
statement.sheetNumber,
|
||||||
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
|
// :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?.endToEndReference,
|
||||||
transaction.information?.customerReference,
|
|
||||||
transaction.information?.mandateReference,
|
transaction.information?.mandateReference,
|
||||||
transaction.information?.creditorIdentifier,
|
transaction.information?.creditorIdentifier,
|
||||||
transaction.information?.originatorsIdentificationCode,
|
transaction.information?.originatorsIdentificationCode,
|
||||||
transaction.information?.compensationAmount,
|
transaction.information?.compensationAmount,
|
||||||
transaction.information?.originalAmount,
|
transaction.information?.originalAmount,
|
||||||
transaction.information?.sepaReference,
|
|
||||||
transaction.information?.deviantOriginator,
|
transaction.information?.deviantOriginator,
|
||||||
transaction.information?.deviantRecipient,
|
transaction.information?.deviantRecipient,
|
||||||
transaction.information?.referenceWithNoSpecialType,
|
transaction.information?.referenceWithNoSpecialType,
|
||||||
transaction.information?.primaNotaNumber,
|
transaction.information?.journalNumber,
|
||||||
transaction.information?.textKeySupplement,
|
transaction.information?.textKeyAddition,
|
||||||
|
|
||||||
transaction.statementLine.currencyType,
|
statement.orderReferenceNumber,
|
||||||
transaction.statementLine.bookingKey,
|
statement.referenceNumber,
|
||||||
transaction.statementLine.referenceForTheAccountOwner,
|
|
||||||
transaction.statementLine.referenceOfTheAccountServicingInstitution,
|
|
||||||
transaction.statementLine.supplementaryDetails,
|
|
||||||
|
|
||||||
statement.transactionReferenceNumber,
|
transaction.statementLine.isReversal,
|
||||||
statement.relatedReferenceNumber
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,9 @@ open class Mt940Parser(
|
||||||
val AccountStatementFieldSeparatorRegex = Regex("(?<!T\\d\\d(:\\d\\d)?):\\d\\d\\w?:")
|
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"
|
const val AccountIdentificationCode = "25"
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ open class Mt940Parser(
|
||||||
|
|
||||||
const val StatementLineCode = "61"
|
const val StatementLineCode = "61"
|
||||||
|
|
||||||
const val InformationToAccountOwnerCode = "86"
|
const val RemittanceInformationFieldCode = "86"
|
||||||
|
|
||||||
const val ClosingBalanceCode = "62"
|
const val ClosingBalanceCode = "62"
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ open class Mt940Parser(
|
||||||
|
|
||||||
val ReferenceTypeRegex = Regex("[A-Z]{4}\\+")
|
val ReferenceTypeRegex = Regex("[A-Z]{4}\\+")
|
||||||
|
|
||||||
val InformationToAccountOwnerSubFieldRegex = Regex("\\?\\d\\d")
|
val RemittanceInformationSubFieldRegex = Regex("\\?\\d\\d")
|
||||||
|
|
||||||
|
|
||||||
const val EndToEndReferenceKey = "EREF+"
|
const val EndToEndReferenceKey = "EREF+"
|
||||||
|
@ -169,8 +169,8 @@ open class Mt940Parser(
|
||||||
val closingBalancePair = fieldsByCode.first { it.first.startsWith(ClosingBalanceCode) }
|
val closingBalancePair = fieldsByCode.first { it.first.startsWith(ClosingBalanceCode) }
|
||||||
|
|
||||||
return AccountStatement(
|
return AccountStatement(
|
||||||
getFieldValue(fieldsByCode, TransactionReferenceNumberCode),
|
getFieldValue(fieldsByCode, OrderReferenceNumberCode),
|
||||||
getOptionalFieldValue(fieldsByCode, RelatedReferenceNumberCode),
|
getOptionalFieldValue(fieldsByCode, ReferenceNumberCode),
|
||||||
accountIdentification[0],
|
accountIdentification[0],
|
||||||
if (accountIdentification.size > 1) accountIdentification[1] else null,
|
if (accountIdentification.size > 1) accountIdentification[1] else null,
|
||||||
statementAndMaySequenceNumber[0].toInt(),
|
statementAndMaySequenceNumber[0].toInt(),
|
||||||
|
@ -210,7 +210,7 @@ open class Mt940Parser(
|
||||||
val statementLine = parseStatementLine(pair.second)
|
val statementLine = parseStatementLine(pair.second)
|
||||||
|
|
||||||
val nextPair = if (index < fieldsByCode.size - 1) fieldsByCode.get(index + 1) else null
|
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))
|
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 transactionType = fieldValue.substring(amountEndIndex, amountEndIndex + 1) // transaction type is 'N', 'S' or 'F'
|
||||||
|
|
||||||
val bookingKeyStart = amountEndIndex + 1
|
val postingKeyStart = amountEndIndex + 1
|
||||||
val bookingKey = fieldValue.substring(bookingKeyStart, bookingKeyStart + 3) // TODO: parse codes, p. 178
|
val postingKey = fieldValue.substring(postingKeyStart, postingKeyStart + 3) // TODO: parse codes, p. 178
|
||||||
|
|
||||||
val customerAndBankReference = fieldValue.substring(bookingKeyStart + 3).split("//")
|
val customerAndBankReference = fieldValue.substring(postingKeyStart + 3).split("//")
|
||||||
val customerReference = customerAndBankReference[0]
|
val customerReference = customerAndBankReference[0].takeIf { it != "NONREF" }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The content of this subfield is the account servicing institution's own reference for the transaction.
|
* 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
|
* 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.
|
* 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 bankReference = if (customerAndBankReference.size > 1) customerAndBankReference[1] else null
|
||||||
var supplementaryDetails: String? = null
|
var furtherInformation: String? = null
|
||||||
|
|
||||||
val bankReferenceAndSupplementaryDetails = bankReference.split("\n")
|
if (bankReference != null && bankReference.contains('\n')) {
|
||||||
if (bankReferenceAndSupplementaryDetails.size > 1) {
|
val bankReferenceAndFurtherInformation = bankReference.split("\n")
|
||||||
bankReference = bankReferenceAndSupplementaryDetails[0].trim()
|
bankReference = bankReferenceAndFurtherInformation[0].trim()
|
||||||
// TODO: parse /OCMT/ and /CHGS/, see page 518
|
// 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,
|
return StatementLine(!!!isDebit, isCancellation, valueDate, bookingDate, null, amount, postingKey,
|
||||||
customerReference, bankReference, supplementaryDetails)
|
customerReference, bankReference, furtherInformation)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun parseNullableInformationToAccountOwner(informationToAccountOwnerString: String): InformationToAccountOwner? {
|
protected open fun parseNullableRemittanceInformationField(remittanceInformationFieldString: String): RemittanceInformationField? {
|
||||||
try {
|
try {
|
||||||
val information = parseInformationToAccountOwner(informationToAccountOwnerString)
|
val information = parseRemittanceInformationField(remittanceInformationFieldString)
|
||||||
|
|
||||||
mapReference(information)
|
mapReference(information)
|
||||||
|
|
||||||
return information
|
return information
|
||||||
} catch (e: Exception) {
|
} 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
|
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
|
// e. g. starts with 0 -> Inlandszahlungsverkehr, starts with '3' -> Wertpapiergeschäft
|
||||||
// see Finanzdatenformate p. 209 - 215
|
// 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 referenceParts = mutableListOf<String>()
|
||||||
val otherPartyName = StringBuilder()
|
val otherPartyName = StringBuilder()
|
||||||
var otherPartyBankCode: String? = null
|
var otherPartyBankId: String? = null
|
||||||
var otherPartyAccountId: String? = null
|
var otherPartyAccountId: String? = null
|
||||||
var bookingText: String? = null
|
var bookingText: String? = null
|
||||||
var primaNotaNumber: String? = null
|
var primaNotaNumber: String? = null
|
||||||
var textKeySupplement: String? = null
|
var textKeySupplement: String? = null
|
||||||
|
|
||||||
val subFieldMatches = InformationToAccountOwnerSubFieldRegex.findAll(informationToAccountOwnerString).toList()
|
val subFieldMatches = RemittanceInformationSubFieldRegex.findAll(remittanceInformationFieldString).toList()
|
||||||
subFieldMatches.forEachIndexed { index, matchResult ->
|
subFieldMatches.forEachIndexed { index, matchResult ->
|
||||||
val fieldCode = matchResult.value.substring(1, 3).toInt()
|
val fieldCode = matchResult.value.substring(1, 3).toInt()
|
||||||
val endIndex = if (index + 1 < subFieldMatches.size) subFieldMatches[index + 1].range.start else informationToAccountOwnerString.length
|
val endIndex = if (index + 1 < subFieldMatches.size) subFieldMatches[index + 1].range.start else remittanceInformationFieldString.length
|
||||||
val fieldValue = informationToAccountOwnerString.substring(matchResult.range.last + 1, endIndex)
|
val fieldValue = remittanceInformationFieldString.substring(matchResult.range.last + 1, endIndex)
|
||||||
|
|
||||||
when (fieldCode) {
|
when (fieldCode) {
|
||||||
0 -> bookingText = fieldValue
|
0 -> bookingText = fieldValue
|
||||||
10 -> primaNotaNumber = fieldValue
|
10 -> primaNotaNumber = fieldValue
|
||||||
in 20..29 -> referenceParts.add(fieldValue)
|
in 20..29 -> referenceParts.add(fieldValue)
|
||||||
30 -> otherPartyBankCode = fieldValue
|
30 -> otherPartyBankId = fieldValue
|
||||||
31 -> otherPartyAccountId = fieldValue
|
31 -> otherPartyAccountId = fieldValue
|
||||||
32, 33 -> otherPartyName.append(fieldValue)
|
32, 33 -> otherPartyName.append(fieldValue)
|
||||||
34 -> textKeySupplement = fieldValue
|
34 -> textKeySupplement = fieldValue
|
||||||
|
@ -345,8 +345,8 @@ open class Mt940Parser(
|
||||||
|
|
||||||
val otherPartyNameString = if (otherPartyName.isBlank()) null else otherPartyName.toString()
|
val otherPartyNameString = if (otherPartyName.isBlank()) null else otherPartyName.toString()
|
||||||
|
|
||||||
return InformationToAccountOwner(
|
return RemittanceInformationField(
|
||||||
reference, otherPartyNameString, otherPartyBankCode, otherPartyAccountId,
|
reference, otherPartyNameString, otherPartyBankId, otherPartyAccountId,
|
||||||
bookingText, primaNotaNumber, textKeySupplement
|
bookingText, primaNotaNumber, textKeySupplement
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -396,7 +396,7 @@ open class Mt940Parser(
|
||||||
*
|
*
|
||||||
* Weitere 4 Verwendungszwecke können zu den Feldschlüsseln 60 bis 63 eingestellt werden.
|
* 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)
|
val referenceParts = getReferenceParts(information.unparsedReference)
|
||||||
|
|
||||||
referenceParts.forEach { entry ->
|
referenceParts.forEach { entry ->
|
||||||
|
@ -431,7 +431,7 @@ open class Mt940Parser(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: there are more. See .pdf from Deutsche Bank
|
// 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) {
|
when (referenceType) {
|
||||||
EndToEndReferenceKey -> information.endToEndReference = typeValue
|
EndToEndReferenceKey -> information.endToEndReference = typeValue
|
||||||
CustomerReferenceKey -> information.customerReference = typeValue
|
CustomerReferenceKey -> information.customerReference = typeValue
|
||||||
|
|
|
@ -11,7 +11,7 @@ open class AccountStatement(
|
||||||
*
|
*
|
||||||
* Max length = 16
|
* Max length = 16
|
||||||
*/
|
*/
|
||||||
val transactionReferenceNumber: String,
|
val orderReferenceNumber: String,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bezugsreferenz oder „NONREF“.
|
* Bezugsreferenz oder „NONREF“.
|
||||||
|
@ -20,7 +20,7 @@ open class AccountStatement(
|
||||||
*
|
*
|
||||||
* Max length = 16
|
* Max length = 16
|
||||||
*/
|
*/
|
||||||
val relatedReferenceNumber: String?,
|
val referenceNumber: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* xxxxxxxxxxx/Konto-Nr. oder yyyyyyyy/Konto-Nr.
|
* xxxxxxxxxxx/Konto-Nr. oder yyyyyyyy/Konto-Nr.
|
||||||
|
@ -50,7 +50,7 @@ open class AccountStatement(
|
||||||
*
|
*
|
||||||
* Max length = 5
|
* Max length = 5
|
||||||
*/
|
*/
|
||||||
val sequenceNumber: Int?,
|
val sheetNumber: Int?,
|
||||||
|
|
||||||
val openingBalance: Balance,
|
val openingBalance: Balance,
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ open class AccountStatement(
|
||||||
*
|
*
|
||||||
* Max length = 65
|
* Max length = 65
|
||||||
*/
|
*/
|
||||||
val multipurposeField: String? = null
|
val remittanceInformationField: String? = null
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ open class Balance(
|
||||||
val isCredit: Boolean,
|
val isCredit: Boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JJMMTT = Buchungsdatum des Saldos oder '0' beim ersten Auszug
|
* JJMMTT = Buchungsdatum des Saldos oder '000000' beim ersten Auszug
|
||||||
*
|
*
|
||||||
* Max length = 6
|
* 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?,
|
val currencyType: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Codes see p. 177 bottom - 179
|
* in Kontowährung
|
||||||
*
|
|
||||||
* After constant „N“
|
|
||||||
*
|
*
|
||||||
* Max length = 15
|
* Max length = 15
|
||||||
*/
|
*/
|
||||||
val amount: Amount,
|
val amount: Amount,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* in Kontowährung
|
* Codes see p. 177 bottom - 179
|
||||||
|
*
|
||||||
|
* After constant „N“
|
||||||
*
|
*
|
||||||
* Length = 3
|
* 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(
|
open class Transaction(
|
||||||
|
|
||||||
val statementLine: StatementLine,
|
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)
|
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? {
|
open fun getSuggestedTanMethod(tanMethods: List<TanMethod>, tanMethodsNotSupportedByApplication: List<TanMethodType> = emptyList()): 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
|
return findPreferredTanMethod(tanMethods, NonVisualOrImageBased, tanMethodsNotSupportedByApplication) // we use NonVisualOrImageBased as it provides a good default for most users
|
||||||
?: 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 !in tanMethodsNotSupportedByApplication }
|
||||||
?: 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 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 ->
|
preferredTanMethods?.forEach { preferredTanMethodType ->
|
||||||
tanMethods.firstOrNull { it.type == preferredTanMethodType }?.let {
|
if (preferredTanMethodType !in tanMethodsNotSupportedByApplication) {
|
||||||
return it
|
tanMethods.firstOrNull { it.type == preferredTanMethodType }?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
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
|
@Serializable
|
||||||
open class AccountTransaction(
|
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 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 bookingDate: LocalDate,
|
||||||
val otherPartyName: String?,
|
|
||||||
val otherPartyBankCode: String?,
|
|
||||||
val otherPartyAccountId: String?,
|
|
||||||
val bookingText: String?,
|
|
||||||
val valueDate: LocalDate,
|
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 openingBalance: Money?,
|
||||||
val closingBalance: Money?,
|
val closingBalance: Money?,
|
||||||
|
|
||||||
val endToEndReference: String?,
|
val statementNumber: Int,
|
||||||
|
val sheetNumber: Int?,
|
||||||
|
|
||||||
val customerReference: String?,
|
val customerReference: String?,
|
||||||
|
val bankReference: String?,
|
||||||
|
val furtherInformation: String?,
|
||||||
|
|
||||||
|
val endToEndReference: String?,
|
||||||
val mandateReference: String?,
|
val mandateReference: String?,
|
||||||
val creditorIdentifier: String?,
|
val creditorIdentifier: String?,
|
||||||
val originatorsIdentificationCode: String?,
|
val originatorsIdentificationCode: String?,
|
||||||
|
|
||||||
val compensationAmount: String?,
|
val compensationAmount: String?,
|
||||||
val originalAmount: String?,
|
val originalAmount: String?,
|
||||||
val sepaReference: String?,
|
|
||||||
val deviantOriginator: String?,
|
val deviantOriginator: String?,
|
||||||
val deviantRecipient: String?,
|
val deviantRecipient: String?,
|
||||||
val referenceWithNoSpecialType: String?,
|
val referenceWithNoSpecialType: String?,
|
||||||
val primaNotaNumber: String?,
|
|
||||||
val textKeySupplement: String?,
|
|
||||||
|
|
||||||
val currencyType: String?,
|
val journalNumber: String?,
|
||||||
val bookingKey: String,
|
val textKeyAddition: String?,
|
||||||
val referenceForTheAccountOwner: String,
|
|
||||||
val referenceOfTheAccountServicingInstitution: String?,
|
|
||||||
val supplementaryDetails: String?,
|
|
||||||
|
|
||||||
val transactionReferenceNumber: String,
|
val orderReferenceNumber: String?,
|
||||||
val relatedReferenceNumber: String?
|
val referenceNumber: String?,
|
||||||
|
|
||||||
|
val isReversal: Boolean
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// for object deserializers
|
// 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)
|
constructor(amount: Money, unparsedReference: String, bookingDate: LocalDate, valueDate: LocalDate, otherPartyName: String?, otherPartyBankId: String?, otherPartyAccountId: String?, postingText: String?)
|
||||||
: this(amount, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
|
: this(amount, unparsedReference, bookingDate, valueDate, otherPartyName, otherPartyBankId, otherPartyAccountId, postingText,
|
||||||
0, null, null, null,
|
null, null, 0, null,
|
||||||
null, null, null, null, null, null, null, null, null, null, null, null, null,
|
null, null, null, null, null, null, null, null, null, null, null, null,
|
||||||
null, "", "", null, null, "", null)
|
null, null, null, null, false)
|
||||||
|
|
||||||
|
|
||||||
open val showOtherPartyName: Boolean
|
open val showOtherPartyName: Boolean
|
||||||
get() = otherPartyName.isNullOrBlank() == false /* && type != "ENTGELTABSCHLUSS" && type != "AUSZAHLUNG" */ // TODO
|
get() = otherPartyName.isNullOrBlank() == false /* && type != "ENTGELTABSCHLUSS" && type != "AUSZAHLUNG" */ // TODO
|
||||||
|
|
||||||
val reference: String
|
|
||||||
get() = sepaReference ?: unparsedReference
|
|
||||||
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is AccountTransaction) return false
|
if (other !is AccountTransaction) return false
|
||||||
|
|
||||||
if (amount != other.amount) 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 (bookingDate != other.bookingDate) return false
|
||||||
if (otherPartyName != other.otherPartyName) 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 (otherPartyAccountId != other.otherPartyAccountId) return false
|
||||||
if (bookingText != other.bookingText) return false
|
if (postingText != other.postingText) return false
|
||||||
if (valueDate != other.valueDate) return false
|
if (valueDate != other.valueDate) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -81,19 +83,19 @@ open class AccountTransaction(
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = amount.hashCode()
|
var result = amount.hashCode()
|
||||||
result = 31 * result + unparsedReference.hashCode()
|
result = 31 * result + reference.hashCode()
|
||||||
result = 31 * result + bookingDate.hashCode()
|
result = 31 * result + bookingDate.hashCode()
|
||||||
result = 31 * result + (otherPartyName?.hashCode() ?: 0)
|
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 + (otherPartyAccountId?.hashCode() ?: 0)
|
||||||
result = 31 * result + (bookingText?.hashCode() ?: 0)
|
result = 31 * result + (postingText?.hashCode() ?: 0)
|
||||||
result = 31 * result + valueDate.hashCode()
|
result = 31 * result + valueDate.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun toString(): String {
|
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 currency: String = Currency.DefaultCurrencyCode, // TODO: may parse to a value object
|
||||||
open val accountLimit: String? = null,
|
open val accountLimit: String? = null,
|
||||||
|
|
||||||
open val countDaysForWhichTransactionsAreKept: Int? = null,
|
open val serverTransactionsRetentionDays: Int? = null,
|
||||||
open val isAccountTypeSupportedByApplication: Boolean = false,
|
open val isAccountTypeSupportedByApplication: Boolean = false,
|
||||||
// TODO: create an enum AccountCapabilities [ RetrieveBalance, RetrieveTransactions, TransferMoney / MoneyTransfer(?), InstantPayment ]
|
// TODO: create an enum AccountCapabilities [ RetrieveBalance, RetrieveTransactions, TransferMoney / MoneyTransfer(?), InstantPayment ]
|
||||||
open val supportsRetrievingTransactions: Boolean = false,
|
open val supportsRetrievingTransactions: Boolean = false,
|
||||||
|
|
|
@ -12,6 +12,7 @@ open class FinTsClientParameter(
|
||||||
password: String,
|
password: String,
|
||||||
|
|
||||||
open val preferredTanMethods: List<TanMethodType>? = null,
|
open val preferredTanMethods: List<TanMethodType>? = null,
|
||||||
|
open val tanMethodsNotSupportedByApplication: List<TanMethodType>? = null,
|
||||||
open val preferredTanMedium: String? = null, // the ID of the medium
|
open val preferredTanMedium: String? = null, // the ID of the medium
|
||||||
open val abortIfTanIsRequired: Boolean = false,
|
open val abortIfTanIsRequired: Boolean = false,
|
||||||
open val finTsModel: BankData? = null
|
open val finTsModel: BankData? = null
|
||||||
|
|
|
@ -21,10 +21,12 @@ open class GetAccountDataParameter(
|
||||||
open val retrieveTransactionsTo: LocalDate? = null,
|
open val retrieveTransactionsTo: LocalDate? = null,
|
||||||
|
|
||||||
preferredTanMethods: List<TanMethodType>? = null,
|
preferredTanMethods: List<TanMethodType>? = null,
|
||||||
|
tanMethodsNotSupportedByApplication: List<TanMethodType>? = null,
|
||||||
preferredTanMedium: String? = null,
|
preferredTanMedium: String? = null,
|
||||||
abortIfTanIsRequired: Boolean = false,
|
abortIfTanIsRequired: Boolean = false,
|
||||||
finTsModel: BankData? = null
|
finTsModel: BankData? = null,
|
||||||
) : FinTsClientParameter(bankCode, loginName, password, preferredTanMethods, preferredTanMedium, abortIfTanIsRequired, finTsModel) {
|
open val defaultBankValues: BankData? = null
|
||||||
|
) : FinTsClientParameter(bankCode, loginName, password, preferredTanMethods, tanMethodsNotSupportedByApplication, preferredTanMedium, abortIfTanIsRequired, finTsModel) {
|
||||||
|
|
||||||
open val retrieveOnlyAccountInfo: Boolean
|
open val retrieveOnlyAccountInfo: Boolean
|
||||||
get() = retrieveBalance == false && retrieveTransactions == RetrieveTransactions.No
|
get() = retrieveBalance == false && retrieveTransactions == RetrieveTransactions.No
|
||||||
|
|
|
@ -34,10 +34,11 @@ open class TransferMoneyParameter(
|
||||||
open val instantPayment: Boolean = false,
|
open val instantPayment: Boolean = false,
|
||||||
|
|
||||||
preferredTanMethods: List<TanMethodType>? = null,
|
preferredTanMethods: List<TanMethodType>? = null,
|
||||||
|
tanMethodsNotSupportedByApplication: List<TanMethodType>? = null,
|
||||||
preferredTanMedium: String? = null,
|
preferredTanMedium: String? = null,
|
||||||
abortIfTanIsRequired: Boolean = false,
|
abortIfTanIsRequired: Boolean = false,
|
||||||
finTsModel: BankData? = null,
|
finTsModel: BankData? = null,
|
||||||
|
|
||||||
open val selectAccountToUseForTransfer: ((List<AccountData>) -> AccountData?)? = null // TODO: use BankAccount instead of AccountData
|
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.NoJobReferenceResponse, segment.jobReference)
|
||||||
assertEquals(TanResponse.NoChallengeResponse, segment.challenge)
|
assertEquals(TanResponse.NoChallengeResponse, segment.challenge)
|
||||||
assertNull(segment.challengeHHD_UC)
|
assertNull(segment.challengeHHD_UC)
|
||||||
assertEquals(null, segment.validityDateTimeForChallenge)
|
assertEquals(null, segment.tanExpirationTime)
|
||||||
assertEquals(null, segment.tanMediaIdentifier)
|
assertEquals(null, segment.tanMediaIdentifier)
|
||||||
}
|
}
|
||||||
?: run { fail("No segment of type TanResponse found in ${result.receivedSegments}") }
|
?: run { fail("No segment of type TanResponse found in ${result.receivedSegments}") }
|
||||||
|
@ -995,7 +995,7 @@ class ResponseParserTest : FinTsTestBase() {
|
||||||
assertEquals(jobReference, segment.jobReference)
|
assertEquals(jobReference, segment.jobReference)
|
||||||
assertEquals(unmaskString(challenge), segment.challenge)
|
assertEquals(unmaskString(challenge), segment.challenge)
|
||||||
assertEquals(challengeHHD_UC, segment.challengeHHD_UC)
|
assertEquals(challengeHHD_UC, segment.challengeHHD_UC)
|
||||||
assertEquals(null, segment.validityDateTimeForChallenge)
|
assertEquals(null, segment.tanExpirationTime)
|
||||||
assertEquals(tanMediaIdentifier, segment.tanMediaIdentifier)
|
assertEquals(tanMediaIdentifier, segment.tanMediaIdentifier)
|
||||||
}
|
}
|
||||||
?: run { fail("No segment of type TanResponse found in ${result.receivedSegments}") }
|
?: run { fail("No segment of type TanResponse found in ${result.receivedSegments}") }
|
||||||
|
@ -1189,16 +1189,16 @@ class ResponseParserTest : FinTsTestBase() {
|
||||||
fun parseAccountTransactionsMt940Parameters_Version4() {
|
fun parseAccountTransactionsMt940Parameters_Version4() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
val countDaysForWhichTransactionsAreKept = 90
|
val serverTransactionsRetentionDays = 90
|
||||||
|
|
||||||
// when
|
// 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
|
// then
|
||||||
assertSuccessfullyParsedSegment(result, InstituteSegmentId.AccountTransactionsMt940Parameters, 21, 4, 4)
|
assertSuccessfullyParsedSegment(result, InstituteSegmentId.AccountTransactionsMt940Parameters, 21, 4, 4)
|
||||||
|
|
||||||
result.getFirstSegmentById<RetrieveAccountTransactionsParameters>(InstituteSegmentId.AccountTransactionsMt940Parameters)?.let { segment ->
|
result.getFirstSegmentById<RetrieveAccountTransactionsParameters>(InstituteSegmentId.AccountTransactionsMt940Parameters)?.let { segment ->
|
||||||
assertEquals(countDaysForWhichTransactionsAreKept, segment.countDaysForWhichTransactionsAreKept)
|
assertEquals(serverTransactionsRetentionDays, segment.serverTransactionsRetentionDays)
|
||||||
assertFalse(segment.settingCountEntriesAllowed)
|
assertFalse(segment.settingCountEntriesAllowed)
|
||||||
assertFalse(segment.settingAllAccountAllowed)
|
assertFalse(segment.settingAllAccountAllowed)
|
||||||
}
|
}
|
||||||
|
@ -1209,16 +1209,16 @@ class ResponseParserTest : FinTsTestBase() {
|
||||||
fun parseAccountTransactionsMt940Parameters_Version6() {
|
fun parseAccountTransactionsMt940Parameters_Version6() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
val countDaysForWhichTransactionsAreKept = 90
|
val serverTransactionsRetentionDays = 90
|
||||||
|
|
||||||
// when
|
// 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
|
// then
|
||||||
assertSuccessfullyParsedSegment(result, InstituteSegmentId.AccountTransactionsMt940Parameters, 23, 6, 4)
|
assertSuccessfullyParsedSegment(result, InstituteSegmentId.AccountTransactionsMt940Parameters, 23, 6, 4)
|
||||||
|
|
||||||
result.getFirstSegmentById<RetrieveAccountTransactionsParameters>(InstituteSegmentId.AccountTransactionsMt940Parameters)?.let { segment ->
|
result.getFirstSegmentById<RetrieveAccountTransactionsParameters>(InstituteSegmentId.AccountTransactionsMt940Parameters)?.let { segment ->
|
||||||
assertEquals(countDaysForWhichTransactionsAreKept, segment.countDaysForWhichTransactionsAreKept)
|
assertEquals(serverTransactionsRetentionDays, segment.serverTransactionsRetentionDays)
|
||||||
assertFalse(segment.settingCountEntriesAllowed)
|
assertFalse(segment.settingCountEntriesAllowed)
|
||||||
assertFalse(segment.settingAllAccountAllowed)
|
assertFalse(segment.settingAllAccountAllowed)
|
||||||
}
|
}
|
||||||
|
@ -1290,16 +1290,16 @@ class ResponseParserTest : FinTsTestBase() {
|
||||||
fun parseCreditCardAccountTransactionsParameters() {
|
fun parseCreditCardAccountTransactionsParameters() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
val countDaysForWhichTransactionsAreKept = 9999
|
val serverTransactionsRetentionDays = 9999
|
||||||
|
|
||||||
// when
|
// 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
|
// then
|
||||||
assertSuccessfullyParsedSegment(result, InstituteSegmentId.CreditCardTransactionsParameters, 15, 2, 4)
|
assertSuccessfullyParsedSegment(result, InstituteSegmentId.CreditCardTransactionsParameters, 15, 2, 4)
|
||||||
|
|
||||||
result.getFirstSegmentById<RetrieveAccountTransactionsParameters>(InstituteSegmentId.CreditCardTransactionsParameters)?.let { segment ->
|
result.getFirstSegmentById<RetrieveAccountTransactionsParameters>(InstituteSegmentId.CreditCardTransactionsParameters)?.let { segment ->
|
||||||
assertEquals(countDaysForWhichTransactionsAreKept, segment.countDaysForWhichTransactionsAreKept)
|
assertEquals(serverTransactionsRetentionDays, segment.serverTransactionsRetentionDays)
|
||||||
assertTrue(segment.settingCountEntriesAllowed)
|
assertTrue(segment.settingCountEntriesAllowed)
|
||||||
assertTrue(segment.settingAllAccountAllowed)
|
assertTrue(segment.settingAllAccountAllowed)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package net.codinux.banking.fints.transactions
|
||||||
import net.codinux.banking.fints.FinTsTestBase
|
import net.codinux.banking.fints.FinTsTestBase
|
||||||
import net.codinux.banking.fints.transactions.mt940.Mt940Parser
|
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.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 net.codinux.banking.fints.transactions.mt940.model.StatementLine
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import net.codinux.banking.fints.extensions.*
|
import net.codinux.banking.fints.extensions.*
|
||||||
|
@ -26,12 +26,12 @@ class Mt940ParserTest : FinTsTestBase() {
|
||||||
|
|
||||||
val AccountStatement1Transaction1Amount = Amount("1234,56")
|
val AccountStatement1Transaction1Amount = Amount("1234,56")
|
||||||
val AccountStatement1Transaction1OtherPartyName = "Sender1"
|
val AccountStatement1Transaction1OtherPartyName = "Sender1"
|
||||||
val AccountStatement1Transaction1OtherPartyBankCode = "AAAADE12"
|
val AccountStatement1Transaction1OtherPartyBankId = "AAAADE12"
|
||||||
val AccountStatement1Transaction1OtherPartyAccountId = "DE99876543210987654321"
|
val AccountStatement1Transaction1OtherPartyAccountId = "DE99876543210987654321"
|
||||||
|
|
||||||
val AccountStatement1Transaction2Amount = Amount("432,10")
|
val AccountStatement1Transaction2Amount = Amount("432,10")
|
||||||
val AccountStatement1Transaction2OtherPartyName = "Receiver2"
|
val AccountStatement1Transaction2OtherPartyName = "Receiver2"
|
||||||
val AccountStatement1Transaction2OtherPartyBankCode = "BBBBDE56"
|
val AccountStatement1Transaction2OtherPartyBankId = "BBBBDE56"
|
||||||
val AccountStatement1Transaction2OtherPartyAccountId = "DE77987654321234567890"
|
val AccountStatement1Transaction2OtherPartyAccountId = "DE77987654321234567890"
|
||||||
|
|
||||||
val AccountStatement1ClosingBalanceAmount = Amount("13580,23")
|
val AccountStatement1ClosingBalanceAmount = Amount("13580,23")
|
||||||
|
@ -67,7 +67,7 @@ class Mt940ParserTest : FinTsTestBase() {
|
||||||
val transaction = statement.transactions.first()
|
val transaction = statement.transactions.first()
|
||||||
assertTurnover(transaction.statementLine, AccountStatement1BookingDate, AccountStatement1Transaction1Amount)
|
assertTurnover(transaction.statementLine, AccountStatement1BookingDate, AccountStatement1Transaction1Amount)
|
||||||
assertTransactionDetails(transaction.information, AccountStatement1Transaction1OtherPartyName,
|
assertTransactionDetails(transaction.information, AccountStatement1Transaction1OtherPartyName,
|
||||||
AccountStatement1Transaction1OtherPartyBankCode, AccountStatement1Transaction1OtherPartyAccountId)
|
AccountStatement1Transaction1OtherPartyBankId, AccountStatement1Transaction1OtherPartyAccountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -90,7 +90,7 @@ class Mt940ParserTest : FinTsTestBase() {
|
||||||
assertEquals(BankCode, statement.bankCodeBicOrIban)
|
assertEquals(BankCode, statement.bankCodeBicOrIban)
|
||||||
assertEquals(CustomerId, statement.accountIdentifier)
|
assertEquals(CustomerId, statement.accountIdentifier)
|
||||||
assertEquals(0, statement.statementNumber)
|
assertEquals(0, statement.statementNumber)
|
||||||
assertNull(statement.sequenceNumber)
|
assertNull(statement.sheetNumber)
|
||||||
|
|
||||||
assertBalance(statement.openingBalance, true, bookingDate, Amount("0,00"))
|
assertBalance(statement.openingBalance, true, bookingDate, Amount("0,00"))
|
||||||
assertBalance(statement.closingBalance, isCredit, bookingDate, amount)
|
assertBalance(statement.closingBalance, isCredit, bookingDate, amount)
|
||||||
|
@ -124,12 +124,12 @@ class Mt940ParserTest : FinTsTestBase() {
|
||||||
val firstTransaction = statement.transactions.first()
|
val firstTransaction = statement.transactions.first()
|
||||||
assertTurnover(firstTransaction.statementLine, AccountStatement1BookingDate, AccountStatement1Transaction1Amount)
|
assertTurnover(firstTransaction.statementLine, AccountStatement1BookingDate, AccountStatement1Transaction1Amount)
|
||||||
assertTransactionDetails(firstTransaction.information, AccountStatement1Transaction1OtherPartyName,
|
assertTransactionDetails(firstTransaction.information, AccountStatement1Transaction1OtherPartyName,
|
||||||
AccountStatement1Transaction1OtherPartyBankCode, AccountStatement1Transaction1OtherPartyAccountId)
|
AccountStatement1Transaction1OtherPartyBankId, AccountStatement1Transaction1OtherPartyAccountId)
|
||||||
|
|
||||||
val secondTransaction = statement.transactions[1]
|
val secondTransaction = statement.transactions[1]
|
||||||
assertTurnover(secondTransaction.statementLine, AccountStatement1BookingDate, AccountStatement1Transaction2Amount, false)
|
assertTurnover(secondTransaction.statementLine, AccountStatement1BookingDate, AccountStatement1Transaction2Amount, false)
|
||||||
assertTransactionDetails(secondTransaction.information, AccountStatement1Transaction2OtherPartyName,
|
assertTransactionDetails(secondTransaction.information, AccountStatement1Transaction2OtherPartyName,
|
||||||
AccountStatement1Transaction2OtherPartyBankCode, AccountStatement1Transaction2OtherPartyAccountId)
|
AccountStatement1Transaction2OtherPartyBankId, AccountStatement1Transaction2OtherPartyAccountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -306,8 +306,8 @@ class Mt940ParserTest : FinTsTestBase() {
|
||||||
assertSize(1, result.first().transactions)
|
assertSize(1, result.first().transactions)
|
||||||
|
|
||||||
result.first().transactions[0].information?.apply {
|
result.first().transactions[0].information?.apply {
|
||||||
assertEquals("BASISLASTSCHRIFT", bookingText)
|
assertEquals("BASISLASTSCHRIFT", postingText)
|
||||||
assertEquals("TUBDDEDD", otherPartyBankCode)
|
assertEquals("TUBDDEDD", otherPartyBankId)
|
||||||
assertEquals("DE87300308801234567890", otherPartyAccountId)
|
assertEquals("DE87300308801234567890", otherPartyAccountId)
|
||||||
assertEquals("6MKL2OT30QENNLIU", endToEndReference)
|
assertEquals("6MKL2OT30QENNLIU", endToEndReference)
|
||||||
assertEquals("?,3SQNdUbxm9z7dB)+gKYDJAKzCM0G", mandateReference)
|
assertEquals("?,3SQNdUbxm9z7dB)+gKYDJAKzCM0G", mandateReference)
|
||||||
|
@ -362,13 +362,13 @@ class Mt940ParserTest : FinTsTestBase() {
|
||||||
assertEquals(amount, statementLine.amount)
|
assertEquals(amount, statementLine.amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun assertTransactionDetails(details: InformationToAccountOwner?, otherPartyName: String,
|
private fun assertTransactionDetails(details: RemittanceInformationField?, otherPartyName: String,
|
||||||
otherPartyBankCode: String, otherPartyAccountId: String) {
|
otherPartyBankId: String, otherPartyAccountId: String) {
|
||||||
|
|
||||||
assertNotNull(details)
|
assertNotNull(details)
|
||||||
|
|
||||||
assertEquals(otherPartyName, details.otherPartyName)
|
assertEquals(otherPartyName, details.otherPartyName)
|
||||||
assertEquals(otherPartyBankCode, details.otherPartyBankCode)
|
assertEquals(otherPartyBankId, details.otherPartyBankId)
|
||||||
assertEquals(otherPartyAccountId, details.otherPartyAccountId)
|
assertEquals(otherPartyAccountId, details.otherPartyAccountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,7 +380,7 @@ class Mt940ParserTest : FinTsTestBase() {
|
||||||
:60F:C${convertMt940Date(AccountStatement1PreviousStatementBookingDate)}EUR$AccountStatement1OpeningBalanceAmount
|
:60F:C${convertMt940Date(AccountStatement1PreviousStatementBookingDate)}EUR$AccountStatement1OpeningBalanceAmount
|
||||||
:61:${convertMt940Date(AccountStatement1BookingDate)}${convertToShortBookingDate(AccountStatement1BookingDate)}CR${AccountStatement1Transaction1Amount}N062NONREF
|
:61:${convertMt940Date(AccountStatement1BookingDate)}${convertToShortBookingDate(AccountStatement1BookingDate)}CR${AccountStatement1Transaction1Amount}N062NONREF
|
||||||
:86:166?00GUTSCHR. UEBERWEISUNG?109249?20EREF+674?21SVWZ+1908301/
|
: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
|
?32$AccountStatement1Transaction1OtherPartyName
|
||||||
:62F:C${convertMt940Date(AccountStatement1BookingDate)}EUR$AccountStatement1ClosingBalanceAmount
|
:62F:C${convertMt940Date(AccountStatement1BookingDate)}EUR$AccountStatement1ClosingBalanceAmount
|
||||||
-
|
-
|
||||||
|
@ -393,11 +393,11 @@ class Mt940ParserTest : FinTsTestBase() {
|
||||||
:60F:C${convertMt940Date(AccountStatement1PreviousStatementBookingDate)}EUR$AccountStatement1OpeningBalanceAmount
|
:60F:C${convertMt940Date(AccountStatement1PreviousStatementBookingDate)}EUR$AccountStatement1OpeningBalanceAmount
|
||||||
:61:${convertMt940Date(AccountStatement1BookingDate)}${convertToShortBookingDate(AccountStatement1BookingDate)}CR${AccountStatement1Transaction1Amount}N062NONREF
|
:61:${convertMt940Date(AccountStatement1BookingDate)}${convertToShortBookingDate(AccountStatement1BookingDate)}CR${AccountStatement1Transaction1Amount}N062NONREF
|
||||||
:86:166?00GUTSCHR. UEBERWEISUNG?109249?20EREF+674?21SVWZ+1908301/
|
: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
|
?32$AccountStatement1Transaction1OtherPartyName
|
||||||
:61:${convertMt940Date(AccountStatement1BookingDate)}${convertToShortBookingDate(AccountStatement1BookingDate)}DR${AccountStatement1Transaction2Amount}N062NONREF
|
:61:${convertMt940Date(AccountStatement1BookingDate)}${convertToShortBookingDate(AccountStatement1BookingDate)}DR${AccountStatement1Transaction2Amount}N062NONREF
|
||||||
:86:166?00ONLINE-UEBERWEISUNG?109249?20EREF+674?21SVWZ+1908301/
|
: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
|
?32$AccountStatement1Transaction2OtherPartyName
|
||||||
:62F:C${convertMt940Date(AccountStatement1BookingDate)}EUR${AccountStatement1With2TransactionsClosingBalanceAmount}
|
:62F:C${convertMt940Date(AccountStatement1BookingDate)}EUR${AccountStatement1With2TransactionsClosingBalanceAmount}
|
||||||
-
|
-
|
||||||
|
|
Loading…
Reference in New Issue