From: Henryk Plötz Date: Sun, 26 Aug 2018 20:06:47 +0000 (+0200) Subject: First working debit (multiple, COR1) X-Git-Tag: v2.0.0~1^2~75 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=21060838fcedc26e45467744d20fba031a75aac2;p=thirdparty%2Fpython-fints.git First working debit (multiple, COR1) --- diff --git a/fints/client.py b/fints/client.py index 26d5ce7..c370efc 100644 --- a/fints/client.py +++ b/fints/client.py @@ -4,7 +4,6 @@ from decimal import Decimal from contextlib import contextmanager from collections import OrderedDict -from fints.segments.debit import HKDME, HKDSE from mt940.models import Balance from sepaxml import SepaTransfer @@ -14,6 +13,7 @@ from .formals import ( KTI1, Account3, BankIdentifier, SynchronisationMode, TwoStepParametersCommon, TANMediaType2, TANMediaClass4, CUSTOMER_ID_ANONYMOUS, + DescriptionRequired, ) from .message import FinTSInstituteMessage from .models import ( @@ -25,10 +25,11 @@ from .security import ( PinTanTwoStepAuthenticationMechanism, ) from .segments import HIBPA3, HIRMG2, HIRMS2, HIUPA4, HIPINS1, HKKOM4 -from .segments.accounts import HISPA1, HKSPA, HKSPA1 -from .segments.auth import HKTAB, HKTAN, HKTAB4, HKTAB5, HKTAN3, HKTAN5 +from .segments.accounts import HISPA1, HKSPA1 +from .segments.auth import HKTAB4, HKTAB5, HKTAN3, HKTAN5 from .segments.depot import HKWPD5, HKWPD6 from .segments.dialog import HISYN4, HKSYN3 +from .segments.debit import HKDSE1, HKDSE2, HKDME1, HKDME2, HKDMC1 from .segments.saldo import HKSAL5, HKSAL6, HKSAL7 from .segments.statement import HKKAZ5, HKKAZ6, HKKAZ7 from .segments.transfer import HKCCM1, HKCCS1 @@ -435,7 +436,7 @@ class FinTS3Client: :param control_sum: Sum of all transfers (required if there are multiple) :param currency: Transfer currency :param book_as_single: Kindly ask the bank to put multiple transactions as separate lines on the bank statement (defaults to ``False``) - :return: Returns a TANChallenge object + :return: Returns a TANChallenge object FIXME Wrong """ with self._get_dialog() as dialog: @@ -445,7 +446,7 @@ class FinTS3Client: command_class = HKCCS1 hiccxs, hkccx = self._find_highest_supported_command( - HKCCM1 if multiple else HKCCS1, + command_class, return_parameter_segment=True ) @@ -456,7 +457,7 @@ class FinTS3Client: ) if multiple: - if hiccxs.parameter.sum_amount_required and not control_sum: + if hiccxs.parameter.sum_amount_required and control_sum is None: raise ValueError("Control sum required.") if book_as_single and not hiccxs.parameter.single_booking_allowed: # FIXME Only do a warning and fall-back to book_as_single=False? @@ -475,22 +476,9 @@ class FinTS3Client: # FIXME Properly find return code return True - def _get_start_sepa_debit_message(self, dialog, account: SEPAAccount, pain_message: str, tan_method, - tan_description, multiple, control_sum, currency, book_as_single): - if multiple: - if not control_sum: - raise ValueError("Control sum required.") - segreq = HKDME(3, account, pain_message, control_sum, currency, book_as_single) - else: - segreq = HKDSE(3, account, pain_message) - segtan = HKTAN(4, '4', '', tan_description, tan_method.version) - return self._new_message(dialog, [ - segreq, - segtan - ]) - - def start_sepa_debit(self, account: SEPAAccount, pain_message: str, tan_method, tan_description='', - multiple=False, control_sum=None, currency='EUR', book_as_single=False): + def start_sepa_debit(self, account: SEPAAccount, pain_message: str, multiple=False, cor1=False, + control_sum=None, currency='EUR', book_as_single=False, + pain_descriptor='urn:iso:std:iso:20022:tech:xsd:pain.008.003.01'): """ Start a custom SEPA debit. @@ -502,25 +490,51 @@ class FinTS3Client: :param control_sum: Sum of all debits (required if there are multiple) :param currency: Debit currency :param book_as_single: Kindly ask the bank to put multiple transactions as separate lines on the bank statement (defaults to ``False``) - :return: Returns a TANChallenge object + :return: Returns a TANChallenge object FIXME Wrong """ - dialog = self._get_dialog() - dialog.sync() - dialog.tan_mechs = [tan_method] - dialog.init() - - with self.pin.protect(): - logger.debug('Sending: {}'.format(self._get_start_sepa_debit_message( - dialog, account, pain_message, tan_method, tan_description, multiple, control_sum, currency, - book_as_single - ))) - - resp = dialog.send(self._get_start_sepa_debit_message( - dialog, account, pain_message, tan_method, tan_description, multiple, control_sum, currency, - book_as_single - )) - logger.debug('Got response: {}'.format(resp)) - return self._tan_requiring_response(dialog, resp) + + with self._get_dialog() as dialog: + if multiple: + if cor1: + command_candidates = (HKDMC1, ) + else: + command_candidates = (HKDME1, HKDME2) + else: + if cor1: + raise Exception("Can't process multiple=False cor1=True") + else: + command_candidates = (HKDSE1, HKDSE2) + + hidxxs, hkdxx = self._find_highest_supported_command( + *command_candidates, + return_parameter_segment=True + ) + + seg = hkdxx( + account=hkdxx._fields['account'].type.from_sepa_account(account), + sepa_descriptor=pain_descriptor, + sepa_pain_message=pain_message, + ) + + if multiple: + if hidxxs.parameter.sum_amount_required and control_sum is None: + raise ValueError("Control sum required.") + if book_as_single and not hidxxs.parameter.single_booking_allowed: + # FIXME Only do a warning and fall-back to book_as_single=False? + raise ValueError("Single booking not allowed by bank.") + + if control_sum: + seg.sum_amount.amount = control_sum + seg.sum_amount.currency = currency + + if book_as_single: + seg.request_single_booking = True + + return self._send_with_possible_retry(dialog, seg, self._continue_start_sepa_debit) + + def _continue_start_sepa_debit(self, command_seg, response): + # FIXME Properly return something + return True def add_response_callback(self, cb): # FIXME document @@ -748,6 +762,8 @@ class FinTS3PinTanClient(FinTS3Client): for resp in response.responses(tan_seg): if resp.code == '0030': return NeedTANResponse(command_seg, response.find_segment_first('HITAN'), resume_func) + if resp.code.startswith('9'): + raise Exception("Error response: {!r}".format(response)) else: response = dialog.send(command_seg) diff --git a/fints/dialog.py b/fints/dialog.py index 3f731b2..6a020f1 100644 --- a/fints/dialog.py +++ b/fints/dialog.py @@ -8,8 +8,8 @@ from .formals import ( from .message import ( FinTSCustomerMessage, FinTSMessage, MessageDirection, ) -from .segments.auth import HKIDN, HKIDN2, HKSYN, HKVVB, HKVVB3 -from .segments.dialog import HKEND, HKEND1 +from .segments.auth import HKIDN2, HKVVB3 +from .segments.dialog import HKEND1 from .segments.message import HNHBK3, HNHBS1 from .utils import compress_datablob, decompress_datablob diff --git a/fints/formals.py b/fints/formals.py index b3791fd..47c39a9 100644 --- a/fints/formals.py +++ b/fints/formals.py @@ -704,3 +704,62 @@ class CommunicationParameter2(DataElementGroup): address_adjunct = DataElementField(type='an', max_length=512, required=False, _d="Kommunikationsadresszusatz") filter_function = DataElementField(type='an', length=3, required=False, _d="Filterfunktion") filter_function_version = DataElementField(type='num', max_length=3, required=False, _d="Version der Filterfunktion") + +class ScheduledDebitParameter1(DataElementGroup): + """Parameter terminierte SEPA-Einzellastschrift einreichen, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + min_advance_notice_FNAL_RCUR = DataElementField(type='num', max_length=4, _d="Minimale Vorlaufzeit FNAL/RCUR") + max_advance_notice_FNAL_RCUR = DataElementField(type='num', max_length=4, _d="Maximale Vorlaufzeit FNAL/RCUR") + min_advance_notice_FRST_OOFF = DataElementField(type='num', max_length=4, _d="Minimale Vorlaufzeit FRST/OOFF") + max_advance_notice_FRST_OOFF = DataElementField(type='num', max_length=4, _d="Maximale Vorlaufzeit FRST/OOFF") + +class ScheduledDebitParameter2(DataElementGroup): + """Parameter terminierte SEPA-Einzellastschrift einreichen, version 2 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + min_advance_notice = DataElementField(type='num', max_length=4, _d="Minimale Vorlaufzeit SEPA-Lastschrift") + max_advance_notice = DataElementField(type='num', max_length=4, _d="Maximale Vorlaufzeit SEPA-Lastschrift") + allowed_purpose_codes = DataElementField(type='an', max_length=4096, required=False, _d="Zulässige purpose codes") + supported_sepa_formats = DataElementField(type='an', max_length=256, max_count=9, required=False, _d="Unterstützte SEPA-Datenformate") + +class ScheduledBatchDebitParameter1(DataElementGroup): + """Parameter terminierte SEPA-Sammellastschrift einreichen, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + min_advance_notice_FNAL_RCUR = DataElementField(type='num', max_length=4, _d="Minimale Vorlaufzeit FNAL/RCUR") + max_advance_notice_FNAL_RCUR = DataElementField(type='num', max_length=4, _d="Maximale Vorlaufzeit FNAL/RCUR") + min_advance_notice_FRST_OOFF = DataElementField(type='num', max_length=4, _d="Minimale Vorlaufzeit FRST/OOFF") + max_advance_notice_FRST_OOFF = DataElementField(type='num', max_length=4, _d="Maximale Vorlaufzeit FRST/OOFF") + max_debit_count = DataElementField(type='num', max_length=7, _d="Maximale Anzahl DirectDebitTransfer TransactionInformation") + sum_amount_required = DataElementField(type='jn', _d="Summenfeld benötigt") + single_booking_allowed = DataElementField(type='jn', _d="Einzelbuchung erlaubt") + +class ScheduledBatchDebitParameter2(DataElementGroup): + """Parameter terminierte SEPA-Sammellastschrift einreichen, version 2 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + min_advance_notice = DataElementField(type='num', max_length=4, _d="Minimale Vorlaufzeit SEPA-Lastschrift") + max_advance_notice = DataElementField(type='num', max_length=4, _d="Maximale Vorlaufzeit SEPA-Lastschrift") + max_debit_count = DataElementField(type='num', max_length=7, _d="Maximale Anzahl DirectDebitTransfer TransactionInformation") + sum_amount_required = DataElementField(type='jn', _d="Summenfeld benötigt") + single_booking_allowed = DataElementField(type='jn', _d="Einzelbuchung erlaubt") + allowed_purpose_codes = DataElementField(type='an', max_length=4096, required=False, _d="Zulässige purpose codes") + supported_sepa_formats = DataElementField(type='an', max_length=256, max_count=9, required=False, _d="Unterstützte SEPA-Datenformate") + + +class ScheduledCOR1BatchDebitParameter1(DataElementGroup): + """Parameter terminierte SEPA-COR1-Sammellastschrift, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + max_debit_count = DataElementField(type='num', max_length=7, _d="Maximale Anzahl DirectDebitTransfer TransactionInformation") + sum_amount_required = DataElementField(type='jn', _d="Summenfeld benötigt") + single_booking_allowed = DataElementField(type='jn', _d="Einzelbuchung erlaubt") + min_advance_notice_FNAL_RCUR = DataElementField(type='num', max_length=4, _d="Minimale Vorlaufzeit FNAL/RCUR") + max_advance_notice_FNAL_RCUR = DataElementField(type='num', max_length=4, _d="Maximale Vorlaufzeit FNAL/RCUR") + min_advance_notice_FRST_OOFF = DataElementField(type='num', max_length=4, _d="Minimale Vorlaufzeit FRST/OOFF") + max_advance_notice_FRST_OOFF = DataElementField(type='num', max_length=4, _d="Maximale Vorlaufzeit FRST/OOFF") + allowed_purpose_codes = DataElementField(type='an', max_length=4096, required=False, _d="Zulässige purpose codes") + supported_sepa_formats = DataElementField(type='an', max_length=256, max_count=9, required=False, _d="Unterstützte SEPA-Datenformate") + + diff --git a/fints/segments/debit.py b/fints/segments/debit.py index d55fe1d..b26e669 100644 --- a/fints/segments/debit.py +++ b/fints/segments/debit.py @@ -1,48 +1,102 @@ -from . import FinTS3SegmentOLD +from . import FinTS3Segment from ..models import SEPAAccount +from ..fields import DataElementField, DataElementGroupField +from ..formals import ScheduledCOR1BatchDebitParameter1, KTI1, Amount1, ScheduledBatchDebitParameter1, ScheduledBatchDebitParameter2, ScheduledDebitParameter1, ScheduledDebitParameter2 +from . import ParameterSegment +class HKDSE1(FinTS3Segment): + """Terminierte SEPA-Einzellastschrift einreichen, version 1 -class HKDSE(FinTS3SegmentOLD): - """ - HKDSE (Einreichung terminierter SEPA-Einzellastschrift) - Section C.10.2.5.4.1 - """ - type = 'HKDSE' - - def __init__(self, segno, account: SEPAAccount, pain_msg): - self.version = 1 - sepa_descriptor = 'urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.008.003.02' - msg = ':'.join([ - account.iban, - account.bic - ]) - data = [ - msg, - sepa_descriptor, - '@{}@{}'.format(len(pain_msg), pain_msg) - ] - super().__init__(segno, data) - - -class HKDME(FinTS3SegmentOLD): - """ - HKDME (Einreichung terminierter SEPA-Sammellastschrift) - Section C.10.3.2.2.1 - """ - type = 'HKDME' - - def __init__(self, segno, account: SEPAAccount, pain_msg, control_sum, currency, book_as_single): - self.version = 1 - sepa_descriptor = 'urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.008.003.02' - msg = ':'.join([ - account.iban, - account.bic - ]) - data = [ - msg, - str(control_sum).replace('.', ',') + ':' + currency, - 'J' if book_as_single else '', - sepa_descriptor, - '@{}@{}'.format(len(pain_msg), pain_msg) - ] - super().__init__(segno, data) + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + account = DataElementGroupField(type=KTI1, _d="Kontoverbindung international") + sepa_descriptor = DataElementField(type='an', max_length=256, _d="SEPA Descriptor") + sepa_pain_message = DataElementField(type='bin', _d="SEPA pain message") + +class HKDSE2(FinTS3Segment): + """Terminierte SEPA-Einzellastschrift einreichen, version 2 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + account = DataElementGroupField(type=KTI1, _d="Kontoverbindung international") + sepa_descriptor = DataElementField(type='an', max_length=256, _d="SEPA Descriptor") + sepa_pain_message = DataElementField(type='bin', _d="SEPA pain message") + +class BatchDebitBase(FinTS3Segment): + account = DataElementGroupField(type=KTI1, _d="Kontoverbindung international") + sum_amount = DataElementGroupField(type=Amount1, _d="Summenfeld") + request_single_booking = DataElementField(type='jn', _d="Einzelbuchung gewünscht") + sepa_descriptor = DataElementField(type='an', max_length=256, _d="SEPA Descriptor") + sepa_pain_message = DataElementField(type='bin', _d="SEPA pain message") + +class HKDME1(BatchDebitBase): + """Einreichung terminierter SEPA-Sammellastschrift, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + +class HKDME2(BatchDebitBase): + """Einreichung terminierter SEPA-Sammellastschrift, version 2 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + +class HKDMC1(BatchDebitBase): + """Terminierte SEPA-COR1-Sammellastschrift einreichen, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + + +class DebitResponseBase(FinTS3Segment): + task_id = DataElementField(type='an', max_length=99, required=False, _d="Auftragsidentifikation") + +class HIDSE1(DebitResponseBase): + """Einreichung terminierter SEPA-Einzellastschrift bestätigen, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + +class HIDSE2(DebitResponseBase): + """Einreichung terminierter SEPA-Einzellastschrift bestätigen, version 2 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + +class HIDME1(DebitResponseBase): + """Einreichung terminierter SEPA-Sammellastschrift bestätigen, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + +class HIDME2(DebitResponseBase): + """Einreichung terminierter SEPA-Sammellastschrift bestätigen, version 2 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + +class HIDMC1(DebitResponseBase): + """Einreichung terminierter SEPA-COR1-Sammellastschrift bestätigen, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + +class HIDSES1(ParameterSegment): + """Terminierte SEPA-Einzellastschrift einreichen Parameter, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + parameter = DataElementGroupField(type=ScheduledDebitParameter1, _d="Parameter terminierte SEPA-Sammellastschrift einreichen") + +class HIDSES2(ParameterSegment): + """Terminierte SEPA-Einzellastschrift einreichen Parameter, version 2 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + parameter = DataElementGroupField(type=ScheduledDebitParameter2, _d="Parameter terminierte SEPA-Sammellastschrift einreichen") + +class HIDMES1(ParameterSegment): + """Terminierte SEPA-Sammellastschrift einreichen Parameter, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + parameter = DataElementGroupField(type=ScheduledBatchDebitParameter1, _d="Parameter terminierte SEPA-Sammellastschrift einreichen") + +class HIDMES2(ParameterSegment): + """Terminierte SEPA-Sammellastschrift einreichen Parameter, version 2 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + parameter = DataElementGroupField(type=ScheduledBatchDebitParameter2, _d="Parameter terminierte SEPA-Sammellastschrift einreichen") + +class HIDMCS1(ParameterSegment): + """Terminierte SEPA-COR1-Sammellastschrift Parameter, version 1 + + Source: FinTS Financial Transaction Services, Schnittstellenspezifikation, Messages -- Multibankfähige Geschäftsvorfälle """ + parameter = DataElementGroupField(type=ScheduledCOR1BatchDebitParameter1, _d="Parameter terminierte SEPA-COR1-Sammellastschrift")