From ed66168c0be8f1d7cc200be097dca70eb04d9bcf Mon Sep 17 00:00:00 2001 From: dankito Date: Thu, 24 Feb 2022 02:13:37 +0100 Subject: [PATCH] Implemented serializing transactions to JSON --- .../client/model/AccountTransaction.kt | 2 ++ .../banking/client/model/BankAccount.kt | 28 ++++++++-------- .../client/model/BankAccountIdentifier.kt | 13 +++++--- .../client/model/BankAccountIdentifierImpl.kt | 11 +++++++ .../banking/client/model/CustomerAccount.kt | 2 ++ .../model/serializer/AmountSerializer.kt | 24 ++++++++++++++ .../model/serializer/CurrencySerializer.kt | 24 ++++++++++++++ .../model/serializer/MoneySerializer.kt | 33 +++++++++++++++++++ .../banking/fints/mapper/FinTsModelMapper.kt | 1 - .../implementierte/tan/TanMedium.kt | 3 ++ .../net/dankito/banking/fints/model/Amount.kt | 4 +++ .../dankito/banking/fints/model/Currency.kt | 4 +++ .../model/DecoupledTanMethodParameters.kt | 3 ++ .../net/dankito/banking/fints/model/Money.kt | 4 +++ .../dankito/banking/fints/model/TanMethod.kt | 2 ++ fints4k/src/nativeMain/kotlin/NativeApp.kt | 26 +++++++++++++-- .../commands/fints4kCommandLineInterface.kt | 4 ++- 17 files changed, 166 insertions(+), 22 deletions(-) create mode 100644 fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccountIdentifierImpl.kt create mode 100644 fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/AmountSerializer.kt create mode 100644 fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/CurrencySerializer.kt create mode 100644 fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/MoneySerializer.kt diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/AccountTransaction.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/AccountTransaction.kt index 29b2d044..37364f0d 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/AccountTransaction.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/AccountTransaction.kt @@ -1,11 +1,13 @@ package net.dankito.banking.client.model import kotlinx.datetime.LocalDate +import kotlinx.serialization.Serializable import net.dankito.banking.fints.model.Amount import net.dankito.banking.fints.model.Money import net.dankito.utils.multiplatform.extensions.atUnixEpochStart +@Serializable open class AccountTransaction( val amount: Money, // TODO: if we decide to stick with Money, create own type, don't use that one from fints.model (or move over from) val unparsedReference: String, diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccount.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccount.kt index 4fa0ff65..f13aa017 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccount.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccount.kt @@ -1,25 +1,27 @@ package net.dankito.banking.client.model import kotlinx.datetime.LocalDate +import kotlinx.serialization.Serializable import net.dankito.banking.fints.model.Currency import net.dankito.banking.fints.model.Money +@Serializable open class BankAccount( - identifier: String, - subAccountNumber: String?, - iban: String?, - val accountHolderName: String, - val type: BankAccountType = BankAccountType.CheckingAccount, - val productName: String? = null, - val currency: String = Currency.DefaultCurrencyCode, // TODO: may parse to a value object - val accountLimit: String? = null, + override val identifier: String, + override val subAccountNumber: String?, + override val iban: String?, + open val accountHolderName: String, + open val type: BankAccountType = BankAccountType.CheckingAccount, + open val productName: String? = null, + open val currency: String = Currency.DefaultCurrencyCode, // TODO: may parse to a value object + open val accountLimit: String? = null, // TODO: create an enum AccountCapabilities [ RetrieveBalance, RetrieveTransactions, TransferMoney / MoneyTransfer(?), InstantPayment ] - val supportsRetrievingTransactions: Boolean = false, - val supportsRetrievingBalance: Boolean = false, - val supportsTransferringMoney: Boolean = false, - val supportsInstantPayment: Boolean = false -) : BankAccountIdentifier(identifier, subAccountNumber, iban) { + open val supportsRetrievingTransactions: Boolean = false, + open val supportsRetrievingBalance: Boolean = false, + open val supportsTransferringMoney: Boolean = false, + open val supportsInstantPayment: Boolean = false +) : BankAccountIdentifier { internal constructor() : this("", null, null, "") // for object deserializers diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccountIdentifier.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccountIdentifier.kt index 7dc214c1..dad8df66 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccountIdentifier.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccountIdentifier.kt @@ -1,8 +1,11 @@ package net.dankito.banking.client.model +interface BankAccountIdentifier { -open class BankAccountIdentifier( - open val identifier: String, - open val subAccountNumber: String?, - open val iban: String?, -) \ No newline at end of file + val identifier: String + + val subAccountNumber: String? + + val iban: String? + +} \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccountIdentifierImpl.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccountIdentifierImpl.kt new file mode 100644 index 00000000..db4fe6c3 --- /dev/null +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/BankAccountIdentifierImpl.kt @@ -0,0 +1,11 @@ +package net.dankito.banking.client.model + +import kotlinx.serialization.Serializable + + +@Serializable +open class BankAccountIdentifierImpl( + override val identifier: String, + override val subAccountNumber: String?, + override val iban: String?, +) : BankAccountIdentifier \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/CustomerAccount.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/CustomerAccount.kt index cbd712b7..65b27e45 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/CustomerAccount.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/CustomerAccount.kt @@ -1,5 +1,6 @@ package net.dankito.banking.client.model +import kotlinx.serialization.Serializable import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMedium import net.dankito.banking.fints.model.TanMethod @@ -7,6 +8,7 @@ import net.dankito.banking.fints.model.TanMethod //import net.dankito.banking.client.model.tan.TanMethod +@Serializable open class CustomerAccount( override var bankCode: String, override var loginName: String, diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/AmountSerializer.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/AmountSerializer.kt new file mode 100644 index 00000000..861f3973 --- /dev/null +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/AmountSerializer.kt @@ -0,0 +1,24 @@ +package net.dankito.banking.client.model.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.dankito.banking.fints.model.Amount + + +class AmountSerializer : KSerializer { + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Amount", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Amount) { + encoder.encodeString(value.string) + } + + override fun deserialize(decoder: Decoder): Amount { + return Amount(decoder.decodeString()) + } + +} \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/CurrencySerializer.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/CurrencySerializer.kt new file mode 100644 index 00000000..e2504dfa --- /dev/null +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/CurrencySerializer.kt @@ -0,0 +1,24 @@ +package net.dankito.banking.client.model.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.dankito.banking.fints.model.Currency + + +class CurrencySerializer : KSerializer { + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Currency", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Currency) { + encoder.encodeString(value.code) + } + + override fun deserialize(decoder: Decoder): Currency { + return Currency(decoder.decodeString()) + } + +} \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/MoneySerializer.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/MoneySerializer.kt new file mode 100644 index 00000000..badabe60 --- /dev/null +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/serializer/MoneySerializer.kt @@ -0,0 +1,33 @@ +package net.dankito.banking.client.model.serializer + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.dankito.banking.fints.model.Amount +import net.dankito.banking.fints.model.Currency +import net.dankito.banking.fints.model.Money + + +class MoneySerializer : KSerializer { + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Money", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Money) { + encoder.encodeString(value.amount.string + " " + value.currency.code) + } + + override fun deserialize(decoder: Decoder): Money { + val value = decoder.decodeString() + val parts = value.split(" ") + + if (parts.size > 1) { + return Money(Amount(parts[0]), parts[1]) + } + + return Money(Amount(value), Currency.DefaultCurrencyCode) + } + +} \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/mapper/FinTsModelMapper.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/mapper/FinTsModelMapper.kt index 86c1ddfa..c6f17ed1 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/mapper/FinTsModelMapper.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/mapper/FinTsModelMapper.kt @@ -3,7 +3,6 @@ package net.dankito.banking.fints.mapper import net.dankito.banking.client.model.* import net.dankito.banking.client.model.AccountTransaction import net.dankito.banking.client.model.parameter.FinTsClientParameter -import net.dankito.banking.client.model.parameter.GetAccountDataParameter import net.dankito.banking.client.model.response.ErrorCode import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen import net.dankito.banking.fints.model.* diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/datenelemente/implementierte/tan/TanMedium.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/datenelemente/implementierte/tan/TanMedium.kt index 3f97a847..59d41f60 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/datenelemente/implementierte/tan/TanMedium.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/messages/datenelemente/implementierte/tan/TanMedium.kt @@ -1,5 +1,7 @@ package net.dankito.banking.fints.messages.datenelemente.implementierte.tan +import kotlinx.serialization.Serializable + /** * Informationen zu Art und Parametrisierung von TAN-Medien. Als TAN-Medien werden sowohl @@ -8,6 +10,7 @@ package net.dankito.banking.fints.messages.datenelemente.implementierte.tan * * Wird das Datenelement „TAN-Medium-Klasse“ mit „B“ (bilateral vereinbart) belegt, so muss im Element „Sicherheitsfunktion, kodiert“ die entsprechende Sicherheitsfunktion in der DEG „Verfahrensparameter Zwei-Schritt-Verfahren“ referenziert werden. */ +@Serializable open class TanMedium( open val mediumClass: TanMediumKlasse, open val status: TanMediumStatus, diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Amount.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Amount.kt index 41d63c09..527badcf 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Amount.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Amount.kt @@ -1,6 +1,10 @@ package net.dankito.banking.fints.model +import kotlinx.serialization.Serializable +import net.dankito.banking.client.model.serializer.AmountSerializer + +@Serializable(with = AmountSerializer::class) open class Amount( val string: String ) { diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Currency.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Currency.kt index 0f8aaf4d..aa2f3d6a 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Currency.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Currency.kt @@ -1,6 +1,10 @@ package net.dankito.banking.fints.model +import kotlinx.serialization.Serializable +import net.dankito.banking.client.model.serializer.CurrencySerializer + +@Serializable(with = CurrencySerializer::class) open class Currency( val code: String ) { diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/DecoupledTanMethodParameters.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/DecoupledTanMethodParameters.kt index ba3d9267..f1b302cd 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/DecoupledTanMethodParameters.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/DecoupledTanMethodParameters.kt @@ -1,6 +1,9 @@ package net.dankito.banking.fints.model +import kotlinx.serialization.Serializable + +@Serializable open class DecoupledTanMethodParameters( open val manualConfirmationAllowed: Boolean, open val periodicStateRequestsAllowed: Boolean, diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Money.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Money.kt index da1c6bbd..4cdcdfd9 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Money.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/Money.kt @@ -1,6 +1,10 @@ package net.dankito.banking.fints.model +import kotlinx.serialization.Serializable +import net.dankito.banking.client.model.serializer.MoneySerializer + +@Serializable(with = MoneySerializer::class) open class Money( val amount: Amount, val currency: Currency diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/TanMethod.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/TanMethod.kt index d4f15af6..9108cb3d 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/TanMethod.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/TanMethod.kt @@ -1,9 +1,11 @@ package net.dankito.banking.fints.model +import kotlinx.serialization.Serializable import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat +@Serializable open class TanMethod( open val displayName: String, open val securityFunction: Sicherheitsfunktion, diff --git a/fints4k/src/nativeMain/kotlin/NativeApp.kt b/fints4k/src/nativeMain/kotlin/NativeApp.kt index afeb9ef3..78fb122d 100644 --- a/fints4k/src/nativeMain/kotlin/NativeApp.kt +++ b/fints4k/src/nativeMain/kotlin/NativeApp.kt @@ -1,4 +1,8 @@ +import com.soywiz.korio.file.std.localCurrentDirVfs +import kotlinx.coroutines.runBlocking import kotlinx.datetime.LocalDate +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import net.dankito.banking.client.model.AccountTransaction import net.dankito.banking.client.model.CustomerAccount import net.dankito.banking.client.model.parameter.GetAccountDataParameter @@ -20,7 +24,7 @@ class NativeApp { getAccountData(GetAccountDataParameter(bankCode, loginName, password)) } - fun getAccountData(param: GetAccountDataParameter) { + fun getAccountData(param: GetAccountDataParameter, outputFilePath: String? = null) { val response = client.getAccountData(param) if (response.error != null) { @@ -28,9 +32,13 @@ class NativeApp { } response.customerAccount?.let { account -> - println("Retrieved response from ${account.bankName} for ${account.customerName}") + if (outputFilePath != null) { + writeResponseToFile(outputFilePath, account) + } else { + println("Retrieved response from ${account.bankName} for ${account.customerName}") displayRetrievedAccountData(account) + } } } @@ -100,4 +108,18 @@ class NativeApp { return date.dayOfMonth.toStringWithTwoDigits() + "." + date.monthNumber.toStringWithTwoDigits() + "." + date.year } + + private fun writeResponseToFile(outputFilePath: String, customer: CustomerAccount) { + try { + val outputFile = localCurrentDirVfs.get(outputFilePath) + println("Writing file to ${outputFile.absolutePath}") + + val json = Json.encodeToString(customer) + + runBlocking { outputFile.writeString(json) } + } catch (e: Exception) { + println("Could not write file to $outputFilePath: $e") + } + } + } \ No newline at end of file diff --git a/fints4k/src/nativeMain/kotlin/commands/fints4kCommandLineInterface.kt b/fints4k/src/nativeMain/kotlin/commands/fints4kCommandLineInterface.kt index ba02780d..e261e404 100644 --- a/fints4k/src/nativeMain/kotlin/commands/fints4kCommandLineInterface.kt +++ b/fints4k/src/nativeMain/kotlin/commands/fints4kCommandLineInterface.kt @@ -38,6 +38,8 @@ class fints4kCommandLineInterface : CliktCommand(name = "fints", printHelpOnEmpt val retrieveTransactionsForLastNDays by option("-l", "--last-n-days", help = "Retrieve transactions for last n days. If set 'retrieveTransactions' gets set to '${RetrieveTransactions.AccordingToRetrieveFromAndTo}' and 'retrieveTransactionsFrom' will be ignored.").int() + val outputFile by option("-o", help = "Write retrieved account transactions to file instead of stdout. Supported formats: JSON") + val preferredTanMethods by option("-m", "--tan-method", help = "Your preferred TAN methods to use if action affords a TAN. Can be repeated like '-m AppTan -m SmsTan'").enum().multiple() val abortIfRequiresTan by option("-a", "--abort-if-requires-tan", help = "If actions should be aborted if it affords a TAN. Defaults to false").flag(default = false) @@ -71,7 +73,7 @@ class fints4kCommandLineInterface : CliktCommand(name = "fints", printHelpOnEmpt app.getAccountData(GetAccountDataParameter(bankCode, loginName, password, null, retrieveBalance, effectiveRetrieveTransactions, - retrieveTransactionsFromDate, retrieveTransactionsToDate, preferredTanMethods, abortIfTanIsRequired = abortIfRequiresTan)) + retrieveTransactionsFromDate, retrieveTransactionsToDate, preferredTanMethods, abortIfTanIsRequired = abortIfRequiresTan), outputFile) } } \ No newline at end of file