Implemented restoring fints4java BankingClient's data

This commit is contained in:
dankl 2020-01-25 20:29:44 +01:00 committed by dankito
parent 19fcbf2823
commit 08f603a9a0
13 changed files with 152 additions and 21 deletions

View File

@ -16,7 +16,9 @@ import java.io.File
class MainWindow : View(messages["application.title"]) { class MainWindow : View(messages["application.title"]) {
private val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), BankingPersistenceJson(File("data/accounts.json")), Base64ServiceJava8(), RouterJavaFx()) private val dataFolder = File("data")
private val presenter = MainWindowPresenter(fints4javaBankingClientCreator(), dataFolder, BankingPersistenceJson(File(dataFolder, "accounts.json")), Base64ServiceJava8(), RouterJavaFx())

View File

@ -20,4 +20,6 @@ interface IBankingClient {
fun transferMoneyAsync(data: TransferMoneyData, bankAccount: BankAccount, callback: (BankingClientResponse) -> Unit) fun transferMoneyAsync(data: TransferMoneyData, bankAccount: BankAccount, callback: (BankingClientResponse) -> Unit)
fun restoreData()
} }

View File

@ -25,6 +25,7 @@ import net.dankito.utils.extensions.ofMaxLength
import net.dankito.utils.web.client.IWebClient import net.dankito.utils.web.client.IWebClient
import net.dankito.utils.web.client.OkHttpWebClient import net.dankito.utils.web.client.OkHttpWebClient
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File
import java.math.BigDecimal import java.math.BigDecimal
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -32,6 +33,7 @@ import kotlin.collections.ArrayList
open class MainWindowPresenter( open class MainWindowPresenter(
protected val bankingClientCreator: IBankingClientCreator, protected val bankingClientCreator: IBankingClientCreator,
protected val dataFolder: File,
protected val persister: IBankingPersistence, protected val persister: IBankingPersistence,
protected val base64Service: IBase64Service, protected val base64Service: IBase64Service,
protected val router: IRouter, protected val router: IRouter,
@ -102,7 +104,15 @@ open class MainWindowPresenter(
val bank = account.bank val bank = account.bank
val bankInfo = BankInfo(bank.name, bank.bankCode, bank.bic, "", "", "", bank.finTsServerAddress, "FinTS V3.0", null) val bankInfo = BankInfo(bank.name, bank.bankCode, bank.bic, "", "", "", bank.finTsServerAddress, "FinTS V3.0", null)
val newClient = bankingClientCreator.createClient(bankInfo, account.customerId, account.password, webClient, base64Service, threadPool, callback) val newClient = bankingClientCreator.createClient(bankInfo, account.customerId, account.password,
dataFolder, webClient, base64Service, threadPool, callback)
try {
newClient.restoreData()
} catch (e: Exception) {
log.error("Could not deserialize account data of $account", e)
// TODO: show error message to user
}
addClientForAccount(account, newClient) addClientForAccount(account, newClient)
} }
@ -123,7 +133,7 @@ open class MainWindowPresenter(
// TODO: move BankInfo out of fints4javaLib // TODO: move BankInfo out of fints4javaLib
open fun addAccountAsync(bankInfo: BankInfo, customerId: String, pin: String, callback: (AddAccountResponse) -> Unit) { open fun addAccountAsync(bankInfo: BankInfo, customerId: String, pin: String, callback: (AddAccountResponse) -> Unit) {
val newClient = bankingClientCreator.createClient(bankInfo, customerId, pin, webClient, base64Service, threadPool, this.callback) val newClient = bankingClientCreator.createClient(bankInfo, customerId, pin, dataFolder, webClient, base64Service, threadPool, this.callback)
newClient.addAccountAsync { response -> newClient.addAccountAsync { response ->
val account = response.account val account = response.account
@ -299,16 +309,7 @@ open class MainWindowPresenter(
protected open fun getClientForAccount(account: Account): IBankingClient? { protected open fun getClientForAccount(account: Account): IBankingClient? {
clientsForAccounts.get(account)?.let { client -> return clientsForAccounts.get(account)
// TODO: is this code still needed after updating data model is implemented?
// account.selectedTanProcedure?.let { selectedTanProcedure ->
// client.customer.selectedTanProcedure = fints4javaModelMapper.mapTanProcedureBack(selectedTanProcedure)
// }
return client
}
return null
} }

View File

@ -45,7 +45,10 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
presenter = MainWindowPresenter(fints4javaBankingClientCreator(), BankingPersistenceJson(File(this.filesDir, "data/accounts.json")), Base64ServiceAndroid(), RouterAndroid(this)) val dataFolder = File(this.filesDir, "data")
presenter = MainWindowPresenter(fints4javaBankingClientCreator(), dataFolder,
BankingPersistenceJson(File(dataFolder, "accounts.json")), Base64ServiceAndroid(), RouterAndroid(this))
initUi() initUi()
} }

View File

@ -17,14 +17,18 @@ import net.dankito.fints.model.mapper.BankDataMapper
import net.dankito.fints.util.IBase64Service import net.dankito.fints.util.IBase64Service
import net.dankito.utils.IThreadPool import net.dankito.utils.IThreadPool
import net.dankito.utils.ThreadPool import net.dankito.utils.ThreadPool
import net.dankito.utils.serialization.JacksonJsonSerializer
import net.dankito.utils.web.client.IWebClient import net.dankito.utils.web.client.IWebClient
import net.dankito.utils.web.client.OkHttpWebClient import net.dankito.utils.web.client.OkHttpWebClient
import org.slf4j.LoggerFactory
import java.io.File
open class fints4javaBankingClient( open class fints4javaBankingClient(
bankInfo: BankInfo, bankInfo: BankInfo,
customerId: String, customerId: String,
pin: String, pin: String,
protected val dataFolder: File,
webClient: IWebClient = OkHttpWebClient(), webClient: IWebClient = OkHttpWebClient(),
base64Service: IBase64Service, base64Service: IBase64Service,
threadPool: IThreadPool = ThreadPool(), threadPool: IThreadPool = ThreadPool(),
@ -32,10 +36,20 @@ open class fints4javaBankingClient(
) : IBankingClient { ) : IBankingClient {
companion object {
val fints4javaClientDataFilename = "fints4javaClientData.json"
private val log = LoggerFactory.getLogger(fints4javaBankingClient::class.java)
}
protected val mapper = net.dankito.banking.mapper.fints4javaModelMapper() protected val mapper = net.dankito.banking.mapper.fints4javaModelMapper()
protected val bankDataMapper = BankDataMapper() protected val bankDataMapper = BankDataMapper()
protected val serializer = JacksonJsonSerializer()
protected val bank = bankDataMapper.mapFromBankInfo(bankInfo) protected val bank = bankDataMapper.mapFromBankInfo(bankInfo)
protected val customer = CustomerData(customerId, pin) protected val customer = CustomerData(customerId, pin)
@ -74,6 +88,8 @@ open class fints4javaBankingClient(
this.account = mapper.mapAccount(customer, bank) this.account = mapper.mapAccount(customer, bank)
val mappedResponse = mapper.mapResponse(account, response) val mappedResponse = mapper.mapResponse(account, response)
saveData()
callback(mappedResponse) callback(mappedResponse)
} }
} }
@ -89,6 +105,8 @@ open class fints4javaBankingClient(
val mappedResponse = mapper.mapResponse(bankAccount, response) val mappedResponse = mapper.mapResponse(bankAccount, response)
saveData()
callback(mappedResponse) callback(mappedResponse)
} }
} }
@ -104,9 +122,32 @@ open class fints4javaBankingClient(
val mappedData = BankTransferData(data.creditorName, data.creditorIban, data.creditorBic, data.amount, data.usage) val mappedData = BankTransferData(data.creditorName, data.creditorIban, data.creditorBic, data.amount, data.usage)
client.doBankTransferAsync(mappedData, account) { response -> client.doBankTransferAsync(mappedData, account) { response ->
saveData()
callback(mapper.mapResponse(response)) callback(mapper.mapResponse(response))
} }
} }
} }
override fun restoreData() {
val deserializedCustomer = serializer.deserializeObject(getFints4javaClientDataFile(), CustomerData::class.java)
deserializedCustomer?.let {
mapper.updateCustomer(customer, deserializedCustomer)
}
}
protected open fun saveData() {
try {
serializer.serializeObject(customer, getFints4javaClientDataFile())
} catch (e: Exception) {
log.error("Could not save customer data for $customer", e)
}
}
protected open fun getFints4javaClientDataFile(): File {
return File(dataFolder, "${bank.bankCode}_${customer.customerId}_$fints4javaClientDataFilename")
}
} }

View File

@ -9,14 +9,23 @@ import net.dankito.fints.model.BankInfo
import net.dankito.utils.IThreadPool import net.dankito.utils.IThreadPool
import net.dankito.utils.web.client.IWebClient import net.dankito.utils.web.client.IWebClient
import net.dankito.utils.web.client.OkHttpWebClient import net.dankito.utils.web.client.OkHttpWebClient
import java.io.File
open class fints4javaBankingClientCreator : IBankingClientCreator { open class fints4javaBankingClientCreator : IBankingClientCreator {
override fun createClient(bankInfo: BankInfo, customerId: String, pin: String, webClient: IWebClient, override fun createClient(
base64Service: IBase64Service, threadPool: IThreadPool, callback: BankingClientCallback): IBankingClient { bankInfo: BankInfo,
customerId: String,
pin: String,
dataFolder: File,
webClient: IWebClient,
base64Service: IBase64Service,
threadPool: IThreadPool,
callback: BankingClientCallback
): IBankingClient {
return fints4javaBankingClient(bankInfo, customerId, pin, OkHttpWebClient(), UiCommonBase64ServiceWrapper(base64Service), threadPool, callback) return fints4javaBankingClient(bankInfo, customerId, pin, dataFolder, OkHttpWebClient(), UiCommonBase64ServiceWrapper(base64Service), threadPool, callback)
} }
} }

View File

@ -61,6 +61,10 @@ open class fints4javaModelMapper {
} }
open fun mapBank(bank: BankData): Bank {
return Bank(bank.name, bank.bankCode, bank.bic, bank.finTs3ServerAddress)
}
open fun mapAccount(customer: CustomerData, bank: BankData): Account { open fun mapAccount(customer: CustomerData, bank: BankData): Account {
val mappedBank = mapBank(bank) val mappedBank = mapBank(bank)
@ -73,10 +77,24 @@ open class fints4javaModelMapper {
return account return account
} }
open fun mapBank(bank: BankData): Bank { // TODO: move to a fints4java internal mapper
return Bank(bank.name, bank.bankCode, bank.bic, bank.finTs3ServerAddress) open fun updateCustomer(customer: CustomerData, updatedCustomer: CustomerData) {
customer.pin = updatedCustomer.pin
customer.name = updatedCustomer.name
customer.supportedTanProcedures = updatedCustomer.supportedTanProcedures
customer.selectedTanProcedure = updatedCustomer.selectedTanProcedure
customer.tanMedia = updatedCustomer.tanMedia
customer.updVersion = updatedCustomer.updVersion
customer.selectedLanguage = updatedCustomer.selectedLanguage
customer.customerSystemId = updatedCustomer.customerSystemId
customer.customerSystemStatus = updatedCustomer.customerSystemStatus
updateBankAccounts(customer, updatedCustomer.accounts)
} }
open fun mapBankAccounts(account: Account, accountData: List<AccountData>): List<BankAccount> { open fun mapBankAccounts(account: Account, accountData: List<AccountData>): List<BankAccount> {
return accountData.map { mapBankAccount(account, it) } return accountData.map { mapBankAccount(account, it) }
} }
@ -103,6 +121,38 @@ open class fints4javaModelMapper {
} }
} }
// TODO: move to a fints4java internal mapper
open fun updateBankAccounts(customer: CustomerData, updatedAccounts: List<AccountData>) {
val accounts = customer.accounts
updatedAccounts.forEach { updatedAccount ->
val matchingExistingAccount = findMatchingBankAccount(accounts, updatedAccount)
if (matchingExistingAccount == null) {
customer.addAccount(updatedAccount)
}
else {
updateBankAccount(matchingExistingAccount, updatedAccount)
}
}
customer.accounts.forEach { account ->
val updatedAccount = findMatchingBankAccount(updatedAccounts, account)
if (updatedAccount == null) {
customer.removeAccount(account)
}
}
}
open fun updateBankAccount(account: AccountData, updatedAccount: AccountData) {
account.allowedJobs = updatedAccount.allowedJobs
account.supportsRetrievingAccountTransactions = updatedAccount.supportsRetrievingAccountTransactions
account.supportsRetrievingBalance = updatedAccount.supportsRetrievingBalance
account.supportsTransferringMoney = updatedAccount.supportsTransferringMoney
}
open fun findAccountForBankAccount(customer: CustomerData, bankAccount: BankAccount): AccountData? { open fun findAccountForBankAccount(customer: CustomerData, bankAccount: BankAccount): AccountData? {
return customer.accounts.firstOrNull { bankAccount.identifier == it.accountIdentifier } return customer.accounts.firstOrNull { bankAccount.identifier == it.accountIdentifier }
} }
@ -111,6 +161,10 @@ open class fints4javaModelMapper {
return account.bankAccounts.firstOrNull { it.identifier == accountData.accountIdentifier } return account.bankAccounts.firstOrNull { it.identifier == accountData.accountIdentifier }
} }
open fun findMatchingBankAccount(accounts: List<AccountData>, accountData: AccountData): AccountData? {
return accounts.firstOrNull { it.accountIdentifier == accountData.accountIdentifier }
}
open fun mapTransactions(bankAccount: BankAccount, transactions: List<net.dankito.fints.model.AccountTransaction>): List<AccountTransaction> { open fun mapTransactions(bankAccount: BankAccount, transactions: List<net.dankito.fints.model.AccountTransaction>): List<AccountTransaction> {
return transactions.map { mapTransaction(bankAccount, it) } return transactions.map { mapTransaction(bankAccount, it) }

View File

@ -14,6 +14,9 @@ open class TanMedium(
) { ) {
internal constructor() : this(TanMediumKlasse.AlleMedien, TanMediumStatus.Verfuegbar)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false

View File

@ -26,7 +26,7 @@ open class AccountData(
var triedToRetrieveTransactionsOfLast90DaysWithoutTan: Boolean = false var triedToRetrieveTransactionsOfLast90DaysWithoutTan: Boolean = false
) { ) {
internal constructor() : this("", null, Laenderkennzeichen.Germany, "", null, "", null, null, "", null, null, listOf()) internal constructor() : this("", null, Laenderkennzeichen.Germany, "", null, "", null, null, "", null, null, listOf()) // for object deserializers
override fun toString(): String { override fun toString(): String {
return "$productName $accountIdentifier $accountHolderName" return "$productName $accountIdentifier $accountHolderName"

View File

@ -29,6 +29,9 @@ open class BankData(
) { ) {
internal constructor() : this("", 0, "", "") // for object deserializers
open fun resetBpdVersion() { open fun resetBpdVersion() {
bpdVersion = BPDVersion.VersionNotReceivedYet bpdVersion = BPDVersion.VersionNotReceivedYet
} }

View File

@ -32,6 +32,8 @@ open class CustomerData(
// for Java // for Java
constructor(customerId: String, pin: String) : this(customerId, pin, customerId) constructor(customerId: String, pin: String) : this(customerId, pin, customerId)
internal constructor() : this("", "") // for object deserializers
val isTanProcedureSelected: Boolean val isTanProcedureSelected: Boolean
get() = selectedTanProcedure != TanProcedureNotSelected get() = selectedTanProcedure != TanProcedureNotSelected
@ -50,6 +52,10 @@ open class CustomerData(
(accounts as? MutableList)?.add(account) (accounts as? MutableList)?.add(account)
} }
open fun removeAccount(account: AccountData) {
(accounts as? MutableList)?.remove(account)
}
override fun toString(): String { override fun toString(): String {
return customerId return customerId

View File

@ -9,6 +9,10 @@ open class TanProcedure(
val type: TanProcedureType val type: TanProcedureType
) { ) {
internal constructor() : this("", Sicherheitsfunktion.Einschritt_Verfahren, TanProcedureType.EnterTan) // for object deserializers
override fun toString(): String { override fun toString(): String {
return "$displayName ($type, ${securityFunction.code})" return "$displayName ($type, ${securityFunction.code})"
} }

View File

@ -6,11 +6,14 @@ open class JobParameters(
val maxCountJobs: Int, val maxCountJobs: Int,
val minimumCountSignatures: Int, val minimumCountSignatures: Int,
val securityClass: Int?, val securityClass: Int?,
segmentString: String segmentString: String // TODO: when serializing / deserializing we don't care for segment string -> remove it
) )
: ReceivedSegment(segmentString) { : ReceivedSegment(segmentString) {
internal constructor() : this("", 0, 0, null, "0:0:0") // for object deserializers
constructor(parameters: JobParameters) constructor(parameters: JobParameters)
: this(parameters.jobName, parameters.maxCountJobs, parameters.minimumCountSignatures, : this(parameters.jobName, parameters.maxCountJobs, parameters.minimumCountSignatures,
parameters.securityClass, parameters.segmentString) parameters.securityClass, parameters.segmentString)