nemiah / phpFinTS

PHP library to communicate with FinTS/HBCI servers
MIT License
130 stars 39 forks source link

Add parameter to checkDecoupledSubmission to not execute action #435

Open Cugar15 opened 3 months ago

Cugar15 commented 3 months ago

The function checkDecoupledSubmission will include the result into the response based on success.

// The decoupled submission is complete and the action's result is included in the response.
        $action->setTanRequest(null);
        // Process the response normally, and maybe keep going for more pages.
        $this->processActionResponse($action, $response->filterByReferenceSegments($action->getRequestSegmentNumbers()));
        if ($action instanceof PaginateableAction && $action->hasMorePages()) {
            $this->execute($action);
        }

If polling is implemented with an ajax function, only waiting or success is expected as return value:

case 'poll':
        if (!isset($fints)) { 
            $action = createFinTs();  // $fints is set here and is global
        } 

        if ($fints->checkDecoupledSubmission($action)) {
           echo "success";
    } else {
               echo "waiting"; 
        }
        storeFinTs($action);
        break;

Based on a success, the control is handed back to the main routine which is then pulling the results and executes:

$action->setTanRequest(null);
$fints->execute($action);

This will require a persisted $fints which can be read by the main routine.

However with the current implementation of checkDecoupledSubmission in case of success the results are pulled already. There will be no tanmode required anymore. From there, $fints cannot be persisted and above polling routine will fail on success.

Suggestion is to add a parameter to checkDecoupledSubmission(BaseAction $action, $execute[true|false]) to avoid pulling the results and just return fail or success.

Hope, above makes sense - happy to discuss.

Philipp91 commented 3 months ago

I don't think it's possible. The specification in section B.4.2.2.1 says:

Mit der Kreditinstitutsantwort werden ggf. erzeugte Antwortsegmente sowie die Rückmeldungen zum Auftrag selbst
zum Kundenprodukt gesendet.

Meaning: Once auth succeeds, the bank sends the results in the same response. phpFinTS is only a library and not a stateful system itself, so it hands back those responses to you.

If that AJAX request isn't a convenient place in your application to deal with the response (why not though?) then you need to persist it somewhere in your database, so that your "main routine" can then pick it up from there. Though I wonder what that "main routine" is: Just another AJAX request or HTTP request? Or some kind of daemon job on your server? Or a cronjob?

Cugar15 commented 3 months ago

Correct, the bank sends response.

The real problem is, that after executing the following marked statements in checkDecoupledSubmission:

        $action->setTanRequest(null);         <----
        // Process the response normally, and maybe keep going for more pages.
        $this->processActionResponse($action, $response->filterByReferenceSegments($action->getRequestSegmentNumbers()));
        if ($action instanceof PaginateableAction && $action->hasMorePages()) {
           $this->execute($action);    <----
        }

it is not longer possible to persist. The following message is issued:

Fatal error:  Uncaught RuntimeException: Cannot serialize this action, because it is not waiting for a TAN.

Eliminating the marked statements, the logic works fine. The logic is a http script which is opening up a html page asking the user to go about the PushTan challange in the Sparkassen APP. The html page is then issuing a periodic polling as described. The return is right now the persisted $fints structure stored in a database.

It will be also possible to return the result of the action - however, I honestly do not know how to do this. In theory, $action needs to be returned, but it cannot be serialized :( Any pointer to this is welcome.

Philipp91 commented 3 months ago

Eliminating the marked statements, the logic works fine.

No it doesn't. What you mean is: it doesn't outright fail. However, if you persist the action after executing it, then of course you're doing that only to unpersist it at a later time and then read out the results from there. But that's not possible, because the serialization logic of actions only ever persists its inputs and its execution state (TAN or not, pagination information, etc.), but never its results. This is partially because those results can be huge, potentially sensitive and partially because it would require custom serialization logic for every action type and we haven't bothered to implement all that.

It will be also possible to return the result of the action - however, I honestly do not know how to do this.

This is what you'll have to do. Because after the authentication succeeds, the bank sends the results, so you can no longer get them from the bank (a second time). The results are available in the action instance inside your PHP program, and its API does not provide a way to serialize them implicitly -- serializing the whole action won't help. You need to get out the results of the action and take care of the serialization yourself. Or you could just take care of the further processing right away, without even serializing them. After all, you're executing those actions for a reason and you have a use for those results, so you might as well run your business logic to process the results right when you get them. But if there's something that prevents you from running that business logic immediately (perhaps a lack of cookies/context/whatever, or perhaps it's some code that blocks for a long time), then you need to serialize the results.

There isn't an API at the BaseAction level that lets you retrieve "the results" in a generic way. You need to use the specific getters on the sub-classes (e.g. GetSEPAAccounts::getAccounts() and so on) to obtain the results and then serialize them. As a side note, it would be very tricky to implement such a generic getter, largely due to pagination (a BaseAction and a response Message combined are the "result" of most actions, and processResponse() joins them such they become available through those getter APIs, but this is not true for paginated actions, which need the BaseAction and multiple Messages). At that point, it would be easier to implement the aforementioned result serialization in each BaseAction sub-class, but that still would be cumbersome to implement and maintain.


To me, this issue seems to be caused by your application design and specifically that "main routine". If you can refactor your code, this might no longer be an issue.