Implemented handling TAN in FinTs4kBankingClient
This commit is contained in:
parent
d57f6d8600
commit
d331a4810c
|
@ -0,0 +1,10 @@
|
||||||
|
package net.codinux.banking.client
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||||
|
import net.codinux.banking.client.model.tan.TanChallenge
|
||||||
|
|
||||||
|
interface BankingClientCallback {
|
||||||
|
|
||||||
|
fun enterTan(tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package net.codinux.banking.client
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||||
|
import net.codinux.banking.client.model.tan.TanChallenge
|
||||||
|
|
||||||
|
open class SimpleBankingClientCallback(
|
||||||
|
protected val enterTan: ((tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) -> Unit)? = null
|
||||||
|
) : BankingClientCallback {
|
||||||
|
|
||||||
|
override fun enterTan(tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) {
|
||||||
|
if (enterTan != null) {
|
||||||
|
enterTan.invoke(tanChallenge, callback)
|
||||||
|
} else {
|
||||||
|
callback(EnterTanResult(null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package net.codinux.banking.client.model.tan
|
package net.codinux.banking.client.model.tan
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.BankAccount
|
||||||
import net.codinux.banking.client.model.CustomerAccount
|
import net.codinux.banking.client.model.CustomerAccount
|
||||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
|
||||||
|
@ -7,16 +8,17 @@ import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
open class TanChallenge(
|
open class TanChallenge(
|
||||||
val type: TanChallengeType,
|
val type: TanChallengeType,
|
||||||
val forAction: ActionRequiringTan,
|
val forAction: ActionRequiringTan,
|
||||||
val customer: CustomerAccount,
|
|
||||||
val messageToShowToUser: String,
|
val messageToShowToUser: String,
|
||||||
val tanMethod: TanMethod,
|
val tanMethod: TanMethod,
|
||||||
val tanImage: TanImage? = null,
|
val tanImage: TanImage? = null,
|
||||||
val flickerCode: FlickerCode? = null
|
val flickerCode: FlickerCode? = null,
|
||||||
|
val customer: CustomerAccount,
|
||||||
|
val account: BankAccount? = null
|
||||||
// TODO: add availableTanMethods, selectedTanMedium, availableTanMedia
|
// TODO: add availableTanMethods, selectedTanMedium, availableTanMedia
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "$tanMethod: $messageToShowToUser" + when (type) {
|
return "$tanMethod $forAction: $messageToShowToUser" + when (type) {
|
||||||
TanChallengeType.EnterTan -> ""
|
TanChallengeType.EnterTan -> ""
|
||||||
TanChallengeType.Image -> ", Image: $tanImage"
|
TanChallengeType.Image -> ", Image: $tanImage"
|
||||||
TanChallengeType.Flickercode -> ", FlickerCode: $flickerCode"
|
TanChallengeType.Flickercode -> ", FlickerCode: $flickerCode"
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package net.codinux.banking.client.fints4k
|
||||||
|
|
||||||
|
import net.codinux.banking.client.BankingClientCallback
|
||||||
|
import net.dankito.banking.fints.callback.FinTsClientCallback
|
||||||
|
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
|
||||||
|
import net.dankito.banking.fints.model.BankData
|
||||||
|
import net.dankito.banking.fints.model.EnterTanGeneratorAtcResult
|
||||||
|
import net.dankito.banking.fints.model.TanMethod
|
||||||
|
|
||||||
|
open class BridgeFintTsToBankingClientCallback(
|
||||||
|
protected val bankingClientCallback: BankingClientCallback,
|
||||||
|
protected val mapper: FinTs4kMapper
|
||||||
|
) : FinTsClientCallback {
|
||||||
|
|
||||||
|
override suspend fun askUserForTanMethod(supportedTanMethods: List<TanMethod>, suggestedTanMethod: TanMethod?): TanMethod? {
|
||||||
|
return suggestedTanMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun enterTan(tanChallenge: net.dankito.banking.fints.model.TanChallenge) {
|
||||||
|
bankingClientCallback.enterTan(mapper.mapTanChallenge(tanChallenge)) { enterTanResult ->
|
||||||
|
if (enterTanResult.enteredTan != null) {
|
||||||
|
tanChallenge.userEnteredTan(enterTanResult.enteredTan!!)
|
||||||
|
} else {
|
||||||
|
tanChallenge.userDidNotEnterTan()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun enterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium): EnterTanGeneratorAtcResult {
|
||||||
|
return EnterTanGeneratorAtcResult.userDidNotEnterAtc()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,20 @@
|
||||||
package net.codinux.banking.client.fints4k
|
package net.codinux.banking.client.fints4k
|
||||||
|
|
||||||
import net.codinux.banking.client.BankingClient
|
import net.codinux.banking.client.BankingClient
|
||||||
import net.codinux.banking.client.model.AccountCredentials
|
import net.codinux.banking.client.BankingClientCallback
|
||||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||||
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
import net.codinux.banking.client.model.response.GetAccountDataResponse
|
||||||
import net.codinux.banking.client.model.response.Response
|
import net.codinux.banking.client.model.response.Response
|
||||||
import net.dankito.banking.fints.FinTsClient
|
import net.dankito.banking.fints.FinTsClient
|
||||||
import net.dankito.banking.fints.callback.SimpleFinTsClientCallback
|
|
||||||
|
|
||||||
open class FinTs4kBankingClient : BankingClient {
|
open class FinTs4kBankingClient(
|
||||||
|
callback: BankingClientCallback
|
||||||
|
) : BankingClient {
|
||||||
|
|
||||||
private val mapper = FinTs4kMapper()
|
private val mapper = FinTs4kMapper()
|
||||||
|
|
||||||
private val client = FinTsClient(SimpleFinTsClientCallback { tanChallenge ->
|
private val client = FinTsClient(BridgeFintTsToBankingClientCallback(callback, mapper))
|
||||||
// callback.enterTan()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
override suspend fun getAccountDataAsync(request: GetAccountDataRequest): Response<GetAccountDataResponse> {
|
override suspend fun getAccountDataAsync(request: GetAccountDataRequest): Response<GetAccountDataResponse> {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package net.codinux.banking.client.fints4k
|
package net.codinux.banking.client.fints4k
|
||||||
|
|
||||||
|
import net.codinux.banking.client.BankingClientCallback
|
||||||
import net.codinux.banking.client.BankingClientForCustomerBase
|
import net.codinux.banking.client.BankingClientForCustomerBase
|
||||||
import net.codinux.banking.client.model.AccountCredentials
|
import net.codinux.banking.client.model.AccountCredentials
|
||||||
|
|
||||||
open class FinTs4kBankingClientForCustomer(credentials: AccountCredentials)
|
open class FinTs4kBankingClientForCustomer(credentials: AccountCredentials, callback: BankingClientCallback)
|
||||||
: BankingClientForCustomerBase(credentials, FinTs4kBankingClient()) {
|
: BankingClientForCustomerBase(credentials, FinTs4kBankingClient(callback)) {
|
||||||
|
|
||||||
constructor(bankCode: String, loginName: String, password: String)
|
constructor(bankCode: String, loginName: String, password: String, callback: BankingClientCallback)
|
||||||
: this(AccountCredentials(bankCode, loginName, password))
|
: this(AccountCredentials(bankCode, loginName, password), callback)
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,15 +1,29 @@
|
||||||
package net.codinux.banking.client.fints4k
|
package net.codinux.banking.client.fints4k
|
||||||
|
|
||||||
import net.codinux.banking.client.model.*
|
import net.codinux.banking.client.model.*
|
||||||
|
import net.codinux.banking.client.model.AccountTransaction
|
||||||
|
import net.codinux.banking.client.model.Amount
|
||||||
|
import net.codinux.banking.client.model.tan.*
|
||||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||||
import net.codinux.banking.client.model.response.*
|
import net.codinux.banking.client.model.response.*
|
||||||
|
import net.codinux.banking.client.model.tan.ActionRequiringTan
|
||||||
|
import net.codinux.banking.client.model.tan.TanChallenge
|
||||||
|
import net.codinux.banking.client.model.tan.TanImage
|
||||||
|
import net.codinux.banking.client.model.tan.TanMethod
|
||||||
|
import net.codinux.banking.client.model.tan.TanMethodType
|
||||||
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
||||||
import net.dankito.banking.client.model.parameter.RetrieveTransactions
|
import net.dankito.banking.client.model.parameter.RetrieveTransactions
|
||||||
import net.dankito.banking.client.model.response.ErrorCode
|
import net.dankito.banking.client.model.response.ErrorCode
|
||||||
import net.dankito.banking.fints.model.Money
|
import net.dankito.banking.fints.mapper.FinTsModelMapper
|
||||||
|
import net.dankito.banking.fints.model.*
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
|
|
||||||
open class FinTs4kMapper {
|
open class FinTs4kMapper {
|
||||||
|
|
||||||
|
private val fintsModelMapper = FinTsModelMapper()
|
||||||
|
|
||||||
|
|
||||||
fun mapToGetAccountDataParameter(credentials: AccountCredentials, options: GetAccountDataOptions) = GetAccountDataParameter(
|
fun mapToGetAccountDataParameter(credentials: AccountCredentials, options: GetAccountDataOptions) = GetAccountDataParameter(
|
||||||
credentials.bankCode, credentials.loginName, credentials.password,
|
credentials.bankCode, credentials.loginName, credentials.password,
|
||||||
null,
|
null,
|
||||||
|
@ -21,33 +35,36 @@ open class FinTs4kMapper {
|
||||||
|
|
||||||
fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<GetAccountDataResponse> {
|
fun map(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<GetAccountDataResponse> {
|
||||||
return if (response.successful && response.customerAccount != null) {
|
return if (response.successful && response.customerAccount != null) {
|
||||||
Response.success(mapCustomer(response.customerAccount!!))
|
Response.success(GetAccountDataResponse(mapCustomer(response.customerAccount!!)))
|
||||||
} else {
|
} else {
|
||||||
mapError(response)
|
mapError(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun mapCustomer(customer: net.dankito.banking.client.model.CustomerAccount): GetAccountDataResponse {
|
private fun mapCustomer(customer: net.dankito.banking.client.model.CustomerAccount): CustomerAccount = CustomerAccount(
|
||||||
val mapped = CustomerAccount(
|
customer.bankCode, customer.loginName, customer.password,
|
||||||
customer.bankCode, customer.loginName, customer.password,
|
customer.bankName, customer.bic, customer.customerName, customer.userId,
|
||||||
customer.bankName, customer.bic, customer.customerName, customer.userId,
|
customer.accounts.map { mapAccount(it) }
|
||||||
customer.accounts.map { mapAccount(it) }
|
)
|
||||||
)
|
|
||||||
|
|
||||||
return GetAccountDataResponse(mapped)
|
fun mapCustomer(bank: BankData): CustomerAccount =
|
||||||
}
|
mapCustomer(fintsModelMapper.map(bank))
|
||||||
|
|
||||||
|
|
||||||
private fun mapAccount(account: net.dankito.banking.client.model.BankAccount): BankAccount = BankAccount(
|
private fun mapAccount(account: net.dankito.banking.client.model.BankAccount): BankAccount = BankAccount(
|
||||||
account.identifier, account.accountHolderName, mapAccountType(account.type), account.iban, account.subAccountNumber,
|
account.identifier, account.accountHolderName, mapAccountType(account.type), account.iban, account.subAccountNumber,
|
||||||
account.productName, account.currency, account.accountLimit, isAccountTypeSupported(account),
|
account.productName, account.currency, account.accountLimit, account.isAccountTypeSupportedByApplication,
|
||||||
mapFeatures(account),
|
mapFeatures(account),
|
||||||
mapAmount(account.balance), account.retrievedTransactionsFrom, account.retrievedTransactionsTo,
|
mapAmount(account.balance), account.retrievedTransactionsFrom, account.retrievedTransactionsTo,
|
||||||
// TODO: map haveAllTransactionsBeenRetrieved and countDaysForWhichTransactionsAreKept
|
// TODO: map haveAllTransactionsBeenRetrieved
|
||||||
|
countDaysForWhichTransactionsAreKept = account.countDaysForWhichTransactionsAreKept,
|
||||||
bookedTransactions = account.bookedTransactions.map { mapTransaction(it) }.toMutableList()
|
bookedTransactions = account.bookedTransactions.map { mapTransaction(it) }.toMutableList()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun mapAccount(account: AccountData): BankAccount =
|
||||||
|
mapAccount(fintsModelMapper.map(account))
|
||||||
|
|
||||||
private fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
private fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
||||||
BankAccountType.valueOf(type.name)
|
BankAccountType.valueOf(type.name)
|
||||||
|
|
||||||
|
@ -66,16 +83,6 @@ open class FinTs4kMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isAccountTypeSupported(account: net.dankito.banking.client.model.BankAccount): Boolean {
|
|
||||||
// TODO:
|
|
||||||
// open val isAccountTypeSupportedByApplication: Boolean
|
|
||||||
// get() = FinTsClient.SupportedAccountTypes.contains(accountType)
|
|
||||||
// || allowedJobNames.contains(CustomerSegmentId.Balance.id)
|
|
||||||
// || allowedJobNames.contains(CustomerSegmentId.AccountTransactionsMt940.id)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
private fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
||||||
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.unparsedReference,
|
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.unparsedReference,
|
||||||
|
@ -92,6 +99,50 @@ open class FinTs4kMapper {
|
||||||
private fun mapAmount(amount: Money) = Amount.fromString(amount.amount.string.replace(',', '.'))
|
private fun mapAmount(amount: Money) = Amount.fromString(amount.amount.string.replace(',', '.'))
|
||||||
|
|
||||||
|
|
||||||
|
fun mapTanChallenge(challenge: net.dankito.banking.fints.model.TanChallenge): TanChallenge {
|
||||||
|
val type = mapTanChallengeType(challenge)
|
||||||
|
val action = mapActionRequiringTan(challenge.forAction)
|
||||||
|
val tanMethod = mapTanMethod(challenge.tanMethod)
|
||||||
|
val customer = mapCustomer(challenge.bank)
|
||||||
|
val account = challenge.account?.let { mapAccount(it) }
|
||||||
|
|
||||||
|
val tanImage = if (challenge is ImageTanChallenge) mapTanImage(challenge.image) else null
|
||||||
|
val flickerCode = if (challenge is FlickerCodeTanChallenge) mapFlickerCode(challenge.flickerCode) else null
|
||||||
|
|
||||||
|
return TanChallenge(type, action, challenge.messageToShowToUser, tanMethod, tanImage, flickerCode, customer, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapTanChallengeType(challenge: net.dankito.banking.fints.model.TanChallenge): TanChallengeType = when {
|
||||||
|
challenge is ImageTanChallenge -> TanChallengeType.Image
|
||||||
|
challenge is FlickerCodeTanChallenge -> TanChallengeType.Flickercode
|
||||||
|
else -> TanChallengeType.EnterTan
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapActionRequiringTan(action: net.dankito.banking.fints.model.ActionRequiringTan): ActionRequiringTan =
|
||||||
|
ActionRequiringTan.valueOf(action.name)
|
||||||
|
|
||||||
|
private fun mapTanMethod(method: net.dankito.banking.fints.model.TanMethod): TanMethod = TanMethod(
|
||||||
|
method.displayName, mapTanMethodType(method.type), method.securityFunction.code, method.maxTanInputLength, mapAllowedTanFormat(method.allowedTanFormat)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun mapTanMethodType(type: net.dankito.banking.fints.model.TanMethodType): TanMethodType =
|
||||||
|
TanMethodType.valueOf(type.name)
|
||||||
|
|
||||||
|
private fun mapAllowedTanFormat(allowedTanFormat: net.dankito.banking.fints.messages.datenelemente.implementierte.tan.AllowedTanFormat?): AllowedTanFormat =
|
||||||
|
allowedTanFormat?.let { AllowedTanFormat.valueOf(it.name) } ?: AllowedTanFormat.Alphanumeric
|
||||||
|
|
||||||
|
private fun mapTanImage(image: net.dankito.banking.fints.tan.TanImage): TanImage =
|
||||||
|
TanImage(image.mimeType, mapToBase64(image.imageBytes), mapException(image.decodingError))
|
||||||
|
|
||||||
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
|
private fun mapToBase64(bytes: ByteArray): String {
|
||||||
|
return Base64.Default.encode(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapFlickerCode(flickerCode: net.dankito.banking.fints.tan.FlickerCode): FlickerCode =
|
||||||
|
FlickerCode(flickerCode.challengeHHD_UC, flickerCode.parsedDataSet, mapException(flickerCode.decodingError))
|
||||||
|
|
||||||
|
|
||||||
private fun <T> mapError(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<T> {
|
private fun <T> mapError(response: net.dankito.banking.client.model.response.GetAccountDataResponse): Response<T> {
|
||||||
return if (response.error != null) {
|
return if (response.error != null) {
|
||||||
Response.error(ErrorType.valueOf(response.error!!.name), if (response.error == ErrorCode.BankReturnedError) null else response.errorMessage,
|
Response.error(ErrorType.valueOf(response.error!!.name), if (response.error == ErrorCode.BankReturnedError) null else response.errorMessage,
|
||||||
|
@ -101,4 +152,7 @@ open class FinTs4kMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun mapException(exception: Exception?): String? =
|
||||||
|
exception?.stackTraceToString()
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package net.codinux.banking.client.fints4k
|
package net.codinux.banking.client.fints4k
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.codinux.banking.client.SimpleBankingClientCallback
|
||||||
import net.codinux.banking.client.model.response.ResponseType
|
import net.codinux.banking.client.model.response.ResponseType
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
@ -18,7 +19,9 @@ class FinTs4kBankingClientTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val underTest = FinTs4kBankingClientForCustomer(bankCode, loginName, password)
|
private val underTest = FinTs4kBankingClientForCustomer(bankCode, loginName, password, SimpleBankingClientCallback { customer, tanChallenge ->
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue