pain_descriptor='urn:iso:std:iso:20022:tech:xsd:pain.008.002.02'
)
- if isinstance(res, NeedTANResponse):
+ while isinstance(res, NeedTANResponse):
print("A TAN is required", res.challenge)
if getattr(res, 'challenge_hhduc', None):
except KeyboardInterrupt:
pass
- tan = input('Please enter TAN:')
+ if result.decoupled:
+ tan = input('Please press enter after confirming the transaction in your app:')
+ else:
+ tan = input('Please enter TAN:')
res = client.send_tan(res, tan)
print(res.status)
endtoend_id='NOTPROVIDED',
)
- if isinstance(res, NeedTANResponse):
+ while isinstance(res, NeedTANResponse):
print("A TAN is required", res.challenge)
if getattr(res, 'challenge_hhduc', None):
except KeyboardInterrupt:
pass
- tan = input('Please enter TAN:')
+ if result.decoupled:
+ tan = input('Please press enter after confirming the transaction in your app:')
+ else:
+ tan = input('Please enter TAN:')
res = client.send_tan(res, tan)
print(res.status)
getpass.getpass('PIN: '),
'REPLACEME' # ENDPOINT
)
+ product_id = 'REPLACEME'
- f = FinTS3PinTanClient(*client_args)
+ f = FinTS3PinTanClient(*client_args, product_id=product_id)
minimal_interactive_cli_bootstrap(f)
terminal_flicker_unix(response.challenge_hhduc)
except KeyboardInterrupt:
pass
- tan = input('Please enter TAN:')
+ if response.decoupled:
+ tan = input('Please press enter after confirming the transaction in your app:')
+ else:
+ 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)
+ while isinstance(f.init_tan_response, NeedTANResponse):
+ f.init_tan_response = ask_for_tan(f.init_tan_response)
# Fetch accounts
accounts = f.get_sepa_accounts()
- if isinstance(accounts, NeedTANResponse):
+ while isinstance(accounts, NeedTANResponse):
accounts = ask_for_tan(accounts)
if len(accounts) == 1:
account = accounts[0]
client_data = f.deconstruct(including_private=True)
- f = FinTS3PinTanClient(*client_args, from_data=client_data)
+ f = FinTS3PinTanClient(*client_args, product_id=product_id, from_data=client_data)
with f.resume_dialog(dialog_data):
while True:
operations = [
endtoend_id='NOTPROVIDED',
)
- if isinstance(res, NeedTANResponse):
- ask_for_tan(res)
+ while isinstance(res, NeedTANResponse):
+ res = ask_for_tan(res)
except FinTSUnsupportedOperation as e:
print("This operation is not supported by this bank:", e)
\ No newline at end of file
PinTanTwoStepAuthenticationMechanism,
)
from .segments.accounts import HISPA1, HKSPA1
-from .segments.auth import HIPINS1, HKTAB4, HKTAB5, HKTAN2, HKTAN3, HKTAN5, HKTAN6
+from .segments.auth import HIPINS1, HKTAB4, HKTAB5, HKTAN2, HKTAN3, HKTAN5, HKTAN6, HKTAN7
from .segments.bank import HIBPA3, HIUPA4, HKKOM4
from .segments.debit import (
HKDBS1, HKDBS2, HKDMB1, HKDMC1, HKDME1, HKDME2,
challenge_html = None #: HTML-safe challenge text, possibly with formatting
challenge_hhduc = None #: HHD_UC challenge to be transmitted to the TAN generator
challenge_matrix = None #: Matrix code challenge: tuple(mime_type, data)
+ decoupled = None #: Use decoupled process
- def __init__(self, command_seg, tan_request, resume_method=None, tan_request_structured=False):
+ def __init__(self, command_seg, tan_request, resume_method=None, tan_request_structured=False, decoupled=False):
self.command_seg = command_seg
self.tan_request = tan_request
self.tan_request_structured = tan_request_structured
+ self.decoupled = decoupled
if hasattr(resume_method, '__func__'):
self.resume_method = resume_method.__func__.__name__
else:
3: HKTAN3,
5: HKTAN5,
6: HKTAN6,
+ 7: HKTAN7,
}
if tan_process == '4' and tan_mechanism.VERSION >= 6:
seg.segment_type = orig_seg.header.type
- if tan_process in ('2', '3'):
+ if tan_process in ('2', '3', 'S'):
seg.task_reference = tan_seg.task_reference
- if tan_process in ('1', '2'):
+ if tan_process in ('1', '2', 'S'):
seg.further_tan_follows = False
return seg
response = dialog.send(command_seg, tan_seg)
for resp in response.responses(tan_seg):
- if resp.code == '0030':
- return NeedTANResponse(command_seg, response.find_segment_first('HITAN'), resume_func, self.is_challenge_structured())
+ if resp.code in ('0030', '3955'):
+ return NeedTANResponse(
+ command_seg,
+ response.find_segment_first('HITAN'),
+ resume_func,
+ self.is_challenge_structured(),
+ resp.code == '3955',
+ )
if resp.code.startswith('9'):
raise Exception("Error response: {!r}".format(response))
else:
"""
Sends a TAN to confirm a pending operation.
+ If ``NeedTANResponse.decoupled`` is ``True``, the ``tan`` parameter is ignored and can be kept empty.
+ If the operation was not yet confirmed using the decoupled app, this method will again return a
+ ``NeedTANResponse``.
+
:param challenge: NeedTANResponse to respond to
:param tan: TAN value
- :return: Currently no response
+ :return: New response after sending TAN
"""
with self._get_dialog() as dialog:
- tan_seg = self._get_tan_segment(challenge.command_seg, '2', challenge.tan_request)
- self._pending_tan = tan
+ if challenge.decoupled:
+ tan_seg = self._get_tan_segment(challenge.command_seg, 'S', challenge.tan_request)
+ else:
+ tan_seg = self._get_tan_segment(challenge.command_seg, '2', challenge.tan_request)
+ self._pending_tan = tan
response = dialog.send(tan_seg)
+ if challenge.decoupled:
+ # TAN process = S
+ status_segment = response.find_segment_first('HITAN')
+ if not status_segment:
+ raise FinTSClientError(
+ "No TAN status received."
+ )
+ for resp in response.responses(tan_seg):
+ if resp.code == '3956':
+ return NeedTANResponse(
+ challenge.command_seg,
+ challenge.tan_request,
+ challenge.resume_method,
+ challenge.tan_request_structured,
+ challenge.decoupled,
+ )
+
resume_func = getattr(self, challenge.resume_method)
return resume_func(challenge.command_seg, response)
supported_media_number = DataElementField(type='num', length=1, required=False, _d="Anzahl unterstützter aktiver TAN-Medien")
+class TwoStepParameters7(TwoStepParametersCommon):
+ zka_id = DataElementField(type='an', max_length=32, _d="DK TAN-Verfahren")
+ zka_version = DataElementField(type='an', max_length=10, _d="Version DK TAN-Verfahren")
+ name = DataElementField(type='an', max_length=30, _d="Name des Zwei-Schritt-Verfahrens")
+ max_length_input = DataElementField(type='num', max_length=2, _d="Maximale Länge des Eingabewertes im Zwei-Schritt-Verfahren")
+ allowed_format = CodeField(enum=AllowedFormat, length=1, _d="Erlaubtes Format im Zwei-Schritt-Verfahren")
+ text_return_value = DataElementField(type='an', max_length=30, _d="Text zur Belegung des Rückgabewertes im Zwei-Schritt-Verfahren")
+ max_length_return_value = DataElementField(type='num', max_length=4, _d="Maximale Länge des Rückgabewertes im Zwei-Schritt-Verfahren")
+ multiple_tans_allowed = DataElementField(type='jn', _d="Mehrfach-TAN erlaubt")
+ tan_time_dialog_association = CodeField(enum=TANTimeDialogAssociation, length=1, _d="TAN Zeit- und Dialogbezug")
+ cancel_allowed = DataElementField(type='jn', _d="Auftragsstorno erlaubt")
+ sms_charge_account_required = CodeField(enum=SMSChargeAccountRequired, length=1, _d="SMS-Abbuchungskonto erforderlich")
+ principal_account_required = CodeField(enum=PrincipalAccountRequired, length=1, _d="Auftraggeberkonto erforderlich")
+ challenge_class_required = DataElementField(type='jn', _d="Challenge-Klasse erforderlich")
+ challenge_structured = DataElementField(type='jn', _d="Challenge strukturiert")
+ initialization_mode = CodeField(enum=InitializationMode, _d="Initialisierungsmodus")
+ description_required = CodeField(enum=DescriptionRequired, length=1, _d="Bezeichnung des TAN-Medium erforderlich")
+ response_hhd_uc_required = DataElementField(type='jn', _d="Antwort HHD_UC erforderlich")
+ supported_media_number = DataElementField(type='num', length=1, required=False, _d="Anzahl unterstützter aktiver TAN-Medien")
+ decoupled_max_poll_number = DataElementField(type='num', max_length=3, required=False, _d="Maximale Anzahl Statusabfragen Decoupled")
+ wait_before_first_poll = DataElementField(type='num', max_length=3, required=False, _d="Wartezeit vor erster Statusabfrage")
+ wait_before_next_poll = DataElementField(type='num', max_length=3, required=False, _d="Wartezeit vor nächster Statusabfrage")
+ manual_confirmation_allowed = DataElementField(type='jn', required=False, _d="Manuelle Bestätigung möglich")
+ automated_polling_allowed = DataElementField(type='jn', required=False, _d="Automatische Statusabfragen erlaubt")
+
+
class ParameterTwostepCommon(DataElementGroup):
onestep_method_allowed = DataElementField(type='jn')
multiple_tasks_allowed = DataElementField(type='jn')
twostep_parameters = DataElementGroupField(type=TwoStepParameters6, min_count=1, max_count=98)
+class ParameterTwostepTAN7(ParameterTwostepCommon):
+ twostep_parameters = DataElementGroupField(type=TwoStepParameters7, min_count=1, max_count=98)
+
+
class TransactionTanRequired(DataElementGroup):
transaction = DataElementField(type='an', max_length=6)
tan_required = DataElementField(type='jn')
ParameterTwostepTAN2, ParameterTwostepTAN3, ParameterTwostepTAN4,
ParameterTwostepTAN5, ParameterTwostepTAN6, ResponseHHDUC,
SystemIDStatus, TANMedia4, TANMedia5, TANMediaClass3,
- TANMediaClass4, TANMediaType2, TANUsageOption,
+ TANMediaClass4, TANMediaType2, TANUsageOption, ParameterTwostepTAN7,
)
from .base import FinTS3Segment, ParameterSegment
response_hhd_uc = DataElementGroupField(type=ResponseHHDUC, required=False, _d="Antwort HHD_UC")
+class HKTAN7(FinTS3Segment):
+ """Zwei-Schritt-TAN-Einreichung, version 7
+
+ Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Sicherheitsverfahren PIN/TAN"""
+ tan_process = DataElementField(type='code', length=1, _d="TAN-Prozess")
+ segment_type = DataElementField(type='an', max_length=6, required=False, _d="Segmentkennung")
+ account = DataElementGroupField(type=KTI1, required=False, _d="Kontoverbindung international Auftraggeber")
+ task_hash_value = DataElementField(type='bin', max_length=256, required=False, _d="Auftrags-Hashwert")
+ task_reference = DataElementField(type='an', max_length=35, required=False, _d="Auftragsreferenz")
+ further_tan_follows = DataElementField(type='jn', length=1, required=False, _d="Weitere TAN folgt")
+ cancel_task = DataElementField(type='jn', length=1, required=False, _d="Auftrag stornieren")
+ sms_charge_account = DataElementGroupField(type=KTI1, required=False, _d="SMS-Abbuchungskonto")
+ challenge_class = DataElementField(type='num', max_length=2, required=False, _d="Challenge-Klasse")
+ parameter_challenge_class = DataElementGroupField(type=ParameterChallengeClass, required=False, _d="Parameter Challenge-Klasse")
+ tan_medium_name = DataElementField(type='an', max_length=32, required=False, _d="Bezeichnung des TAN-Mediums")
+ response_hhd_uc = DataElementGroupField(type=ResponseHHDUC, required=False, _d="Antwort HHD_UC")
+
+
class HITAN2(FinTS3Segment):
"""Zwei-Schritt-TAN-Einreichung Rückmeldung, version 2
tan_medium_name = DataElementField(type='an', max_length=32, required=False, _d="Bezeichnung des TAN-Mediums")
+class HITAN7(FinTS3Segment):
+ """Zwei-Schritt-TAN-Einreichung Rückmeldung, version 7
+
+ Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Sicherheitsverfahren PIN/TAN"""
+ tan_process = DataElementField(type='code', length=1, _d="TAN-Prozess")
+ task_hash_value = DataElementField(type='bin', max_length=256, required=False, _d="Auftrags-Hashwert")
+ task_reference = DataElementField(type='an', max_length=35, required=False, _d="Auftragsreferenz")
+ challenge = DataElementField(type='an', max_length=2048, required=False, _d="Challenge")
+ challenge_hhduc = DataElementField(type='bin', required=False, _d="Challenge HHD_UC")
+ challenge_valid_until = DataElementGroupField(type=ChallengeValidUntil, required=False, _d="Gültigkeitsdatum und -uhrzeit für Challenge")
+ tan_medium_name = DataElementField(type='an', max_length=32, required=False, _d="Bezeichnung des TAN-Mediums")
+
+
class HKTAB4(FinTS3Segment):
"""TAN-Generator/Liste anzeigen Bestand, version 4
parameter = DataElementGroupField(type=ParameterTwostepTAN6)
+class HITANS7(HITANSBase):
+ parameter = DataElementGroupField(type=ParameterTwostepTAN7)
+
+
class HIPINS1(ParameterSegment):
"""PIN/TAN-spezifische Informationen, version 1