Fehler Sparkasse pushTAN 2.0 Verfahren FinTS
Hallo zusammen,
ich würde um Hilfe bitten bei folgendem Problem (ähnlicher Fall wie #423):
Allerdings diesmal beim Abruf von Kontoauszugs-Daten. An der Anwendung wurde keine Änderung vorgenommen seit der letzten erfolgreichen Nutzung.
TanMode (923) wird von der Bank zurückgeliefert und kann ausgewählt werden. TanMedium-Auswahl wird ebenfalls zurückgeliefert (smsTAN, pushTAN, chipTAN, Alle Geräte). Bei Auswahl von PushTAN oder Alle Geräte wird folgender Fehler von der Bank geliefert:
FinTS errors: 9050 (global): Die Nachricht enthält Fehler. 9800 (global): Dialog abgebrochen 9955 (wrt seg 4): Auftrag nicht ausgeführt - Die Gerätebezeichnung ist unbekannt. (MBV07390100255) FinTS warnings: 3905 (global): Es wurde keine Challenge erzeugt. 3920 (wrt seg 4): Zugelassene Zwei-Schritt-Verfahren für den Benutzer. [923] Request segments: HKVVB:4:3+16+0+0+XXXXXXXXXXXXXXXXXXXXXXXXX+1.0'
Hallo,
vermutlich hat deine Sparkasse auch die "Geräteerkennung" aktiviert, unsere hat das diese Woche ausgerollt: https://www.f-i.de/fints
Wenn das der selbe Fall ist den ich aktuell habe dann brauchst du:
- die dev-master Version, also im composer: "nemiah/php-fints": "dev-master", (behebt den Fehler den du hast)
- eine persistente $fints->kundensystemId, an dieser erkennt die Sparkasse das Gerät welches die Anfrage stellt, wenn diese nicht vor dem Login gesetzt wird dann kommt die TAN Abfrage jedesmal aufs Neue.
An der Stelle hoffe ich uns kann noch jemand helfen: Ist dafür $fints->persist() gedacht? Mir scheint das etwas overkill zu sein dafür dass ich einfach nur die kundensystemId speichern und wieder setzen möchte. Direkt kann man diese aber nicht setzen, da sie private ist und keinen setter hat.
Ich habe testweise die kundensystemId nach dem Login mit PushTAN ausgelesen, in der FinTs Klasse auf public geändert und direkt beschrieben, damit funktioniert der Ablauf dann ohne dass erneut nach TAN gefragt wird.
Hello @wienen-it-dev ,
did you find any solution for this problem?
I have tried what @Magellanth explained here and did not work with me:
I have added this to login function:
if (file_exists('kundensystemId/kundensystemId.txt')) { $this->kundensystemId = file_get_contents('kundensystemId/kundensystemId.txt'); }
and this:
file_put_contents('kundensystemId/kundensystemId.txt', $this->kundensystemId);
to the ensureSynchronized() function
of course also changed the kundensystemId to public.
tested and then the same error.
Hi again, yes what I have done following the instructures of @Magellanth WORKED with me after changing to the live account bank. thanks.
Hi Magellanth,
vielen Dank für Deine schnelle Hilfe. Ein erster Versuch mit der dev-master Version war nicht erfolgreich (selber Fehler), obwohl $fints->persist() bereits genutzt wird.
Ein zweiter Anlauf mit manuell angepasster kundensystemId steht noch aus.
Hi @Magellanth,
vielen Dank nochmals für Deine Hilfe. Allerdings besteht derselbe Fehler auch mit der dev-master Version und mit manuellem Setzen der kundensystemId.
Auf https://www.f-i.de/fints wird allerdings Rückmeldecode 9391 genannt, welcher in der Fehlermeldung aus dem Eingangposting jedoch nicht enthalten ist.
Könnte es sich noch um ein anderes Problem handeln? Oder gibt es mittlerweile eine weitere verfügbare Lösung?
Hi @mmnasir,
thank you very much for your comment.
Unfortunately your solution didn't work for me after adding the kundensystemId to functions login() and ensureSynchronized().
Maybe I missed out a step? You mentioned you changed the kundensystemId to public. Why is it necessary, as it seems you only used kundensystemId within FinTs class?
@Philipp91 Since $fints->kundensystemId was implemented by you, could you please elaborate how it is meant to be used?
As I understand it, an initial value is provided by the bank and needs to be persisted. But since there is no setter for it, there surely must be another way to do it. persist() seems to be too granular for this, as it also perists dialog and message.
Yes you're meant to use persist() for this. What's your concrete concern with it being "too granular" or "overkill"?
You also need persist() when you response to a TAN challenge at a later time, so I don't see how you could possibly get around using it at least in some parts of your application. And at that point, at least in my application, there's some central code that always uses persist and restores the instance later, no matter if it's a case of decoupled submission, other TAN challenge, or no process going on and it just ends up storing the kundensystemID and some nullsand the selected TAN mode/medium (which doesn't hurt).
@wienen-it-dev Can you post a stack trace?
That's how I used it until now: to persist dialogs while querying for a TAN. So what would be a good point to persist it under normal circumstances? Maybe after any successful action? Is there no danger for any by-catch which would cause trouble when starting new sometime later?
So what would be a good point to persist it under normal circumstances?
In my application, I persist when I'm completely done with the FinTs instance otherwise, i.e. when I've run all the operations/requests/... that I needed, even if it was a longer sequence. Basically when my PHP program is about to end (or at least the part that needs a bank connection).
Is there no danger for any by-catch which would cause trouble when starting new sometime later?
There is a bit, if a previous operation got interrupted (like the user never entered the TAN) and the bank cleared the state on their end due to some timeout. To be fair, that risk is there even while you're just doing multi-step operations.
That's what $fints->forgetDialog() is for. You can call it (1) after restoring a FinTs instance when you know you want to start a new operation, or (2) when an error occurred. The "forgetting" tries to align the dialog state with the bank, assuming that the bank also forgot the dialog on their end.
Thank you very much for the explanation! I will implement it that way, too, then. I did not know about forgetDialog which was a missing piece, I think.
Update on my productive setup: I changed the workaround with direct override of kundensystemId to the usage of persist(). To get an idea of how it works:
//init finTs connection with a persistence string at the start of the code
$this->finTs = FinTs::new($options, $credentials, $this->bankingAccount->getPersistenceString());
if($this->bankingAccount->isPersisted()) {
//fresh start, forget any dialog that may have been persisted
$this->finTs->forgetDialog();
}
//do the work...
//end of script
$this->finTs->close();
$persistanceString = $this->finTs->persist(true);
//save this string...
@Philipp91 I think the usage of persist should be integrated to the examples (at least i did not see it there).
@wienen-it-dev Can you post a stack trace?
@Philipp91 Sorry right now I don't have a stack trace. I updated php-fints to dev-master (but with syntax modifications for PHP 7.4). Still getting the error below.
Will it make an impact to update to PHP 8.3?
The bank (Sparkasse) confirmed Geräteerkennung is active.
9050 (global): Die Nachricht enthält Fehler. 9800 (global): Dialog abgebrochen 9955 (wrt seg 4): Auftrag nicht ausgeführt - Die Gerätebezeichnung ist unbekannt. (MBV07390100255) FinTS warnings: 3905 (global): Es wurde keine Challenge erzeugt. 3920 (wrt seg 4): Zugelassene Zwei-Schritt-Verfahren für den Benutzer. [921, 922] Request segments: HKVVB:4:3+16+0+0+XXXXXXXXXXXXXXXXXXXXXXXXX+1.0'
Yes, an example of persist / forgetDialog would be great.
My workflow is roughly described at the bottom, which has been working for quite a while with PushTAN / ChipTAN / NoTAN.
Recently another bank account got another error below, which has not been resolved yet. Not sure if these errors are related, as bank statement import stopped working for three different bank accounts within the past few weeks.
Appreciate any input how to solve these issues.
Sie möchten sich "auf einem neuen Gerät anmelden"? Gehen Sie wie folgt vor: Stecken Sie Ihre Karte in den TAN-Generator und drücken Sie die Taste "TAN". Geben Sie den "Startcode XXXXXXXX" ein und bestätigen Sie mit der Taste "OK". Bitte nutzen Sie dieses Gerät: Kartennummer: *********XXXX
workflow: $fints = \Fhp\FinTs::new($options, $credentials, $persistedInstance);
$fints->getTanModes();
$tanMode->needsTanMedium();
$fints->getTanMedia($tanMode);
if($tanMode == 900) // photoTAN does not require tanMedium { $tanMedium = null; }
$fints->selectTanMode($tanMode, $tanMedium);
$login_result = $fints->login();
$login_needs_tan = $login_result->needsTan();
if($login_needs_tan) { handleStrongAuthentication($login_result, $fints); }
$getSepaAccounts = \Fhp\Action\GetSEPAAccounts::create(); $fints->execute($getSepaAccounts);
$getStatement = \Fhp\Action\GetStatementOfAccount::create($sepaAccount, $from, $to); $fints->execute($getStatement);
I am having trouble getting this to work, too. We share the same credentials for multiple bank accounts and when restoring the persisted instance I either get this:
26.09.2024 16:40:15 WARN [hbci_account_tr…] 9050 (global): Die Nachricht enthält Fehler.
26.09.2024 16:40:15 WARN [hbci_account_tr…] 9800 (global): Dialog abgebrochen
26.09.2024 16:40:15 WARN [hbci_account_tr…] 9110 (global): Technischer Fehler
in Fhp/Protocol/ServerException.php:170
or this (Postbank just sent an empty response, as in zero bytes):
26.09.2024 16:37:16 ERROR [hbci_account_tr…] Message: Undefined array key 0
26.09.2024 16:37:16 ERROR [hbci_account_tr…] Backtrace:
...
26.09.2024 16:37:16 ERROR [hbci_account_tr…] library/Fhp/Protocol/Message.php => error_function:303
26.09.2024 16:37:16 ERROR [hbci_account_tr…] library/Fhp/FinTs.php => parse:955
26.09.2024 16:37:16 ERROR [hbci_account_tr…] library/Fhp/FinTs.php => sendMessage:875
26.09.2024 16:37:16 ERROR [hbci_account_tr…] library/Fhp/FinTs.php => endDialog:649
26.09.2024 16:37:16 ERROR [hbci_account_tr…] library/Fhp/FinTs.php => ensureBpdAvailable:728
26.09.2024 16:37:16 ERROR [hbci_account_tr…] library/Fhp/FinTs.php => getSelectedTanMode:759
26.09.2024 16:37:16 ERROR [hbci_account_tr…] library/Fhp/FinTs.php => requireTanMode:257
I noticed the messageId being persisted and not destroyed by forgetDialog. Maybe that's the issue?
This whole persisting thing feels so wrong and quirky... Can't we just get system id accessors?
I think the usage of persist should be integrated to the examples (at least i did not see it there).
There are samples that use the persist APIs, though that's in the "middle" (between sending an action and submitting the TAN), whereas what we're discussing here is the "outside", i.e. how you persist the instance over longer periods of time, to execute completely new actions in the future without having to login from scratch.
There is currently no sample like that, because all samples take the form of simple scripts that run from top to bottom. That said, it would indeed make sense to demonstrate how to integrate the library into a larger application with a long-term storage for persisted instances. Feel free to contribute such an example.
Will it make an impact to update to PHP 8.3?
No.
Die Gerätebezeichnung ist unbekannt. (MBV07390100255)
Is that the name of your TAN medium?
Is that string part of your request? If so, in which field?
Which method are you calling when this error happens? (That's why I asked for a stack trace, but feel free to just describe it.)
3920 (wrt seg 4): Zugelassene Zwei-Schritt-Verfahren für den Benutzer. [921, 922]
If you let it list available TAN media after receiving this error, does it list the one above?
Sie möchten sich "auf einem neuen Gerät anmelden"? Gehen Sie wie folgt vor:
Assuming this happens in the handleStrongAuthentication part of your code, it seems correct to me. A bank asking for a TAN is nothing unusual. There are banks that give you read-only access to bank statements for one day or a month or three months, depending on the bank, after entering a TAN just once. But after that time is over, they'll ask for another TAN. Other banks just always ask for a TAN.
or this (Postbank just sent an empty response, as in zero bytes)
Huh. Do they do this repeatedly/reproducibly?
This whole persisting thing feels so wrong and quirky... Can't we just get system id accessors?
Feel free to patch the library locally (inside your vendor directory) to get those accessors, make your application use it, and if that fixes things (and at the same time using the existing persist mechanism for some reason doesn't fix it) then send a PR.
Huh. Do they do this repeatedly/reproducibly?
Yes. I would guess it's a bug on their side but a proper error message from them would help to debug my issue...
Feel free to patch the library locally [...] then send a PR
Alright. I already did the first part and it seems to work just fine. I'll leave it for a few days and create the PR then.
Thanks for your help! I guess this whole thematic will pick up momentum soon when more and more banks require the system id to be handled.
Alright. I already did the first part and it seems to work just fine. I'll leave it for a few days and create the PR then.
Hi Roben, i have the same problems. Would you share your code with me?
I could then try it out (my getters and setters havent work last week, but i had no time to debug).
Thank you in Advance.
Will it make an impact to update to PHP 8.3?
No.
Die Gerätebezeichnung ist unbekannt. (MBV07390100255)
Is that the name of your TAN medium?
Is that string part of your request? If so, in which field?
This string is provided by the exception, which is thrown. It looks more like an error code, as you can find same error message on google.
Which method are you calling when this error happens? (That's why I asked for a stack trace, but feel free to just describe it.)
I managed to get a stack trace. Sorry it is not straight forward to get, as I don't have access to the bank account myself.
#0 /nemiah/php-fints/lib/Fhp/FinTs.php(979): Fhp\Protocol\ServerException::detectAndThrowErrors() #1 /nemiah/php-fints/lib/Fhp/FinTs.php(312): Fhp\FinTs->sendMessage() #2 /nemiah/php-fints/lib/Fhp/FinTs.php(268): Fhp\FinTs->execute()
3920 (wrt seg 4): Zugelassene Zwei-Schritt-Verfahren für den Benutzer. [921, 922]
If you let it list available TAN media after receiving this error, does it list the one above?
I'll try to get this information later on.
Thank you for your help.
Wild guess: Let's say "Gerätebezeichnung" refers to the TanMedium. You might call selectTanMedium() yourself or a prior selection might have been persist()-ed. Maybe that old selection isn't valid anymore, hence that error. So you would have to run (your user) through the process again where you call getTanMedia() and pick one of the valid ones.
Another wild guess: Code 9955 has a very particular meaning in the spec: "Ein-Schritt-TANVerfahren nicht zugelassen". Even though that doesn't match the error message that they send you, is it possible that you're somehow sending requests that indicate to the bank that you don't want to do 2-factor authentication, but the bank thinks you should, hence the error? I.e. do you not call selectTanMode() at all or with the second parameter set to null (nor have you called it with a non-null parameter in your persisted instance)?
Hi Roben, i have the same problems. Would you share your code with me? I could then try it out (my getters and setters havent work last week, but i had no time to debug). Thank you in Advance.
Hi. There is not much to share. The only thing I did was making the property temporarily public (public $kundensystemId). I extract this property after any successful action and restore it immediately after FinTs::new().
When this approach has been tested for a few days I will revert my temporary change and create a PR with a proper getter and setter for the property.
Hi Roben, i have the same problems. Would you share your code with me? I could then try it out (my getters and setters havent work last week, but i had no time to debug). Thank you in Advance.
Hi. There is not much to share. The only thing I did was making the property temporarily public (
public $kundensystemId). I extract this property after any successful action and restore it immediately afterFinTs::new().When this approach has been tested for a few days I will revert my temporary change and create a PR with a proper getter and setter for the property.
Thanks! Maybe it would be good idea to pass the $kundensystemId directly within the constructor...
As I just learned it's not that simple. (Is it ever?) You are not allowed to submit the $kundensystemId when calling getTanModes(). So you have to be a bit more careful when to set it and adding it to the constructor is not that helpful in this regard.
@Philipp91 I think that's also what happened in https://github.com/nemiah/phpFinTS/issues/453#issuecomment-2377229140. I always restored the persisted instance in my Wrapper, no matter what Action eventually was called.
Then please post stack traces of your error. So far, I had assumed the 26.09.2024 16:40:15 WARN [hbci_account_tr…] 9050 (global): Die Nachricht enthält Fehler. comes from your actual action, but then it seems like it comes from getTanModes()? Or are you getting a different kind of error from there?
Note that there's this call tree (simplified):
- getTanModes:
- ensureTanModesAvailable:
- if ($this->allowedTanModes === null)
- ensureBpdAvailable:
- early-exits if BPD is already there, but it's not because you didn't persist it
- starts a dialog as anonymous (kundensystemId does not matter here)
- ends the dialog
- ensureSynchronized:
- if ($this->kundensystemId === null) <---- you're affecting this
- ensureBpdAvailable: early-exits because BPD is now there
- starts a synchronization dialog, i.e. with your login but without TAN(mode) and without kundensystemId
- stores new kundensystemId
- possibly receives UPD
- ends the dialog
- if ($this->kundensystemId === null) <---- you're affecting this
- ensureBpdAvailable:
If you restore only the kundensystemId manually, you make it bypass the synchronization. If you persist fully, none of this runs because the allowedTanModes would have been persisted.
Without stack traces, I'm unable to tell whether the errors you are seeing happen (a) during synchronization, in which case we should be able to fix them even if skipping the synchronization happens to work for you today, or (b) during your main operation after all of the above, and the error happens because you didn't freshly executing some of the above "thanks" to it being available from the persisted instance.
Workaround - Saving the kundensystemId
First, we save the kundensystemId when it becomes available:
if ($this->kundensystemId === null && $action->getKundensystemId()) {
$this->kundensystemId = $action->getKundensystemId();
file_put_contents(__DIR__ . '/kundensystemId.txt', $this->kundensystemId);
}
In this block (function processActionResponse), we're saving the kundensystemId to a file named kundensystemId.txt whenever it's available. This allows us to persist the ID for future use.
Loading the kundensystemId
Now, to load the saved kundensystemId when the object is constructed (function __construct):
$filePath = __DIR__ . '/kundensystemId.txt';
if (file_exists($filePath)) {
$content = trim(file_get_contents($filePath));
if (preg_match('/^[a-f0-9]{29,33}$/i', $content)) {
$this->kundensystemId = $content;
} else {
throw new \InvalidArgumentException("Die Datei enthält keinen gültigen Hash mit einer Länge zwischen 29 und 33 Zeichen: '$content'");
}
}
It's a bit of a quick and dirty solution, but it worked well for my needs, and hopefully, it can help you too.