Implemented continueing at Aufsetzpunkt

This commit is contained in:
dankl 2019-10-26 18:22:06 +02:00 committed by dankito
parent 14bc302c6d
commit ee3cd937df
14 changed files with 266 additions and 31 deletions

View File

@ -6,8 +6,11 @@ ext {
kotlinVersion = '1.3.41' kotlinVersion = '1.3.41'
javaUtilsVersion = '1.0.8' javaUtilsVersion = '1.0.8'
androidUtilsVersion = '1.1.0' androidUtilsVersion = '1.1.0'
javaFxUtilsVersion = '1.0.3'
junitVersion = '4.12' junitVersion = '4.12'
assertJVersion = '3.12.2' assertJVersion = '3.12.2'

View File

@ -31,4 +31,6 @@ dependencies {
testCompile "ch.qos.logback:logback-core:$logbackVersion" testCompile "ch.qos.logback:logback-core:$logbackVersion"
testCompile "ch.qos.logback:logback-classic:$logbackVersion" testCompile "ch.qos.logback:logback-classic:$logbackVersion"
testCompile "net.dankito.utils:java-fx-utils:$javaFxUtilsVersion"
} }

View File

@ -255,15 +255,36 @@ open class FinTsClient @JvmOverloads constructor(
tryGetTransactionsOfLast90DaysWithoutTan(bank, customer) tryGetTransactionsOfLast90DaysWithoutTan(bank, customer)
} }
val bookedAndUnbookedTransactions = getTransactionsFromResponse(response, transactions)
return GetTransactionsResponse(response, return GetTransactionsResponse(response,
transactions.bookedTransactions.sortedByDescending { it.bookingDate }, bookedAndUnbookedTransactions.first.sortedByDescending { it.bookingDate },
transactions.unbookedTransactions, bookedAndUnbookedTransactions.second,
balance) balance)
} }
return GetTransactionsResponse(response) return GetTransactionsResponse(response)
} }
protected open fun getTransactionsFromResponse(response: Response, transactions: ReceivedAccountTransactions): Pair<List<AccountTransaction>, List<Any>> {
val bookedTransactions = mutableListOf<AccountTransaction>()
val unbookedTransactions = mutableListOf<Any>()
bookedTransactions.addAll(transactions.bookedTransactions)
unbookedTransactions.addAll(transactions.unbookedTransactions)
response.followUpResponse?.let { followUpResponse ->
followUpResponse.getFirstSegmentById<ReceivedAccountTransactions>(InstituteSegmentId.AccountTransactionsMt940)?.let { followUpTransactions ->
val followUpBookedAndUnbookedTransactions = getTransactionsFromResponse(followUpResponse, followUpTransactions)
bookedTransactions.addAll(followUpBookedAndUnbookedTransactions.first)
unbookedTransactions.addAll(followUpBookedAndUnbookedTransactions.second)
}
}
return Pair(bookedTransactions, unbookedTransactions)
}
protected open fun getBalanceAfterDialogInit(bank: BankData, customer: CustomerData, protected open fun getBalanceAfterDialogInit(bank: BankData, customer: CustomerData,
dialogData: DialogData): Response { dialogData: DialogData): Response {
@ -433,6 +454,32 @@ open class FinTsClient @JvmOverloads constructor(
customer: CustomerData, dialogData: DialogData): Response { customer: CustomerData, dialogData: DialogData): Response {
val response = getAndHandleResponseForMessage(message, bank) val response = getAndHandleResponseForMessage(message, bank)
val handledResponse = handleMayRequiredTan(response, bank, customer, dialogData)
// if there's a Aufsetzpunkt (continuationId) set, then response is not complete yet, there's more information to fetch by sending this Aufsetzpunkt
handledResponse.aufsetzpunkt?.let { continuationId ->
handledResponse.followUpResponse = getFollowUpMessageForContinuationId(handledResponse, continuationId, message, bank, customer, dialogData)
handledResponse.hasFollowUpMessageButCouldNotReceiveIt = handledResponse.followUpResponse == null
}
return handledResponse
}
protected open fun getFollowUpMessageForContinuationId(response: Response, continuationId: String, message: MessageBuilderResult,
bank: BankData, customer: CustomerData, dialogData: DialogData): Response? {
messageBuilder.rebuildMessageWithContinuationId(message, continuationId, bank, customer, dialogData)?.let { followUpMessage ->
return getAndHandleResponseForMessageThatMayRequiresTan(followUpMessage, bank, customer, dialogData)
}
return null
}
protected open fun getAndHandleResponseForMessageThatMayRequiresTan(message: String, bank: BankData,
customer: CustomerData, dialogData: DialogData): Response {
val response = getAndHandleResponseForMessage(message, bank)
return handleMayRequiredTan(response, bank, customer, dialogData) return handleMayRequiredTan(response, bank, customer, dialogData)
} }

View File

@ -1,6 +1,7 @@
package net.dankito.fints.messages package net.dankito.fints.messages
import net.dankito.fints.extensions.containsAny import net.dankito.fints.extensions.containsAny
import net.dankito.fints.messages.datenelemente.implementierte.Aufsetzpunkt
import net.dankito.fints.messages.datenelemente.implementierte.Synchronisierungsmodus import net.dankito.fints.messages.datenelemente.implementierte.Synchronisierungsmodus
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanProcess import net.dankito.fints.messages.datenelemente.implementierte.tan.TanProcess
import net.dankito.fints.messages.segmente.ISegmentNumberGenerator import net.dankito.fints.messages.segmente.ISegmentNumberGenerator
@ -107,11 +108,12 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
else if (result.isAllowed(6)) KontoumsaetzeZeitraumMt940Version6(generator.resetSegmentNumber(2), parameter, bank, customer) else if (result.isAllowed(6)) KontoumsaetzeZeitraumMt940Version6(generator.resetSegmentNumber(2), parameter, bank, customer)
else KontoumsaetzeZeitraumMt940Version5(generator.resetSegmentNumber(2), parameter, bank, customer) else KontoumsaetzeZeitraumMt940Version5(generator.resetSegmentNumber(2), parameter, bank, customer)
val segments = listOf(
return MessageBuilderResult(createSignedMessage(bank, customer, dialogData, listOf(
transactionsJob, transactionsJob,
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.AccountTransactionsMt940) ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.AccountTransactionsMt940)
))) )
return createMessageBuilderResult(bank, customer, dialogData, segments)
} }
return result return result
@ -122,24 +124,26 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
val result = getSupportedVersionsOfJob(CustomerSegmentId.Balance, customer, listOf(5)) val result = getSupportedVersionsOfJob(CustomerSegmentId.Balance, customer, listOf(5))
if (result.isJobVersionSupported) { if (result.isJobVersionSupported) {
return MessageBuilderResult(createSignedMessage(bank, customer, dialogData, listOf( val segments = listOf(
Saldenabfrage(generator.resetSegmentNumber(2), bank, customer), Saldenabfrage(generator.resetSegmentNumber(2), bank, customer),
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.Balance) ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.Balance)
))) )
return createMessageBuilderResult(bank, customer, dialogData, segments)
} }
return result return result
} }
open fun createSendEnteredTanMessage(enteredTan: String, tanResponse: TanResponse, bank: BankData, customer: CustomerData, dialogData: DialogData): MessageBuilderResult { open fun createSendEnteredTanMessage(enteredTan: String, tanResponse: TanResponse, bank: BankData, customer: CustomerData, dialogData: DialogData): String {
val tanProcess = if (tanResponse.tanProcess == TanProcess.TanProcess1) TanProcess.TanProcess1 else TanProcess.TanProcess2 val tanProcess = if (tanResponse.tanProcess == TanProcess.TanProcess1) TanProcess.TanProcess1 else TanProcess.TanProcess2
return MessageBuilderResult(createSignedMessage(bank, customer, dialogData, enteredTan, listOf( return createSignedMessage(bank, customer, dialogData, enteredTan, listOf(
ZweiSchrittTanEinreichung(generator.resetSegmentNumber(2), tanProcess, null, ZweiSchrittTanEinreichung(generator.resetSegmentNumber(2), tanProcess, null,
tanResponse.jobHashValue, tanResponse.jobReference, false, null, tanResponse.tanMediaIdentifier) tanResponse.jobHashValue, tanResponse.jobReference, false, null, tanResponse.tanMediaIdentifier)
))) ))
} }
@ -150,10 +154,12 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
if (result.isJobVersionSupported) { if (result.isJobVersionSupported) {
getSepaUrnFor(CustomerSegmentId.SepaAccountInfoParameters, customer, "pain.001.001.03")?.let { urn -> getSepaUrnFor(CustomerSegmentId.SepaAccountInfoParameters, customer, "pain.001.001.03")?.let { urn ->
return MessageBuilderResult(createSignedMessage(bank, customer, dialogData, listOf( val segments = listOf(
SepaEinzelueberweisung(generator.resetSegmentNumber(2), urn, customer, bank.bic, bankTransferData), SepaEinzelueberweisung(generator.resetSegmentNumber(2), urn, customer, bank.bic, bankTransferData),
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.SepaBankTransfer) ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.SepaBankTransfer)
))) )
return createMessageBuilderResult(bank, customer, dialogData, segments)
} }
return MessageBuilderResult(true, false, result.allowedVersions, result.supportedVersions, null) // TODO: how to tell that we don't support required SEPA pain version? return MessageBuilderResult(true, false, result.allowedVersions, result.supportedVersions, null) // TODO: how to tell that we don't support required SEPA pain version?
@ -163,6 +169,29 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
} }
open fun rebuildMessageWithContinuationId(message: MessageBuilderResult, continuationId: String, bank: BankData,
customer: CustomerData, dialogData: DialogData): MessageBuilderResult? {
// val copiedSegments = message.messageBodySegments.map { }
val aufsetzpunkte = message.messageBodySegments.flatMap { it.dataElementsAndGroups }.filterIsInstance<Aufsetzpunkt>()
if (aufsetzpunkte.isEmpty()) {
// return MessageBuilderResult(message.isJobAllowed, message.isJobVersionSupported, message.allowedVersions, message.supportedVersions, null)
return null
}
aufsetzpunkte.forEach { it.resetContinuationId(continuationId) }
dialogData.increaseMessageNumber()
return createMessageBuilderResult(bank, customer, dialogData, message.messageBodySegments)
}
protected open fun createMessageBuilderResult(bank: BankData, customer: CustomerData, dialogData: DialogData, segments: List<Segment>): MessageBuilderResult {
return MessageBuilderResult(createSignedMessage(bank, customer, dialogData, segments), segments)
}
open fun createSignedMessage(bank: BankData, customer: CustomerData, dialogData: DialogData, open fun createSignedMessage(bank: BankData, customer: CustomerData, dialogData: DialogData,
payloadSegments: List<Segment>): String { payloadSegments: List<Segment>): String {

View File

@ -1,17 +1,21 @@
package net.dankito.fints.messages package net.dankito.fints.messages
import net.dankito.fints.messages.segmente.Segment
open class MessageBuilderResult( open class MessageBuilderResult(
val isJobAllowed: Boolean, val isJobAllowed: Boolean,
val isJobVersionSupported: Boolean, val isJobVersionSupported: Boolean,
val allowedVersions: List<Int>, val allowedVersions: List<Int>,
val supportedVersions: List<Int>, val supportedVersions: List<Int>,
val createdMessage: String? val createdMessage: String?,
val messageBodySegments: List<Segment> = listOf()
) { ) {
constructor(isJobAllowed: Boolean) : this(isJobAllowed, false, listOf(), listOf(), null) constructor(isJobAllowed: Boolean) : this(isJobAllowed, false, listOf(), listOf(), null)
constructor(createdMessage: String) : this(true, true, listOf(), listOf(), createdMessage) constructor(createdMessage: String, messageBodySegments: List<Segment>)
: this(true, true, listOf(), listOf(), createdMessage, messageBodySegments)
open fun isAllowed(version: Int): Boolean { open fun isAllowed(version: Int): Boolean {

View File

@ -14,7 +14,8 @@ abstract class AlphanumerischesDatenelement @JvmOverloads constructor(
override fun validate() { override fun validate() {
super.validate() super.validate()
if (writeToOutput && value != null) { // if value is null and value has to be written to output then validation already fails above if (writeToOutput) {
value?.let { value -> // if value is null and value has to be written to output then validation already fails above
if (value.contains("\r") || value.contains("\n")) { if (value.contains("\r") || value.contains("\n")) {
throwValidationException("Alphanumerischer Wert '$value' darf kein Carriage Return (\r) oder " + throwValidationException("Alphanumerischer Wert '$value' darf kein Carriage Return (\r) oder " +
"Line Feed (\n) enthalten.") "Line Feed (\n) enthalten.")
@ -28,5 +29,6 @@ abstract class AlphanumerischesDatenelement @JvmOverloads constructor(
} }
} }
} }
}
} }

View File

@ -37,7 +37,7 @@ open class BinaerDatenelement @JvmOverloads constructor(data: String?, existenzs
if (writeToOutput) { if (writeToOutput) {
checkIfMandatoryValueIsSet() checkIfMandatoryValueIsSet()
value?.let { // if value is null and value has to be written to output then validation already fails above value?.let { value -> // if value is null and value has to be written to output then validation already fails above
maxLength?.let { maxLength?.let {
if (value.length > maxLength) { if (value.length > maxLength) {
throwValidationException("Binäre Daten dürfen nur eine maximale Größe von $maxLength Bytes " + throwValidationException("Binäre Daten dürfen nur eine maximale Größe von $maxLength Bytes " +

View File

@ -9,10 +9,11 @@ import net.dankito.fints.messages.datenelemente.Datenelement
/** /**
* Es gilt der vollständige FinTS-Basiszeichensatz. * Es gilt der vollständige FinTS-Basiszeichensatz.
*/ */
abstract class TextDatenelement(val value: String?, existenzstatus: Existenzstatus) : Datenelement(existenzstatus) { abstract class TextDatenelement(var value: String?, existenzstatus: Existenzstatus) : Datenelement(existenzstatus) {
override val isValueSet = value != null override val isValueSet
get() = value != null
override fun format(): String { override fun format(): String {
if (writeToOutput) { if (writeToOutput) {

View File

@ -10,4 +10,10 @@ import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDat
* einzigen Auftragssegment erfolgen kann (s. [Formals]). * einzigen Auftragssegment erfolgen kann (s. [Formals]).
*/ */
open class Aufsetzpunkt(continuationId: String?, existenzstatus: Existenzstatus) open class Aufsetzpunkt(continuationId: String?, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(continuationId, existenzstatus, 35) : AlphanumerischesDatenelement(continuationId, existenzstatus, 35) {
open fun resetContinuationId(continuationId: String?) {
value = continuationId
}
}

View File

@ -49,6 +49,9 @@ open class Response constructor(
open val segmentFeedbacks: List<SegmentFeedback> open val segmentFeedbacks: List<SegmentFeedback>
get() = getSegmentsById(InstituteSegmentId.SegmentFeedback) get() = getSegmentsById(InstituteSegmentId.SegmentFeedback)
open val aufsetzpunkt: String? // TODO: what to do if there are multiple Aufsetzpunkte?
get() = segmentFeedbacks.flatMap { it.feedbacks }.filterIsInstance<AufsetzpunktFeedback>().firstOrNull()?.aufsetzpunkt
open val errorsToShowToUser: List<String> open val errorsToShowToUser: List<String>
get() { get() {
val errorMessages = segmentFeedbacks val errorMessages = segmentFeedbacks
@ -66,6 +69,11 @@ open class Response constructor(
} }
open var followUpResponse: Response? = null
open var hasFollowUpMessageButCouldNotReceiveIt: Boolean? = false
/** /**
* Returns an empty list of response didn't contain any job parameters. * Returns an empty list of response didn't contain any job parameters.
* *

View File

@ -29,13 +29,15 @@ open class ResponseParser @JvmOverloads constructor(
) { ) {
companion object { companion object {
val EncryptionDataSegmentHeaderPattern = Pattern.compile("${MessageSegmentId.EncryptionData.id}:\\d{1,3}:\\d{1,3}\\+") val EncryptionDataSegmentHeaderPattern: Pattern = Pattern.compile("${MessageSegmentId.EncryptionData.id}:\\d{1,3}:\\d{1,3}\\+")
val JobParametersSegmentPattern = Pattern.compile("HI[A-Z]{3}S") val JobParametersSegmentPattern: Pattern = Pattern.compile("HI[A-Z]{3}S")
val FeedbackParametersSeparator = "; " const val FeedbackParametersSeparator = "; "
val SupportedTanProceduresForUserResponseCode = 3920 const val AufsetzpunktResponseCode = 3040
const val SupportedTanProceduresForUserResponseCode = 3920
private val log = LoggerFactory.getLogger(ResponseParser::class.java) private val log = LoggerFactory.getLogger(ResponseParser::class.java)
} }
@ -138,6 +140,9 @@ open class ResponseParser @JvmOverloads constructor(
val supportedProcedures = parseCodeEnum(dataElements.subList(3, dataElements.size), Sicherheitsfunktion.values()) val supportedProcedures = parseCodeEnum(dataElements.subList(3, dataElements.size), Sicherheitsfunktion.values())
return SupportedTanProceduresForUserFeedback(supportedProcedures, message) return SupportedTanProceduresForUserFeedback(supportedProcedures, message)
} }
else if (responseCode == AufsetzpunktResponseCode) {
return AufsetzpunktFeedback(parseString(dataElements[3]), message)
}
val parameter = if (dataElements.size > 3) dataElements.subList(3, dataElements.size).joinToString(FeedbackParametersSeparator) else null val parameter = if (dataElements.size > 3) dataElements.subList(3, dataElements.size).joinToString(FeedbackParametersSeparator) else null

View File

@ -0,0 +1,10 @@
package net.dankito.fints.response.segments
import net.dankito.fints.response.ResponseParser
open class AufsetzpunktFeedback(
val aufsetzpunkt: String,
message: String
)
: Feedback(ResponseParser.AufsetzpunktResponseCode, message)

View File

@ -1,10 +1,18 @@
package net.dankito.fints.messages package net.dankito.fints.messages
import net.dankito.fints.FinTsTestBase import net.dankito.fints.FinTsTestBase
import net.dankito.fints.model.AccountData
import net.dankito.fints.model.DialogData import net.dankito.fints.model.DialogData
import net.dankito.fints.model.GetTransactionsParameter
import net.dankito.fints.response.segments.AccountType
import net.dankito.fints.response.segments.JobParameters
import net.dankito.fints.util.FinTsUtils import net.dankito.fints.util.FinTsUtils
import net.dankito.utils.datetime.asUtilDate
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Test import org.junit.Test
import java.time.LocalDate
import java.time.Month
import java.util.* import java.util.*
@ -27,6 +35,13 @@ class MessageBuilderTest : FinTsTestBase() {
} }
@After
fun tearDown() {
Bank.supportedJobs = listOf()
Customer.accounts = listOf()
}
@Test @Test
fun createAnonymousDialogInitMessage() { fun createAnonymousDialogInitMessage() {
@ -102,4 +117,94 @@ class MessageBuilderTest : FinTsTestBase() {
)) ))
} }
@Test
fun createGetTransactionsMessage_JobIsNotAllowed() {
// when
val result = underTest.createGetTransactionsMessage(GetTransactionsParameter(), Bank, Customer, Product, DialogData.DialogInitDialogData)
// then
assertThat(result.isJobAllowed).isFalse()
}
@Test
fun createGetTransactionsMessage_JobVersionIsNotSupported() {
// given
val getTransactionsJob = JobParameters("HKKAZ", 1, 1, null, "HKKAZ:73:5")
val getTransactionsJobWithPreviousVersion = JobParameters("HKKAZ", 1, 1, null, "HKKAZ:72:4")
Bank.supportedJobs = listOf(getTransactionsJob)
val account = AccountData(CustomerId, null, BankCountryCode, BankCode, null, CustomerId, AccountType.Girokonto, "EUR", "", null, null, listOf(getTransactionsJob.jobName), listOf(getTransactionsJobWithPreviousVersion))
Customer.accounts = listOf(account)
// when
val result = underTest.createGetTransactionsMessage(GetTransactionsParameter(), Bank, Customer, Product, DialogData.DialogInitDialogData)
// then
assertThat(result.isJobAllowed).isTrue()
assertThat(result.isJobVersionSupported).isFalse()
}
@Test
fun createGetTransactionsMessage() {
// given
val getTransactionsJob = JobParameters("HKKAZ", 1, 1, null, "HKKAZ:73:5")
Bank.supportedJobs = listOf(getTransactionsJob)
val account = AccountData(CustomerId, null, BankCountryCode, BankCode, null, CustomerId, AccountType.Girokonto, "EUR", "", null, null, listOf(getTransactionsJob.jobName), listOf(getTransactionsJob))
Customer.accounts = listOf(account)
val fromDate = LocalDate.of(2019, Month.AUGUST, 6).asUtilDate()
val toDate = LocalDate.of(2019, Month.OCTOBER, 21).asUtilDate()
val maxCountEntries = 99
// when
val result = underTest.createGetTransactionsMessage(GetTransactionsParameter(false, fromDate, toDate, maxCountEntries), Bank, Customer, Product, DialogData.DialogInitDialogData)
// then
assertThat(result.createdMessage).isNotNull()
assertThat(normalizeBinaryData(result.createdMessage!!)).isEqualTo(normalizeBinaryData(
"HNHBK:1:3+000000000362+300+0+1'" +
"HNVSK:998:3+PIN:2+998+1+1::0+1:$Date:$Time+2:16:14:@8@ :5:1+280:$BankCode:$CustomerId:V:0:0+0'" +
"HNVSD:999:1+@198@" + "HNSHK:2:4+PIN:2+${SecurityFunction.code}+$ControlReference+1+1+1::0+1+1:$Date:$Time+1:999:1+6:10:16+280:$BankCode:$CustomerId:S:0:0'" +
"HKKAZ:3:${getTransactionsJob.segmentVersion}+$CustomerId::280:$BankCode+N+${convertDate(fromDate)}+${convertDate(toDate)}+$maxCountEntries'" +
"HKTAN:4:6+4+HKKAZ'" +
"HNSHA:5:2+$ControlReference++$Pin''" +
"HNHBS:6:1+1'"
))
}
@Test
fun createGetTransactionsMessage_WithContinuationIdSet() {
// given
val getTransactionsJob = JobParameters("HKKAZ", 1, 1, null, "HKKAZ:73:5")
Bank.supportedJobs = listOf(getTransactionsJob)
val account = AccountData(CustomerId, null, BankCountryCode, BankCode, null, CustomerId, AccountType.Girokonto, "EUR", "", null, null, listOf(getTransactionsJob.jobName), listOf(getTransactionsJob))
Customer.accounts = listOf(account)
val fromDate = LocalDate.of(2019, Month.AUGUST, 6).asUtilDate()
val toDate = LocalDate.of(2019, Month.OCTOBER, 21).asUtilDate()
val maxCountEntries = 99
val continuationId = "9345-10-26-11.52.15.693455"
// when
val result = underTest.createGetTransactionsMessage(GetTransactionsParameter(false, fromDate, toDate, maxCountEntries, false, continuationId), Bank, Customer, Product, DialogData.DialogInitDialogData)
// then
assertThat(result.createdMessage).isNotNull()
assertThat(normalizeBinaryData(result.createdMessage!!)).isEqualTo(normalizeBinaryData(
"HNHBK:1:3+000000000389+300+0+1'" +
"HNVSK:998:3+PIN:2+998+1+1::0+1:$Date:$Time+2:16:14:@8@ :5:1+280:$BankCode:$CustomerId:V:0:0+0'" +
"HNVSD:999:1+@225@" + "HNSHK:2:4+PIN:2+${SecurityFunction.code}+$ControlReference+1+1+1::0+1+1:$Date:$Time+1:999:1+6:10:16+280:$BankCode:$CustomerId:S:0:0'" +
"HKKAZ:3:${getTransactionsJob.segmentVersion}+$CustomerId::280:$BankCode+N+${convertDate(fromDate)}+${convertDate(toDate)}+$maxCountEntries+$continuationId'" +
"HKTAN:4:6+4+HKKAZ'" +
"HNSHA:5:2+$ControlReference++$Pin''" +
"HNHBS:6:1+1'"
))
}
} }

View File

@ -208,6 +208,19 @@ class ResponseParserTest : FinTsTestBase() {
} }
@Test
fun parseSegmentFeedback_Aufsetzpunkt() {
// when
val result = underTest.parse("HIRMS:4:2:3+0020::Der Auftrag wurde ausgeführt.+0020::Die gebuchten Umsätze wurden übermittelt.+3040::Es liegen weitere Informationen vor.:9345-10-26-11.52.15.693455")
// then
assertCouldParseSegment(result, InstituteSegmentId.SegmentFeedback, 4, 2, 3)
assertThat(result.aufsetzpunkt).isEqualTo("9345-10-26-11.52.15.693455")
}
@Test @Test
fun parseSegmentFeedback_AllowedUserTanProcedures() { fun parseSegmentFeedback_AllowedUserTanProcedures() {