nemiah / phpFinTS

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

Umstellung auf FinTsNew #198

Closed NabilHanna closed 4 years ago

NabilHanna commented 4 years ago

Hallo zusammen,

ich versuche momentan auf FinTsNew umzustellen. Ich stehe hier vor dem Problem, dass ich nach $fints->submitTan() nicht so recht weiß, wie es weitergeht. Mit dem Sample komme ich so nicht weiter... Das scheint mir nicht ganz komplett.

Wann ist das Senden der TAN komplettiert, sodass ich z.B. Umsätze abfragen kann?

Fatal error: Uncaught Fhp\Protocol\TanRequiredException: This action requires a TAN to be completed. in /vendor/nemiah/php-fints/lib/Fhp/BaseAction.php:168
Stack trace: #0 /vendor/nemiah/php-fints/lib/Fhp/Action/GetStatementOfAccount.php(108): Fhp\BaseAction->ensureSuccess() 
#1 /vendor/nemiah/php-fints/lib/Fhp/Action/GetStatementOfAccount.php(119): Fhp\Action\GetStatementOfAccount->getParsedMT940() 
#2 /inc/bookings.inc(147): Fhp\Action\GetStatementOfAccount->getStatement() 
#3 /index.php(107): include('/www/htdocs/w01...') 
#4 {main} thrown in /vendor/nemiah/php-fints/lib/Fhp/BaseAction.php on line 168
Philipp91 commented 4 years ago

Wenn submitTan() fertig ist, sind die Umsätze in der Action, die du an submitTan() übergeben hast.

Du hast eine Fehlermeldung gepostet. Wie sieht dein Code dazu aus, der dazu führt? Sind möglicherweise zwei TANs nötig (Login und Umsatzabruf)?

NabilHanna commented 4 years ago

Ok, verstanden. Das funktioniert nun soweit.

Mir fehlt in der neuen Klasse nur noch die alte Funktion: $fints->getSaldo($account)

Kommt die Funktion in die neue Klasse, oder wie holt man sich mittlerweile einen aktuellen Saldo zu einem Konto?

ampaze commented 4 years ago

Die Action dafür gibt es tatsächlich noch nicht, du kriegst aber auch mit dem Umsatzabruf das Saldo.


Edit: Evtl hast du Lust eine getBalanceAction zu implementieren?

Philipp91 commented 4 years ago

Wobei der reine Saldoabruf je nach Bank evtl keine TAN braucht.

ampaze notifications@github.com schrieb am Mo., 17. Feb. 2020, 09:05:

Die Action dafür gibt es tatsächlich noch nicht, du kriegst aber auch mit dem Umsatzabruf das Saldo.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/nemiah/phpFinTS/issues/198?email_source=notifications&email_token=AATCUW7XYODOIRZR6ZXVTF3RDJANTA5CNFSM4KDJXC62YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEL5NN4Y#issuecomment-586864371, or unsubscribe https://github.com/notifications/unsubscribe-auth/AATCUW5JG4HJRK4TEKICZYDRDJANTANCNFSM4KDJXC6Q .

interose commented 4 years ago

Hallo, ich versuche gerade meinen symfony wrapper auf die neue FinTs Klasse umzustellen.

Folgende Fehlermeldung erhalte ich beim Aufruf von submitTan:

Argument 1 passed to Fhp\Segment\Common\KtvV3::fromAccount() must be an instance of Fhp\Model\SEPAAccount, null given, called in /Users/mmeirose/Projects/symfony/sfmoney/vendor/nemiah/php-fints/lib/Fhp/Action/GetStatementOfAccount.php on line 138

Nachfolgend mal ein Auszug aus dem Wrapper. Login und getSepaAccounts funktioniert noch ohne Tan, getStatement verlangt nach einer Tan. Ich speicher mir den state in der Session und habe schon den Inhalt vor dem Speichern in der Session mit dem Inhalt nach dem Auslesen verglichen um auszuschließen dass das Speichern in der Session da etwas kaputt macht.

Hätte jemand vielleicht einen Hinweis für mich?

/**
 * @param \DateTime $from
 * @param \DateTime $to
 * @param string|null $tan
 * @return array
 * @throws TanRequiredException
 * @throws \Fhp\CurlException
 * @throws \Fhp\Protocol\ServerException
 */
public function getByRange(\DateTime $from, \DateTime $to, ?string $tan = null)
{
    if (!is_null($tan)) {
        list($persistedInstance, $persistedAction) = unserialize($this->session->get('fin_ts_instance'));
        $this->create($persistedInstance);
        $action = unserialize($persistedAction);
        $this->finTs->submitTan($action, $tan);
    } else {
        $this->create();
    }

    $this->login();

    $getSepaAccounts = \Fhp\Action\GetSEPAAccounts::create();
    $this->finTs->execute($getSepaAccounts);
    if ($getSepaAccounts->needsTan()) {
        $this->preserveState($getSepaAccounts);
    }
    $oneAccount = $getSepaAccounts->getAccounts()[0];

    $getStatement = \Fhp\Action\GetStatementOfAccount::create($oneAccount, $from, $to);

    $this->finTs->execute($getStatement);

    if ($getStatement->needsTan()) {
        $this->preserveState($getStatement);
    }

    $soa = $getStatement->getStatement();

    return $this->getTransactions($soa);
}

/**
 * @param string|null $persistedInstance
 */
private function create(?string $persistedInstance = null)
{
    $options = new FinTsOptions();
    $options->url = $this->configServer;
    $options->bankCode = $this->configBankCode;
    $options->productName = $this->configProductName;
    $options->productVersion = $this->configProductVersion;

    $credentials = Credentials::create($this->configUser, $this->configPin);

    $this->finTs = new FinTsNew($options, $credentials, $persistedInstance);

    $this->finTs->selectTanMode($this->configTanMechanism, $this->configTanMediaName);
}

/**
 * @throws TanRequiredException
 * @throws \Fhp\CurlException
 * @throws \Fhp\Protocol\ServerException
 */
private function login()
{
    $login = $this->finTs->login();

    if ($login->needsTan()) {
        $this->preserveState($login);
    }
}

/**
 * @param BaseAction $action
 *
 * @throws TanRequiredException
 */
private function preserveState(BaseAction $action)
{
    $persistedAction = serialize($action);
    $persistedFints = $this->finTs->persist();

    $this->session->set('fin_ts_instance', serialize([$persistedFints, $persistedAction]));

    throw new TanRequiredException();
}
ampaze commented 4 years ago

Sieht danach aus, als ob

$oneAccount = $getSepaAccounts->getAccounts()[0];

eben null zurückliefert?

Du solltest jede Action nach dem Ausführen immer auf Erfolg prüfen. Das wird zwar sobald man versucht das Ergebnis der Aktion abzufragen implizit gemacht, aber manche Aktionen haben keine Ergebnisdaten z.B. Überweisung. Also z.B. so

$getSepaAccounts->ensureSuccess();
$oneAccount = $getSepaAccounts->getAccounts()[0];

Außerdem solltest wenn du die TAN übermittelt hast, nicht wieder mit login und getStatement anfangen. Du hast an der Stelle dann das Ergebnis der Aktion die eine TAN brauchte ja schon.

Du musst prüfen was $action für eine Klasse ist und dann dort weitermachen wo du von einer TAN-Abfrage unterbrochen wurdest.

Wenn die TAN also durch getStatement kam, dann enthält $action bereits das Ergebnis.

Bei mir sieht das so aus: $action ist entweder null oder das Ergebnis der TAN-Behandlung

if (!is_null($work)) {
    // Keine Action fortgeführt, also erstmal Login
    if (is_null($action)) {
        $action = $fints->login();
        $action->ensureSuccess();
    }

    // Login ist erfolgt, also eigentliche Action ausführen
    if ($action instanceof DialogInitialization) {
        if ($work instanceof BaseAction) {
            $action = $work;
            $fints->execute($action);
        } else {
            $action = $work($fints, $action);
        }
        $action->ensureSuccess();
    }
}
Philipp91 commented 4 years ago

$getSepaAccounts->getAccounts() sollte intern ensureSuccess() aufrufen. Tut es das nicht?

Aber das zurückgegebene Array kann natürlich immer noch leer sein.

Am Do., 12. März 2020 um 11:49 Uhr schrieb ampaze <notifications@github.com

:

Sieht danach aus, als ob

$oneAccount = $getSepaAccounts->getAccounts()[0];

eben null zurückliefert?

Du solltest jede Action nach dem Ausführen immer auf Erfolg prüfen. Das wird zwar sobald man versucht das Ergebnis der Aktion abzufragen implizit gemacht, aber manche Aktionen haben keine Ergebnisdaten z.B. Überweisung. Also z.B. so

$getSepaAccounts->ensureSuccess(); $oneAccount = $getSepaAccounts->getAccounts()[0];


Außerdem solltest wenn du die TAN übermittelt hast, nicht wieder mit login und getStatement anfangen. Du hast an der Stelle dann das Ergebnis der Aktion die eine TAN brauchte ja schon.

Du musst prüfen was $action für eine Klasse ist und dann dort weitermachen wo du von einer TAN-Abfrage unterbrochen wurdest.

Wenn die TAN also durch getStatement kam, dann enthält $action bereits das Ergebnis.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/nemiah/phpFinTS/issues/198#issuecomment-598122420, or unsubscribe https://github.com/notifications/unsubscribe-auth/AATCUW4HM7EXBOCHS2L4IRTRHC43BANCNFSM4KDJXC6Q .

ampaze commented 4 years ago

$getSepaAccounts->getAccounts() sollte intern ensureSuccess() aufrufen. Tut es das nicht?

Ja tut es, hab ich doch geschrieben? Aber z.B. DialogInitialization oder SendSEPADirectDebit eben nicht.

interose commented 4 years ago

@ampaze Bzgl. login und getStatement gebe ich dir recht, das wollte ich noch einbauen. Allerdings fällt er bei mir schon bei submitTan auf die Nase und aus diesem Grund habe ich das noch nicht berücksichtigt.

$getSepaAccounts->getAccounts() liefert auf jeden Fall ein Array mit Inhalt zurück.

Ich habe das Speichern des Status mal so wie in den Beispielen eingebaut, d.h. via Datei.

Was mir beim durchsteppen gerade aufgefallen ist, ist folgendes: Vor dem serialisieren schaut die Action noch so aus

Screenshot 2020-03-13 at 09 41 13

nach dem unserialize dann so:

Screenshot 2020-03-13 at 09 36 45

Was ich mir gerade auch nicht erklären kann ist, wenn ich den unserialize gleich zur Laufzeit durchführe, verliert er den account ebenfalls:

Screenshot 2020-03-13 at 09 42 15

ampaze commented 4 years ago

Liegt vlt an dem doppelten Serialisieren, session->set kann auch direkt Array speichern.

Deine Fehlermeldung kann nicht vom $this->finTs->submitTan($action, $tan); kommen, dort wird ja nur die bestehende Action benutzt aber nicht instanziiert. Guck dir mal den ganzen Callstack an.

interose commented 4 years ago

Okay, jetzt verstehe ich warum der Account fehlt ;)

BaseAction.php - Zeile 66

submitTan in der FinTsNew überprüft zunächst die übermittelte TAN. Am Ende dann

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

Meine Umsatzabfrage enthält ein paginationToken und demzufolge wird execute aufgerufen. Der execute versucht dann wieder einen createRequest. In dem createRequest wird aber auf $this->account zugegriffen, welches aber ja beim ersten Durchlauf nicht serialisiert wurde und demzufolge jetzt nach der tan eingabe nicht zurück gelesen werden kann und somit null ist.

ampaze commented 4 years ago

Ah! Das erklärt es. Müsste dann vermutlich geändert werden und der Account mit drin bleiben.

Philipp91 commented 4 years ago

Also kannst du versuchen, serialize() und unserialize() so abzuändern, dass alle Felder zusätzlich drin sind, die ursprünglich über den Konstruktor reingekommen sind?

Ich hoffe mal, dass für die zweite und alle folgenden Seiten dann keine (weiteren) TANs mehr angefragt werden können, sonst müsste man ja sogar die Ergebnisse (rawMT940) mit serialisieren.

interose commented 4 years ago

Hallo Philipp, also wenn ich die serialize/unserialize Methode der Klasse GetStatementOfAccount wie folgt anpasse, funktioniert es:

public function serialize(): string
{
    return serialize([parent::serialize(), $this->bankName, $this->account, $this->from, $this->to, $this->allAccounts]);
}

public function unserialize($serialized)
{
    list($parentSerialized, $this->bankName, $this->account, $this->from, $this->to, $this->allAccounts) = unserialize($serialized);
    parent::unserialize($parentSerialized);
}