2020-01-02 21:39:02 +00:00
|
|
|
package net.dankito.banking
|
|
|
|
|
2020-01-02 23:35:36 +00:00
|
|
|
import net.dankito.banking.ui.BankingClientCallback
|
2020-01-02 21:39:02 +00:00
|
|
|
import net.dankito.banking.ui.IBankingClient
|
|
|
|
import net.dankito.banking.ui.model.parameters.GetTransactionsParameter
|
|
|
|
import net.dankito.banking.ui.model.parameters.TransferMoneyData
|
|
|
|
import net.dankito.banking.ui.model.responses.AddAccountResponse
|
|
|
|
import net.dankito.banking.ui.model.responses.BankingClientResponse
|
|
|
|
import net.dankito.banking.ui.model.responses.GetTransactionsResponse
|
2020-05-18 19:23:03 +00:00
|
|
|
import net.dankito.banking.fints.FinTsClientForCustomer
|
|
|
|
import net.dankito.banking.fints.callback.FinTsClientCallback
|
|
|
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
|
|
|
|
import net.dankito.banking.fints.model.*
|
|
|
|
import net.dankito.banking.fints.util.IBase64Service
|
2020-06-03 15:49:29 +00:00
|
|
|
import net.dankito.banking.fints.util.PureKotlinBase64Service
|
|
|
|
import net.dankito.banking.fints.webclient.IWebClient
|
|
|
|
import net.dankito.banking.fints.webclient.KtorWebClient
|
2020-07-02 21:08:35 +00:00
|
|
|
import net.dankito.banking.extensions.toMoney
|
2020-07-15 19:45:20 +00:00
|
|
|
import net.dankito.banking.fints.response.client.FinTsClientResponse
|
2020-09-11 10:25:05 +00:00
|
|
|
import net.dankito.banking.ui.model.*
|
|
|
|
import net.dankito.banking.ui.model.MessageLogEntry
|
|
|
|
import net.dankito.banking.ui.model.mapper.IModelCreator
|
2020-07-12 10:26:16 +00:00
|
|
|
import net.dankito.banking.util.ISerializer
|
|
|
|
import net.dankito.utils.multiplatform.File
|
|
|
|
import net.dankito.utils.multiplatform.log.LoggerFactory
|
2020-01-02 21:39:02 +00:00
|
|
|
|
|
|
|
|
2020-05-16 18:19:42 +00:00
|
|
|
open class fints4kBankingClient(
|
2020-09-11 10:25:05 +00:00
|
|
|
protected val customer: TypedCustomer,
|
|
|
|
protected val modelCreator: IModelCreator,
|
2020-01-25 19:29:44 +00:00
|
|
|
protected val dataFolder: File,
|
2020-07-12 10:26:16 +00:00
|
|
|
protected val serializer: ISerializer,
|
2020-06-03 15:49:29 +00:00
|
|
|
webClient: IWebClient = KtorWebClient(),
|
|
|
|
base64Service: IBase64Service = PureKotlinBase64Service(),
|
2020-01-02 23:35:36 +00:00
|
|
|
callback: BankingClientCallback
|
2020-01-02 21:39:02 +00:00
|
|
|
|
|
|
|
) : IBankingClient {
|
|
|
|
|
2020-01-25 19:29:44 +00:00
|
|
|
companion object {
|
2020-05-16 18:19:42 +00:00
|
|
|
val fints4kClientDataFilename = "fints4kClientData.json"
|
2020-01-25 19:29:44 +00:00
|
|
|
|
2020-07-12 10:26:16 +00:00
|
|
|
private val log = LoggerFactory.getLogger(fints4kBankingClient::class)
|
2020-01-25 19:29:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-11 10:25:05 +00:00
|
|
|
protected val mapper = net.dankito.banking.mapper.fints4kModelMapper(modelCreator)
|
2020-01-02 21:39:02 +00:00
|
|
|
|
2020-07-27 14:57:38 +00:00
|
|
|
protected var didTryToGetAccountDataFromBank = false
|
|
|
|
|
2020-01-25 19:29:44 +00:00
|
|
|
|
2020-09-08 12:23:56 +00:00
|
|
|
protected val bank = restoreDataOrMapFromUiModel(customer)
|
2020-01-02 21:39:02 +00:00
|
|
|
|
2020-01-02 23:35:36 +00:00
|
|
|
|
2020-09-08 12:07:17 +00:00
|
|
|
protected open val client = FinTsClientForCustomer(bank, createFinTsClientCallback(callback), webClient, base64Service)
|
2020-01-02 21:39:02 +00:00
|
|
|
|
|
|
|
|
2020-05-16 20:51:51 +00:00
|
|
|
override val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
2020-07-12 10:26:16 +00:00
|
|
|
get() = client.messageLogWithoutSensitiveData.map { MessageLogEntry(it.message, it.time, customer) }
|
2020-05-16 20:51:51 +00:00
|
|
|
|
|
|
|
|
2020-01-02 21:39:02 +00:00
|
|
|
override fun addAccountAsync(callback: (AddAccountResponse) -> Unit) {
|
|
|
|
client.addAccountAsync { response ->
|
2020-07-15 19:45:20 +00:00
|
|
|
handleAddAccountResponse(response, callback)
|
|
|
|
}
|
|
|
|
}
|
2020-01-02 21:39:02 +00:00
|
|
|
|
2020-07-15 19:45:20 +00:00
|
|
|
protected open fun handleAddAccountResponse(response: net.dankito.banking.fints.response.client.AddAccountResponse,
|
|
|
|
callback: (AddAccountResponse) -> Unit) {
|
2020-09-08 14:25:28 +00:00
|
|
|
mapper.mapBank(customer, bank)
|
2020-07-15 19:45:20 +00:00
|
|
|
val mappedResponse = mapper.mapResponse(customer, response)
|
2020-01-25 19:29:44 +00:00
|
|
|
|
2020-07-15 19:45:20 +00:00
|
|
|
saveData()
|
|
|
|
|
|
|
|
callback(mappedResponse)
|
2020-01-02 21:39:02 +00:00
|
|
|
}
|
|
|
|
|
2020-07-15 19:45:20 +00:00
|
|
|
|
2020-09-11 10:25:05 +00:00
|
|
|
override fun getTransactionsAsync(bankAccount: TypedBankAccount, parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) {
|
2020-07-27 14:57:38 +00:00
|
|
|
findAccountForBankAccount(bankAccount) { account, errorMessage ->
|
|
|
|
if (account == null) {
|
|
|
|
callback(GetTransactionsResponse(bankAccount, false, errorMessage))
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
val mappedParameter = GetTransactionsParameter(parameter.alsoRetrieveBalance, parameter.fromDate,
|
2020-09-16 13:07:21 +00:00
|
|
|
parameter.toDate, null, parameter.abortIfTanIsRequired) {
|
|
|
|
parameter.retrievedChunkListener?.invoke(mapper.mapTransactions(bankAccount, it))
|
|
|
|
}
|
2020-07-27 14:57:38 +00:00
|
|
|
|
|
|
|
doGetTransactionsAsync(mappedParameter, account, bankAccount, callback)
|
|
|
|
}
|
2020-07-15 19:45:20 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-25 19:29:44 +00:00
|
|
|
|
2020-07-15 19:45:20 +00:00
|
|
|
protected open fun doGetTransactionsAsync(parameter: net.dankito.banking.fints.model.GetTransactionsParameter,
|
2020-09-11 10:25:05 +00:00
|
|
|
account: AccountData, bankAccount: TypedBankAccount, callback: (GetTransactionsResponse) -> Unit) {
|
2020-07-15 19:45:20 +00:00
|
|
|
client.getTransactionsAsync(parameter, account) { response ->
|
|
|
|
handleGetTransactionsResponse(bankAccount, response, callback)
|
2020-01-02 21:39:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-11 10:25:05 +00:00
|
|
|
protected open fun handleGetTransactionsResponse(bankAccount: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse,
|
2020-07-15 19:45:20 +00:00
|
|
|
callback: (GetTransactionsResponse) -> Unit) {
|
|
|
|
val mappedResponse = mapper.mapResponse(bankAccount, response)
|
|
|
|
|
|
|
|
saveData()
|
|
|
|
|
|
|
|
callback(mappedResponse)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-02 14:54:33 +00:00
|
|
|
override fun transferMoneyAsync(data: TransferMoneyData, callback: (BankingClientResponse) -> Unit) {
|
|
|
|
findAccountForBankAccount(data.account) { account, errorMessage ->
|
2020-07-27 14:57:38 +00:00
|
|
|
if (account == null) {
|
|
|
|
callback(BankingClientResponse(false, errorMessage))
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
val mappedData = BankTransferData(data.creditorName, data.creditorIban, data.creditorBic, data.amount.toMoney(), data.usage, data.instantPayment)
|
|
|
|
|
|
|
|
doBankTransferAsync(mappedData, account, callback)
|
|
|
|
}
|
2020-07-15 19:45:20 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-25 19:29:44 +00:00
|
|
|
|
2020-07-15 19:45:20 +00:00
|
|
|
protected open fun doBankTransferAsync(data: BankTransferData, account: AccountData, callback: (BankingClientResponse) -> Unit) {
|
|
|
|
client.doBankTransferAsync(data, account) { response ->
|
|
|
|
handleBankTransferResponse(callback, response)
|
2020-01-02 21:39:02 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-19 15:22:43 +00:00
|
|
|
|
2020-07-15 19:45:20 +00:00
|
|
|
protected open fun handleBankTransferResponse(callback: (BankingClientResponse) -> Unit, response: FinTsClientResponse) {
|
|
|
|
saveData()
|
|
|
|
|
|
|
|
callback(mapper.mapResponse(response))
|
|
|
|
}
|
|
|
|
|
2020-01-25 19:29:44 +00:00
|
|
|
|
2020-09-11 10:25:05 +00:00
|
|
|
override fun dataChanged(customer: TypedCustomer) {
|
2020-09-08 14:25:28 +00:00
|
|
|
mapper.mapChangesFromUiToClientModel(customer, bank)
|
|
|
|
}
|
|
|
|
|
2020-09-13 23:00:09 +00:00
|
|
|
override fun deletedAccount(customer: TypedCustomer, wasLastAccountWithThisCredentials: Boolean) {
|
|
|
|
if (wasLastAccountWithThisCredentials) {
|
|
|
|
getFints4kClientDataFile(customer).delete()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-08 14:25:28 +00:00
|
|
|
|
2020-09-11 10:25:05 +00:00
|
|
|
protected open fun findAccountForBankAccount(bankAccount: TypedBankAccount, findAccountResult: (AccountData?, error: String?) -> Unit) {
|
2020-09-08 12:07:17 +00:00
|
|
|
val mappedAccount = mapper.findAccountForBankAccount(bank, bankAccount)
|
2020-07-27 14:57:38 +00:00
|
|
|
|
|
|
|
if (mappedAccount != null) {
|
|
|
|
findAccountResult(mappedAccount, null)
|
|
|
|
}
|
|
|
|
else if (didTryToGetAccountDataFromBank == false) { // then try to get account data by fetching data from bank
|
|
|
|
addAccountAsync { response ->
|
|
|
|
didTryToGetAccountDataFromBank = !!! response.isSuccessful
|
|
|
|
|
2020-09-08 12:07:17 +00:00
|
|
|
findAccountResult(mapper.findAccountForBankAccount(bank, bankAccount),
|
2020-08-16 21:55:24 +00:00
|
|
|
response.errorToShowToUser)
|
2020-07-27 14:57:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
findAccountResult(null, "Cannot find account for ${bankAccount.identifier}") // TODO: translate
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-11 10:25:05 +00:00
|
|
|
protected open fun restoreDataOrMapFromUiModel(customer: TypedCustomer): BankData {
|
2020-09-13 22:10:13 +00:00
|
|
|
if (isNewAccount(customer)) {
|
|
|
|
return mapToBankData(customer)
|
|
|
|
}
|
|
|
|
|
|
|
|
return restoreData(customer) ?: mapToBankData(customer)
|
|
|
|
}
|
|
|
|
|
|
|
|
protected open fun isNewAccount(customer: TypedCustomer): Boolean {
|
|
|
|
return customer.accounts.isEmpty()
|
|
|
|
}
|
|
|
|
|
|
|
|
protected open fun mapToBankData(customer: TypedCustomer): BankData {
|
|
|
|
return BankData(customer.bankCode, customer.customerId, customer.password, customer.finTsServerAddress, customer.bic, customer.bankName)
|
2020-09-08 12:23:56 +00:00
|
|
|
}
|
2020-04-28 16:17:47 +00:00
|
|
|
|
2020-09-11 10:25:05 +00:00
|
|
|
protected open fun restoreData(customer: TypedCustomer): BankData? {
|
2020-09-08 12:23:56 +00:00
|
|
|
try {
|
2020-09-13 23:00:09 +00:00
|
|
|
return serializer.deserializeObject(getFints4kClientDataFile(customer), BankData::class)
|
2020-09-08 12:23:56 +00:00
|
|
|
} catch (e: Exception) {
|
2020-09-18 23:37:54 +00:00
|
|
|
log.warn(e) { "Could not deserialize bank data of $customer" }
|
2020-01-25 19:29:44 +00:00
|
|
|
}
|
2020-09-08 12:23:56 +00:00
|
|
|
|
|
|
|
return null
|
2020-01-25 19:29:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected open fun saveData() {
|
|
|
|
try {
|
2020-09-08 12:23:56 +00:00
|
|
|
val clientDataFile = getFints4kClientDataFile(bank.bankCode, bank.customerId)
|
2020-06-14 19:00:45 +00:00
|
|
|
|
2020-09-08 12:07:17 +00:00
|
|
|
serializer.serializeObject(bank, clientDataFile)
|
2020-01-25 19:29:44 +00:00
|
|
|
} catch (e: Exception) {
|
2020-09-08 12:07:17 +00:00
|
|
|
log.error("Could not save customer data for $bank", e)
|
2020-01-25 19:29:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-13 23:00:09 +00:00
|
|
|
protected open fun getFints4kClientDataFile(customer: TypedCustomer): File {
|
|
|
|
return getFints4kClientDataFile(customer.bankCode, customer.customerId)
|
|
|
|
}
|
|
|
|
|
2020-09-08 12:23:56 +00:00
|
|
|
protected open fun getFints4kClientDataFile(bankCode: String, customerId: String): File {
|
2020-07-27 14:58:29 +00:00
|
|
|
val folder = File(dataFolder, "fints4k-client")
|
|
|
|
|
|
|
|
folder.mkdirs()
|
|
|
|
|
2020-09-08 12:23:56 +00:00
|
|
|
return File(folder, "${bankCode}_${customerId}_$fints4kClientDataFilename")
|
2020-01-25 19:29:44 +00:00
|
|
|
}
|
|
|
|
|
2020-07-15 19:45:20 +00:00
|
|
|
|
2020-07-21 08:59:33 +00:00
|
|
|
protected open fun createFinTsClientCallback(clientCallback: BankingClientCallback): FinTsClientCallback {
|
2020-07-15 19:45:20 +00:00
|
|
|
return object : FinTsClientCallback {
|
|
|
|
|
2020-07-21 08:59:33 +00:00
|
|
|
override fun askUserForTanProcedure(supportedTanProcedures: List<TanProcedure>, suggestedTanProcedure: TanProcedure?, callback: (TanProcedure?) -> Unit) {
|
|
|
|
handleAskUserForTanProcedure(supportedTanProcedures, suggestedTanProcedure, callback)
|
2020-07-15 19:45:20 +00:00
|
|
|
}
|
|
|
|
|
2020-09-08 12:07:17 +00:00
|
|
|
override fun enterTan(bank: BankData, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) {
|
|
|
|
handleEnterTan(bank, tanChallenge, callback, clientCallback)
|
2020-07-15 19:45:20 +00:00
|
|
|
}
|
|
|
|
|
2020-09-08 12:07:17 +00:00
|
|
|
override fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium, callback: (EnterTanGeneratorAtcResult) -> Unit) {
|
|
|
|
handleEnterTanGeneratorAtc(bank, tanMedium, callback, clientCallback)
|
2020-07-15 19:45:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-21 08:59:33 +00:00
|
|
|
protected open fun handleAskUserForTanProcedure(supportedTanProcedures: List<TanProcedure>, suggestedTanProcedure: TanProcedure?, callback: (TanProcedure?) -> Unit) {
|
2020-07-15 19:45:20 +00:00
|
|
|
// we simply return suggestedTanProcedure as even so it's not user's preferred TAN procedure she still can select it in EnterTanDialog
|
2020-07-21 08:59:33 +00:00
|
|
|
callback(suggestedTanProcedure)
|
2020-07-15 19:45:20 +00:00
|
|
|
}
|
|
|
|
|
2020-09-08 12:07:17 +00:00
|
|
|
protected open fun handleEnterTan(bank: BankData, tanChallenge: TanChallenge, enterTanCallback: (EnterTanResult) -> Unit, clientCallback: BankingClientCallback) {
|
|
|
|
mapper.updateTanMediaAndProcedures(this@fints4kBankingClient.customer, bank)
|
2020-07-15 19:45:20 +00:00
|
|
|
|
2020-07-21 08:59:33 +00:00
|
|
|
clientCallback.enterTan(this@fints4kBankingClient.customer, mapper.mapTanChallenge(tanChallenge)) { result ->
|
2020-09-08 12:07:17 +00:00
|
|
|
enterTanCallback(mapper.mapEnterTanResult(result, bank))
|
2020-07-21 08:59:33 +00:00
|
|
|
}
|
2020-07-15 19:45:20 +00:00
|
|
|
}
|
|
|
|
|
2020-09-08 12:07:17 +00:00
|
|
|
protected open fun handleEnterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium, enterAtcCallback: (EnterTanGeneratorAtcResult) -> Unit, clientCallback: BankingClientCallback) {
|
|
|
|
mapper.updateTanMediaAndProcedures(this@fints4kBankingClient.customer, bank)
|
2020-07-15 19:45:20 +00:00
|
|
|
|
2020-07-21 08:59:33 +00:00
|
|
|
clientCallback.enterTanGeneratorAtc(mapper.mapTanMedium(tanMedium)) { result ->
|
|
|
|
enterAtcCallback(mapper.mapEnterTanGeneratorAtcResult(result))
|
|
|
|
}
|
2020-07-15 19:45:20 +00:00
|
|
|
}
|
|
|
|
|
2020-01-02 21:39:02 +00:00
|
|
|
}
|