Started requesting balance and account transactions, but not fully working yet

This commit is contained in:
dankl 2019-10-07 00:15:00 +02:00 committed by dankito
parent 8ac4f24476
commit 5329cc8418
17 changed files with 385 additions and 1 deletions

View File

@ -101,6 +101,39 @@ open class FinTsClient(
} }
open fun getTransactions(bank: BankData, customer: CustomerData, product: ProductData): Response {
val dialogData = DialogData()
val initDialogResponse = initDialog(bank, customer, product, dialogData)
if (initDialogResponse.successful == false) {
return initDialogResponse
}
dialogData.increaseMessageNumber()
val balanceRequest = messageBuilder.createGetBalanceMessage(bank, customer, product, dialogData)
val balanceResponse = getAndHandleResponseForMessage(balanceRequest, bank)
if (balanceResponse.successful == false) {
return balanceResponse
}
dialogData.increaseMessageNumber()
val requestBody = messageBuilder.createGetTransactionsMessage(bank, customer, product, dialogData)
val response = getAndHandleResponseForMessage(requestBody, bank)
closeDialog(bank, customer, dialogData)
return response
}
protected open fun getAndHandleResponseForMessage(requestBody: String, bank: BankData): Response { protected open fun getAndHandleResponseForMessage(requestBody: String, bank: BankData): Response {
val webResponse = getResponseForMessage(requestBody, bank) val webResponse = getResponseForMessage(requestBody, bank)
@ -149,6 +182,7 @@ open class FinTsClient(
protected open fun updateCustomerData(customer: CustomerData, response: Response) { protected open fun updateCustomerData(customer: CustomerData, response: Response) {
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.
if (customer.selectedLanguage == Dialogsprache.Default && bankParameters.supportedLanguages.isNotEmpty()) { if (customer.selectedLanguage == Dialogsprache.Default && bankParameters.supportedLanguages.isNotEmpty()) {
customer.selectedLanguage = bankParameters.supportedLanguages.first() customer.selectedLanguage = bankParameters.supportedLanguages.first()
} }

View File

@ -8,10 +8,12 @@ import net.dankito.fints.messages.segmente.SegmentNumberGenerator
import net.dankito.fints.messages.segmente.Synchronisierung import net.dankito.fints.messages.segmente.Synchronisierung
import net.dankito.fints.messages.segmente.id.CustomerSegmentId import net.dankito.fints.messages.segmente.id.CustomerSegmentId
import net.dankito.fints.messages.segmente.implementierte.* import net.dankito.fints.messages.segmente.implementierte.*
import net.dankito.fints.messages.segmente.implementierte.umsaetze.KontoumsaetzeZeitraumMt940Version5
import net.dankito.fints.model.BankData import net.dankito.fints.model.BankData
import net.dankito.fints.model.CustomerData import net.dankito.fints.model.CustomerData
import net.dankito.fints.model.DialogData import net.dankito.fints.model.DialogData
import net.dankito.fints.model.ProductData import net.dankito.fints.model.ProductData
import net.dankito.fints.messages.segmente.implementierte.umsaetze.Saldenabfrage
import net.dankito.fints.util.FinTsUtils import net.dankito.fints.util.FinTsUtils
import java.util.concurrent.ThreadLocalRandom import java.util.concurrent.ThreadLocalRandom
@ -85,6 +87,28 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
} }
open fun createGetTransactionsMessage(bank: BankData, customer: CustomerData, product: ProductData, dialogData: DialogData): String {
return createSignedMessage(bank, customer, dialogData, listOf(
KontoumsaetzeZeitraumMt940Version5(generator.resetSegmentNumber(2), bank, customer),
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.AccountTransactionsMt940)
))
}
open fun createGetBalanceMessage(bank: BankData, customer: CustomerData, product: ProductData, dialogData: DialogData): String {
return createSignedMessage(bank, customer, dialogData, listOf(
Saldenabfrage(
generator.resetSegmentNumber(2),
bank,
customer,
false
),
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.Balance)
))
}
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

@ -0,0 +1,13 @@
package net.dankito.fints.messages.datenelemente.implementierte
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDatenelement
/**
* Information darüber, wie die Beantwortung des Kundenauftrags an einem bestimmten Punkt kontrolliert
* beendet und aufgesetzt werden kann, falls die Rückmeldung des Kreditinstituts nicht in einem
* einzigen Auftragssegment erfolgen kann (s. [Formals]).
*/
open class Aufsetzpunkt(continuationId: String, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(continuationId, existenzstatus, 35)

View File

@ -0,0 +1,12 @@
package net.dankito.fints.messages.datenelemente.implementierte.account
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.JaNein
/**
* Mit dieser Option kann gewählt werden, ob die angeforderten Informationen (z.B. Salden, Umsätze)
* nur zu dem angegebenen oder zu allen Anlagekonten des Kunden, für die er eine
* Zugriffsberechtigung besitzt, zurückgemeldet werden sollen.
*/
open class AlleKonten(allAccounts: Boolean, existenzstatus: Existenzstatus) : JaNein(allAccounts, existenzstatus)

View File

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

View File

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

View File

@ -0,0 +1,15 @@
package net.dankito.fints.messages.datenelemente.implementierte.account
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**
* Identifikationsnummer des Kontos (Kontonummer, Depotnummer, Kreditkartennummer etc.).
* Das DE dient auch zur Aufnahme von internationalen (alphanumerischen) Kontonummern und
* zukünftig 20-stelligen Kreditkartenkontonummern.
*
* Es ist zu beachten, dass Kontonummern auch führende Nullen beinhalten können, die
* bankfachlich relevant sind und nicht abgeschnitten werden dürfen.
*/
open class KontoDepotnummer(number: String, existenzstatus: Existenzstatus) : Identifikation(number, existenzstatus)

View File

@ -0,0 +1,11 @@
package net.dankito.fints.messages.datenelemente.implementierte.account
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenelement
/**
* Maximale Anzahl rückzumeldender Einträge bei Abholaufträgen, Kreditinstitutsangeboten
* oder informationen (vgl. [Formals], Kap. B.6.3).
*/
open class MaximaleAnzahlEintraege(maxAmount: Int, existenzstatus: Existenzstatus) : NumerischesDatenelement(maxAmount, 4, existenzstatus)

View File

@ -0,0 +1,12 @@
package net.dankito.fints.messages.datenelemente.implementierte.account
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Identifikation
/**
* Unterkontonummer, falls unter einer Kontonummer verschiedene Unterkonten (z.B. Währungskonten)
* geführt werden. Anstatt einer Kontonummer kann auch ein anderes identifizierendes Merkmal
* angegeben werden (z.B. der entsprechende ISO-Währungscode bei Währungskonten).
*/
open class Unterkontomerkmal(attribute: String, existenzstatus: Existenzstatus) : Identifikation(attribute, existenzstatus)

View File

@ -0,0 +1,29 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.account
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.account.KontoDepotnummer
import net.dankito.fints.messages.datenelemente.implementierte.account.Unterkontomerkmal
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
import net.dankito.fints.messages.datenelementgruppen.implementierte.Kreditinstitutskennung
/**
* Anhand dieses Formats können sowohl deutsche als auch internationale Bankverbindungen beschrieben
* werden. Die Belegung für wichtige europäische Länder ist dem Kapitel E.5 zu entnehmen.
*
* Falls bei einem Institut unter einer Kontonummer verschiedene Konten mit unterschiedlichen Merkmalen
* geführt werden (z.B. verschiedene Währungen oder Festgeldanlagen), wird von diesem Institut in den
* UPD zu jeder dieser Kontonummern zur Unterscheidung zusätzlich ein Unterkontomerkmal angegeben.
* Dieser ist dann bei jeder Auftraggeberkontoverbindung anzugeben.
*/
open class Kontoverbindung(
bankCountryCode: Int,
bankCode: String,
accountNumber: String,
subAccountAttribute: String? = null
) : Datenelementgruppe(listOf(
KontoDepotnummer(accountNumber, Existenzstatus.Mandatory),
Unterkontomerkmal(subAccountAttribute ?: "", Existenzstatus.Optional),
Kreditinstitutskennung(bankCountryCode, bankCode)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,37 @@
package net.dankito.fints.messages.datenelementgruppen.implementierte.account
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.account.BIC
import net.dankito.fints.messages.datenelemente.implementierte.account.IBAN
import net.dankito.fints.messages.datenelemente.implementierte.account.KontoDepotnummer
import net.dankito.fints.messages.datenelemente.implementierte.account.Unterkontomerkmal
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
import net.dankito.fints.messages.datenelementgruppen.implementierte.Kreditinstitutskennung
import net.dankito.fints.model.BankData
import net.dankito.fints.model.CustomerData
/**
* Die Kontoverbindung international dient zur Verwendung von IBAN und BIC sowie auch der
* nationalen Elemente Kreditinstitutskennung und Konto-/Depotnummer mit optionalem
* Unterkontomerkmal, strukturell angelehnt an das Aggregate Account in ISO20022.
*/
open class KontoverbindungInternational(
iban: String?,
bic: String?,
bankCountryCode: Int? = null,
bankCode: String? = null,
accountNumber: String? = null,
subAccountAttribute: String? = null
) : Datenelementgruppe(listOf(
IBAN(iban ?: "", Existenzstatus.Optional),
BIC(bic ?: "", Existenzstatus.Optional),
KontoDepotnummer(accountNumber ?: "", Existenzstatus.Optional),
Unterkontomerkmal(subAccountAttribute ?: "", Existenzstatus.Optional),
Kreditinstitutskennung(bankCountryCode ?: 0, bankCode ?: "", Existenzstatus.Optional)
), Existenzstatus.Mandatory) {
constructor(bank: BankData, customer: CustomerData, subAccountAttribute: String?)
: this(customer.iban, bank.bic, bank.countryCode, bank.bankCode, customer.customerId, subAccountAttribute)
}

View File

@ -11,6 +11,10 @@ enum class CustomerSegmentId(override val id: String) : ISegmentId {
Synchronization("HKSYN"), Synchronization("HKSYN"),
Tan("HKTAN") Tan("HKTAN"),
Balance("HKSAL"),
AccountTransactionsMt940("HKKAZ")
} }

View File

@ -0,0 +1,40 @@
package net.dankito.fints.messages.segmente.implementierte.umsaetze
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Datum
import net.dankito.fints.messages.datenelemente.implementierte.account.AlleKonten
import net.dankito.fints.messages.datenelementgruppen.Datenelementgruppe
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.segmente.Segment
import net.dankito.fints.messages.segmente.id.CustomerSegmentId
/**
* Die Lösung bietet dem Kunden die Möglichkeit, auf seinem System verlorengegangene Buchungen erneut zu erhalten.
*
* Die maximale Anzahl der rückzumeldenden Buchungspositionen kann begrenzt werden. Eine Buchungsposition besteht
* aus einem :61:/:86:-Block eines MT 940-Formats. Es muss davon unabhängig immer ein gültiges MT 940-Format
* zurückgemeldet werden, d.h. die Felder :20: bis :60: und :62: bis :86: sind obligatorischer Bestandteil der Rückmeldung.
*
* Der maximale Zeitraum, für den rückwirkend Buchungen beim Kreditinstitut gespeichert sind, wird in den
* Bankparameterdaten übermittelt.
*/
abstract class KontoumsaetzeZeitraumMt940Base(
segmentVersion: Int,
segmentNumber: Int,
account: Datenelementgruppe,
allAccounts: Boolean = false,
fromDate: Int? = null,
toDate: Int? = null,
maxAmount: Int? = null,
continuationId: String? = null
)
: Segment(listOf(
Segmentkopf(CustomerSegmentId.AccountTransactionsMt940, segmentVersion, segmentNumber),
account,
AlleKonten(allAccounts, Existenzstatus.Mandatory),
Datum(fromDate ?: 0, Existenzstatus.Optional)
// Datum(toDate ?: 0, Existenzstatus.Optional),
// MaximaleAnzahlEintraege(maxAmount ?: 0, Existenzstatus.Optional), // > 0. O: „Eingabe Anzahl Einträge erlaubt“ (BPD) = „J“. N: sonst
// Aufsetzpunkt(continuationId ?: "", Existenzstatus.Optional) // M: vom Institut wurde ein Aufsetzpunkt rückgemeldet. N: sonst
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,38 @@
package net.dankito.fints.messages.segmente.implementierte.umsaetze
import net.dankito.fints.messages.datenelementgruppen.implementierte.account.Kontoverbindung
import net.dankito.fints.model.BankData
import net.dankito.fints.model.CustomerData
/**
* Die Lösung bietet dem Kunden die Möglichkeit, auf seinem System verlorengegangene Buchungen erneut zu erhalten.
*
* Die maximale Anzahl der rückzumeldenden Buchungspositionen kann begrenzt werden. Eine Buchungsposition besteht
* aus einem :61:/:86:-Block eines MT 940-Formats. Es muss davon unabhängig immer ein gültiges MT 940-Format
* zurückgemeldet werden, d.h. die Felder :20: bis :60: und :62: bis :86: sind obligatorischer Bestandteil der Rückmeldung.
*
* Der maximale Zeitraum, für den rückwirkend Buchungen beim Kreditinstitut gespeichert sind, wird in den
* Bankparameterdaten übermittelt.
*/
open class KontoumsaetzeZeitraumMt940Version5(
segmentNumber: Int,
bank: BankData,
customer: CustomerData,
subAccountAttribute: String? = null,
allAccounts: Boolean = false,
fromDate: Int? = null,
toDate: Int? = null,
maxAmount: Int? = null,
continuationId: String? = null
)
: KontoumsaetzeZeitraumMt940Base(
5,
segmentNumber,
Kontoverbindung(bank.countryCode, bank.bankCode, customer.customerId, subAccountAttribute),
allAccounts,
fromDate,
toDate,
maxAmount,
continuationId
)

View File

@ -0,0 +1,38 @@
package net.dankito.fints.messages.segmente.implementierte.umsaetze
import net.dankito.fints.messages.datenelementgruppen.implementierte.account.KontoverbindungInternational
import net.dankito.fints.model.BankData
import net.dankito.fints.model.CustomerData
/**
* Die Lösung bietet dem Kunden die Möglichkeit, auf seinem System verlorengegangene Buchungen erneut zu erhalten.
*
* Die maximale Anzahl der rückzumeldenden Buchungspositionen kann begrenzt werden. Eine Buchungsposition besteht
* aus einem :61:/:86:-Block eines MT 940-Formats. Es muss davon unabhängig immer ein gültiges MT 940-Format
* zurückgemeldet werden, d.h. die Felder :20: bis :60: und :62: bis :86: sind obligatorischer Bestandteil der Rückmeldung.
*
* Der maximale Zeitraum, für den rückwirkend Buchungen beim Kreditinstitut gespeichert sind, wird in den
* Bankparameterdaten übermittelt.
*/
open class KontoumsaetzeZeitraumMt940Version7(
segmentNumber: Int,
bank: BankData,
customer: CustomerData,
subAccountAttribute: String? = null, // TODO: move to CustomerData.accounts
allAccounts: Boolean = false,
fromDate: Int? = null,
toDate: Int? = null,
maxAmount: Int? = null,
continuationId: String? = null
)
: KontoumsaetzeZeitraumMt940Base(
7,
segmentNumber,
KontoverbindungInternational(bank, customer, subAccountAttribute),
allAccounts,
fromDate,
toDate,
maxAmount,
continuationId
)

View File

@ -0,0 +1,26 @@
package net.dankito.fints.messages.segmente.implementierte.umsaetze
import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.implementierte.account.AlleKonten
import net.dankito.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.fints.messages.datenelementgruppen.implementierte.account.Kontoverbindung
import net.dankito.fints.messages.segmente.Segment
import net.dankito.fints.messages.segmente.id.CustomerSegmentId
import net.dankito.fints.model.BankData
import net.dankito.fints.model.CustomerData
open class Saldenabfrage(
segmentNumber: Int,
bank: BankData,
customer: CustomerData, // TODO: pass AccountData instead
allAccounts: Boolean,
maxAmountEntries: Int? = null
)
: Segment(listOf(
Segmentkopf(CustomerSegmentId.Balance, 5, segmentNumber),
Kontoverbindung(bank.countryCode, bank.bankCode, customer.customerId),
AlleKonten(allAccounts, Existenzstatus.Mandatory)
// MaximaleAnzahlEintraege(maxAmountEntries ?: 0, Existenzstatus.Optional),
// Aufsetzpunkt("", Existenzstatus.Optional)
), Existenzstatus.Mandatory)

View File

@ -0,0 +1,37 @@
package net.dankito.fints.messages.segmente.implementierte.umsaetze
import net.dankito.fints.FinTsTestBase
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class SaldenabfrageTest : FinTsTestBase() {
@Test
fun format_NotAllAccounts() {
// given
val underTest =
Saldenabfrage(3, Bank, Customer, false)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKSAL:3:5+$CustomerId::280:$BankCode+N")
}
@Test
fun format_AllAccounts() {
// given
val underTest =
Saldenabfrage(3, Bank, Customer, true)
// when
val result = underTest.format()
// then
assertThat(result).isEqualTo("HKSAL:3:5+$CustomerId::280:$BankCode+J")
}
}