Extracted RequestExecutor
This commit is contained in:
parent
41d02ec343
commit
4ddb55e612
|
@ -1,11 +1,9 @@
|
||||||
package net.dankito.banking.fints
|
package net.dankito.banking.fints
|
||||||
|
|
||||||
import net.dankito.banking.fints.callback.FinTsClientCallback
|
import net.dankito.banking.fints.callback.FinTsClientCallback
|
||||||
import net.dankito.banking.fints.log.MessageLogCollector
|
|
||||||
import net.dankito.banking.fints.messages.MessageBuilder
|
import net.dankito.banking.fints.messages.MessageBuilder
|
||||||
import net.dankito.banking.fints.model.*
|
import net.dankito.banking.fints.model.*
|
||||||
import net.dankito.banking.fints.model.mapper.ModelMapper
|
import net.dankito.banking.fints.model.mapper.ModelMapper
|
||||||
import net.dankito.banking.fints.response.ResponseParser
|
|
||||||
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
|
||||||
|
@ -20,17 +18,19 @@ import net.dankito.banking.fints.webclient.KtorWebClient
|
||||||
open class FinTsClientForCustomer(
|
open class FinTsClientForCustomer(
|
||||||
val bank: BankData,
|
val bank: BankData,
|
||||||
callback: FinTsClientCallback,
|
callback: FinTsClientCallback,
|
||||||
webClient: IWebClient = KtorWebClient(),
|
requestExecutor: RequestExecutor = RequestExecutor(),
|
||||||
base64Service: IBase64Service = PureKotlinBase64Service(),
|
|
||||||
messageBuilder: MessageBuilder = MessageBuilder(),
|
messageBuilder: MessageBuilder = MessageBuilder(),
|
||||||
responseParser: ResponseParser = ResponseParser(),
|
|
||||||
mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
||||||
messageLogCollector: MessageLogCollector = MessageLogCollector(),
|
|
||||||
modelMapper: ModelMapper = ModelMapper(messageBuilder),
|
modelMapper: ModelMapper = ModelMapper(messageBuilder),
|
||||||
product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically){}
|
product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
protected val client = FinTsClient(FinTsJobExecutor(callback, webClient, base64Service, messageBuilder, responseParser, mt940Parser, messageLogCollector, modelMapper, product))
|
constructor(bank: BankData, callback: FinTsClientCallback, webClient: IWebClient = KtorWebClient(), base64Service: IBase64Service = PureKotlinBase64Service(),
|
||||||
|
product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0")) // TODO: get version dynamically)
|
||||||
|
: this(bank, callback, RequestExecutor(MessageBuilder(), webClient, base64Service))
|
||||||
|
|
||||||
|
|
||||||
|
protected val client = FinTsClient(FinTsJobExecutor(callback, requestExecutor, messageBuilder, mt940Parser, modelMapper, product))
|
||||||
|
|
||||||
|
|
||||||
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package net.dankito.banking.fints
|
package net.dankito.banking.fints
|
||||||
|
|
||||||
import net.dankito.banking.fints.callback.FinTsClientCallback
|
import net.dankito.banking.fints.callback.FinTsClientCallback
|
||||||
import net.dankito.banking.fints.log.IMessageLogAppender
|
|
||||||
import net.dankito.banking.fints.log.MessageLogCollector
|
|
||||||
import net.dankito.banking.fints.messages.MessageBuilder
|
import net.dankito.banking.fints.messages.MessageBuilder
|
||||||
import net.dankito.banking.fints.messages.MessageBuilderResult
|
import net.dankito.banking.fints.messages.MessageBuilderResult
|
||||||
import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.signatur.VersionDesSicherheitsverfahrens
|
||||||
|
@ -13,7 +11,6 @@ import net.dankito.banking.fints.model.*
|
||||||
import net.dankito.banking.fints.model.mapper.ModelMapper
|
import net.dankito.banking.fints.model.mapper.ModelMapper
|
||||||
import net.dankito.banking.fints.response.BankResponse
|
import net.dankito.banking.fints.response.BankResponse
|
||||||
import net.dankito.banking.fints.response.InstituteSegmentId
|
import net.dankito.banking.fints.response.InstituteSegmentId
|
||||||
import net.dankito.banking.fints.response.ResponseParser
|
|
||||||
import net.dankito.banking.fints.response.client.FinTsClientResponse
|
import net.dankito.banking.fints.response.client.FinTsClientResponse
|
||||||
import net.dankito.banking.fints.response.client.GetTanMediaListResponse
|
import net.dankito.banking.fints.response.client.GetTanMediaListResponse
|
||||||
import net.dankito.banking.fints.response.client.GetTransactionsResponse
|
import net.dankito.banking.fints.response.client.GetTransactionsResponse
|
||||||
|
@ -23,15 +20,8 @@ import net.dankito.banking.fints.tan.FlickerCodeDecoder
|
||||||
import net.dankito.banking.fints.tan.TanImageDecoder
|
import net.dankito.banking.fints.tan.TanImageDecoder
|
||||||
import net.dankito.banking.fints.transactions.IAccountTransactionsParser
|
import net.dankito.banking.fints.transactions.IAccountTransactionsParser
|
||||||
import net.dankito.banking.fints.transactions.Mt940AccountTransactionsParser
|
import net.dankito.banking.fints.transactions.Mt940AccountTransactionsParser
|
||||||
import net.dankito.banking.fints.util.IBase64Service
|
|
||||||
import net.dankito.banking.fints.util.PureKotlinBase64Service
|
|
||||||
import net.dankito.utils.multiplatform.log.Logger
|
|
||||||
import net.dankito.utils.multiplatform.log.LoggerFactory
|
import net.dankito.utils.multiplatform.log.LoggerFactory
|
||||||
import net.dankito.banking.fints.webclient.IWebClient
|
|
||||||
import net.dankito.banking.fints.webclient.KtorWebClient
|
|
||||||
import net.dankito.banking.fints.webclient.WebClientResponse
|
|
||||||
import net.dankito.utils.multiplatform.Date
|
import net.dankito.utils.multiplatform.Date
|
||||||
import net.dankito.utils.multiplatform.getInnerExceptionMessage
|
|
||||||
import net.dankito.utils.multiplatform.ObjectReference
|
import net.dankito.utils.multiplatform.ObjectReference
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,12 +32,9 @@ import net.dankito.utils.multiplatform.ObjectReference
|
||||||
*/
|
*/
|
||||||
open class FinTsJobExecutor(
|
open class FinTsJobExecutor(
|
||||||
protected open val callback: FinTsClientCallback,
|
protected open val callback: FinTsClientCallback,
|
||||||
protected open val webClient: IWebClient = KtorWebClient(),
|
protected open val requestExecutor: RequestExecutor = RequestExecutor(),
|
||||||
protected open val base64Service: IBase64Service = PureKotlinBase64Service(),
|
|
||||||
protected open val messageBuilder: MessageBuilder = MessageBuilder(),
|
protected open val messageBuilder: MessageBuilder = MessageBuilder(),
|
||||||
protected open val responseParser: ResponseParser = ResponseParser(),
|
|
||||||
protected open val mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
protected open val mt940Parser: IAccountTransactionsParser = Mt940AccountTransactionsParser(),
|
||||||
protected open val messageLogCollector: MessageLogCollector = MessageLogCollector(),
|
|
||||||
protected open val modelMapper: ModelMapper = ModelMapper(messageBuilder),
|
protected open val modelMapper: ModelMapper = ModelMapper(messageBuilder),
|
||||||
protected open val product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically
|
protected open val product: ProductData = ProductData("15E53C26816138699C7B6A3E8", "1.0.0") // TODO: get version dynamically
|
||||||
) {
|
) {
|
||||||
|
@ -58,20 +45,11 @@ open class FinTsJobExecutor(
|
||||||
|
|
||||||
|
|
||||||
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
||||||
get() = messageLogCollector.messageLogWithoutSensitiveData
|
get() = requestExecutor.messageLogWithoutSensitiveData
|
||||||
|
|
||||||
protected open val messageLogAppender: IMessageLogAppender = object : IMessageLogAppender {
|
|
||||||
|
|
||||||
override fun logError(message: String, e: Exception?, logger: Logger?, bank: BankData?) {
|
|
||||||
messageLogCollector.logError(message, e, logger, bank)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
responseParser.logAppender = messageLogAppender
|
mt940Parser.logAppender = requestExecutor.messageLogAppender // TODO: find a better solution to append messages to MessageLog
|
||||||
mt940Parser.logAppender = messageLogAppender
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -382,142 +360,17 @@ open class FinTsJobExecutor(
|
||||||
|
|
||||||
|
|
||||||
protected open fun getAndHandleResponseForMessage(message: MessageBuilderResult, dialogContext: DialogContext, callback: (BankResponse) -> Unit) {
|
protected open fun getAndHandleResponseForMessage(message: MessageBuilderResult, dialogContext: DialogContext, callback: (BankResponse) -> Unit) {
|
||||||
if (message.createdMessage == null) {
|
requestExecutor.getAndHandleResponseForMessage(
|
||||||
callback(BankResponse(false, messageCreationError = message))
|
message,
|
||||||
}
|
dialogContext,
|
||||||
else {
|
{ tanResponse, bankResponse, tanRequiredCallback -> handleEnteringTanRequired(tanResponse, bankResponse, dialogContext, tanRequiredCallback) },
|
||||||
getAndHandleResponseForMessage(message.createdMessage, dialogContext) { response ->
|
callback)
|
||||||
handleMayRequiresTan(response, dialogContext) { handledResponse ->
|
|
||||||
// if there's a Aufsetzpunkt (continuationId) set, then response is not complete yet, there's more information to fetch by sending this Aufsetzpunkt
|
|
||||||
handledResponse.aufsetzpunkt?.let { continuationId ->
|
|
||||||
if (handledResponse.followUpResponse == null) { // for re-sent messages followUpResponse is already set and dialog already closed -> would be overwritten with an error response that dialog is closed
|
|
||||||
if (message.isSendEnteredTanMessage() == false) { // for sending TAN no follow up message can be created -> filter out, otherwise chunkedResponseHandler would get called twice for same response
|
|
||||||
dialogContext.chunkedResponseHandler?.invoke(handledResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
getFollowUpMessageForContinuationId(handledResponse, continuationId, message, dialogContext) { followUpResponse ->
|
|
||||||
handledResponse.followUpResponse = followUpResponse
|
|
||||||
handledResponse.hasFollowUpMessageButCouldNotReceiveIt = handledResponse.followUpResponse == null
|
|
||||||
|
|
||||||
callback(handledResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
callback(handledResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?: run {
|
|
||||||
// e.g. response = enter TAN response, but handledResponse is then response after entering TAN, e.g. account transactions
|
|
||||||
// -> chunkedResponseHandler would get called for same handledResponse multiple times
|
|
||||||
if (response == handledResponse) {
|
|
||||||
dialogContext.chunkedResponseHandler?.invoke(handledResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(handledResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun getAndHandleResponseForMessage(requestBody: String, dialogContext: DialogContext, callback: (BankResponse) -> Unit) {
|
|
||||||
addMessageLog(requestBody, MessageLogEntryType.Sent, dialogContext)
|
|
||||||
|
|
||||||
getResponseForMessage(requestBody, dialogContext.bank.finTs3ServerAddress) { webResponse ->
|
|
||||||
val response = handleResponse(webResponse, dialogContext)
|
|
||||||
|
|
||||||
dialogContext.response = response
|
|
||||||
|
|
||||||
response.messageHeader?.let { header -> dialogContext.dialogId = header.dialogId }
|
|
||||||
dialogContext.didBankCloseDialog = response.didBankCloseDialog
|
|
||||||
|
|
||||||
callback(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun getResponseForMessage(requestBody: String, finTs3ServerAddress: String, callback: (WebClientResponse) -> Unit) {
|
|
||||||
val encodedRequestBody = base64Service.encode(requestBody)
|
|
||||||
|
|
||||||
webClient.post(finTs3ServerAddress, encodedRequestBody, "application/octet-stream", IWebClient.DefaultUserAgent, callback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun fireAndForgetMessage(message: MessageBuilderResult, dialogContext: DialogContext) {
|
protected open fun fireAndForgetMessage(message: MessageBuilderResult, dialogContext: DialogContext) {
|
||||||
message.createdMessage?.let { requestBody ->
|
requestExecutor.fireAndForgetMessage(message, dialogContext)
|
||||||
addMessageLog(requestBody, MessageLogEntryType.Sent, dialogContext)
|
|
||||||
|
|
||||||
getResponseForMessage(requestBody, dialogContext.bank.finTs3ServerAddress) { }
|
|
||||||
|
|
||||||
// if really needed add received response to message log here
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun handleResponse(webResponse: WebClientResponse, dialogContext: DialogContext): BankResponse {
|
|
||||||
val responseBody = webResponse.body
|
|
||||||
|
|
||||||
if (webResponse.successful && responseBody != null) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
val decodedResponse = decodeBase64Response(responseBody)
|
|
||||||
|
|
||||||
addMessageLog(decodedResponse, MessageLogEntryType.Received, dialogContext)
|
|
||||||
|
|
||||||
return responseParser.parse(decodedResponse)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logError("Could not decode responseBody:\r\n'$responseBody'", dialogContext, e)
|
|
||||||
|
|
||||||
return BankResponse(false, errorMessage = e.getInnerExceptionMessage())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val bank = dialogContext.bank
|
|
||||||
logError("Request to $bank (${bank.finTs3ServerAddress}) failed", dialogContext, webResponse.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return BankResponse(false, errorMessage = webResponse.error?.getInnerExceptionMessage())
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun decodeBase64Response(responseBody: String): String {
|
|
||||||
return base64Service.decode(responseBody.replace("\r", "").replace("\n", ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected open fun getFollowUpMessageForContinuationId(response: BankResponse, continuationId: String, message: MessageBuilderResult,
|
|
||||||
dialogContext: DialogContext, callback: (BankResponse?) -> Unit) {
|
|
||||||
|
|
||||||
messageBuilder.rebuildMessageWithContinuationId(message, continuationId, dialogContext)?.let { followUpMessage ->
|
|
||||||
getAndHandleResponseForMessage(followUpMessage, dialogContext, callback)
|
|
||||||
}
|
|
||||||
?: run { callback(null) }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected open fun handleMayRequiresTan(response: BankResponse, dialogContext: DialogContext, callback: (BankResponse) -> Unit) { // TODO: use response from DialogContext
|
|
||||||
|
|
||||||
if (response.isStrongAuthenticationRequired) {
|
|
||||||
if (dialogContext.abortIfTanIsRequired) {
|
|
||||||
response.tanRequiredButWeWereToldToAbortIfSo = true
|
|
||||||
|
|
||||||
callback(response)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
else if (response.tanResponse != null) {
|
|
||||||
response.tanResponse?.let { tanResponse ->
|
|
||||||
handleEnteringTanRequired(tanResponse, response, dialogContext, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check if response contains '3931 TAN-Generator gesperrt, Synchronisierung erforderlich' or
|
|
||||||
// '3933 TAN-Generator gesperrt, Synchronisierung erforderlich Kartennummer ##########' message,
|
|
||||||
// call callback.enterAtc() and implement and call HKTSY job (p. 77)
|
|
||||||
|
|
||||||
// TODO: also check '9931 Sperrung des Kontos nach %1 Fehlversuchen' -> if %1 == 3 synchronize TAN generator
|
|
||||||
// as it's quite unrealistic that user entered TAN wrong three times, in most cases TAN generator is not synchronized
|
|
||||||
|
|
||||||
callback(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun handleEnteringTanRequired(tanResponse: TanResponse, response: BankResponse, dialogContext: DialogContext, callback: (BankResponse) -> Unit) {
|
protected open fun handleEnteringTanRequired(tanResponse: TanResponse, response: BankResponse, dialogContext: DialogContext, callback: (BankResponse) -> Unit) {
|
||||||
val bank = dialogContext.bank // TODO: copy required data to TanChallenge
|
val bank = dialogContext.bank // TODO: copy required data to TanChallenge
|
||||||
|
@ -555,7 +408,8 @@ open class FinTsJobExecutor(
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(tanChallenge: TanChallenge, tanResponse: TanResponse,
|
protected open fun mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(tanChallenge: TanChallenge, tanResponse: TanResponse,
|
||||||
userDidCancelEnteringTan: ObjectReference<Boolean>, dialogContext: DialogContext) {
|
userDidCancelEnteringTan: ObjectReference<Boolean>, dialogContext: DialogContext
|
||||||
|
) {
|
||||||
dialogContext.bank.selectedTanMethod.decoupledParameters?.let { decoupledTanMethodParameters ->
|
dialogContext.bank.selectedTanMethod.decoupledParameters?.let { decoupledTanMethodParameters ->
|
||||||
if (tanResponse.tanProcess == TanProcess.AppTan && decoupledTanMethodParameters.periodicStateRequestsAllowed) {
|
if (tanResponse.tanProcess == TanProcess.AppTan && decoupledTanMethodParameters.periodicStateRequestsAllowed) {
|
||||||
automaticallyRetrieveIfUserEnteredDecoupledTan(tanChallenge, userDidCancelEnteringTan, dialogContext)
|
automaticallyRetrieveIfUserEnteredDecoupledTan(tanChallenge, userDidCancelEnteringTan, dialogContext)
|
||||||
|
@ -840,13 +694,4 @@ open class FinTsJobExecutor(
|
||||||
return modelMapper.isJobSupported(bank, segmentId)
|
return modelMapper.isJobSupported(bank, segmentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected open fun addMessageLog(message: String, type: MessageLogEntryType, dialogContext: DialogContext) {
|
|
||||||
messageLogCollector.addMessageLog(message, type, dialogContext.bank)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun logError(message: String, dialogContext: DialogContext, e: Exception?) {
|
|
||||||
messageLogAppender.logError(message, e, log, dialogContext.bank)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
package net.dankito.banking.fints
|
||||||
|
|
||||||
|
import net.dankito.banking.fints.log.IMessageLogAppender
|
||||||
|
import net.dankito.banking.fints.log.MessageLogCollector
|
||||||
|
import net.dankito.banking.fints.messages.MessageBuilder
|
||||||
|
import net.dankito.banking.fints.messages.MessageBuilderResult
|
||||||
|
import net.dankito.banking.fints.model.*
|
||||||
|
import net.dankito.banking.fints.response.BankResponse
|
||||||
|
import net.dankito.banking.fints.response.ResponseParser
|
||||||
|
import net.dankito.banking.fints.response.segments.TanResponse
|
||||||
|
import net.dankito.banking.fints.util.IBase64Service
|
||||||
|
import net.dankito.banking.fints.util.PureKotlinBase64Service
|
||||||
|
import net.dankito.banking.fints.webclient.IWebClient
|
||||||
|
import net.dankito.banking.fints.webclient.KtorWebClient
|
||||||
|
import net.dankito.banking.fints.webclient.WebClientResponse
|
||||||
|
import net.dankito.utils.multiplatform.getInnerExceptionMessage
|
||||||
|
import net.dankito.utils.multiplatform.log.Logger
|
||||||
|
import net.dankito.utils.multiplatform.log.LoggerFactory
|
||||||
|
|
||||||
|
|
||||||
|
open class RequestExecutor(
|
||||||
|
protected open val messageBuilder: MessageBuilder = MessageBuilder(),
|
||||||
|
protected open val webClient: IWebClient = KtorWebClient(),
|
||||||
|
protected open val base64Service: IBase64Service = PureKotlinBase64Service(),
|
||||||
|
protected open val responseParser: ResponseParser = ResponseParser(),
|
||||||
|
protected open val messageLogCollector: MessageLogCollector = MessageLogCollector()
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(FinTsJobExecutor::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open val messageLogWithoutSensitiveData: List<MessageLogEntry>
|
||||||
|
get() = messageLogCollector.messageLogWithoutSensitiveData
|
||||||
|
|
||||||
|
internal open val messageLogAppender: IMessageLogAppender = object : IMessageLogAppender {
|
||||||
|
|
||||||
|
override fun logError(message: String, e: Exception?, logger: Logger?, bank: BankData?) {
|
||||||
|
messageLogCollector.logError(message, e, logger, bank)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
open fun getAndHandleResponseForMessage(message: MessageBuilderResult, dialogContext: DialogContext,
|
||||||
|
tanRequiredCallback: (TanResponse, BankResponse, callback: (BankResponse) -> Unit) -> Unit, callback: (BankResponse) -> Unit) {
|
||||||
|
if (message.createdMessage == null) {
|
||||||
|
callback(BankResponse(false, messageCreationError = message))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getAndHandleResponseForMessage(message.createdMessage, dialogContext) { response ->
|
||||||
|
handleMayRequiresTan(response, dialogContext, tanRequiredCallback) { handledResponse ->
|
||||||
|
// if there's a Aufsetzpunkt (continuationId) set, then response is not complete yet, there's more information to fetch by sending this Aufsetzpunkt
|
||||||
|
handledResponse.aufsetzpunkt?.let { continuationId ->
|
||||||
|
if (handledResponse.followUpResponse == null) { // for re-sent messages followUpResponse is already set and dialog already closed -> would be overwritten with an error response that dialog is closed
|
||||||
|
if (message.isSendEnteredTanMessage() == false) { // for sending TAN no follow up message can be created -> filter out, otherwise chunkedResponseHandler would get called twice for same response
|
||||||
|
dialogContext.chunkedResponseHandler?.invoke(handledResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
getFollowUpMessageForContinuationId(handledResponse, continuationId, message, dialogContext, tanRequiredCallback) { followUpResponse ->
|
||||||
|
handledResponse.followUpResponse = followUpResponse
|
||||||
|
handledResponse.hasFollowUpMessageButCouldNotReceiveIt = handledResponse.followUpResponse == null
|
||||||
|
|
||||||
|
callback(handledResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback(handledResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?: run {
|
||||||
|
// e.g. response = enter TAN response, but handledResponse is then response after entering TAN, e.g. account transactions
|
||||||
|
// -> chunkedResponseHandler would get called for same handledResponse multiple times
|
||||||
|
if (response == handledResponse) {
|
||||||
|
dialogContext.chunkedResponseHandler?.invoke(handledResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(handledResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun getAndHandleResponseForMessage(requestBody: String, dialogContext: DialogContext, callback: (BankResponse) -> Unit) {
|
||||||
|
addMessageLog(requestBody, MessageLogEntryType.Sent, dialogContext)
|
||||||
|
|
||||||
|
getResponseForMessage(requestBody, dialogContext.bank.finTs3ServerAddress) { webResponse ->
|
||||||
|
val response = handleResponse(webResponse, dialogContext)
|
||||||
|
|
||||||
|
dialogContext.response = response
|
||||||
|
|
||||||
|
response.messageHeader?.let { header -> dialogContext.dialogId = header.dialogId }
|
||||||
|
dialogContext.didBankCloseDialog = response.didBankCloseDialog
|
||||||
|
|
||||||
|
callback(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun getResponseForMessage(requestBody: String, finTs3ServerAddress: String, callback: (WebClientResponse) -> Unit) {
|
||||||
|
val encodedRequestBody = base64Service.encode(requestBody)
|
||||||
|
|
||||||
|
webClient.post(finTs3ServerAddress, encodedRequestBody, "application/octet-stream", IWebClient.DefaultUserAgent, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun fireAndForgetMessage(message: MessageBuilderResult, dialogContext: DialogContext) {
|
||||||
|
message.createdMessage?.let { requestBody ->
|
||||||
|
addMessageLog(requestBody, MessageLogEntryType.Sent, dialogContext)
|
||||||
|
|
||||||
|
getResponseForMessage(requestBody, dialogContext.bank.finTs3ServerAddress) { }
|
||||||
|
|
||||||
|
// if really needed add received response to message log here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun handleResponse(webResponse: WebClientResponse, dialogContext: DialogContext): BankResponse {
|
||||||
|
val responseBody = webResponse.body
|
||||||
|
|
||||||
|
if (webResponse.successful && responseBody != null) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
val decodedResponse = decodeBase64Response(responseBody)
|
||||||
|
|
||||||
|
addMessageLog(decodedResponse, MessageLogEntryType.Received, dialogContext)
|
||||||
|
|
||||||
|
return responseParser.parse(decodedResponse)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError("Could not decode responseBody:\r\n'$responseBody'", dialogContext, e)
|
||||||
|
|
||||||
|
return BankResponse(false, errorMessage = e.getInnerExceptionMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val bank = dialogContext.bank
|
||||||
|
logError("Request to $bank (${bank.finTs3ServerAddress}) failed", dialogContext, webResponse.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return BankResponse(false, errorMessage = webResponse.error?.getInnerExceptionMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun decodeBase64Response(responseBody: String): String {
|
||||||
|
return base64Service.decode(responseBody.replace("\r", "").replace("\n", ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun getFollowUpMessageForContinuationId(response: BankResponse, continuationId: String, message: MessageBuilderResult, dialogContext: DialogContext,
|
||||||
|
tanRequiredCallback: (TanResponse, BankResponse, callback: (BankResponse) -> Unit) -> Unit,
|
||||||
|
callback: (BankResponse?) -> Unit) {
|
||||||
|
|
||||||
|
messageBuilder.rebuildMessageWithContinuationId(message, continuationId, dialogContext)?.let { followUpMessage ->
|
||||||
|
getAndHandleResponseForMessage(followUpMessage, dialogContext, tanRequiredCallback, callback)
|
||||||
|
}
|
||||||
|
?: run { callback(null) }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun handleMayRequiresTan(response: BankResponse, dialogContext: DialogContext,
|
||||||
|
tanRequiredCallback: (TanResponse, BankResponse, callback: (BankResponse) -> Unit) -> Unit,
|
||||||
|
callback: (BankResponse) -> Unit) { // TODO: use response from DialogContext
|
||||||
|
|
||||||
|
if (response.isStrongAuthenticationRequired) {
|
||||||
|
if (dialogContext.abortIfTanIsRequired) {
|
||||||
|
response.tanRequiredButWeWereToldToAbortIfSo = true
|
||||||
|
|
||||||
|
callback(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
else if (response.tanResponse != null) {
|
||||||
|
response.tanResponse?.let { tanResponse ->
|
||||||
|
tanRequiredCallback(tanResponse, response, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check if response contains '3931 TAN-Generator gesperrt, Synchronisierung erforderlich' or
|
||||||
|
// '3933 TAN-Generator gesperrt, Synchronisierung erforderlich Kartennummer ##########' message,
|
||||||
|
// call callback.enterAtc() and implement and call HKTSY job (p. 77)
|
||||||
|
|
||||||
|
// TODO: also check '9931 Sperrung des Kontos nach %1 Fehlversuchen' -> if %1 == 3 synchronize TAN generator
|
||||||
|
// as it's quite unrealistic that user entered TAN wrong three times, in most cases TAN generator is not synchronized
|
||||||
|
|
||||||
|
callback(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun addMessageLog(message: String, type: MessageLogEntryType, dialogContext: DialogContext) {
|
||||||
|
messageLogCollector.addMessageLog(message, type, dialogContext.bank)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun logError(message: String, dialogContext: DialogContext, e: Exception?) {
|
||||||
|
messageLogAppender.logError(message, e, log, dialogContext.bank)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue