Implemented EnterTanResult so that UI can tell fints4java to change user's TAN medium which it then does transparently

This commit is contained in:
dankl 2019-12-31 16:01:00 +01:00 committed by dankito
parent 6e7d24ebfc
commit 2951445390
8 changed files with 88 additions and 91 deletions

View File

@ -15,10 +15,7 @@ import net.dankito.banking.fints4java.android.ui.dialogs.EnterAtcDialog
import net.dankito.banking.fints4java.android.ui.dialogs.EnterTanDialog import net.dankito.banking.fints4java.android.ui.dialogs.EnterTanDialog
import net.dankito.fints.FinTsClientCallback import net.dankito.fints.FinTsClientCallback
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.model.CustomerData import net.dankito.fints.model.*
import net.dankito.fints.model.EnterTanGeneratorAtcResult
import net.dankito.fints.model.TanChallenge
import net.dankito.fints.model.TanProcedure
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
@ -35,7 +32,7 @@ class MainActivity : AppCompatActivity() {
return supportedTanProcedures.first() return supportedTanProcedures.first()
} }
override fun enterTan(customer: CustomerData, tanChallenge: TanChallenge): String? { override fun enterTan(customer: CustomerData, tanChallenge: TanChallenge): EnterTanResult {
return getTanFromUserOffUiThread(customer, tanChallenge) return getTanFromUserOffUiThread(customer, tanChallenge)
} }
@ -92,8 +89,8 @@ class MainActivity : AppCompatActivity() {
// return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() // return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
// } // }
private fun getTanFromUserOffUiThread(customer: CustomerData, tanChallenge: TanChallenge): String? { private fun getTanFromUserOffUiThread(customer: CustomerData, tanChallenge: TanChallenge): EnterTanResult {
val enteredTan = AtomicReference<String>(null) val enteredTan = AtomicReference<EnterTanResult>(null)
val tanEnteredLatch = CountDownLatch(1) val tanEnteredLatch = CountDownLatch(1)
val account = presenter.getAccountForCustomer(customer) val account = presenter.getAccountForCustomer(customer)

View File

@ -3,13 +3,11 @@ package net.dankito.banking.fints4java.android.ui
import net.dankito.banking.ui.model.Account import net.dankito.banking.ui.model.Account
import net.dankito.banking.ui.model.AccountTransaction import net.dankito.banking.ui.model.AccountTransaction
import net.dankito.banking.ui.model.BankAccount import net.dankito.banking.ui.model.BankAccount
import net.dankito.banking.ui.model.TanMedium
import net.dankito.banking.ui.model.responses.AddAccountResponse import net.dankito.banking.ui.model.responses.AddAccountResponse
import net.dankito.banking.ui.model.responses.GetTransactionsResponse import net.dankito.banking.ui.model.responses.GetTransactionsResponse
import net.dankito.fints.FinTsClientCallback import net.dankito.fints.FinTsClientCallback
import net.dankito.fints.FinTsClientForCustomer import net.dankito.fints.FinTsClientForCustomer
import net.dankito.fints.banks.BankFinder import net.dankito.fints.banks.BankFinder
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.model.BankInfo import net.dankito.fints.model.BankInfo
import net.dankito.fints.model.BankTransferData import net.dankito.fints.model.BankTransferData
import net.dankito.fints.model.CustomerData import net.dankito.fints.model.CustomerData
@ -144,16 +142,6 @@ open class MainWindowPresenter(protected val base64Service: IBase64Service,
} }
open fun changeTanMediumAsync(newUsedTanMedium: TanMedium, account: Account, callback: (FinTsClientResponse) -> Unit) {
(newUsedTanMedium.originalObject as? TanGeneratorTanMedium)?.let { tanGeneratorTanMedium ->
getClientForAccount(account)?.changeTanMedium(tanGeneratorTanMedium, callback)
// TODO: find a way to update account.tanMedia afterwards
}
// TODO: what to do if newActiveTanMedium.originalObject is not of type TanGeneratorTanMedium?
}
open fun preloadBanksAsync() { open fun preloadBanksAsync() {
searchBanksByBankCodeAsync("1") { } searchBanksByBankCodeAsync("1") { }
} }

View File

@ -1,9 +1,7 @@
package net.dankito.banking.fints4java.android.ui.dialogs package net.dankito.banking.fints4java.android.ui.dialogs
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.DialogFragment import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.text.InputType import android.text.InputType
import android.view.LayoutInflater import android.view.LayoutInflater
@ -15,11 +13,11 @@ import net.dankito.banking.fints4java.android.ui.MainWindowPresenter
import net.dankito.banking.fints4java.android.ui.adapter.TanMediumAdapter import net.dankito.banking.fints4java.android.ui.adapter.TanMediumAdapter
import net.dankito.banking.fints4java.android.ui.listener.ListItemSelectedListener import net.dankito.banking.fints4java.android.ui.listener.ListItemSelectedListener
import net.dankito.banking.ui.model.Account import net.dankito.banking.ui.model.Account
import net.dankito.banking.ui.model.TanMedium
import net.dankito.banking.ui.model.TanMediumStatus import net.dankito.banking.ui.model.TanMediumStatus
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.model.EnterTanResult
import net.dankito.fints.model.TanChallenge import net.dankito.fints.model.TanChallenge
import net.dankito.fints.model.TanProcedureType import net.dankito.fints.model.TanProcedureType
import net.dankito.fints.response.client.FinTsClientResponse
import net.dankito.fints.tan.FlickercodeDecoder import net.dankito.fints.tan.FlickercodeDecoder
@ -36,13 +34,13 @@ open class EnterTanDialog : DialogFragment() {
protected lateinit var presenter: MainWindowPresenter protected lateinit var presenter: MainWindowPresenter
protected lateinit var tanEnteredCallback: (String?) -> Unit protected lateinit var tanEnteredCallback: (EnterTanResult) -> Unit
protected val tanMediumAdapter = TanMediumAdapter() protected val tanMediumAdapter = TanMediumAdapter()
open fun show(account: Account, tanChallenge: TanChallenge, presenter: MainWindowPresenter, activity: AppCompatActivity, open fun show(account: Account, tanChallenge: TanChallenge, presenter: MainWindowPresenter, activity: AppCompatActivity,
fullscreen: Boolean = false, tanEnteredCallback: (String?) -> Unit) { fullscreen: Boolean = false, tanEnteredCallback: (EnterTanResult) -> Unit) {
this.account = account this.account = account
this.tanChallenge = tanChallenge this.tanChallenge = tanChallenge
@ -93,42 +91,23 @@ open class EnterTanDialog : DialogFragment() {
rootView.spnTanMedium.adapter = tanMediumAdapter rootView.spnTanMedium.adapter = tanMediumAdapter
rootView.spnTanMedium.onItemSelectedListener = ListItemSelectedListener(tanMediumAdapter) { selectedTanMedium -> rootView.spnTanMedium.onItemSelectedListener = ListItemSelectedListener(tanMediumAdapter) { selectedTanMedium ->
if (selectedTanMedium.status != TanMediumStatus.Used) { if (selectedTanMedium.status != TanMediumStatus.Used) {
presenter.changeTanMediumAsync(selectedTanMedium, account) { response -> (selectedTanMedium.originalObject as? TanGeneratorTanMedium)?.let { tanGeneratorTanMedium ->
handleChangeTanMediumResponse(selectedTanMedium, response) tanEnteredCallback(EnterTanResult.userAsksToChangeTanMedium(tanGeneratorTanMedium))
// TODO: find a way to update account.tanMedia afterwards
dismiss() // TODO: really dismiss? what if changing TAN medium fails?
} }
// TODO: what to do if newActiveTanMedium.originalObject is not of type TanGeneratorTanMedium?
} }
} }
} }
private fun handleChangeTanMediumResponse(newUsedTanMedium: TanMedium, response: FinTsClientResponse) {
activity?.let { activity ->
activity.runOnUiThread {
handleChangeTanMediumResponseOnUiThread(activity, newUsedTanMedium, response)
}
}
}
protected open fun handleChangeTanMediumResponseOnUiThread(context: Context, newUsedTanMedium: TanMedium, response: FinTsClientResponse) {
if (response.isSuccessful) {
dismiss()
AlertDialog.Builder(context)
.setMessage(context.getString(R.string.dialog_enter_tan_tan_medium_successfully_changed, newUsedTanMedium.displayName))
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
.show()
}
else {
AlertDialog.Builder(context)
.setMessage(context.getString(R.string.dialog_enter_tan_error_changing_tan_medium, newUsedTanMedium.displayName, presenter.getErrorToShowToUser(response)))
.setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
.show()
}
}
protected open fun enteringTanDone(enteredTan: String?) { protected open fun enteringTanDone(enteredTan: String?) {
tanEnteredCallback(enteredTan) val result = if (enteredTan != null) EnterTanResult.userEnteredTan(enteredTan) else EnterTanResult.userDidNotEnterTan()
tanEnteredCallback(result)
dismiss() dismiss()
} }

View File

@ -356,19 +356,14 @@ open class FinTsClient @JvmOverloads constructor(
open fun changeTanMedium(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData, customer: CustomerData): FinTsClientResponse { open fun changeTanMedium(newActiveTanMedium: TanGeneratorTanMedium, bank: BankData, customer: CustomerData): FinTsClientResponse {
val lastCreatedMessage = messageBuilder.lastCreatedMessage
// lastCreatedMessage?.let { closeDialog(bank, customer, ) } // TODO: close previous dialog
var enteredAtc: EnterTanGeneratorAtcResult? = null var enteredAtc: EnterTanGeneratorAtcResult? = null
if (bank.changeTanMediumParameters?.enteringAtcAndTanRequired == true) { if (bank.changeTanMediumParameters?.enteringAtcAndTanRequired == true) {
enteredAtc = callback.enterTanGeneratorAtc(customer, newActiveTanMedium) enteredAtc = callback.enterTanGeneratorAtc(customer, newActiveTanMedium)
if (enteredAtc == null) { if (enteredAtc == null) {
return FinTsClientResponse(Response(false, exception = val message = "Bank requires to enter ATC and TAN in order to change TAN medium." // TODO: translate
Exception("Bank requires to enter ATC and TAN in order to change TAN medium."))) // TODO: translate return FinTsClientResponse(Response(false, exception = Exception(message)))
} }
} }
@ -392,11 +387,6 @@ open class FinTsClient @JvmOverloads constructor(
closeDialog(bank, customer, dialogData) closeDialog(bank, customer, dialogData)
lastCreatedMessage?.let {
resendMessageInNewDialogAsync(lastCreatedMessage, bank, customer)
}
return FinTsClientResponse(response) return FinTsClientResponse(response)
} }
@ -433,24 +423,14 @@ open class FinTsClient @JvmOverloads constructor(
} }
protected open fun resendMessageInNewDialogAsync(message: MessageBuilderResult, bank: BankData,
customer: CustomerData) {
threadPool.runAsync {
resendMessageInNewDialog(message, bank, customer)
}
}
protected open fun resendMessageInNewDialog(message: MessageBuilderResult, bank: BankData, protected open fun resendMessageInNewDialog(message: MessageBuilderResult, bank: BankData,
customer: CustomerData): FinTsClientResponse { customer: CustomerData): Response {
log.info("Resending message ${message.messageBodySegments.map { it.dataElementsAndGroups.firstOrNull()?.format() }} in a new dialog") // TODO: remove again
val dialogData = DialogData() val dialogData = DialogData()
val initDialogResponse = initDialog(bank, customer, dialogData) val initDialogResponse = initDialog(bank, customer, dialogData)
if (initDialogResponse.successful == false) { if (initDialogResponse.successful == false) {
return FinTsClientResponse(initDialogResponse) return initDialogResponse
} }
@ -460,7 +440,7 @@ open class FinTsClient @JvmOverloads constructor(
closeDialog(bank, customer, dialogData) closeDialog(bank, customer, dialogData)
return FinTsClientResponse(response) return response
} }
@ -669,16 +649,19 @@ open class FinTsClient @JvmOverloads constructor(
if (response.isStrongAuthenticationRequired) { if (response.isStrongAuthenticationRequired) {
response.tanResponse?.let { tanResponse -> response.tanResponse?.let { tanResponse ->
// TODO: is this true for all tan procedures? // TODO: is this true for all tan procedures?
val enteredTan = callback.enterTan(customer, TanChallenge(tanResponse.challenge ?: "", val enteredTanResult = callback.enterTan(customer, TanChallenge(tanResponse.challenge ?: "",
tanResponse.challengeHHD_UC ?: "", customer.selectedTanProcedure)) tanResponse.challengeHHD_UC ?: "", customer.selectedTanProcedure))
if (enteredTan == null) { if (enteredTanResult.changeTanMediumTo is TanGeneratorTanMedium) {
return handleUserAsksToChangeTanMediumAndResendLastMessage(enteredTanResult.changeTanMediumTo, bank, customer, dialogData)
}
else if (enteredTanResult.enteredTan == null) {
// i tried to send a HKTAN with cancelJob = true but then i saw there are no tan procedures 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 procedures 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.tanRequiredButNotProvided = true response.tanRequiredButNotProvided = true
} }
else { else {
return sendTanToBank(enteredTan, tanResponse, bank, customer, dialogData) return sendTanToBank(enteredTanResult.enteredTan, tanResponse, bank, customer, dialogData)
} }
} }
} }
@ -703,6 +686,24 @@ open class FinTsClient @JvmOverloads constructor(
return getAndHandleResponseForMessageThatMayRequiresTan(message, bank, customer, dialogData) return getAndHandleResponseForMessageThatMayRequiresTan(message, bank, customer, dialogData)
} }
protected open fun handleUserAsksToChangeTanMediumAndResendLastMessage(changeTanMediumTo: TanGeneratorTanMedium, bank: BankData,
customer: CustomerData, dialogData: DialogData): Response {
val lastCreatedMessage = messageBuilder.lastCreatedMessage
lastCreatedMessage?.let { closeDialog(bank, customer, dialogData) }
val changeTanMediumResponse = changeTanMedium(changeTanMediumTo, bank, customer)
if (changeTanMediumResponse.isSuccessful == false || lastCreatedMessage == null) {
return changeTanMediumResponse.toResponse()
}
return resendMessageInNewDialog(lastCreatedMessage, bank, customer)
}
protected open fun updateBankData(bank: BankData, response: Response) { protected open fun updateBankData(bank: BankData, response: Response) {
response.getFirstSegmentById<BankParameters>(InstituteSegmentId.BankParameters)?.let { bankParameters -> response.getFirstSegmentById<BankParameters>(InstituteSegmentId.BankParameters)?.let { bankParameters ->

View File

@ -1,17 +1,14 @@
package net.dankito.fints package net.dankito.fints
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium import net.dankito.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
import net.dankito.fints.model.CustomerData import net.dankito.fints.model.*
import net.dankito.fints.model.EnterTanGeneratorAtcResult
import net.dankito.fints.model.TanChallenge
import net.dankito.fints.model.TanProcedure
interface FinTsClientCallback { interface FinTsClientCallback {
fun askUserForTanProcedure(supportedTanProcedures: List<TanProcedure>): TanProcedure? fun askUserForTanProcedure(supportedTanProcedures: List<TanProcedure>): TanProcedure?
fun enterTan(customer: CustomerData, tanChallenge: TanChallenge): String? fun enterTan(customer: CustomerData, 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.

View File

@ -0,0 +1,35 @@
package net.dankito.fints.model
import net.dankito.fints.messages.datenelemente.implementierte.tan.TanMedium
open class EnterTanResult constructor(
val enteredTan: String?,
val changeTanMediumTo: TanMedium?
) {
companion object {
fun userEnteredTan(enteredTan: String): EnterTanResult {
return EnterTanResult(enteredTan, null)
}
fun userDidNotEnterTan(): EnterTanResult {
return EnterTanResult(null, null)
}
fun userAsksToChangeTanMedium(changeTanMediumTo: TanMedium): EnterTanResult {
return EnterTanResult(null, changeTanMediumTo)
}
}
override fun toString(): String {
if (changeTanMediumTo != null) {
return "User asks to change TAN medium to $changeTanMediumTo"
}
return "enteredTan = $enteredTan"
}
}

View File

@ -42,8 +42,8 @@ public class JavaShowcase {
@Nullable @Nullable
@Override @Override
public String enterTan(@NotNull CustomerData customer, @NotNull TanChallenge tanChallenge) { public EnterTanResult enterTan(@NotNull CustomerData customer, @NotNull TanChallenge tanChallenge) {
return null; return EnterTanResult.Companion.userDidNotEnterTan();
} }
@Nullable @Nullable

View File

@ -37,10 +37,10 @@ class FinTsClientTest {
return supportedTanProcedures.first() return supportedTanProcedures.first()
} }
override fun enterTan(customer: CustomerData, tanChallenge: TanChallenge): String? { override fun enterTan(customer: CustomerData, tanChallenge: TanChallenge): EnterTanResult {
didAskUserToEnterTan.set(true) didAskUserToEnterTan.set(true)
return null return EnterTanResult.userDidNotEnterTan()
} }
override fun enterTanGeneratorAtc(customer: CustomerData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult? { override fun enterTanGeneratorAtc(customer: CustomerData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult? {