Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FinTS3Client._touchdown... attributes missing after restoring client #125

Open
tloebhard opened this issue Jan 14, 2021 · 5 comments · May be fixed by #126
Open

FinTS3Client._touchdown... attributes missing after restoring client #125

tloebhard opened this issue Jan 14, 2021 · 5 comments · May be fixed by #126

Comments

@tloebhard
Copy link

Describe the bug
For getting older transactions from Skatbank and comdirect there is a TAN needed. I need to pause dialogue, deconstruct client (including private data) and store request. After restoring/resuming and sending TAN there is an exception in _continue_fetch_with_touchdowns, because self._touchdown_args (amonst others) is not set.
TAN seems to be processed already correctly and accepted by bank.

Bank I tested this with
Name of the bank: comdirect (using PhotoTAN)
FinTS URL: https://fints.comdirect.de/fints

Name of the bank: Skatbank (using VR-SecureGo (Push TAN))
FinTS URL: https://hbci11.fiducia.de/cgi-bin/hbciservlet

Expected behavior
After sending TAN it should return requested transactions

Code required to reproduce
(copied from https://python-fints.readthedocs.io/en/latest/trouble.html)

import datetime
import getpass
import logging
import sys
from decimal import Decimal

from fints.client import FinTS3PinTanClient, NeedTANResponse, FinTSUnsupportedOperation, NeedRetryResponse
from fints.hhd.flicker import terminal_flicker_unix
from fints.utils import minimal_interactive_cli_bootstrap

logging.basicConfig(level=logging.DEBUG)

client_args = (
    'REPLACEME',  # BLZ
    'REPLACEME',  # USER
    getpass.getpass('PIN: '),
    'REPLACEME'  # ENDPOINT
)

f = FinTS3PinTanClient(*client_args)
minimal_interactive_cli_bootstrap(f)


def ask_for_tan(response):
    print("A TAN is required")
    print(response.challenge)
    if getattr(response, 'challenge_hhduc', None):
        try:
            terminal_flicker_unix(response.challenge_hhduc)
        except KeyboardInterrupt:
            pass
    tan = input('Please enter TAN:')
    return f.send_tan(response, tan)


# Open the actual dialog
with f:
    # Since PSD2, a TAN might be needed for dialog initialization. Let's check if there is one required
    if f.init_tan_response:
        ask_for_tan(f.init_tan_response)

    # Fetch accounts
    accounts = f.get_sepa_accounts()
    if isinstance(accounts, NeedTANResponse):
        accounts = ask_for_tan(accounts)
    if len(accounts) == 1:
        account = accounts[0]
    else:
        print("Multiple accounts available, choose one")
        for i, mm in enumerate(accounts):
            print(i, mm.iban)
        choice = input("Choice: ").strip()
        account = accounts[int(choice)]

    res = f.get_transactions(account, datetime.date.today() - datetime.timedelta(days=120),
                             datetime.date.today())

    # Test pausing and resuming the dialog
    dialog_data = f.pause_dialog()

client_data = f.deconstruct(including_private=True)
tan_request_data = res.get_data()

tan_request = NeedRetryResponse.from_data(tan_request_data)
f = FinTS3PinTanClient(*client_args, from_data=client_data)
with f.resume_dialog(dialog_data):
    res = ask_for_tan(tan_request)
    print("Found", len(res), "transactions")

Log output / error message

Traceback (most recent call last):
  File ".../PycharmProjects/fints_test/pyfintstest2.py", line 67, in <module>
    res = ask_for_tan(tan_request)
  File ".../PycharmProjects/fints_test/pyfintstest2.py", line 33, in ask_for_tan
    return f.send_tan(response, tan)
  File "...\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\fints\client.py", line 1264, in send_tan
    return resume_func(challenge.command_seg, response)
  File "...\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\LocalCache\local-packages\Python38\site-packages\fints\client.py", line 456, in _continue_fetch_with_touchdowns
    for resp in response.response_segments(command_seg, *self._touchdown_args, **self._touchdown_kwargs):
AttributeError: 'FinTS3PinTanClient' object has no attribute '_touchdown_args'

Solution / Quick hack
After setting the missing attributes in ask_for_tan before f.send_tan - it works: (just some dirty copy paste from _fetch_with_touchdowns and get_transactions )

from fints.utils import mt940_to_array
import fints.segments.statement

def ask_for_tan(response):
    [...]
    tan = input('Please enter TAN:')
    f._touchdown_args = ['HIKAZ']
    f._touchdown_kwargs = {}
    f._touchdown_responses = []
    f._touchdown_counter = 1
    f._touchdown_response_processor = lambda responses: mt940_to_array(''.join([seg.statement_booked.decode('iso-8859-1') for seg in responses]))
    hkkaz = f._find_highest_supported_command(fints.segments.statement.HKKAZ5,
                                                   fints.segments.statement.HKKAZ6,
                                                   fints.segments.statement.HKKAZ7)
    f._touchdown_segment_factory = lambda touchdown: hkkaz(
        account=hkkaz._fields['account'].type.from_sepa_account(account),
        all_accounts=False,
        date_start=datetime.date.today() - datetime.timedelta(days=120),
        date_end=datetime.date.today(),
        touchdown_point=touchdown,
    )
    return f.send_tan(response, tan)

I think it needs to be saved in deconstruct and restored in set_data ?

@raphaelm
Copy link
Owner

I think it needs to be saved in deconstruct and restored in set_data ?

That's probably the easiest solution, although it's probably not easy to store the lambda. I'm not sure if it would be theoretically better to store the information in NeedRetryResponse.from_data instead and reconstruct from there. The latter might make more sense if it is allowed to do a different operation "in-between". NeedRetryResponse would kinda need to know which operation it belongs to (HKKAZ in this case) and then send_tan could initialize the touchpoint state based on that information

@tloebhard
Copy link
Author

send_tan could initialize the touchpoint state

Quite pragmatic, but what do you think of this solution just before return statement in send_tan

            # Restore _touchdown_... attributes
            if challenge.resume_method == '_continue_fetch_with_touchdowns' and challenge.command_seg.TYPE == 'HKKAZ':
                self._touchdown_args = ['HIKAZ']
                self._touchdown_kwargs = {}
                self._touchdown_responses = []
                self._touchdown_counter = 1
                self._touchdown_dialog = dialog
                self._touchdown_response_processor = lambda responses: mt940_to_array(
                    ''.join([seg.statement_booked.decode('iso-8859-1') for seg in responses]))
                hkkaz = self._find_highest_supported_command(HKKAZ5, HKKAZ6, HKKAZ7)
                self._touchdown_segment_factory = lambda touchdown: hkkaz(
                    account=challenge.command_seg.account,
                    all_accounts=False,
                    date_start=challenge.command_seg.date_start,
                    date_end=challenge.command_seg.date_end,
                    touchdown_point=touchdown,
                )

Any idea of using less hardcoding or maybe have a more flexible way of handling operations, ...?
This solution at least works for getting transactions with comdirect and Skatbank.

@tloebhard
Copy link
Author

...and we need to do that for all operations? Maybe it could be centralized somewhere?

for get_transactions_xml it seems to be:

            if tan_request.command_seg.TYPE == 'HKCAZ':
                client._touchdown_args = ['HICAZ']
                self._touchdown_kwargs = {}
                self._touchdown_responses = []
                self._touchdown_counter = 1
                self._touchdown_dialog = dialog
                client._touchdown_response_processor = FinTS3Client._response_handler_get_transactions_xml
                hkcaz = self._find_highest_supported_command(fints.segments.statement.HKCAZ1)
                client._touchdown_segment_factory = lambda touchdown: hkcaz(
                    account=challenge.command_seg.account,
                    all_accounts=False,
                    date_start=challenge.command_seg.date_start,
                    date_end=challenge.command_seg.date_end,
                    touchdown_point=touchdown,
                    supported_camt_messages=SupportedMessageTypes(
                        ['urn:iso:std:iso:20022:tech:xsd:camt.052.001.02']),
                )

@DanielK990
Copy link

DanielK990 commented Mar 22, 2023

Hi,
is there any chance that this will be fixed?
The problem also appears in my code with Comdirect bank was well.

Nevertheless, the outlined workaround seems to work for me after slightly modifying it.


 def do_process_tan(self, tan, fints_client):
        print("TAN entered " + tan)
        tan_request = NeedRetryResponse.from_data(self.fints_tan_data)
        fints_client._touchdown_args = ['HIKAZ']
        fints_client._touchdown_kwargs = {}
        fints_client._touchdown_responses = []
        fints_client._touchdown_counter = 1
        fints_client._touchdown_dialog = fints_client._get_dialog()
        fints_client._touchdown_response_processor = lambda responses: mt940_to_array(''.join([seg.statement_booked.decode('iso-8859-1') for seg in responses]))
        hkkaz = fints_client._find_highest_supported_command(fints.segments.statement.HKKAZ5,
                                                       fints.segments.statement.HKKAZ6,
                                                       fints.segments.statement.HKKAZ7)
        fints_client._touchdown_segment_factory = lambda touchdown: hkkaz(
            account=tan_request.command_seg.account,
            all_accounts=False,
            date_start=tan_request.command_seg.date_start,
            date_end=tan_request.command_seg.date_end,
            touchdown_point=touchdown,
        )
                
        return fints_client.send_tan(tan_request, tan)

Thanks,
Regards,
Daniel

@raphaelm
Copy link
Owner

raphaelm commented Apr 8, 2023

is there any chance that this will be fixed?

Maybe! It would require either

  • me running into the problem which means I have a way to debug it and motivation to put time into it
  • someone taking the time to do a well-written PR with a fix

I don't remember running into this when I tested the implementation with my banks, but I'm not sure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants