Implemented serializing transactions to JSON

This commit is contained in:
dankito 2022-02-24 02:13:37 +01:00
parent 85d6b079d6
commit ed66168c0b
17 changed files with 166 additions and 22 deletions

View File

@ -1,11 +1,13 @@
package net.dankito.banking.client.model package net.dankito.banking.client.model
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.serialization.Serializable
import net.dankito.banking.fints.model.Amount import net.dankito.banking.fints.model.Amount
import net.dankito.banking.fints.model.Money import net.dankito.banking.fints.model.Money
import net.dankito.utils.multiplatform.extensions.atUnixEpochStart import net.dankito.utils.multiplatform.extensions.atUnixEpochStart
@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, val unparsedReference: String,

View File

@ -1,25 +1,27 @@
package net.dankito.banking.client.model package net.dankito.banking.client.model
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.serialization.Serializable
import net.dankito.banking.fints.model.Currency import net.dankito.banking.fints.model.Currency
import net.dankito.banking.fints.model.Money import net.dankito.banking.fints.model.Money
@Serializable
open class BankAccount( open class BankAccount(
identifier: String, override val identifier: String,
subAccountNumber: String?, override val subAccountNumber: String?,
iban: String?, override val iban: String?,
val accountHolderName: String, open val accountHolderName: String,
val type: BankAccountType = BankAccountType.CheckingAccount, open val type: BankAccountType = BankAccountType.CheckingAccount,
val productName: String? = null, open val productName: String? = null,
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
val accountLimit: String? = null, open val accountLimit: String? = null,
// TODO: create an enum AccountCapabilities [ RetrieveBalance, RetrieveTransactions, TransferMoney / MoneyTransfer(?), InstantPayment ] // TODO: create an enum AccountCapabilities [ RetrieveBalance, RetrieveTransactions, TransferMoney / MoneyTransfer(?), InstantPayment ]
val supportsRetrievingTransactions: Boolean = false, open val supportsRetrievingTransactions: Boolean = false,
val supportsRetrievingBalance: Boolean = false, open val supportsRetrievingBalance: Boolean = false,
val supportsTransferringMoney: Boolean = false, open val supportsTransferringMoney: Boolean = false,
val supportsInstantPayment: Boolean = false open val supportsInstantPayment: Boolean = false
) : BankAccountIdentifier(identifier, subAccountNumber, iban) { ) : BankAccountIdentifier {
internal constructor() : this("", null, null, "") // for object deserializers internal constructor() : this("", null, null, "") // for object deserializers

View File

@ -1,8 +1,11 @@
package net.dankito.banking.client.model package net.dankito.banking.client.model
interface BankAccountIdentifier {
open class BankAccountIdentifier( val identifier: String
open val identifier: String,
open val subAccountNumber: String?, val subAccountNumber: String?
open val iban: String?,
) val iban: String?
}

View File

@ -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

View File

@ -1,5 +1,6 @@
package net.dankito.banking.client.model 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.messages.datenelemente.implementierte.tan.TanMedium
import net.dankito.banking.fints.model.TanMethod 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 //import net.dankito.banking.client.model.tan.TanMethod
@Serializable
open class CustomerAccount( open class CustomerAccount(
override var bankCode: String, override var bankCode: String,
override var loginName: String, override var loginName: String,

View File

@ -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<Amount> {
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())
}
}

View File

@ -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<Currency> {
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())
}
}

View File

@ -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<Money> {
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)
}
}

View File

@ -3,7 +3,6 @@ package net.dankito.banking.fints.mapper
import net.dankito.banking.client.model.* import net.dankito.banking.client.model.*
import net.dankito.banking.client.model.AccountTransaction import net.dankito.banking.client.model.AccountTransaction
import net.dankito.banking.client.model.parameter.FinTsClientParameter 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.client.model.response.ErrorCode
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen
import net.dankito.banking.fints.model.* import net.dankito.banking.fints.model.*

View File

@ -1,5 +1,7 @@
package net.dankito.banking.fints.messages.datenelemente.implementierte.tan 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 * 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. * 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 class TanMedium(
open val mediumClass: TanMediumKlasse, open val mediumClass: TanMediumKlasse,
open val status: TanMediumStatus, open val status: TanMediumStatus,

View File

@ -1,6 +1,10 @@
package net.dankito.banking.fints.model 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( open class Amount(
val string: String val string: String
) { ) {

View File

@ -1,6 +1,10 @@
package net.dankito.banking.fints.model 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( open class Currency(
val code: String val code: String
) { ) {

View File

@ -1,6 +1,9 @@
package net.dankito.banking.fints.model package net.dankito.banking.fints.model
import kotlinx.serialization.Serializable
@Serializable
open class DecoupledTanMethodParameters( open class DecoupledTanMethodParameters(
open val manualConfirmationAllowed: Boolean, open val manualConfirmationAllowed: Boolean,
open val periodicStateRequestsAllowed: Boolean, open val periodicStateRequestsAllowed: Boolean,

View File

@ -1,6 +1,10 @@
package net.dankito.banking.fints.model 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( open class Money(
val amount: Amount, val amount: Amount,
val currency: Currency val currency: Currency

View File

@ -1,9 +1,11 @@
package net.dankito.banking.fints.model 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.signatur.Sicherheitsfunktion
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat
@Serializable
open class TanMethod( open class TanMethod(
open val displayName: String, open val displayName: String,
open val securityFunction: Sicherheitsfunktion, open val securityFunction: Sicherheitsfunktion,

View File

@ -1,4 +1,8 @@
import com.soywiz.korio.file.std.localCurrentDirVfs
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.LocalDate 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.AccountTransaction
import net.dankito.banking.client.model.CustomerAccount import net.dankito.banking.client.model.CustomerAccount
import net.dankito.banking.client.model.parameter.GetAccountDataParameter import net.dankito.banking.client.model.parameter.GetAccountDataParameter
@ -20,7 +24,7 @@ class NativeApp {
getAccountData(GetAccountDataParameter(bankCode, loginName, password)) getAccountData(GetAccountDataParameter(bankCode, loginName, password))
} }
fun getAccountData(param: GetAccountDataParameter) { fun getAccountData(param: GetAccountDataParameter, outputFilePath: String? = null) {
val response = client.getAccountData(param) val response = client.getAccountData(param)
if (response.error != null) { if (response.error != null) {
@ -28,11 +32,15 @@ class NativeApp {
} }
response.customerAccount?.let { account -> response.customerAccount?.let { account ->
if (outputFilePath != null) {
writeResponseToFile(outputFilePath, account)
} else {
println("Retrieved response from ${account.bankName} for ${account.customerName}") println("Retrieved response from ${account.bankName} for ${account.customerName}")
displayRetrievedAccountData(account) displayRetrievedAccountData(account)
} }
} }
}
fun transferMoney(param: TransferMoneyParameter) { fun transferMoney(param: TransferMoneyParameter) {
@ -100,4 +108,18 @@ class NativeApp {
return date.dayOfMonth.toStringWithTwoDigits() + "." + date.monthNumber.toStringWithTwoDigits() + "." + date.year 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")
}
}
} }

View File

@ -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 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<TanMethodType>().multiple() 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<TanMethodType>().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) 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, app.getAccountData(GetAccountDataParameter(bankCode, loginName, password, null, retrieveBalance, effectiveRetrieveTransactions,
retrieveTransactionsFromDate, retrieveTransactionsToDate, preferredTanMethods, abortIfTanIsRequired = abortIfRequiresTan)) retrieveTransactionsFromDate, retrieveTransactionsToDate, preferredTanMethods, abortIfTanIsRequired = abortIfRequiresTan), outputFile)
} }
} }