Implemented handling enter TAN requests
This commit is contained in:
parent
356b0f7823
commit
1216267fec
|
@ -1,16 +1,25 @@
|
||||||
package net.dankito.banking.fints.rest
|
package net.dankito.banking.fints.rest
|
||||||
|
|
||||||
|
import net.dankito.banking.fints.response.client.AddAccountResponse
|
||||||
|
import net.dankito.banking.fints.response.client.GetTransactionsResponse
|
||||||
import net.dankito.banking.fints.rest.model.dto.request.AddAccountRequestDto
|
import net.dankito.banking.fints.rest.model.dto.request.AddAccountRequestDto
|
||||||
import net.dankito.banking.fints.rest.mapper.DtoMapper
|
import net.dankito.banking.fints.rest.mapper.DtoMapper
|
||||||
import net.dankito.banking.fints.rest.model.dto.request.GetAccountsTransactionsRequestDto
|
import net.dankito.banking.fints.rest.model.dto.request.GetAccountsTransactionsRequestDto
|
||||||
|
import net.dankito.banking.fints.rest.model.dto.request.TanResponseDto
|
||||||
|
import net.dankito.banking.fints.rest.model.dto.response.AddAccountResponseDto
|
||||||
import net.dankito.banking.fints.rest.model.dto.response.GetAccountsTransactionsResponseDto
|
import net.dankito.banking.fints.rest.model.dto.response.GetAccountsTransactionsResponseDto
|
||||||
|
import net.dankito.banking.fints.rest.model.dto.response.RestResponse
|
||||||
import net.dankito.banking.fints.rest.service.fints4kService
|
import net.dankito.banking.fints.rest.service.fints4kService
|
||||||
|
import net.dankito.banking.fints.rest.service.model.GetAccountsTransactionsResponse
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.ws.rs.*
|
import javax.ws.rs.*
|
||||||
import javax.ws.rs.core.MediaType
|
import javax.ws.rs.core.MediaType
|
||||||
|
|
||||||
|
|
||||||
@Path("/fints/v1")
|
@Path("/fints/v1")
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
class fints4kResource {
|
class fints4kResource {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -20,31 +29,44 @@ class fints4kResource {
|
||||||
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
|
||||||
@Path("addaccount")
|
@Path("addaccount")
|
||||||
fun addAccount(
|
fun addAccount(request: AddAccountRequestDto): RestResponse<AddAccountResponseDto> {
|
||||||
request: AddAccountRequestDto,
|
val response = service.getAddAccountResponse(request)
|
||||||
@DefaultValue("false") @QueryParam("showRawResponse") showRawResponse: Boolean
|
|
||||||
): Any {
|
|
||||||
val clientResponse = service.getAddAccountResponse(request)
|
|
||||||
|
|
||||||
if (showRawResponse) {
|
return mapper.createRestResponse(response) { successResponse -> mapper.map(successResponse) }
|
||||||
return clientResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapper.map(clientResponse)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
|
||||||
@Path("transactions")
|
@Path("transactions")
|
||||||
fun getAccountTransactions(request: GetAccountsTransactionsRequestDto): GetAccountsTransactionsResponseDto {
|
fun getAccountTransactions(request: GetAccountsTransactionsRequestDto): GetAccountsTransactionsResponseDto {
|
||||||
val accountsTransactions = service.getAccountTransactions(request)
|
val response = service.getAccountTransactions(request)
|
||||||
|
|
||||||
return mapper.mapTransactions(accountsTransactions)
|
return mapper.map(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("tanresponse")
|
||||||
|
fun tanResponse(dto: TanResponseDto): RestResponse<Any> {
|
||||||
|
val response = service.handleTanResponse(dto)
|
||||||
|
|
||||||
|
// couldn't make it that compiler access ResponseHolder<*> for mapper.createRestResponse(), resulted in very cryptic "{"arity":0}" response -> handle it manually
|
||||||
|
response.response?.let { successResponse ->
|
||||||
|
return RestResponse.success(mapSuccessResponse(successResponse))
|
||||||
|
}
|
||||||
|
|
||||||
|
// all other cases map here, the responseMapper callback has no function
|
||||||
|
return mapper.createRestResponse(response) { it!! }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapSuccessResponse(successResponse: Any): Any {
|
||||||
|
return when (successResponse) {
|
||||||
|
is AddAccountResponse -> mapper.map(successResponse)
|
||||||
|
is GetAccountsTransactionsResponse -> mapper.map(successResponse)
|
||||||
|
is GetTransactionsResponse -> mapper.mapTransactions(successResponse)
|
||||||
|
else -> successResponse // add others / new ones here
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,13 +4,28 @@ import net.dankito.banking.fints.model.*
|
||||||
import net.dankito.banking.fints.response.client.AddAccountResponse
|
import net.dankito.banking.fints.response.client.AddAccountResponse
|
||||||
import net.dankito.banking.fints.response.client.FinTsClientResponse
|
import net.dankito.banking.fints.response.client.FinTsClientResponse
|
||||||
import net.dankito.banking.fints.response.client.GetTransactionsResponse
|
import net.dankito.banking.fints.response.client.GetTransactionsResponse
|
||||||
|
import net.dankito.banking.fints.rest.model.ResponseHolder
|
||||||
import net.dankito.banking.fints.rest.model.dto.response.*
|
import net.dankito.banking.fints.rest.model.dto.response.*
|
||||||
|
import net.dankito.banking.fints.rest.service.model.GetAccountsTransactionsResponse
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import javax.ws.rs.InternalServerErrorException
|
import javax.ws.rs.InternalServerErrorException
|
||||||
|
|
||||||
|
|
||||||
open class DtoMapper {
|
open class DtoMapper {
|
||||||
|
|
||||||
|
fun <DomainType, DtoType> createRestResponse(responseHolder: ResponseHolder<DomainType>, responseMapper: (DomainType) -> DtoType): RestResponse<DtoType> {
|
||||||
|
responseHolder.response?.let { response ->
|
||||||
|
return RestResponse.success(responseMapper(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
responseHolder.enterTanRequest?.let { enterTanRequest ->
|
||||||
|
return RestResponse.requiresTan(enterTanRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return RestResponse.error(responseHolder.error ?: "Unknown error")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun map(response: AddAccountResponse): AddAccountResponseDto {
|
open fun map(response: AddAccountResponse): AddAccountResponseDto {
|
||||||
return AddAccountResponseDto(
|
return AddAccountResponseDto(
|
||||||
response.successful,
|
response.successful,
|
||||||
|
@ -38,20 +53,21 @@ open class DtoMapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun mapTransactions(accountsTransactions: List<GetTransactionsResponse>?): GetAccountsTransactionsResponseDto {
|
open fun map(response: GetAccountsTransactionsResponse?): GetAccountsTransactionsResponseDto {
|
||||||
|
// TODO: is this still the case?
|
||||||
// TODO: if a TAN is required then accountsTransactions contains null value(s) (but why?) -> application crashes
|
// TODO: if a TAN is required then accountsTransactions contains null value(s) (but why?) -> application crashes
|
||||||
if (accountsTransactions == null) {
|
if (response == null) {
|
||||||
throw InternalServerErrorException("Could not fetch account transactions. Either TAN hasn't been entered or developers made a mistake.")
|
throw InternalServerErrorException("Could not fetch account transactions. Either TAN hasn't been entered or developers made a mistake.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetAccountsTransactionsResponseDto(accountsTransactions.mapNotNull { map(it) }) // TODO: is this correct removing accounts from result for which no transactions have been retrieved?
|
return GetAccountsTransactionsResponseDto(
|
||||||
}
|
// TODO: is this correct removing accounts from result for which no transactions have been retrieved?
|
||||||
|
response.transactionsPerAccount.filter { it.response?.retrievedData?.isNotEmpty() != false }
|
||||||
open fun map(accountTransactions: GetTransactionsResponse): GetAccountTransactionsResponseDto? {
|
.map { createRestResponse(it) { transactionsResponse -> mapTransactions(transactionsResponse) } }
|
||||||
if (accountTransactions.retrievedData.isEmpty()) {
|
)
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun mapTransactions(accountTransactions: GetTransactionsResponse): GetAccountTransactionsResponseDto {
|
||||||
val retrievedData = accountTransactions.retrievedData.first()
|
val retrievedData = accountTransactions.retrievedData.first()
|
||||||
val balance = mapNullable(retrievedData.balance)
|
val balance = mapNullable(retrievedData.balance)
|
||||||
val bookedTransactions = map(retrievedData.bookedTransactions)
|
val bookedTransactions = map(retrievedData.bookedTransactions)
|
||||||
|
|
|
@ -6,8 +6,9 @@ import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
|
||||||
open class EnterTanContext(
|
class EnterTanContext(
|
||||||
open val enterTanResult: AtomicReference<EnterTanResult>,
|
val enterTanResult: AtomicReference<EnterTanResult>,
|
||||||
open val countDownLatch: CountDownLatch,
|
val responseHolder: ResponseHolder<*>,
|
||||||
open val tanRequestedTimeStamp: Date = Date()
|
val countDownLatch: CountDownLatch,
|
||||||
|
val tanRequestedTimeStamp: Date = Date()
|
||||||
)
|
)
|
|
@ -1,11 +1,9 @@
|
||||||
package net.dankito.banking.fints.rest.model
|
package net.dankito.banking.fints.rest.model
|
||||||
|
|
||||||
import net.dankito.banking.fints.model.BankData
|
|
||||||
import net.dankito.banking.fints.model.TanChallenge
|
import net.dankito.banking.fints.model.TanChallenge
|
||||||
|
|
||||||
|
|
||||||
open class EnteringTanRequested(
|
class EnteringTanRequested(
|
||||||
open val tanRequestId: String,
|
val tanRequestId: String,
|
||||||
open val bank: BankData,
|
val tanChallenge: TanChallenge
|
||||||
open val tanChallenge: TanChallenge
|
|
||||||
)
|
)
|
|
@ -0,0 +1,63 @@
|
||||||
|
package net.dankito.banking.fints.rest.model
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
|
||||||
|
class ResponseHolder<T>() {
|
||||||
|
|
||||||
|
private var responseReceivedLatch = CountDownLatch(1)
|
||||||
|
|
||||||
|
constructor(error: String) : this() {
|
||||||
|
setError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var response: T? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
var error: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
var enterTanRequest: EnteringTanRequested? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
|
||||||
|
fun setResponse(response: T) {
|
||||||
|
this.response = response
|
||||||
|
|
||||||
|
signalResponseReceived()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setError(error: String) {
|
||||||
|
this.error = error
|
||||||
|
|
||||||
|
signalResponseReceived()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setEnterTanRequest(enterTanRequest: EnteringTanRequested) {
|
||||||
|
this.enterTanRequest = enterTanRequest
|
||||||
|
|
||||||
|
signalResponseReceived()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun waitForResponse() {
|
||||||
|
responseReceivedLatch.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetAfterEnteringTan() {
|
||||||
|
this.enterTanRequest = null
|
||||||
|
|
||||||
|
responseReceivedLatch = CountDownLatch(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun signalResponseReceived() {
|
||||||
|
responseReceivedLatch.countDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Error: $error, TAN requested: $enterTanRequest, success: $response"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package net.dankito.banking.fints.rest.model.dto.request
|
||||||
|
|
||||||
|
import net.dankito.banking.fints.model.EnterTanResult
|
||||||
|
|
||||||
|
|
||||||
|
class TanResponseDto(
|
||||||
|
val tanRequestId: String,
|
||||||
|
val enterTanResult: EnterTanResult
|
||||||
|
)
|
|
@ -2,5 +2,5 @@ package net.dankito.banking.fints.rest.model.dto.response
|
||||||
|
|
||||||
|
|
||||||
open class GetAccountsTransactionsResponseDto(
|
open class GetAccountsTransactionsResponseDto(
|
||||||
open val accounts: List<GetAccountTransactionsResponseDto>
|
open val transactionsPerAccount: List<RestResponse<GetAccountTransactionsResponseDto>>
|
||||||
)
|
)
|
|
@ -0,0 +1,12 @@
|
||||||
|
package net.dankito.banking.fints.rest.model.dto.response
|
||||||
|
|
||||||
|
|
||||||
|
enum class ResponseType {
|
||||||
|
|
||||||
|
Success,
|
||||||
|
|
||||||
|
Error,
|
||||||
|
|
||||||
|
TanRequired
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package net.dankito.banking.fints.rest.model.dto.response
|
||||||
|
|
||||||
|
import net.dankito.banking.fints.rest.model.EnteringTanRequested
|
||||||
|
|
||||||
|
|
||||||
|
class RestResponse<T>(
|
||||||
|
val status: ResponseType,
|
||||||
|
val errorMessage: String?,
|
||||||
|
val successResponse: T?,
|
||||||
|
val enteringTanRequested: EnteringTanRequested? = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun <T> success(result: T): RestResponse<T> {
|
||||||
|
return RestResponse(ResponseType.Success, null, result, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> error(errorMessage: String): RestResponse<T> {
|
||||||
|
return RestResponse(ResponseType.Error, errorMessage, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> requiresTan(enteringTanRequested: EnteringTanRequested): RestResponse<T> {
|
||||||
|
return RestResponse(ResponseType.TanRequired, null, null, enteringTanRequested)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,15 +2,18 @@ package net.dankito.banking.fints.rest.service
|
||||||
|
|
||||||
import net.dankito.banking.bankfinder.InMemoryBankFinder
|
import net.dankito.banking.bankfinder.InMemoryBankFinder
|
||||||
import net.dankito.banking.fints.FinTsClientForCustomer
|
import net.dankito.banking.fints.FinTsClientForCustomer
|
||||||
|
import net.dankito.banking.fints.callback.FinTsClientCallback
|
||||||
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
|
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
|
||||||
import net.dankito.banking.fints.model.*
|
import net.dankito.banking.fints.model.*
|
||||||
import net.dankito.banking.fints.response.BankResponse
|
|
||||||
import net.dankito.banking.fints.response.client.AddAccountResponse
|
import net.dankito.banking.fints.response.client.AddAccountResponse
|
||||||
import net.dankito.banking.fints.response.client.GetTransactionsResponse
|
import net.dankito.banking.fints.response.client.GetTransactionsResponse
|
||||||
import net.dankito.banking.fints.rest.model.BankAccessData
|
import net.dankito.banking.fints.rest.model.BankAccessData
|
||||||
import net.dankito.banking.fints.rest.model.EnterTanContext
|
import net.dankito.banking.fints.rest.model.EnterTanContext
|
||||||
import net.dankito.banking.fints.rest.model.dto.request.AccountRequestDto
|
import net.dankito.banking.fints.rest.model.EnteringTanRequested
|
||||||
|
import net.dankito.banking.fints.rest.model.ResponseHolder
|
||||||
import net.dankito.banking.fints.rest.model.dto.request.GetAccountsTransactionsRequestDto
|
import net.dankito.banking.fints.rest.model.dto.request.GetAccountsTransactionsRequestDto
|
||||||
|
import net.dankito.banking.fints.rest.model.dto.request.TanResponseDto
|
||||||
|
import net.dankito.banking.fints.rest.service.model.GetAccountsTransactionsResponse
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
@ -25,21 +28,21 @@ class fints4kService {
|
||||||
|
|
||||||
protected val clientCache = ConcurrentHashMap<String, FinTsClientForCustomer>()
|
protected val clientCache = ConcurrentHashMap<String, FinTsClientForCustomer>()
|
||||||
|
|
||||||
protected val tanRequests = mutableMapOf<String, EnterTanContext>()
|
// TODO: create clean up job for timed out TAN requests
|
||||||
|
protected val tanRequests = ConcurrentHashMap<String, EnterTanContext>()
|
||||||
|
|
||||||
|
|
||||||
fun getAddAccountResponse(accessData: BankAccessData): AddAccountResponse {
|
fun getAddAccountResponse(accessData: BankAccessData): ResponseHolder<AddAccountResponse> {
|
||||||
val (bank, errorMessage) = mapToBankData(accessData)
|
val (bank, errorMessage) = mapToBankData(accessData)
|
||||||
|
|
||||||
if (errorMessage != null) {
|
if (errorMessage != null) {
|
||||||
return AddAccountResponse(BankResponse(false, errorMessage = errorMessage), bank)
|
return ResponseHolder(errorMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
return getAccountData(bank)
|
return getAccountData(bank)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: as in most cases we really just want the account data, so just retrieve these without balances and transactions
|
protected fun getAccountData(bank: BankData): ResponseHolder<AddAccountResponse> {
|
||||||
protected fun getAccountData(bank: BankData): AddAccountResponse {
|
|
||||||
return getAsyncResponse(bank) { client, responseRetrieved ->
|
return getAsyncResponse(bank) { client, responseRetrieved ->
|
||||||
client.addAccountAsync(AddAccountParameter(bank)) { response ->
|
client.addAccountAsync(AddAccountParameter(bank)) { response ->
|
||||||
responseRetrieved(response)
|
responseRetrieved(response)
|
||||||
|
@ -48,28 +51,32 @@ class fints4kService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getAccountTransactions(dto: GetAccountsTransactionsRequestDto): List<GetTransactionsResponse> {
|
fun getAccountTransactions(dto: GetAccountsTransactionsRequestDto): GetAccountsTransactionsResponse {
|
||||||
val (bank, errorMessage) = mapToBankData(dto.credentials)
|
val (bank, errorMessage) = mapToBankData(dto.credentials)
|
||||||
|
|
||||||
if (errorMessage != null) {
|
if (errorMessage != null) {
|
||||||
return listOf(GetTransactionsResponse(BankResponse(false, errorMessage = errorMessage)))
|
return GetAccountsTransactionsResponse(listOf(ResponseHolder(errorMessage)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return dto.accounts.map { accountDto ->
|
val retrievedAccounts = getAccounts(bank)
|
||||||
val account = findAccount(dto.credentials, accountDto)
|
|
||||||
|
val transactionsPerAccount = dto.accounts.map { accountDto ->
|
||||||
|
val account = retrievedAccounts?.firstOrNull { it.accountIdentifier == accountDto.identifier }
|
||||||
|
|
||||||
return@map if (account != null) {
|
return@map if (account != null) {
|
||||||
val parameter = GetTransactionsParameter(account, dto.alsoRetrieveBalance, dto.fromDate, dto.toDate, abortIfTanIsRequired = dto.abortIfTanIsRequired)
|
val parameter = GetTransactionsParameter(account, dto.alsoRetrieveBalance, dto.fromDate, dto.toDate, abortIfTanIsRequired = dto.abortIfTanIsRequired)
|
||||||
getAccountTransactions(bank, parameter)
|
getAccountTransactions(bank, parameter)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
GetTransactionsResponse(BankResponse(false, errorMessage = "Account with identifier '${accountDto.identifier}' not found. Available accounts: " +
|
ResponseHolder("Account with identifier '${accountDto.identifier}' not found. Available accounts: " +
|
||||||
"${getCachedClient(dto.credentials)?.bank?.accounts?.map { it.accountIdentifier }?.joinToString(", ")}"))
|
"${retrievedAccounts?.joinToString(", ") { it.accountIdentifier }}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAccountTransactions(bank: BankData, parameter: GetTransactionsParameter): GetTransactionsResponse {
|
return GetAccountsTransactionsResponse(transactionsPerAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAccountTransactions(bank: BankData, parameter: GetTransactionsParameter): ResponseHolder<GetTransactionsResponse> {
|
||||||
return getAsyncResponse(bank) { client, responseRetrieved ->
|
return getAsyncResponse(bank) { client, responseRetrieved ->
|
||||||
client.getTransactionsAsync(parameter) { response ->
|
client.getTransactionsAsync(parameter) { response ->
|
||||||
responseRetrieved(response)
|
responseRetrieved(response)
|
||||||
|
@ -78,50 +85,68 @@ class fints4kService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected fun <T> getAsyncResponse(bank: BankData, executeRequest: (FinTsClientForCustomer, ((T) -> Unit)) -> Unit): T {
|
fun handleTanResponse(dto: TanResponseDto): ResponseHolder<*> {
|
||||||
val result = AtomicReference<T>()
|
tanRequests.remove(dto.tanRequestId)?.let { enterTanContext ->
|
||||||
val countDownLatch = CountDownLatch(1)
|
val responseHolder = enterTanContext.responseHolder
|
||||||
|
responseHolder.resetAfterEnteringTan()
|
||||||
|
|
||||||
val client = getClient(bank, result, countDownLatch)
|
enterTanContext.enterTanResult.set(dto.enterTanResult)
|
||||||
|
enterTanContext.countDownLatch.countDown()
|
||||||
|
|
||||||
|
responseHolder.waitForResponse()
|
||||||
|
|
||||||
|
return responseHolder
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseHolder<Any>("No TAN request found for TAN Request ID '${dto.tanRequestId}'")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected fun <T> getAsyncResponse(bank: BankData, executeRequest: (FinTsClientForCustomer, ((T) -> Unit)) -> Unit): ResponseHolder<T> {
|
||||||
|
val responseHolder = ResponseHolder<T>()
|
||||||
|
|
||||||
|
val client = getClient(bank, responseHolder)
|
||||||
|
|
||||||
executeRequest(client) { response ->
|
executeRequest(client) { response ->
|
||||||
result.set(response)
|
responseHolder.setResponse(response)
|
||||||
countDownLatch.countDown()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
countDownLatch.await()
|
responseHolder.waitForResponse()
|
||||||
|
|
||||||
return result.get()
|
return responseHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> getClient(bank: BankData, result: AtomicReference<T>, countDownLatch: CountDownLatch): FinTsClientForCustomer {
|
private fun <T> getClient(bank: BankData, responseHolder: ResponseHolder<T>): FinTsClientForCustomer {
|
||||||
val cacheKey = getCacheKey(bank.bankCode, bank.customerId)
|
val cacheKey = getCacheKey(bank.bankCode, bank.customerId)
|
||||||
|
|
||||||
clientCache[cacheKey]?.let {
|
clientCache[cacheKey]?.let {
|
||||||
|
// TODO: this will not work for two parallel calls for the same account if both calls require entering a TAN as second one overwrites callback and ResponseHolder of first one -> first one blocks forever
|
||||||
|
it.setCallback(createFinTsClientCallback(responseHolder)) // we have to newly create callback otherwise ResponseHolder instance of when client was created is used -> its CountDownLatch would never signal
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
// val client = FinTsClient(SimpleFinTsClientCallback { supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod? ->
|
val client = FinTsClientForCustomer(bank, createFinTsClientCallback(responseHolder))
|
||||||
val client = FinTsClientForCustomer(bank, SimpleFinTsClientCallback({ bank, tanChallenge -> handleEnterTan(bank, tanChallenge, countDownLatch, result) }) { supportedTanMethods, suggestedTanMethod ->
|
|
||||||
suggestedTanMethod
|
|
||||||
})
|
|
||||||
|
|
||||||
clientCache[cacheKey] = client
|
clientCache[cacheKey] = client
|
||||||
|
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun <T> handleEnterTan(bank: BankData, tanChallenge: TanChallenge, originatingRequestLatch: CountDownLatch, originatingRequestResult: AtomicReference<T>): EnterTanResult {
|
private fun <T> createFinTsClientCallback(responseHolder: ResponseHolder<T>): FinTsClientCallback {
|
||||||
|
return SimpleFinTsClientCallback({ bank, tanChallenge -> handleEnterTan(bank, tanChallenge, responseHolder) }) { supportedTanMethods, suggestedTanMethod ->
|
||||||
|
suggestedTanMethod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun <T> handleEnterTan(bank: BankData, tanChallenge: TanChallenge, responseHolder: ResponseHolder<T>): EnterTanResult {
|
||||||
val enterTanResult = AtomicReference<EnterTanResult>()
|
val enterTanResult = AtomicReference<EnterTanResult>()
|
||||||
val enterTanLatch = CountDownLatch(1)
|
val enterTanLatch = CountDownLatch(1)
|
||||||
|
|
||||||
val tanRequestId = UUID.randomUUID().toString()
|
val tanRequestId = UUID.randomUUID().toString()
|
||||||
|
|
||||||
// TODO: find a solution for returning TAN challenge to caller
|
tanRequests.put(tanRequestId, EnterTanContext(enterTanResult, responseHolder, enterTanLatch))
|
||||||
//originatingRequestResult.set(EnteringTanRequested(tanRequestId, bank, tanChallenge))
|
|
||||||
originatingRequestLatch.countDown()
|
|
||||||
|
|
||||||
tanRequests.put(tanRequestId, EnterTanContext(enterTanResult, enterTanLatch))
|
responseHolder.setEnterTanRequest(EnteringTanRequested(tanRequestId, tanChallenge))
|
||||||
|
|
||||||
enterTanLatch.await()
|
enterTanLatch.await()
|
||||||
|
|
||||||
|
@ -146,10 +171,23 @@ class fints4kService {
|
||||||
return Pair(bank, null)
|
return Pair(bank, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun findAccount(credentials: BankAccessData, accountDto: AccountRequestDto): AccountData? {
|
|
||||||
return getCachedClient(credentials)?.bank?.accounts?.firstOrNull { it.accountIdentifier == accountDto.identifier }
|
protected fun getAccounts(bank: BankData): List<AccountData>? {
|
||||||
|
getCachedClient(bank)?.bank?.accounts?.let {
|
||||||
|
return it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val addAccountResponse = getAccountData(bank)
|
||||||
|
|
||||||
|
return addAccountResponse.response?.bank?.accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getCachedClient(bank: BankData): FinTsClientForCustomer? {
|
||||||
|
val cacheKey = getCacheKey(bank.bankCode, bank.customerId)
|
||||||
|
|
||||||
|
return clientCache[cacheKey]
|
||||||
|
}
|
||||||
|
|
||||||
private fun getCachedClient(credentials: BankAccessData): FinTsClientForCustomer? {
|
private fun getCachedClient(credentials: BankAccessData): FinTsClientForCustomer? {
|
||||||
val cacheKey = getCacheKey(credentials.bankCode, credentials.loginName)
|
val cacheKey = getCacheKey(credentials.bankCode, credentials.loginName)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package net.dankito.banking.fints.rest.service.model
|
||||||
|
|
||||||
|
import net.dankito.banking.fints.response.client.GetTransactionsResponse
|
||||||
|
import net.dankito.banking.fints.rest.model.ResponseHolder
|
||||||
|
|
||||||
|
|
||||||
|
class GetAccountsTransactionsResponse(
|
||||||
|
val transactionsPerAccount: List<ResponseHolder<GetTransactionsResponse>>
|
||||||
|
)
|
Loading…
Reference in New Issue