Replaced callbacks with coroutines

This commit is contained in:
dankito 2022-02-19 13:17:02 +01:00
parent bdd28f2587
commit 54c430af2b
14 changed files with 375 additions and 426 deletions

View File

@ -3,6 +3,7 @@ package net.codinux.banking.fints4k.android
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.FinTsClientDeprecated import net.dankito.banking.fints.FinTsClientDeprecated
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
@ -27,10 +28,11 @@ class Presenter {
fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String, retrievedResult: (AddAccountResponse) -> Unit) { fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String, retrievedResult: (AddAccountResponse) -> Unit) {
fintsClient.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress)) { response -> GlobalScope.launch(Dispatchers.IO) {
val response = fintsClient.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress))
log.info("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}") log.info("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}")
GlobalScope.launch(Dispatchers.Main) { withContext(Dispatchers.Main) {
retrievedResult(response) retrievedResult(response)
} }
} }

View File

@ -1,3 +1,5 @@
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.dankito.banking.fints.FinTsClientDeprecated import net.dankito.banking.fints.FinTsClientDeprecated
import net.dankito.banking.fints.model.AccountTransaction import net.dankito.banking.fints.model.AccountTransaction
import net.dankito.banking.fints.model.AddAccountParameter import net.dankito.banking.fints.model.AddAccountParameter
@ -23,7 +25,8 @@ class AccountTransactionsView(props: AccountTransactionsViewProps) : RComponent<
// due to CORS your bank's servers can not be requested directly from browser -> set a CORS proxy url in main.kt // due to CORS your bank's servers can not be requested directly from browser -> set a CORS proxy url in main.kt
// TODO: set your credentials here // TODO: set your credentials here
props.client.addAccountAsync(AddAccountParameter("", "", "", "")) { response -> GlobalScope.launch {
val response = props.client.addAccountAsync(AddAccountParameter("", "", "", ""))
if (response.successful) { if (response.successful) {
val balance = response.retrievedData.sumOf { it.balance?.amount?.string?.replace(',', '.')?.toDoubleOrNull() ?: 0.0 } // i know, double is not an appropriate data type for amounts val balance = response.retrievedData.sumOf { it.balance?.amount?.string?.replace(',', '.')?.toDoubleOrNull() ?: 0.0 } // i know, double is not an appropriate data type for amounts

View File

@ -44,7 +44,7 @@ open class FinTsClientDeprecated(
open fun getAnonymousBankInfoAsync(bank: BankData, callback: (FinTsClientResponse) -> Unit) { open fun getAnonymousBankInfoAsync(bank: BankData, callback: (FinTsClientResponse) -> Unit) {
GlobalScope.launch { GlobalScope.launch {
getAnonymousBankInfo(bank, callback) callback(getAnonymousBankInfo(bank))
} }
} }
@ -54,90 +54,74 @@ open class FinTsClientDeprecated(
* *
* On success [bank] parameter is updated afterwards. * On success [bank] parameter is updated afterwards.
*/ */
open fun getAnonymousBankInfo(bank: BankData, callback: (FinTsClientResponse) -> Unit) { open suspend fun getAnonymousBankInfo(bank: BankData): FinTsClientResponse {
val context = JobContext(JobContextType.AnonymousBankInfo, this.callback, product, bank) val context = JobContext(JobContextType.AnonymousBankInfo, this.callback, product, bank)
jobExecutor.getAnonymousBankInfo(context) { response -> val response = jobExecutor.getAnonymousBankInfo(context)
callback(FinTsClientResponse(context, response)) return FinTsClientResponse(context, response)
}
} }
open fun addAccountAsync(parameter: AddAccountParameter, callback: (AddAccountResponse) -> Unit) { open suspend fun addAccountAsync(parameter: AddAccountParameter): AddAccountResponse {
val bank = parameter.bank val bank = parameter.bank
val context = JobContext(JobContextType.AddAccount, this.callback, product, bank) val context = JobContext(JobContextType.AddAccount, this.callback, product, bank)
/* First dialog: Get user's basic data like BPD, customer system ID and her TAN methods */ /* First dialog: Get user's basic data like BPD, customer system ID and her TAN methods */
jobExecutor.retrieveBasicDataLikeUsersTanMethods(context, parameter.preferredTanMethods, parameter.preferredTanMedium) { newUserInfoResponse -> val newUserInfoResponse = jobExecutor.retrieveBasicDataLikeUsersTanMethods(context, parameter.preferredTanMethods, parameter.preferredTanMedium)
if (newUserInfoResponse.successful == false) { // bank parameter (FinTS server address, ...) already seem to be wrong if (newUserInfoResponse.successful == false) { // bank parameter (FinTS server address, ...) already seem to be wrong
callback(AddAccountResponse(context, newUserInfoResponse)) return AddAccountResponse(context, newUserInfoResponse)
return@retrieveBasicDataLikeUsersTanMethods
} }
/* Second dialog, executed in retrieveBasicDataLikeUsersTanMethods() if required: some banks require that in order to initialize a dialog with /* Second dialog, executed in retrieveBasicDataLikeUsersTanMethods() if required: some banks require that in order to initialize a dialog with
strong customer authorization TAN media is required */ strong customer authorization TAN media is required */
addAccountGetAccountsAndTransactions(context, parameter, callback) return addAccountGetAccountsAndTransactions(context, parameter)
}
} }
protected open fun addAccountGetAccountsAndTransactions(context: JobContext, parameter: AddAccountParameter, protected open suspend fun addAccountGetAccountsAndTransactions(context: JobContext, parameter: AddAccountParameter): AddAccountResponse {
callback: (AddAccountResponse) -> Unit) {
/* Third dialog: Now we can initialize our first dialog with strong customer authorization. Use it to get UPD and customer's accounts */ /* Third dialog: Now we can initialize our first dialog with strong customer authorization. Use it to get UPD and customer's accounts */
jobExecutor.getAccounts(context) { getAccountsResponse -> val getAccountsResponse = jobExecutor.getAccounts(context)
if (getAccountsResponse.successful == false) { if (getAccountsResponse.successful == false) {
callback(AddAccountResponse(context, getAccountsResponse)) return AddAccountResponse(context, getAccountsResponse)
return@getAccounts
} }
/* Fourth dialog (if requested): Try to retrieve account balances and transactions of last 90 days without TAN */ /* Fourth dialog (if requested): Try to retrieve account balances and transactions of last 90 days without TAN */
if (parameter.fetchBalanceAndTransactions) { if (parameter.fetchBalanceAndTransactions) {
addAccountGetAccountBalancesAndTransactions(context, getAccountsResponse, callback) return addAccountGetAccountBalancesAndTransactions(context, getAccountsResponse)
} }
else { else {
addAccountDone(context, getAccountsResponse, listOf(), callback) return addAccountDone(context, getAccountsResponse, listOf())
}
} }
} }
protected open fun addAccountGetAccountBalancesAndTransactions(context: JobContext, getAccountsResponse: BankResponse, protected open suspend fun addAccountGetAccountBalancesAndTransactions(context: JobContext, getAccountsResponse: BankResponse): AddAccountResponse {
callback: (AddAccountResponse) -> Unit) {
val bank = context.bank val bank = context.bank
val retrievedTransactionsResponses = mutableListOf<GetAccountTransactionsResponse>() val retrievedTransactionsResponses = mutableListOf<GetAccountTransactionsResponse>()
val accountsSupportingRetrievingTransactions = bank.accounts.filter { it.supportsRetrievingBalance || it.supportsRetrievingAccountTransactions } val accountsSupportingRetrievingTransactions = bank.accounts.filter { it.supportsRetrievingBalance || it.supportsRetrievingAccountTransactions }
val countAccountsSupportingRetrievingTransactions = accountsSupportingRetrievingTransactions.size
var countRetrievedAccounts = 0
if (countAccountsSupportingRetrievingTransactions == 0) { if (accountsSupportingRetrievingTransactions.isEmpty()) {
addAccountDone(context, getAccountsResponse, retrievedTransactionsResponses, callback) return addAccountDone(context, getAccountsResponse, retrievedTransactionsResponses)
return // not necessary just to make it clearer that code below doesn't get called
} }
accountsSupportingRetrievingTransactions.forEach { account -> accountsSupportingRetrievingTransactions.forEach { account ->
tryGetAccountTransactionsOfLast90DaysWithoutTan(bank, account) { response -> retrievedTransactionsResponses.add(tryGetAccountTransactionsOfLast90DaysWithoutTan(bank, account))
retrievedTransactionsResponses.add(response) }
countRetrievedAccounts++ return addAccountDone(context, getAccountsResponse, retrievedTransactionsResponses)
if (countRetrievedAccounts == countAccountsSupportingRetrievingTransactions) {
addAccountDone(context, getAccountsResponse, retrievedTransactionsResponses, callback)
}
}
}
} }
protected open fun addAccountDone(context: JobContext, getAccountsResponse: BankResponse, protected open fun addAccountDone(context: JobContext, getAccountsResponse: BankResponse,
retrievedTransactionsResponses: List<GetAccountTransactionsResponse>, retrievedTransactionsResponses: List<GetAccountTransactionsResponse>): AddAccountResponse {
callback: (AddAccountResponse) -> Unit) {
callback(AddAccountResponse(context, getAccountsResponse, retrievedTransactionsResponses)) return AddAccountResponse(context, getAccountsResponse, retrievedTransactionsResponses)
} }
@ -147,9 +131,9 @@ open class FinTsClientDeprecated(
* *
* Check if bank supports this. * Check if bank supports this.
*/ */
open fun tryGetAccountTransactionsOfLast90DaysWithoutTan(bank: BankData, account: AccountData, callback: (GetAccountTransactionsResponse) -> Unit) { open suspend fun tryGetAccountTransactionsOfLast90DaysWithoutTan(bank: BankData, account: AccountData): GetAccountTransactionsResponse {
getAccountTransactionsAsync(createGetAccountTransactionsOfLast90DaysParameter(bank, account), callback) return getAccountTransactionsAsync(createGetAccountTransactionsOfLast90DaysParameter(bank, account))
} }
protected open fun createGetAccountTransactionsOfLast90DaysParameter(bank: BankData, account: AccountData): GetAccountTransactionsParameter { protected open fun createGetAccountTransactionsOfLast90DaysParameter(bank: BankData, account: AccountData): GetAccountTransactionsParameter {
@ -159,36 +143,35 @@ open class FinTsClientDeprecated(
return GetAccountTransactionsParameter(bank, account, account.supportsRetrievingBalance, ninetyDaysAgo, abortIfTanIsRequired = true) return GetAccountTransactionsParameter(bank, account, account.supportsRetrievingBalance, ninetyDaysAgo, abortIfTanIsRequired = true)
} }
open fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter, callback: (GetAccountTransactionsResponse) -> Unit) { open suspend fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter): GetAccountTransactionsResponse {
val context = JobContext(JobContextType.GetTransactions, this.callback, product, parameter.bank, parameter.account) val context = JobContext(JobContextType.GetTransactions, this.callback, product, parameter.bank, parameter.account)
jobExecutor.getTransactionsAsync(context, parameter, callback) return jobExecutor.getTransactionsAsync(context, parameter)
} }
open fun getTanMediaList(bank: BankData, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, open suspend fun getTanMediaList(bank: BankData, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle,
tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien, callback: (GetTanMediaListResponse) -> Unit) { tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien): GetTanMediaListResponse {
val context = JobContext(JobContextType.GetTanMedia, this.callback, product, bank) val context = JobContext(JobContextType.GetTanMedia, this.callback, product, bank)
jobExecutor.getTanMediaList(context, tanMediaKind, tanMediumClass, callback) return jobExecutor.getTanMediaList(context, tanMediaKind, tanMediumClass)
} }
open fun changeTanMedium(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData, callback: (FinTsClientResponse) -> Unit) { open suspend fun changeTanMedium(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData): FinTsClientResponse {
val context = JobContext(JobContextType.ChangeTanMedium, this.callback, product, bank) val context = JobContext(JobContextType.ChangeTanMedium, this.callback, product, bank)
jobExecutor.changeTanMedium(context, newActiveTanMedium) { response -> val response = jobExecutor.changeTanMedium(context, newActiveTanMedium)
callback(FinTsClientResponse(context, response)) return FinTsClientResponse(context, response)
}
} }
open fun doBankTransferAsync(bankTransferData: BankTransferData, bank: BankData, account: AccountData, callback: (FinTsClientResponse) -> Unit) { open suspend fun doBankTransferAsync(bankTransferData: BankTransferData, bank: BankData, account: AccountData): FinTsClientResponse {
val context = JobContext(JobContextType.TransferMoney, this.callback, product, bank, account) val context = JobContext(JobContextType.TransferMoney, this.callback, product, bank, account)
jobExecutor.doBankTransferAsync(context, bankTransferData, callback) return jobExecutor.doBankTransferAsync(context, bankTransferData)
} }
} }

View File

@ -39,22 +39,22 @@ open class FinTsClientForCustomer(
} }
open fun addAccountAsync(callback: (AddAccountResponse) -> Unit) { open suspend fun addAccountAsync(): AddAccountResponse {
addAccountAsync(bank.toAddAccountParameter(), callback) return addAccountAsync(bank.toAddAccountParameter())
} }
open fun addAccountAsync(parameter: AddAccountParameter, callback: (AddAccountResponse) -> Unit) { open suspend fun addAccountAsync(parameter: AddAccountParameter): AddAccountResponse {
client.addAccountAsync(parameter, callback) return client.addAccountAsync(parameter)
} }
open fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter, callback: (GetAccountTransactionsResponse) -> Unit) { open suspend fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter): GetAccountTransactionsResponse {
client.getAccountTransactionsAsync(parameter, callback) return client.getAccountTransactionsAsync(parameter)
} }
open fun doBankTransferAsync(bankTransferData: BankTransferData, account: AccountData, callback: (FinTsClientResponse) -> Unit) { open suspend fun doBankTransferAsync(bankTransferData: BankTransferData, account: AccountData): FinTsClientResponse {
client.doBankTransferAsync(bankTransferData, bank, account, callback) return client.doBankTransferAsync(bankTransferData, bank, account)
} }
} }

View File

@ -41,21 +41,21 @@ open class FinTsJobExecutor(
} }
open fun getAnonymousBankInfo(context: JobContext, callback: (BankResponse) -> Unit) { open suspend fun getAnonymousBankInfo(context: JobContext): BankResponse {
context.startNewDialog() context.startNewDialog()
val message = messageBuilder.createAnonymousDialogInitMessage(context) val message = messageBuilder.createAnonymousDialogInitMessage(context)
getAndHandleResponseForMessage(context, message) { response -> val response = getAndHandleResponseForMessage(context, message)
if (response.successful) { if (response.successful) {
closeAnonymousDialog(context, response) closeAnonymousDialog(context, response)
} }
callback(response) return response
}
} }
protected open fun closeAnonymousDialog(context: JobContext, response: BankResponse) { protected open suspend fun closeAnonymousDialog(context: JobContext, response: BankResponse) {
// bank already closed dialog -> there's no need to send dialog end message // bank already closed dialog -> there's no need to send dialog end message
if (shouldNotCloseDialog(context)) { if (shouldNotCloseDialog(context)) {
@ -75,8 +75,8 @@ open class FinTsJobExecutor(
* *
* Be aware this method resets BPD, UPD and selected TAN method! * Be aware this method resets BPD, UPD and selected TAN method!
*/ */
open fun retrieveBasicDataLikeUsersTanMethods(context: JobContext, preferredTanMethods: List<TanMethodType>? = null, preferredTanMedium: String? = null, open suspend fun retrieveBasicDataLikeUsersTanMethods(context: JobContext, preferredTanMethods: List<TanMethodType>? = null, preferredTanMedium: String? = null,
closeDialog: Boolean = false, callback: (BankResponse) -> Unit) { closeDialog: Boolean = false): BankResponse {
val bank = context.bank val bank = context.bank
// just to ensure settings are in its initial state and that bank sends us bank parameter (BPD), // just to ensure settings are in its initial state and that bank sends us bank parameter (BPD),
@ -96,38 +96,38 @@ open class FinTsJobExecutor(
val message = messageBuilder.createInitDialogMessage(context) val message = messageBuilder.createInitDialogMessage(context)
getAndHandleResponseForMessage(context, message) { response -> val response = getAndHandleResponseForMessage(context, message)
closeDialog(context) closeDialog(context)
handleGetUsersTanMethodsResponse(context, response) { getTanMethodsResponse -> val getTanMethodsResponse = handleGetUsersTanMethodsResponse(context, response)
if (bank.tanMethodsAvailableForUser.isEmpty()) { // could not retrieve supported tan methods for user if (bank.tanMethodsAvailableForUser.isEmpty()) { // could not retrieve supported tan methods for user
callback(getTanMethodsResponse) return getTanMethodsResponse
} else { } else {
getUsersTanMethod(context, preferredTanMethods) { getUsersTanMethod(context, preferredTanMethods)
if (bank.isTanMethodSelected == false) { if (bank.isTanMethodSelected == false) {
callback(getTanMethodsResponse) return getTanMethodsResponse
} else if (bank.tanMedia.isEmpty() && isJobSupported(bank, CustomerSegmentId.TanMediaList)) { // tan media not retrieved yet } else if (bank.tanMedia.isEmpty() && isJobSupported(bank, CustomerSegmentId.TanMediaList)) { // tan media not retrieved yet
getTanMediaList(context, TanMedienArtVersion.Alle, TanMediumKlasse.AlleMedien, preferredTanMedium) { getTanMediaList(context, TanMedienArtVersion.Alle, TanMediumKlasse.AlleMedien, preferredTanMedium)
callback(getTanMethodsResponse) // TODO: judge if bank requires selecting TAN media and if though evaluate getTanMediaListResponse
} return getTanMethodsResponse // TODO: judge if bank requires selecting TAN media and if though evaluate getTanMediaListResponse
} else { } else {
callback(getTanMethodsResponse) return getTanMethodsResponse
}
}
}
} }
} }
} }
protected open fun handleGetUsersTanMethodsResponse(context: JobContext, response: BankResponse, callback: (BankResponse) -> Unit) { protected open suspend fun handleGetUsersTanMethodsResponse(context: JobContext, response: BankResponse): BankResponse {
val getUsersTanMethodsResponse = GetUserTanMethodsResponse(response) val getUsersTanMethodsResponse = GetUserTanMethodsResponse(response)
// even though it is required by specification some banks don't support retrieving user's TAN method by setting TAN method to '999' // even though it is required by specification some banks don't support retrieving user's TAN method by setting TAN method to '999'
if (bankDoesNotSupportRetrievingUsersTanMethods(getUsersTanMethodsResponse)) { if (bankDoesNotSupportRetrievingUsersTanMethods(getUsersTanMethodsResponse)) {
getBankDataForNewUserViaAnonymousDialog(context, callback) // TODO: should not be necessary anymore return getBankDataForNewUserViaAnonymousDialog(context) // TODO: should not be necessary anymore
} }
else { else {
callback(getUsersTanMethodsResponse) return getUsersTanMethodsResponse
} }
} }
@ -138,73 +138,70 @@ open class FinTsJobExecutor(
} }
// TODO: this is only a quick fix. Find a better and general solution // TODO: this is only a quick fix. Find a better and general solution
protected open fun getBankDataForNewUserViaAnonymousDialog(context: JobContext, callback: (BankResponse) -> Unit) { protected open suspend fun getBankDataForNewUserViaAnonymousDialog(context: JobContext): BankResponse {
getAnonymousBankInfo(context) { anonymousBankInfoResponse -> val anonymousBankInfoResponse = getAnonymousBankInfo(context)
val bank = context.bank val bank = context.bank
if (anonymousBankInfoResponse.successful == false) { if (anonymousBankInfoResponse.successful == false) {
callback(anonymousBankInfoResponse) return anonymousBankInfoResponse
} else if (bank.tanMethodsSupportedByBank.isEmpty()) { // should only be a theoretical error } else if (bank.tanMethodsSupportedByBank.isEmpty()) { // should only be a theoretical error
callback(BankResponse(true, internalError = "Die TAN Verfahren der Bank konnten nicht ermittelt werden")) // TODO: translate return BankResponse(true, internalError = "Die TAN Verfahren der Bank konnten nicht ermittelt werden") // TODO: translate
} else { } else {
bank.tanMethodsAvailableForUser = bank.tanMethodsSupportedByBank bank.tanMethodsAvailableForUser = bank.tanMethodsSupportedByBank
getUsersTanMethod(context) { didSelectTanMethod -> val didSelectTanMethod = getUsersTanMethod(context)
if (didSelectTanMethod) { if (didSelectTanMethod) {
initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context) { initDialogResponse -> val initDialogResponse = initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context)
closeDialog(context) closeDialog(context)
callback(initDialogResponse) return initDialogResponse
}
} else { } else {
callback(createNoTanMethodSelectedResponse(bank)) return createNoTanMethodSelectedResponse(bank)
}
}
} }
} }
} }
open fun getAccounts(context: JobContext, callback: (BankResponse) -> Unit) { open suspend fun getAccounts(context: JobContext): BankResponse {
initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context) { response -> val response = initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context)
closeDialog(context) closeDialog(context)
callback(response) return response
}
} }
open fun getTransactionsAsync(context: JobContext, parameter: GetAccountTransactionsParameter, callback: (GetAccountTransactionsResponse) -> Unit) { open suspend fun getTransactionsAsync(context: JobContext, parameter: GetAccountTransactionsParameter): GetAccountTransactionsResponse {
val dialogContext = context.startNewDialog() // TODO: initDialogWithStrongCustomerAuthentication() also starts a new dialog in initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks() val dialogContext = context.startNewDialog() // TODO: initDialogWithStrongCustomerAuthentication() also starts a new dialog in initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks()
initDialogWithStrongCustomerAuthentication(context) { initDialogResponse -> val initDialogResponse = initDialogWithStrongCustomerAuthentication(context)
if (initDialogResponse.successful == false) { if (initDialogResponse.successful == false) {
callback(GetAccountTransactionsResponse(context, initDialogResponse, RetrievedAccountData.unsuccessful(parameter.account))) return GetAccountTransactionsResponse(context, initDialogResponse, RetrievedAccountData.unsuccessful(parameter.account))
} }
else { else {
// we now retrieved the fresh account information from FinTS server, use that one // we now retrieved the fresh account information from FinTS server, use that one
parameter.account = getUpdatedAccount(context, parameter.account) parameter.account = getUpdatedAccount(context, parameter.account)
mayGetBalance(context, parameter) { balanceResponse -> val balanceResponse = mayGetBalance(context, parameter)
if (dialogContext.didBankCloseDialog) { if (dialogContext.didBankCloseDialog) {
callback(GetAccountTransactionsResponse(context, balanceResponse ?: initDialogResponse, RetrievedAccountData.unsuccessful(parameter.account))) return GetAccountTransactionsResponse(context, balanceResponse ?: initDialogResponse, RetrievedAccountData.unsuccessful(parameter.account))
} } else {
else { return getTransactionsAfterInitAndGetBalance(context, parameter, balanceResponse)
getTransactionsAfterInitAndGetBalance(context, parameter, balanceResponse, callback)
}
}
} }
} }
} }
private fun getUpdatedAccount(context: JobContext, account: AccountData): AccountData { protected open fun getUpdatedAccount(context: JobContext, account: AccountData): AccountData {
return context.bank.accounts.firstOrNull { it.accountIdentifier == account.accountIdentifier } ?: account return context.bank.accounts.firstOrNull { it.accountIdentifier == account.accountIdentifier } ?: account
} }
protected open fun getTransactionsAfterInitAndGetBalance(context: JobContext, parameter: GetAccountTransactionsParameter, protected open suspend fun getTransactionsAfterInitAndGetBalance(context: JobContext, parameter: GetAccountTransactionsParameter,
balanceResponse: BankResponse?, callback: (GetAccountTransactionsResponse) -> Unit) { balanceResponse: BankResponse?): GetAccountTransactionsResponse {
var balance: Money? = balanceResponse?.getFirstSegmentById<BalanceSegment>(InstituteSegmentId.Balance)?.let { var balance: Money? = balanceResponse?.getFirstSegmentById<BalanceSegment>(InstituteSegmentId.Balance)?.let {
Money(it.balance, it.currency) Money(it.balance, it.currency)
} }
@ -234,7 +231,8 @@ open class FinTsJobExecutor(
} }
} }
getAndHandleResponseForMessage(context, message) { response -> val response = getAndHandleResponseForMessage(context, message)
closeDialog(context) closeDialog(context)
val successful = response.tanRequiredButWeWereToldToAbortIfSo val successful = response.tanRequiredButWeWereToldToAbortIfSo
@ -244,19 +242,17 @@ open class FinTsJobExecutor(
?: bookedTransactions.minByOrNull { it.valueDate.millisSinceEpochAtEuropeBerlin }?.valueDate ?: bookedTransactions.minByOrNull { it.valueDate.millisSinceEpochAtEuropeBerlin }?.valueDate
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError) val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError)
callback(GetAccountTransactionsResponse(context, response, retrievedData, return GetAccountTransactionsResponse(context, response, retrievedData,
if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null)) if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null)
}
} }
protected open fun mayGetBalance(context: JobContext, parameter: GetAccountTransactionsParameter, callback: (BankResponse?) -> Unit) { protected open suspend fun mayGetBalance(context: JobContext, parameter: GetAccountTransactionsParameter): BankResponse? {
if (parameter.alsoRetrieveBalance && parameter.account.supportsRetrievingBalance) { if (parameter.alsoRetrieveBalance && parameter.account.supportsRetrievingBalance) {
val message = messageBuilder.createGetBalanceMessage(context, parameter.account) val message = messageBuilder.createGetBalanceMessage(context, parameter.account)
getAndHandleResponseForMessage(context, message, callback) return getAndHandleResponseForMessage(context, message)
} } else {
else { return null
callback(null)
} }
} }
@ -272,38 +268,37 @@ open class FinTsJobExecutor(
* *
* If you change customer system id during a dialog your messages get rejected by bank institute. * If you change customer system id during a dialog your messages get rejected by bank institute.
*/ */
protected open fun synchronizeCustomerSystemId(context: JobContext, callback: (FinTsClientResponse) -> Unit) { protected open suspend fun synchronizeCustomerSystemId(context: JobContext): FinTsClientResponse {
context.startNewDialog() context.startNewDialog()
val message = messageBuilder.createSynchronizeCustomerSystemIdMessage(context) val message = messageBuilder.createSynchronizeCustomerSystemIdMessage(context)
getAndHandleResponseForMessage(context, message) { response -> val response = getAndHandleResponseForMessage(context, message)
if (response.successful) { if (response.successful) {
closeDialog(context) closeDialog(context)
} }
callback(FinTsClientResponse(context, response)) return FinTsClientResponse(context, response)
}
} }
open fun getTanMediaList(context: JobContext, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien, open suspend fun getTanMediaList(context: JobContext, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien): GetTanMediaListResponse {
callback: (GetTanMediaListResponse) -> Unit) { return getTanMediaList(context, tanMediaKind, tanMediumClass, null)
getTanMediaList(context, tanMediaKind, tanMediumClass, null, callback)
} }
protected open fun getTanMediaList(context: JobContext, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien, protected open suspend fun getTanMediaList(context: JobContext, tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle, tanMediumClass: TanMediumKlasse = TanMediumKlasse.AlleMedien,
preferredTanMedium: String? = null, callback: (GetTanMediaListResponse) -> Unit) { preferredTanMedium: String? = null): GetTanMediaListResponse {
sendMessageInNewDialogAndHandleResponse(context, CustomerSegmentId.TanMediaList, false, { val response = sendMessageInNewDialogAndHandleResponse(context, CustomerSegmentId.TanMediaList, false) {
messageBuilder.createGetTanMediaListMessage(context, tanMediaKind, tanMediumClass) messageBuilder.createGetTanMediaListMessage(context, tanMediaKind, tanMediumClass)
}) { response ->
handleGetTanMediaListResponse(context, response, preferredTanMedium, callback)
}
} }
protected open fun handleGetTanMediaListResponse(context: JobContext, response: BankResponse, preferredTanMedium: String? = null, callback: (GetTanMediaListResponse) -> Unit) { return handleGetTanMediaListResponse(context, response, preferredTanMedium)
}
protected open fun handleGetTanMediaListResponse(context: JobContext, response: BankResponse, preferredTanMedium: String? = null): GetTanMediaListResponse {
val bank = context.bank val bank = context.bank
// TAN media list (= TAN generator list) is only returned for users with chipTAN TAN methods // TAN media list (= TAN generator list) is only returned for users with chipTAN TAN methods
@ -318,83 +313,80 @@ open class FinTsJobExecutor(
?: bank.tanMedia.firstOrNull { it.mediumName != null } ?: bank.tanMedia.firstOrNull { it.mediumName != null }
} }
callback(GetTanMediaListResponse(context, response, tanMediaList)) return GetTanMediaListResponse(context, response, tanMediaList)
} }
open fun changeTanMedium(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium, callback: (BankResponse) -> Unit) { open suspend fun changeTanMedium(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium): BankResponse {
val bank = context.bank val bank = context.bank
if (bank.changeTanMediumParameters?.enteringAtcAndTanRequired == true) { if (bank.changeTanMediumParameters?.enteringAtcAndTanRequired == true) {
context.callback.enterTanGeneratorAtc(bank, newActiveTanMedium) { enteredAtc -> val enteredAtc = context.callback.enterTanGeneratorAtc(bank, newActiveTanMedium)
if (enteredAtc.hasAtcBeenEntered == false) { if (enteredAtc.hasAtcBeenEntered == false) {
val message = "Bank requires to enter ATC and TAN in order to change TAN medium." // TODO: translate val message = "Bank requires to enter ATC and TAN in order to change TAN medium." // TODO: translate
callback(BankResponse(false, internalError = message)) return BankResponse(false, internalError = message)
} } else {
else { return sendChangeTanMediumMessage(context, newActiveTanMedium, enteredAtc)
sendChangeTanMediumMessage(context, newActiveTanMedium, enteredAtc, callback)
}
} }
} }
else { else {
sendChangeTanMediumMessage(context, newActiveTanMedium, null, callback) return sendChangeTanMediumMessage(context, newActiveTanMedium, null)
} }
} }
protected open fun sendChangeTanMediumMessage(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium, enteredAtc: EnterTanGeneratorAtcResult?, protected open suspend fun sendChangeTanMediumMessage(context: JobContext, newActiveTanMedium: TanGeneratorTanMedium, enteredAtc: EnterTanGeneratorAtcResult?): BankResponse {
callback: (BankResponse) -> Unit) {
sendMessageInNewDialogAndHandleResponse(context, null, true, { return sendMessageInNewDialogAndHandleResponse(context, null, true) {
messageBuilder.createChangeTanMediumMessage(context, newActiveTanMedium, enteredAtc?.tan, enteredAtc?.atc) messageBuilder.createChangeTanMediumMessage(context, newActiveTanMedium, enteredAtc?.tan, enteredAtc?.atc)
}, callback) }
} }
open fun doBankTransferAsync(context: JobContext, bankTransferData: BankTransferData, callback: (FinTsClientResponse) -> Unit) { open suspend fun doBankTransferAsync(context: JobContext, bankTransferData: BankTransferData): FinTsClientResponse {
sendMessageInNewDialogAndHandleResponse(context, null, true, { val response = sendMessageInNewDialogAndHandleResponse(context, null, true) {
val updatedAccount = getUpdatedAccount(context, context.account!!) val updatedAccount = getUpdatedAccount(context, context.account!!)
messageBuilder.createBankTransferMessage(context, bankTransferData, updatedAccount) messageBuilder.createBankTransferMessage(context, bankTransferData, updatedAccount)
}) { response ->
callback(FinTsClientResponse(context, response))
} }
return FinTsClientResponse(context, response)
} }
protected open fun getAndHandleResponseForMessage(context: JobContext, message: MessageBuilderResult, callback: (BankResponse) -> Unit) { protected open suspend fun getAndHandleResponseForMessage(context: JobContext, message: MessageBuilderResult): BankResponse {
requestExecutor.getAndHandleResponseForMessage(message, context, val response = requestExecutor.getAndHandleResponseForMessage(message, context, { tanResponse, bankResponse ->
{ tanResponse, bankResponse, tanRequiredCallback ->
// if we receive a message that tells us a TAN is required below callback doesn't get called for that message -> update data here // if we receive a message that tells us a TAN is required below callback doesn't get called for that message -> update data here
// for Hypovereinsbank it's absolutely necessary to update bank data (more specific: PinInfo / HIPINS) after first strong authentication dialog init response // for Hypovereinsbank it's absolutely necessary to update bank data (more specific: PinInfo / HIPINS) after first strong authentication dialog init response
// as HIPINS differ in anonymous and in authenticated dialog. Anonymous dialog tells us for HKSAL and HKKAZ no TAN is needed // as HIPINS differ in anonymous and in authenticated dialog. Anonymous dialog tells us for HKSAL and HKKAZ no TAN is needed
updateBankAndCustomerDataIfResponseSuccessful(context, bankResponse) updateBankAndCustomerDataIfResponseSuccessful(context, bankResponse)
handleEnteringTanRequired(context, tanResponse, bankResponse, tanRequiredCallback)
}) { response -> handleEnteringTanRequired(context, tanResponse, bankResponse)
})
// TODO: really update data only on complete successfully response? as it may contain useful information anyway // TODO: extract method for this code part // TODO: really update data only on complete successfully response? as it may contain useful information anyway // TODO: extract method for this code part
updateBankAndCustomerDataIfResponseSuccessful(context, response) updateBankAndCustomerDataIfResponseSuccessful(context, response)
callback(response) return response
}
} }
protected open fun fireAndForgetMessage(context: JobContext, message: MessageBuilderResult) { protected open suspend fun fireAndForgetMessage(context: JobContext, message: MessageBuilderResult) {
requestExecutor.fireAndForgetMessage(context, message) requestExecutor.fireAndForgetMessage(context, message)
} }
protected open fun handleEnteringTanRequired(context: JobContext, tanResponse: TanResponse, response: BankResponse, callback: (BankResponse) -> Unit) { protected open suspend fun handleEnteringTanRequired(context: JobContext, tanResponse: TanResponse, response: BankResponse): BankResponse {
val bank = context.bank // TODO: copy required data to TanChallenge val bank = context.bank // TODO: copy required data to TanChallenge
val tanChallenge = createTanChallenge(tanResponse, bank) val tanChallenge = createTanChallenge(tanResponse, bank)
val userDidCancelEnteringTan = ObjectReference(false) val userDidCancelEnteringTan = ObjectReference(false)
context.callback.enterTan(bank, tanChallenge) { enteredTanResult -> val enteredTanResult = context.callback.enterTan(bank, tanChallenge)
userDidCancelEnteringTan.value = true userDidCancelEnteringTan.value = true
handleEnterTanResult(context, enteredTanResult, tanResponse, response, callback) return handleEnterTanResult(context, enteredTanResult, tanResponse, response)
}
mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context, tanChallenge, tanResponse, userDidCancelEnteringTan) // TODO:
// mayRetrieveAutomaticallyIfUserEnteredDecoupledTan(context, tanChallenge, tanResponse, userDidCancelEnteringTan)
} }
protected open fun createTanChallenge(tanResponse: TanResponse, bank: BankData): TanChallenge { protected open fun createTanChallenge(tanResponse: TanResponse, bank: BankData): TanChallenge {
@ -431,36 +423,36 @@ open class FinTsJobExecutor(
log.info("automaticallyRetrieveIfUserEnteredDecoupledTan() called for $tanChallenge") log.info("automaticallyRetrieveIfUserEnteredDecoupledTan() called for $tanChallenge")
} }
protected open fun handleEnterTanResult(context: JobContext, enteredTanResult: EnterTanResult, tanResponse: TanResponse, protected open suspend fun handleEnterTanResult(context: JobContext, enteredTanResult: EnterTanResult, tanResponse: TanResponse,
response: BankResponse, callback: (BankResponse) -> Unit) { response: BankResponse): BankResponse {
if (enteredTanResult.changeTanMethodTo != null) { if (enteredTanResult.changeTanMethodTo != null) {
handleUserAsksToChangeTanMethodAndResendLastMessage(context, enteredTanResult.changeTanMethodTo, callback) return handleUserAsksToChangeTanMethodAndResendLastMessage(context, enteredTanResult.changeTanMethodTo)
} }
else if (enteredTanResult.changeTanMediumTo is TanGeneratorTanMedium) { else if (enteredTanResult.changeTanMediumTo is TanGeneratorTanMedium) {
handleUserAsksToChangeTanMediumAndResendLastMessage(context, enteredTanResult.changeTanMediumTo, return handleUserAsksToChangeTanMediumAndResendLastMessage(context, enteredTanResult.changeTanMediumTo,
enteredTanResult.changeTanMediumResultCallback, callback) enteredTanResult.changeTanMediumResultCallback)
} }
else if (enteredTanResult.enteredTan == null) { else if (enteredTanResult.enteredTan == null) {
// i tried to send a HKTAN with cancelJob = true but then i saw there are no tan methods that support cancellation (at least not at my bank) // i tried to send a HKTAN with cancelJob = true but then i saw there are no tan methods that support cancellation (at least not at my bank)
// but it's not required anyway, tan times out after some time. Simply don't respond anything and close dialog // but it's not required anyway, tan times out after some time. Simply don't respond anything and close dialog
response.tanRequiredButUserDidNotEnterOne = true response.tanRequiredButUserDidNotEnterOne = true
callback(response) return response
} }
else { else {
sendTanToBank(context, enteredTanResult.enteredTan, tanResponse, callback) return sendTanToBank(context, enteredTanResult.enteredTan, tanResponse)
} }
} }
protected open fun sendTanToBank(context: JobContext, enteredTan: String, tanResponse: TanResponse, callback: (BankResponse) -> Unit) { protected open suspend fun sendTanToBank(context: JobContext, enteredTan: String, tanResponse: TanResponse): BankResponse {
val message = messageBuilder.createSendEnteredTanMessage(context, enteredTan, tanResponse) val message = messageBuilder.createSendEnteredTanMessage(context, enteredTan, tanResponse)
getAndHandleResponseForMessage(context, message, callback) return getAndHandleResponseForMessage(context, message)
} }
protected open fun handleUserAsksToChangeTanMethodAndResendLastMessage(context: JobContext, changeTanMethodTo: TanMethod, callback: (BankResponse) -> Unit) { protected open suspend fun handleUserAsksToChangeTanMethodAndResendLastMessage(context: JobContext, changeTanMethodTo: TanMethod): BankResponse {
context.bank.selectedTanMethod = changeTanMethodTo context.bank.selectedTanMethod = changeTanMethodTo
@ -469,131 +461,125 @@ open class FinTsJobExecutor(
lastCreatedMessage?.let { closeDialog(context) } lastCreatedMessage?.let { closeDialog(context) }
resendMessageInNewDialog(context, lastCreatedMessage, callback) return resendMessageInNewDialog(context, lastCreatedMessage)
} }
protected open fun handleUserAsksToChangeTanMediumAndResendLastMessage(context: JobContext, changeTanMediumTo: TanGeneratorTanMedium, protected open suspend fun handleUserAsksToChangeTanMediumAndResendLastMessage(context: JobContext, changeTanMediumTo: TanGeneratorTanMedium,
changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?, changeTanMediumResultCallback: ((FinTsClientResponse) -> Unit)?): BankResponse {
callback: (BankResponse) -> Unit) {
val lastCreatedMessage = context.dialog.currentMessage val lastCreatedMessage = context.dialog.currentMessage
lastCreatedMessage?.let { closeDialog(context) } lastCreatedMessage?.let { closeDialog(context) }
changeTanMedium(context, changeTanMediumTo) { changeTanMediumResponse -> val changeTanMediumResponse = changeTanMedium(context, changeTanMediumTo)
changeTanMediumResultCallback?.invoke(FinTsClientResponse(context, changeTanMediumResponse)) changeTanMediumResultCallback?.invoke(FinTsClientResponse(context, changeTanMediumResponse))
if (changeTanMediumResponse.successful == false || lastCreatedMessage == null) { if (changeTanMediumResponse.successful == false || lastCreatedMessage == null) {
callback(changeTanMediumResponse) return changeTanMediumResponse
} }
else { else {
resendMessageInNewDialog(context, lastCreatedMessage, callback) return resendMessageInNewDialog(context, lastCreatedMessage)
}
} }
} }
protected open fun resendMessageInNewDialog(context: JobContext, lastCreatedMessage: MessageBuilderResult?, callback: (BankResponse) -> Unit) { protected open suspend fun resendMessageInNewDialog(context: JobContext, lastCreatedMessage: MessageBuilderResult?): BankResponse {
if (lastCreatedMessage != null) { // do not use previousDialogContext.currentMessage as this may is previous dialog's dialog close message if (lastCreatedMessage != null) { // do not use previousDialogContext.currentMessage as this may is previous dialog's dialog close message
context.startNewDialog(chunkedResponseHandler = context.dialog.chunkedResponseHandler) context.startNewDialog(chunkedResponseHandler = context.dialog.chunkedResponseHandler)
initDialogWithStrongCustomerAuthentication(context) { initDialogResponse -> val initDialogResponse = initDialogWithStrongCustomerAuthentication(context)
if (initDialogResponse.successful == false) { if (initDialogResponse.successful == false) {
callback(initDialogResponse) return initDialogResponse
} } else {
else {
val newMessage = messageBuilder.rebuildMessage(context, lastCreatedMessage) val newMessage = messageBuilder.rebuildMessage(context, lastCreatedMessage)
getAndHandleResponseForMessage(context, newMessage) { response -> val response = getAndHandleResponseForMessage(context, newMessage)
closeDialog(context) closeDialog(context)
callback(response) return response
}
}
} }
} }
else { else {
val errorMessage = "There's no last action (like retrieve account transactions, transfer money, ...) to re-send with new TAN method. Probably an internal programming error." // TODO: translate val errorMessage = "There's no last action (like retrieve account transactions, transfer money, ...) to re-send with new TAN method. Probably an internal programming error." // TODO: translate
callback(BankResponse(false, internalError = errorMessage)) // should never come to this return BankResponse(false, internalError = errorMessage) // should never come to this
} }
} }
protected open fun sendMessageInNewDialogAndHandleResponse(context: JobContext, segmentForNonStrongCustomerAuthenticationTwoStepTanProcess: CustomerSegmentId? = null, protected open suspend fun sendMessageInNewDialogAndHandleResponse(context: JobContext, segmentForNonStrongCustomerAuthenticationTwoStepTanProcess: CustomerSegmentId? = null,
closeDialog: Boolean = true, createMessage: () -> MessageBuilderResult, callback: (BankResponse) -> Unit) { closeDialog: Boolean = true, createMessage: () -> MessageBuilderResult): BankResponse {
context.startNewDialog(closeDialog) context.startNewDialog(closeDialog)
if (segmentForNonStrongCustomerAuthenticationTwoStepTanProcess == null) { val initDialogResponse = if (segmentForNonStrongCustomerAuthenticationTwoStepTanProcess == null) {
initDialogWithStrongCustomerAuthentication(context) { initDialogResponse -> initDialogWithStrongCustomerAuthentication(context)
sendMessageAndHandleResponseAfterDialogInitialization(context, initDialogResponse, createMessage, callback) } else {
} initDialogMessageWithoutStrongCustomerAuthenticationAfterSuccessfulChecks(context, segmentForNonStrongCustomerAuthenticationTwoStepTanProcess)
}
else {
initDialogMessageWithoutStrongCustomerAuthenticationAfterSuccessfulChecks(context, segmentForNonStrongCustomerAuthenticationTwoStepTanProcess) { initDialogResponse ->
sendMessageAndHandleResponseAfterDialogInitialization(context, initDialogResponse, createMessage, callback)
}
}
} }
private fun sendMessageAndHandleResponseAfterDialogInitialization(context: JobContext, initDialogResponse: BankResponse, return sendMessageAndHandleResponseAfterDialogInitialization(context, initDialogResponse, createMessage)
createMessage: () -> MessageBuilderResult, callback: (BankResponse) -> Unit) { }
protected open suspend fun sendMessageAndHandleResponseAfterDialogInitialization(context: JobContext, initDialogResponse: BankResponse,
createMessage: () -> MessageBuilderResult): BankResponse {
if (initDialogResponse.successful == false) { if (initDialogResponse.successful == false) {
callback(initDialogResponse) return initDialogResponse
} }
else { else {
val message = createMessage() val message = createMessage()
getAndHandleResponseForMessage(context, message) { response -> val response = getAndHandleResponseForMessage(context, message)
closeDialog(context) closeDialog(context)
callback(response) return response
}
} }
} }
protected open fun initDialogWithStrongCustomerAuthentication(context: JobContext, callback: (BankResponse) -> Unit) { protected open suspend fun initDialogWithStrongCustomerAuthentication(context: JobContext): BankResponse {
// we first need to retrieve supported tan methods and jobs before we can do anything // we first need to retrieve supported tan methods and jobs before we can do anything
ensureBasicBankDataRetrieved(context) { retrieveBasicBankDataResponse -> val retrieveBasicBankDataResponse = ensureBasicBankDataRetrieved(context)
if (retrieveBasicBankDataResponse.successful == false) { if (retrieveBasicBankDataResponse.successful == false) {
callback(retrieveBasicBankDataResponse) return retrieveBasicBankDataResponse
} }
else { else {
// as in the next step we have to supply user's tan method, ensure user selected his or her // as in the next step we have to supply user's tan method, ensure user selected his or her
ensureTanMethodIsSelected(context) { tanMethodSelectedResponse -> val tanMethodSelectedResponse = ensureTanMethodIsSelected(context)
if (tanMethodSelectedResponse.successful == false) { if (tanMethodSelectedResponse.successful == false) {
callback(tanMethodSelectedResponse) return tanMethodSelectedResponse
} }
else { else {
initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context, callback) return initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context)
}
}
} }
} }
} }
protected open fun initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context: JobContext, callback: (BankResponse) -> Unit) { protected open suspend fun initDialogWithStrongCustomerAuthenticationAfterSuccessfulPreconditionChecks(context: JobContext): BankResponse {
context.startNewDialog(false) // don't know if it's ok for all invocations of this method to set closeDialog to false (was actually only set in getAccounts()) context.startNewDialog(false) // don't know if it's ok for all invocations of this method to set closeDialog to false (was actually only set in getAccounts())
val message = messageBuilder.createInitDialogMessage(context) val message = messageBuilder.createInitDialogMessage(context)
getAndHandleResponseForMessage(context, message, callback) return getAndHandleResponseForMessage(context, message)
} }
protected open fun initDialogMessageWithoutStrongCustomerAuthenticationAfterSuccessfulChecks(context: JobContext, segmentIdForTwoStepTanProcess: CustomerSegmentId?, protected open suspend fun initDialogMessageWithoutStrongCustomerAuthenticationAfterSuccessfulChecks(context: JobContext, segmentIdForTwoStepTanProcess: CustomerSegmentId?): BankResponse {
callback: (BankResponse) -> Unit) {
val message = messageBuilder.createInitDialogMessageWithoutStrongCustomerAuthentication(context, segmentIdForTwoStepTanProcess) val message = messageBuilder.createInitDialogMessageWithoutStrongCustomerAuthentication(context, segmentIdForTwoStepTanProcess)
getAndHandleResponseForMessage(context, message, callback) return getAndHandleResponseForMessage(context, message)
} }
protected open fun closeDialog(context: JobContext) { protected open suspend fun closeDialog(context: JobContext) {
// bank already closed dialog -> there's no need to send dialog end message // bank already closed dialog -> there's no need to send dialog end message
if (shouldNotCloseDialog(context)) { if (shouldNotCloseDialog(context)) {
@ -605,82 +591,80 @@ open class FinTsJobExecutor(
fireAndForgetMessage(context, dialogEndRequestBody) fireAndForgetMessage(context, dialogEndRequestBody)
} }
private fun shouldNotCloseDialog(context: JobContext): Boolean { protected open fun shouldNotCloseDialog(context: JobContext): Boolean {
return context.dialog.closeDialog == false || context.dialog.didBankCloseDialog return context.dialog.closeDialog == false || context.dialog.didBankCloseDialog
} }
protected open fun ensureBasicBankDataRetrieved(context: JobContext, callback: (BankResponse) -> Unit) { protected open suspend fun ensureBasicBankDataRetrieved(context: JobContext): BankResponse {
val bank = context.bank val bank = context.bank
if (bank.tanMethodsSupportedByBank.isEmpty() || bank.supportedJobs.isEmpty()) { if (bank.tanMethodsSupportedByBank.isEmpty() || bank.supportedJobs.isEmpty()) {
retrieveBasicDataLikeUsersTanMethods(context) { getBankInfoResponse -> val getBankInfoResponse = retrieveBasicDataLikeUsersTanMethods(context)
if (getBankInfoResponse.successful == false) { if (getBankInfoResponse.successful == false) {
callback(getBankInfoResponse) return getBankInfoResponse
} else if (bank.tanMethodsSupportedByBank.isEmpty() || bank.supportedJobs.isEmpty()) { } else if (bank.tanMethodsSupportedByBank.isEmpty() || bank.supportedJobs.isEmpty()) {
callback(BankResponse(false, internalError = return BankResponse(false, internalError =
"Could not retrieve basic bank data like supported tan methods or supported jobs")) // TODO: translate // TODO: add as messageToShowToUser "Could not retrieve basic bank data like supported tan methods or supported jobs") // TODO: translate // TODO: add as messageToShowToUser
} else { } else {
callback(BankResponse(true)) return BankResponse(true)
}
} }
} }
else { else {
callback(BankResponse(true)) return BankResponse(true)
} }
} }
protected open fun ensureTanMethodIsSelected(context: JobContext, callback: (BankResponse) -> Unit) { protected open suspend fun ensureTanMethodIsSelected(context: JobContext): BankResponse {
val bank = context.bank val bank = context.bank
if (bank.isTanMethodSelected == false) { if (bank.isTanMethodSelected == false) {
if (bank.tanMethodsAvailableForUser.isEmpty()) { if (bank.tanMethodsAvailableForUser.isEmpty()) {
retrieveBasicDataLikeUsersTanMethods(context) { retrieveBasicDataResponse -> return retrieveBasicDataLikeUsersTanMethods(context)
callback(retrieveBasicDataResponse) }
else {
getUsersTanMethod(context)
return createNoTanMethodSelectedResponse(bank)
} }
} }
else { else {
getUsersTanMethod(context) { return createNoTanMethodSelectedResponse(bank)
callback(createNoTanMethodSelectedResponse(bank))
}
}
}
else {
callback(createNoTanMethodSelectedResponse(bank))
} }
} }
private fun createNoTanMethodSelectedResponse(bank: BankData): BankResponse { protected open fun createNoTanMethodSelectedResponse(bank: BankData): BankResponse {
val noTanMethodSelected = !!!bank.isTanMethodSelected val noTanMethodSelected = !!!bank.isTanMethodSelected
val errorMessage = if (noTanMethodSelected) "User did not select a TAN method" else null // TODO: translate val errorMessage = if (noTanMethodSelected) "User did not select a TAN method" else null // TODO: translate
return BankResponse(true, noTanMethodSelected = noTanMethodSelected, internalError = errorMessage) return BankResponse(true, noTanMethodSelected = noTanMethodSelected, internalError = errorMessage)
} }
open fun getUsersTanMethod(context: JobContext, preferredTanMethods: List<TanMethodType>? = null, done: (Boolean) -> Unit) { open suspend fun getUsersTanMethod(context: JobContext, preferredTanMethods: List<TanMethodType>? = null): Boolean {
val bank = context.bank val bank = context.bank
if (bank.tanMethodsAvailableForUser.size == 1) { // user has only one TAN method -> set it and we're done if (bank.tanMethodsAvailableForUser.size == 1) { // user has only one TAN method -> set it and we're done
bank.selectedTanMethod = bank.tanMethodsAvailableForUser.first() bank.selectedTanMethod = bank.tanMethodsAvailableForUser.first()
done(true) return true
} }
else { else {
tanMethodSelector.findPreferredTanMethod(bank.tanMethodsAvailableForUser, preferredTanMethods)?.let { tanMethodSelector.findPreferredTanMethod(bank.tanMethodsAvailableForUser, preferredTanMethods)?.let {
bank.selectedTanMethod = it bank.selectedTanMethod = it
done(true) return true
return
} }
// we know user's supported tan methods, now ask user which one to select // we know user's supported tan methods, now ask user which one to select
val suggestedTanMethod = tanMethodSelector.getSuggestedTanMethod(bank.tanMethodsAvailableForUser) val suggestedTanMethod = tanMethodSelector.getSuggestedTanMethod(bank.tanMethodsAvailableForUser)
context.callback.askUserForTanMethod(bank.tanMethodsAvailableForUser, suggestedTanMethod) { selectedTanMethod ->
val selectedTanMethod = context.callback.askUserForTanMethod(bank.tanMethodsAvailableForUser, suggestedTanMethod)
if (selectedTanMethod != null) { if (selectedTanMethod != null) {
bank.selectedTanMethod = selectedTanMethod bank.selectedTanMethod = selectedTanMethod
done(true) return true
} }
else { else {
done(false) return false
}
} }
} }
} }

View File

@ -21,20 +21,21 @@ open class RequestExecutor(
) { ) {
companion object { companion object {
private val log = LoggerFactory.getLogger(FinTsJobExecutor::class) private val log = LoggerFactory.getLogger(RequestExecutor::class)
} }
open fun getAndHandleResponseForMessage(message: MessageBuilderResult, context: JobContext, open suspend fun getAndHandleResponseForMessage(message: MessageBuilderResult, context: JobContext, tanRequiredCallback: suspend (TanResponse, BankResponse) -> BankResponse): BankResponse {
tanRequiredCallback: (TanResponse, BankResponse, callback: (BankResponse) -> Unit) -> Unit, callback: (BankResponse) -> Unit) {
if (message.createdMessage == null) { if (message.createdMessage == null) {
log.error("Could not create FinTS message to be sent to bank. isJobAllowed ${message.isJobAllowed}, isJobVersionSupported = ${message.isJobVersionSupported}," + log.error("Could not create FinTS message to be sent to bank. isJobAllowed ${message.isJobAllowed}, isJobVersionSupported = ${message.isJobVersionSupported}," +
"allowedVersions = ${message.allowedVersions}, supportedVersions = ${message.supportedVersions}.") "allowedVersions = ${message.allowedVersions}, supportedVersions = ${message.supportedVersions}.")
callback(BankResponse(false, messageThatCouldNotBeCreated = message, internalError = "Could not create FinTS message to be sent to bank")) // TODO: translate return BankResponse(false, messageThatCouldNotBeCreated = message, internalError = "Could not create FinTS message to be sent to bank") // TODO: translate
} }
else { else {
getAndHandleResponseForMessage(context, message.createdMessage) { response -> val response = getAndHandleResponseForMessage(context, message.createdMessage)
handleMayRequiresTan(context, response, tanRequiredCallback) { handledResponse ->
val handledResponse = handleMayRequiresTan(context, response, tanRequiredCallback)
// if there's a Aufsetzpunkt (continuationId) set, then response is not complete yet, there's more information to fetch by sending this Aufsetzpunkt // 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 -> 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 (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
@ -42,15 +43,15 @@ open class RequestExecutor(
context.dialog.chunkedResponseHandler?.invoke(handledResponse) context.dialog.chunkedResponseHandler?.invoke(handledResponse)
} }
getFollowUpMessageForContinuationId(context, handledResponse, continuationId, message, tanRequiredCallback) { followUpResponse -> val followUpResponse = getFollowUpMessageForContinuationId(context, handledResponse, continuationId, message, tanRequiredCallback)
handledResponse.followUpResponse = followUpResponse handledResponse.followUpResponse = followUpResponse
handledResponse.hasFollowUpMessageButCouldNotReceiveIt = handledResponse.followUpResponse == null handledResponse.hasFollowUpMessageButCouldNotReceiveIt = handledResponse.followUpResponse == null
callback(handledResponse) return handledResponse
}
} }
else { else {
callback(handledResponse) return handledResponse
} }
} }
?: run { ?: run {
@ -60,17 +61,16 @@ open class RequestExecutor(
context.dialog.chunkedResponseHandler?.invoke(handledResponse) context.dialog.chunkedResponseHandler?.invoke(handledResponse)
} }
callback(handledResponse) return handledResponse
}
}
} }
} }
} }
protected open fun getAndHandleResponseForMessage(context: JobContext, requestBody: String, callback: (BankResponse) -> Unit) { protected open suspend fun getAndHandleResponseForMessage(context: JobContext, requestBody: String): BankResponse {
addMessageLog(context, MessageLogEntryType.Sent, requestBody) addMessageLog(context, MessageLogEntryType.Sent, requestBody)
getResponseForMessage(requestBody, context.bank.finTs3ServerAddress) { webResponse -> val webResponse = getResponseForMessage(requestBody, context.bank.finTs3ServerAddress)
val response = handleResponse(context, webResponse) val response = handleResponse(context, webResponse)
val dialog = context.dialog val dialog = context.dialog
@ -79,21 +79,20 @@ open class RequestExecutor(
response.messageHeader?.let { header -> dialog.dialogId = header.dialogId } response.messageHeader?.let { header -> dialog.dialogId = header.dialogId }
dialog.didBankCloseDialog = response.didBankCloseDialog dialog.didBankCloseDialog = response.didBankCloseDialog
callback(response) return response
}
} }
protected open fun getResponseForMessage(requestBody: String, finTs3ServerAddress: String, callback: (WebClientResponse) -> Unit) { protected open suspend fun getResponseForMessage(requestBody: String, finTs3ServerAddress: String): WebClientResponse {
val encodedRequestBody = base64Service.encode(requestBody) val encodedRequestBody = base64Service.encode(requestBody)
webClient.post(finTs3ServerAddress, encodedRequestBody, "application/octet-stream", IWebClient.DefaultUserAgent, callback) return webClient.post(finTs3ServerAddress, encodedRequestBody, "application/octet-stream", IWebClient.DefaultUserAgent)
} }
open fun fireAndForgetMessage(context: JobContext, message: MessageBuilderResult) { open suspend fun fireAndForgetMessage(context: JobContext, message: MessageBuilderResult) {
message.createdMessage?.let { requestBody -> message.createdMessage?.let { requestBody ->
addMessageLog(context, MessageLogEntryType.Sent, requestBody) addMessageLog(context, MessageLogEntryType.Sent, requestBody)
getResponseForMessage(requestBody, context.bank.finTs3ServerAddress) { } getResponseForMessage(requestBody, context.bank.finTs3ServerAddress)
// if really needed add received response to message log here // if really needed add received response to message log here
} }
@ -129,34 +128,30 @@ open class RequestExecutor(
} }
protected open fun getFollowUpMessageForContinuationId(context: JobContext, response: BankResponse, continuationId: String, message: MessageBuilderResult, protected open suspend fun getFollowUpMessageForContinuationId(context: JobContext, response: BankResponse, continuationId: String, message: MessageBuilderResult,
tanRequiredCallback: (TanResponse, BankResponse, callback: (BankResponse) -> Unit) -> Unit, tanRequiredCallback: suspend (TanResponse, BankResponse) -> BankResponse): BankResponse? {
callback: (BankResponse?) -> Unit) {
messageBuilder.rebuildMessageWithContinuationId(context, message, continuationId)?.let { followUpMessage -> messageBuilder.rebuildMessageWithContinuationId(context, message, continuationId)?.let { followUpMessage ->
getAndHandleResponseForMessage(followUpMessage, context, tanRequiredCallback, callback) return getAndHandleResponseForMessage(followUpMessage, context, tanRequiredCallback)
} }
?: run { callback(null) }
return null
} }
protected open fun handleMayRequiresTan(context: JobContext, response: BankResponse, protected open suspend fun handleMayRequiresTan(context: JobContext, response: BankResponse,
tanRequiredCallback: (TanResponse, BankResponse, callback: (BankResponse) -> Unit) -> Unit, tanRequiredCallback: suspend (TanResponse, BankResponse) -> BankResponse): BankResponse { // TODO: use response from DialogContext
callback: (BankResponse) -> Unit) { // TODO: use response from DialogContext
if (response.isStrongAuthenticationRequired) { if (response.isStrongAuthenticationRequired) {
if (context.dialog.abortIfTanIsRequired) { if (context.dialog.abortIfTanIsRequired) {
response.tanRequiredButWeWereToldToAbortIfSo = true response.tanRequiredButWeWereToldToAbortIfSo = true
callback(response) return response
return
} }
else if (response.tanResponse != null) { else if (response.tanResponse != null) {
response.tanResponse?.let { tanResponse -> response.tanResponse?.let { tanResponse ->
tanRequiredCallback(tanResponse, response, callback) return tanRequiredCallback(tanResponse, response)
} }
return
} }
} }
@ -167,7 +162,7 @@ open class RequestExecutor(
// TODO: also check '9931 Sperrung des Kontos nach %1 Fehlversuchen' -> if %1 == 3 synchronize TAN generator // 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 // as it's quite unrealistic that user entered TAN wrong three times, in most cases TAN generator is not synchronized
callback(response) return response
} }

View File

@ -16,15 +16,15 @@ interface FinTsClientCallback {
* If you do not support an enter tan dialog or if your enter tan dialog supports selecting a TAN method, it's * If you do not support an enter tan dialog or if your enter tan dialog supports selecting a TAN method, it's
* best returning [suggestedTanMethod] and to not show an extra select TAN method dialog. * best returning [suggestedTanMethod] and to not show an extra select TAN method dialog.
*/ */
fun askUserForTanMethod(supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?, callback: (TanMethod?) -> Unit) suspend fun askUserForTanMethod(supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?): TanMethod?
fun enterTan(bank: BankData, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) suspend fun enterTan(bank: BankData, tanChallenge: TanChallenge): EnterTanResult
/** /**
* This method gets called for chipTan TAN generators when the bank asks the customer to synchronize her/his TAN generator. * This method gets called for chipTan TAN generators when the bank asks the customer to synchronize her/his TAN generator.
* *
* If you do not support entering TAN generator ATC, return [EnterTanGeneratorAtcResult.userDidNotEnterAtc] * If you do not support entering TAN generator ATC, return [EnterTanGeneratorAtcResult.userDidNotEnterAtc]
*/ */
fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium, callback: (EnterTanGeneratorAtcResult) -> Unit) suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult
} }

View File

@ -6,18 +6,16 @@ import net.dankito.banking.fints.model.*
open class NoOpFinTsClientCallback : FinTsClientCallback { open class NoOpFinTsClientCallback : FinTsClientCallback {
override fun askUserForTanMethod(supportedTanMethods: List<TanMethod>, override suspend fun askUserForTanMethod(supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?): TanMethod? {
suggestedTanMethod: TanMethod?, callback: (TanMethod?) -> Unit) { return suggestedTanMethod
callback(suggestedTanMethod)
} }
override fun enterTan(bank: BankData, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) { override suspend fun enterTan(bank: BankData, tanChallenge: TanChallenge): EnterTanResult {
callback(EnterTanResult.userDidNotEnterTan()) return EnterTanResult.userDidNotEnterTan()
} }
override fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium, callback: (EnterTanGeneratorAtcResult) -> Unit) { override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
callback(EnterTanGeneratorAtcResult.userDidNotEnterAtc()) return EnterTanGeneratorAtcResult.userDidNotEnterAtc()
} }
} }

View File

@ -5,9 +5,9 @@ import net.dankito.banking.fints.model.*
open class SimpleFinTsClientCallback( open class SimpleFinTsClientCallback(
protected val enterTan: ((bank: BankData, tanChallenge: TanChallenge) -> EnterTanResult)? = null, protected open val enterTan: ((bank: BankData, tanChallenge: TanChallenge) -> EnterTanResult)? = null,
protected val enterTanGeneratorAtc: ((bank: BankData, tanMedium: TanGeneratorTanMedium) -> EnterTanGeneratorAtcResult)? = null, protected open val enterTanGeneratorAtc: ((bank: BankData, tanMedium: TanGeneratorTanMedium) -> EnterTanGeneratorAtcResult)? = null,
protected val askUserForTanMethod: ((supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?) -> TanMethod?)? = null protected open val askUserForTanMethod: ((supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?) -> TanMethod?)? = null
) : FinTsClientCallback { ) : FinTsClientCallback {
constructor() : this(null) // Swift does not support default parameter values -> create constructor overloads constructor() : this(null) // Swift does not support default parameter values -> create constructor overloads
@ -15,18 +15,17 @@ open class SimpleFinTsClientCallback(
constructor(enterTan: ((bank: BankData, tanChallenge: TanChallenge) -> EnterTanResult)?) : this(enterTan, null) constructor(enterTan: ((bank: BankData, tanChallenge: TanChallenge) -> EnterTanResult)?) : this(enterTan, null)
override fun askUserForTanMethod(supportedTanMethods: List<TanMethod>, override suspend fun askUserForTanMethod(supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?): TanMethod? {
suggestedTanMethod: TanMethod?, callback: (TanMethod?) -> Unit) {
callback(askUserForTanMethod?.invoke(supportedTanMethods, suggestedTanMethod) ?: suggestedTanMethod) return askUserForTanMethod?.invoke(supportedTanMethods, suggestedTanMethod) ?: suggestedTanMethod
} }
override fun enterTan(bank: BankData, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) { override suspend fun enterTan(bank: BankData, tanChallenge: TanChallenge): EnterTanResult {
callback(enterTan?.invoke(bank, tanChallenge) ?: EnterTanResult.userDidNotEnterTan()) return enterTan?.invoke(bank, tanChallenge) ?: EnterTanResult.userDidNotEnterTan()
} }
override fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium, callback: (EnterTanGeneratorAtcResult) -> Unit) { override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
callback(enterTanGeneratorAtc?.invoke(bank, tanMedium) ?: EnterTanGeneratorAtcResult.userDidNotEnterAtc()) return enterTanGeneratorAtc?.invoke(bank, tanMedium) ?: EnterTanGeneratorAtcResult.userDidNotEnterAtc()
} }
} }

View File

@ -20,6 +20,10 @@ interface IWebClient {
} }
fun post(url: String, body: String, contentType: String = "application/octet-stream", userAgent: String = DefaultUserAgent, callback: (WebClientResponse) -> Unit) suspend fun post(url: String, body: String): WebClientResponse { // some platforms don't support default parameters
return post(url, body)
}
suspend fun post(url: String, body: String, contentType: String = "application/octet-stream", userAgent: String = DefaultUserAgent): WebClientResponse
} }

View File

@ -35,13 +35,11 @@ open class KtorWebClient : IWebClient {
} }
override fun post(url: String, body: String, contentType: String, userAgent: String, callback: (WebClientResponse) -> Unit) { override suspend fun post(url: String, body: String, contentType: String, userAgent: String): WebClientResponse {
GlobalScope.async { return postInCoroutine(url, body, contentType, userAgent)
postInCoroutine(url, body, contentType, userAgent, callback)
}
} }
protected open suspend fun postInCoroutine(url: String, body: String, contentType: String, userAgent: String, callback: (WebClientResponse) -> Unit) { protected open suspend fun postInCoroutine(url: String, body: String, contentType: String, userAgent: String): WebClientResponse {
try { try {
val clientResponse = client.post(url) { val clientResponse = client.post(url) {
contentType(ContentType.Application.OctetStream) contentType(ContentType.Application.OctetStream)
@ -50,11 +48,11 @@ open class KtorWebClient : IWebClient {
val responseBody = clientResponse.bodyAsText() val responseBody = clientResponse.bodyAsText()
callback(WebClientResponse(clientResponse.status.value == 200, clientResponse.status.value, body = responseBody)) return WebClientResponse(clientResponse.status.value == 200, clientResponse.status.value, body = responseBody)
} catch (e: Exception) { } catch (e: Exception) {
log.error(e) { "Could not send request to url '$url'" } log.error(e) { "Could not send request to url '$url'" }
callback(WebClientResponse(false, error = e)) return WebClientResponse(false, error = e)
} }
} }

View File

@ -10,8 +10,8 @@ class ProxyingWebClient(proxyUrl: String, private val delegate: IWebClient) : IW
private val proxyUrl = if (proxyUrl.endsWith("/")) proxyUrl else proxyUrl + "/" private val proxyUrl = if (proxyUrl.endsWith("/")) proxyUrl else proxyUrl + "/"
override fun post(url: String, body: String, contentType: String, userAgent: String, callback: (WebClientResponse) -> Unit) { override suspend fun post(url: String, body: String, contentType: String, userAgent: String): WebClientResponse {
delegate.post(proxyUrl + url, body, contentType, userAgent, callback) return delegate.post(proxyUrl + url, body, contentType, userAgent)
} }
} }

View File

@ -1,13 +1,10 @@
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import net.dankito.banking.fints.FinTsClientDeprecated import net.dankito.banking.fints.FinTsClientDeprecated
import net.dankito.banking.fints.FinTsJobExecutor
import net.dankito.banking.fints.RequestExecutor
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
import net.dankito.banking.fints.model.AddAccountParameter import net.dankito.banking.fints.model.AddAccountParameter
import net.dankito.banking.fints.model.RetrievedAccountData import net.dankito.banking.fints.model.RetrievedAccountData
import net.dankito.banking.fints.response.client.AddAccountResponse import net.dankito.banking.fints.response.client.AddAccountResponse
import net.dankito.banking.fints.webclient.BlockingKtorWebClient
import net.dankito.utils.multiplatform.extensions.* import net.dankito.utils.multiplatform.extensions.*
import platform.posix.exit import platform.posix.exit
@ -25,15 +22,15 @@ class Application {
fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String) { fun retrieveAccountData(bankCode: String, customerId: String, pin: String, finTs3ServerAddress: String) {
runBlocking { runBlocking {
val client = FinTsClientDeprecated(SimpleFinTsClientCallback(), FinTsJobExecutor(RequestExecutor(webClient = BlockingKtorWebClient()))) val client = FinTsClientDeprecated(SimpleFinTsClientCallback())
val response = client.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress))
client.addAccountAsync(AddAccountParameter(bankCode, customerId, pin, finTs3ServerAddress)) { response ->
println("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}") println("Retrieved response from ${response.bank.bankName} for ${response.bank.customerName}")
displayRetrievedAccountData(response) displayRetrievedAccountData(response)
} }
} }
}
private fun displayRetrievedAccountData(response: AddAccountResponse) { private fun displayRetrievedAccountData(response: AddAccountResponse) {
if (response.retrievedData.isEmpty()) { if (response.retrievedData.isEmpty()) {

View File

@ -1,14 +0,0 @@
package net.dankito.banking.fints.webclient
import kotlinx.coroutines.runBlocking
open class BlockingKtorWebClient : KtorWebClient() {
override fun post(url: String, body: String, contentType: String, userAgent: String, callback: (WebClientResponse) -> Unit) {
runBlocking {
postInCoroutine(url, body, contentType, userAgent, callback)
}
}
}