Added updateAccountTransactions() to BankingClient
This commit is contained in:
parent
9a2bc6b430
commit
000a169a00
|
@ -1,8 +1,10 @@
|
||||||
package net.codinux.banking.client
|
package net.codinux.banking.client
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.*
|
||||||
import net.codinux.banking.client.model.options.RetrieveTransactions
|
import net.codinux.banking.client.model.options.RetrieveTransactions
|
||||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||||
|
import net.codinux.banking.client.model.response.GetTransactionsResponse
|
||||||
import net.codinux.banking.client.model.response.Response
|
import net.codinux.banking.client.model.response.Response
|
||||||
|
|
||||||
interface BankingClient {
|
interface BankingClient {
|
||||||
|
@ -30,4 +32,11 @@ interface BankingClient {
|
||||||
*/
|
*/
|
||||||
suspend fun getAccountDataAsync(request: GetAccountDataRequest): Response<GetAccountDataResponse>
|
suspend fun getAccountDataAsync(request: GetAccountDataRequest): Response<GetAccountDataResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience wrapper around [getAccountDataAsync].
|
||||||
|
* Updates account's transactions beginning from [BankAccount.lastTransactionRetrievalTime].
|
||||||
|
* This may requires TAN if [BankAccount.lastTransactionRetrievalTime] is older than 90 days.
|
||||||
|
*/
|
||||||
|
suspend fun updateAccountTransactionsAsync(user: UserAccount, accounts: List<BankAccount>? = null): Response<List<GetTransactionsResponse>>
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
package net.codinux.banking.client
|
package net.codinux.banking.client
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.BankAccount
|
||||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||||
import net.codinux.banking.client.model.options.RetrieveTransactions
|
import net.codinux.banking.client.model.options.RetrieveTransactions
|
||||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||||
|
import net.codinux.banking.client.model.response.GetTransactionsResponse
|
||||||
import net.codinux.banking.client.model.response.Response
|
import net.codinux.banking.client.model.response.Response
|
||||||
|
|
||||||
interface BankingClientForUser {
|
interface BankingClientForUser {
|
||||||
|
@ -30,4 +32,11 @@ interface BankingClientForUser {
|
||||||
*/
|
*/
|
||||||
suspend fun getAccountDataAsync(options: GetAccountDataOptions): Response<GetAccountDataResponse>
|
suspend fun getAccountDataAsync(options: GetAccountDataOptions): Response<GetAccountDataResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience wrapper around [getAccountDataAsync].
|
||||||
|
* Updates account's transactions beginning from [BankAccount.lastTransactionRetrievalTime].
|
||||||
|
* This may requires TAN if [BankAccount.lastTransactionRetrievalTime] is older than 90 days.
|
||||||
|
*/
|
||||||
|
suspend fun updateAccountTransactionsAsync(): Response<List<GetTransactionsResponse>>
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,15 +1,27 @@
|
||||||
package net.codinux.banking.client
|
package net.codinux.banking.client
|
||||||
|
|
||||||
import net.codinux.banking.client.model.AccountCredentials
|
import net.codinux.banking.client.model.AccountCredentials
|
||||||
|
import net.codinux.banking.client.model.UserAccount
|
||||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||||
|
import net.codinux.banking.client.model.response.GetTransactionsResponse
|
||||||
|
import net.codinux.banking.client.model.response.Response
|
||||||
|
|
||||||
abstract class BankingClientForUserBase(
|
abstract class BankingClientForUserBase(
|
||||||
protected val credentials: AccountCredentials,
|
protected val credentials: AccountCredentials,
|
||||||
protected val client: BankingClient
|
protected val client: BankingClient
|
||||||
) : BankingClientForUser {
|
) : BankingClientForUser {
|
||||||
|
|
||||||
|
private lateinit var user: UserAccount
|
||||||
|
|
||||||
override suspend fun getAccountDataAsync(options: GetAccountDataOptions) =
|
override suspend fun getAccountDataAsync(options: GetAccountDataOptions) =
|
||||||
client.getAccountDataAsync(GetAccountDataRequest(credentials, options))
|
client.getAccountDataAsync(GetAccountDataRequest(credentials, options)).also {
|
||||||
|
it.data?.user?.let { retrievedUser ->
|
||||||
|
this.user = retrievedUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateAccountTransactionsAsync(): Response<List<GetTransactionsResponse>> =
|
||||||
|
client.updateAccountTransactionsAsync(user)
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
package net.codinux.banking.client
|
package net.codinux.banking.client
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import net.codinux.banking.client.model.BankAccount
|
||||||
|
import net.codinux.banking.client.model.UserAccount
|
||||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||||
|
|
||||||
|
@ -12,6 +14,11 @@ fun BankingClient.getAccountData(request: GetAccountDataRequest) = runBlocking {
|
||||||
this@getAccountData.getAccountDataAsync(request)
|
this@getAccountData.getAccountDataAsync(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun BankingClient.updateAccountTransactions(user: UserAccount, accounts: List<BankAccount>? = null) = runBlocking {
|
||||||
|
this@updateAccountTransactions.updateAccountTransactionsAsync(user, accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun BankingClientForUser.getAccountData() = runBlocking {
|
fun BankingClientForUser.getAccountData() = runBlocking {
|
||||||
this@getAccountData.getAccountDataAsync()
|
this@getAccountData.getAccountDataAsync()
|
||||||
}
|
}
|
||||||
|
@ -19,3 +26,7 @@ fun BankingClientForUser.getAccountData() = runBlocking {
|
||||||
fun BankingClientForUser.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
fun BankingClientForUser.getAccountData(options: GetAccountDataOptions) = runBlocking {
|
||||||
this@getAccountData.getAccountDataAsync(options)
|
this@getAccountData.getAccountDataAsync(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun BankingClientForUser.updateAccountTransactions() = runBlocking {
|
||||||
|
this@updateAccountTransactions.updateAccountTransactionsAsync()
|
||||||
|
}
|
|
@ -40,5 +40,8 @@ open class BankAccount(
|
||||||
open val displayName: String
|
open val displayName: String
|
||||||
get() = userSetDisplayName ?: productName ?: identifier
|
get() = userSetDisplayName ?: productName ?: identifier
|
||||||
|
|
||||||
|
fun supportsAnyFeature(vararg features: BankAccountFeatures): Boolean =
|
||||||
|
features.any { this.features.contains(it) }
|
||||||
|
|
||||||
override fun toString() = "$type $identifier $productName (IBAN: $iban)"
|
override fun toString() = "$type $identifier $productName (IBAN: $iban)"
|
||||||
}
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package net.codinux.banking.client.model.extensions
|
||||||
|
|
||||||
|
import kotlinx.datetime.*
|
||||||
|
import kotlin.js.JsName
|
||||||
|
|
||||||
|
val UnixEpochStart: LocalDate = LocalDate.parse("1970-01-01")
|
||||||
|
|
||||||
|
|
||||||
|
fun LocalDate.Companion.todayAtSystemDefaultTimeZone(): LocalDate {
|
||||||
|
return nowAt(TimeZone.currentSystemDefault())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LocalDate.Companion.todayAtEuropeBerlin(): LocalDate {
|
||||||
|
return nowAt(TimeZone.europeBerlin)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsName("nowAtForDate")
|
||||||
|
fun LocalDate.Companion.nowAt(timeZone: TimeZone): LocalDate {
|
||||||
|
return Clock.System.todayIn(timeZone)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun LocalDate.minusDays(days: Int): LocalDate {
|
||||||
|
return this.minus(days, DateTimeUnit.DAY)
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package net.codinux.banking.client.model.extensions
|
||||||
|
|
||||||
|
import kotlinx.datetime.*
|
||||||
|
|
||||||
|
|
||||||
|
fun LocalDateTime.Companion.nowAtUtc(): LocalDateTime {
|
||||||
|
return nowAt(TimeZone.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LocalDateTime.Companion.nowAtEuropeBerlin(): LocalDateTime {
|
||||||
|
return nowAt(TimeZone.europeBerlin)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LocalDateTime.Companion.nowAt(timeZone: TimeZone): LocalDateTime {
|
||||||
|
return Clock.System.now().toLocalDateTime(timeZone)
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package net.codinux.banking.client.model.extensions
|
||||||
|
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
|
||||||
|
|
||||||
|
val TimeZone.Companion.europeBerlin: TimeZone
|
||||||
|
get() = TimeZone.of("Europe/Berlin")
|
|
@ -0,0 +1,22 @@
|
||||||
|
package net.codinux.banking.client.model.response
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import net.codinux.banking.client.model.AccountTransaction
|
||||||
|
import net.codinux.banking.client.model.Amount
|
||||||
|
import net.codinux.banking.client.model.BankAccount
|
||||||
|
import net.codinux.banking.client.model.UnbookedAccountTransaction
|
||||||
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
|
||||||
|
@NoArgConstructor
|
||||||
|
open class GetTransactionsResponse(
|
||||||
|
val account: BankAccount,
|
||||||
|
val balance: Amount? = null,
|
||||||
|
val bookedTransactions: List<AccountTransaction>,
|
||||||
|
val unbookedTransactions: List<UnbookedAccountTransaction>,
|
||||||
|
val transactionRetrievalTime: LocalDateTime,
|
||||||
|
val retrievedTransactionsFrom: LocalDate? = null,
|
||||||
|
val retrievedTransactionsTo: LocalDate? = null
|
||||||
|
) {
|
||||||
|
override fun toString() = "${account.productName} $balance, ${bookedTransactions.size} booked transactions from $retrievedTransactionsFrom"
|
||||||
|
}
|
|
@ -77,7 +77,7 @@ kotlin {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":BankingClient"))
|
api(project(":BankingClient"))
|
||||||
|
|
||||||
api("net.codinux.banking:fints4k:1.0.0-Alpha-12")
|
api("net.codinux.banking:fints4k:1.0.0-Alpha-13-SNAPSHOT")
|
||||||
|
|
||||||
api("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinxDateTimeVersion")
|
api("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinxDateTimeVersion")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,15 @@ package net.codinux.banking.client.fints4k
|
||||||
|
|
||||||
import net.codinux.banking.client.BankingClient
|
import net.codinux.banking.client.BankingClient
|
||||||
import net.codinux.banking.client.BankingClientCallback
|
import net.codinux.banking.client.BankingClientCallback
|
||||||
|
import net.codinux.banking.client.model.BankAccount
|
||||||
|
import net.codinux.banking.client.model.BankAccountFeatures
|
||||||
|
import net.codinux.banking.client.model.UserAccount
|
||||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
import net.codinux.banking.client.model.response.*
|
||||||
import net.codinux.banking.client.model.response.Response
|
|
||||||
import net.codinux.banking.fints.FinTsClient
|
import net.codinux.banking.fints.FinTsClient
|
||||||
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
||||||
|
import net.codinux.banking.fints.model.BankData
|
||||||
|
|
||||||
open class FinTs4kBankingClient(
|
open class FinTs4kBankingClient(
|
||||||
config: FinTsClientConfiguration = FinTsClientConfiguration(),
|
config: FinTsClientConfiguration = FinTsClientConfiguration(),
|
||||||
|
@ -28,4 +31,28 @@ open class FinTs4kBankingClient(
|
||||||
return mapper.map(response)
|
return mapper.map(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun updateAccountTransactionsAsync(user: UserAccount, accounts: List<BankAccount>?): Response<List<GetTransactionsResponse>> {
|
||||||
|
val accountsToRequest = (accounts ?: user.accounts).filter { it.supportsAnyFeature(BankAccountFeatures.RetrieveBalance, BankAccountFeatures.RetrieveBalance) }
|
||||||
|
|
||||||
|
if (accountsToRequest.isNotEmpty()) {
|
||||||
|
var finTsModel: BankData? = null
|
||||||
|
|
||||||
|
val responses = accountsToRequest.map { account ->
|
||||||
|
val parameter = mapper.mapToUpdateAccountTransactionsParameter(user, account, finTsModel)
|
||||||
|
|
||||||
|
val response = client.getAccountDataAsync(parameter)
|
||||||
|
|
||||||
|
if (response.finTsModel != null) {
|
||||||
|
finTsModel = response.finTsModel // so that basic account data doesn't have to be retrieved another time if user has multiple accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
Triple(account, parameter, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapper.map(responses)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.error(ErrorType.NoneOfTheAccountsSupportsRetrievingData, "Keiner der Konten unterstützt das Abholen der Umsätze oder des Kontostands") // TODO: translate
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package net.codinux.banking.client.fints4k
|
package net.codinux.banking.client.fints4k
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
import net.codinux.banking.client.model.*
|
import net.codinux.banking.client.model.*
|
||||||
import net.codinux.banking.client.model.AccountTransaction
|
import net.codinux.banking.client.model.AccountTransaction
|
||||||
import net.codinux.banking.client.model.Amount
|
import net.codinux.banking.client.model.Amount
|
||||||
|
@ -11,6 +12,7 @@ import net.codinux.banking.client.model.tan.TanChallenge
|
||||||
import net.codinux.banking.client.model.tan.TanImage
|
import net.codinux.banking.client.model.tan.TanImage
|
||||||
import net.codinux.banking.client.model.tan.TanMethod
|
import net.codinux.banking.client.model.tan.TanMethod
|
||||||
import net.codinux.banking.client.model.tan.TanMethodType
|
import net.codinux.banking.client.model.tan.TanMethodType
|
||||||
|
import net.codinux.banking.fints.extensions.nowAtEuropeBerlin
|
||||||
import net.dankito.banking.client.model.BankAccountIdentifierImpl
|
import net.dankito.banking.client.model.BankAccountIdentifierImpl
|
||||||
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
||||||
import net.dankito.banking.client.model.parameter.RetrieveTransactions
|
import net.dankito.banking.client.model.parameter.RetrieveTransactions
|
||||||
|
@ -41,6 +43,21 @@ open class FinTs4kMapper {
|
||||||
abortIfTanIsRequired = options.abortIfTanIsRequired
|
abortIfTanIsRequired = options.abortIfTanIsRequired
|
||||||
)
|
)
|
||||||
|
|
||||||
|
open fun mapToUpdateAccountTransactionsParameter(user: UserAccount, account: BankAccount, finTsModel: BankData?): GetAccountDataParameter {
|
||||||
|
val accountIdentifier = BankAccountIdentifierImpl(account.identifier, account.subAccountNumber, account.iban)
|
||||||
|
val from = account.lastTransactionRetrievalTime?.date
|
||||||
|
val retrieveTransactions = if (from != null) RetrieveTransactions.AccordingToRetrieveFromAndTo else RetrieveTransactions.OfLast90Days
|
||||||
|
// val preferredTanMethods = listOf(mapTanMethodType(user.selectedTanMethod.type)) // TODO: currently we aren't saving TanMethods in database, re-enable as soon as TanMethods get saved
|
||||||
|
val preferredTanMethods = emptyList<net.codinux.banking.fints.model.TanMethodType>()
|
||||||
|
|
||||||
|
return GetAccountDataParameter(user.bankCode, user.loginName, user.password!!, listOf(accountIdentifier), true,
|
||||||
|
retrieveTransactions, from,
|
||||||
|
preferredTanMethods = preferredTanMethods,
|
||||||
|
preferredTanMedium = user.selectedTanMediumName,
|
||||||
|
finTsModel = finTsModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun mapTanMethodType(type: TanMethodType): net.codinux.banking.fints.model.TanMethodType =
|
protected open fun mapTanMethodType(type: TanMethodType): net.codinux.banking.fints.model.TanMethodType =
|
||||||
net.codinux.banking.fints.model.TanMethodType.valueOf(type.name)
|
net.codinux.banking.fints.model.TanMethodType.valueOf(type.name)
|
||||||
|
|
||||||
|
@ -53,6 +70,27 @@ open class FinTs4kMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun map(responses: List<Triple<BankAccount, GetAccountDataParameter, net.dankito.banking.client.model.response.GetAccountDataResponse>>): Response<List<GetTransactionsResponse>> {
|
||||||
|
val type = if (responses.all { it.third.successful }) ResponseType.Success else ResponseType.Error
|
||||||
|
|
||||||
|
// TODO: update UserAccount and BankAccount objects according to retrieved data
|
||||||
|
val mappedResponses = responses.map { (account, param, getAccountDataResponse) ->
|
||||||
|
val user = getAccountDataResponse.customerAccount
|
||||||
|
val finTsBankAccount = user?.accounts?.firstOrNull { it.identifier == account.identifier && it.subAccountNumber == account.subAccountNumber }
|
||||||
|
|
||||||
|
if (getAccountDataResponse.successful && user != null && finTsBankAccount != null) {
|
||||||
|
Response.success(GetTransactionsResponse(account, mapAmount(finTsBankAccount.balance), mapBookedTransactions(finTsBankAccount), emptyList(),
|
||||||
|
finTsBankAccount.lastTransactionRetrievalTime ?: LocalDateTime.nowAtEuropeBerlin(), param.retrieveTransactionsFrom, param.retrieveTransactionsTo))
|
||||||
|
} else {
|
||||||
|
mapError(getAccountDataResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val data = mappedResponses.filter { it.type == ResponseType.Success }.mapNotNull { it.data }
|
||||||
|
|
||||||
|
return (object : Response<List<GetTransactionsResponse>>(type, data, mappedResponses.firstNotNullOfOrNull { it.error }) { })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun mapToUserAccountViewInfo(bank: BankData): UserAccountViewInfo = UserAccountViewInfo(
|
open fun mapToUserAccountViewInfo(bank: BankData): UserAccountViewInfo = UserAccountViewInfo(
|
||||||
bank.bankCode, bank.customerId, bank.bankName, getBankingGroup(bank.bankName, bank.bic)
|
bank.bankCode, bank.customerId, bank.bankName, getBankingGroup(bank.bankName, bank.bic)
|
||||||
|
@ -87,7 +125,7 @@ open class FinTs4kMapper {
|
||||||
mapAmount(account.balance), account.retrievedTransactionsFrom, account.lastTransactionRetrievalTime,
|
mapAmount(account.balance), account.retrievedTransactionsFrom, account.lastTransactionRetrievalTime,
|
||||||
// TODO: map haveAllTransactionsBeenRetrieved
|
// TODO: map haveAllTransactionsBeenRetrieved
|
||||||
countDaysForWhichTransactionsAreKept = account.countDaysForWhichTransactionsAreKept,
|
countDaysForWhichTransactionsAreKept = account.countDaysForWhichTransactionsAreKept,
|
||||||
bookedTransactions = account.bookedTransactions.map { mapTransaction(it) }.toMutableList()
|
bookedTransactions = mapBookedTransactions(account).toMutableList()
|
||||||
)
|
)
|
||||||
|
|
||||||
protected open fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
protected open fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
||||||
|
@ -109,6 +147,9 @@ open class FinTs4kMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun mapBookedTransactions(account: net.dankito.banking.client.model.BankAccount): List<AccountTransaction> =
|
||||||
|
account.bookedTransactions.map { mapTransaction(it) }
|
||||||
|
|
||||||
protected open fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
protected open fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
||||||
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.unparsedReference,
|
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.unparsedReference,
|
||||||
transaction.bookingDate, transaction.valueDate,
|
transaction.bookingDate, transaction.valueDate,
|
||||||
|
|
Loading…
Reference in New Issue