loadConfig()) { return false; } $result = $this->forToken(function ($token) use ($forceDate) { $lastTransactionId = $this->lastPaymentIDs[$token] ?? null; $data = $this->downloadPaymentsData($token, $lastTransactionId, $forceDate); if (!$data) { return false; } $transactions = $data->transactionList->transaction ?? []; foreach ($transactions as $transaction) { try { $bankTransaction = (new BankTransaction()) ->setType($transaction->column8->value ?? false) ->setPrice($transaction->column1->value ?? false) ->setCurrency($transaction->column14->value ?? false) ->setId($transaction->column22->value ?? false) ->setComment($transaction->column25->value ?? false) ->setAccountNumber($transaction->column2->value ?? false) ->setBankCode($transaction->column3->value ?? false) ->setVariableSymbol($transaction->column5->value ?? false) ->setRef($transaction->column27->value ?? false) ->setMsg($transaction->column16->value ?? false); $this->checkPayment($bankTransaction); $this->lastPaymentIDs[$token] = $bankTransaction->getId(); } catch (\Throwable $exception) { // capture to sentry $this->sentryLogger->captureException(new \Exception('Failure in FioBankApi payment processing!', 0, $exception), ['extra' => (array) $transaction]); } } return true; }); \Settings::getDefault()->saveValue(self::SETTINGS, $this->lastPaymentIDs, false); return $result; } protected function downloadPaymentsData($token, ?int $lastTransactionId = null, ?\DateTime $fromDate = null) { $client = $this->curlUtil->getClient(options: ['max_duration' => 60]); try { // Podle data má přednost, když kontroluju z adminu, nechci limitovat podle ID if ($fromDate) { $response = $client->request('GET', "https://fioapi.fio.cz/v1/rest/set-last-date/{$token}/{$fromDate->format('Y-m-d')}/"); $response->getContent(); } // Nastavit zarážku na poslední přečtené ID elseif ($lastTransactionId) { $response = $client->request('GET', "https://fioapi.fio.cz/v1/rest/set-last-id/{$token}/{$lastTransactionId}/"); $response->getContent(); } // Stáhnout seznam transakcí $response = $client->request('GET', "https://fioapi.fio.cz/v1/rest/last/{$token}/transactions.json"); $data = json_decode_strict($response->getContent()); $data = $data->accountStatement; getLogger()->notice('Fio downloadPaymentsData', ['lastTransactionId' => $lastTransactionId, 'token' => $token, 'transactionCount' => count($data->transactionList->transaction ?? [])]); } catch (ClientExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface|TransportExceptionInterface|\JsonException|DecodingExceptionInterface $e) { if ($e instanceof ServerExceptionInterface) { // neexistující nebo neaktivní token - k cemu taky mame 401, ze? if ($e->getCode() == 500) { addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_SYNC, 'FIO API: Neexistující nebo neaktivní token. Zkontrolujte nastavení a internetové bankovnictví.', tags: [BankAutoPaymentBundle::LOG_TAG_FIO]); return false; } } addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_SYNC, 'FIO API: Chyba při stahování plateb.', ['exception' => $e->getMessage()], [BankAutoPaymentBundle::LOG_TAG_FIO]); return false; } return $data; } protected function forToken(callable $callback) { $tokenString = str_replace("\r", '', $this->config['tokens'] ?? ''); $tokens = explode(PHP_EOL, $tokenString); $responses = []; foreach ($tokens as $token) { $token = StringUtil::unicode_trim($token); if (!empty($token)) { $responses[] = $callback($token); } } return !in_array(false, $responses); } protected function loadConfig() { if (!empty($this->config)) { return true; } $cfg = Config::get(); $dbcfg = \Settings::getDefault(); $dbConfig = $dbcfg->payments[self::getName()] ?? []; $config = $cfg['Modules']['bank_auto_payments']['fio_bank'] ?? []; if (!is_array($config)) { $config = []; } $this->config = array_merge($config, $dbConfig); $this->lastPaymentIDs = $dbcfg->loadValue(self::SETTINGS) ?? []; return !empty($this->config); } public function sendPayments($czk = [], $eur = []) { // Pořadí je daný APIčkem - domácí platby, euro platby a ostatní $toSend = [ 'DomesticTransaction' => $czk, 'T2Transaction' => $eur, ]; $types = [ 'DomesticTransaction' => 'CZK', 'T2Transaction' => 'EUR', ]; $accounts = []; // Načti config, aby se odeslalo všechno, nebo nic if (!empty($czk)) { $accounts['CZK'] = [ 'token' => $this->getTokenForCurrency('CZK'), 'account' => $this->getAccountForCurrency('CZK'), ]; } if (!empty($eur)) { $accounts['EUR'] = [ 'token' => $this->getTokenForCurrency('EUR'), 'account' => $this->getAccountForCurrency('EUR'), ]; } $return = []; foreach ($toSend as $type => $values) { if (count($values) == 0) { continue; } $currency = $types[$type]; $xml = new \SimpleXMLElement(''); foreach ($values as $id => $item) { $transactionElement = $xml->Orders->addChild($type); // accountFrom musí být na začátku elementu foreach (['accountFrom' => $accounts[$currency]['account']] + $item as $key => $value) { $transactionElement->addChild($key, strval($value)); } } $tmpFilename = tempnam('data/tmp', 'fio_'); if (!$tmpFilename) { throw new \Exception('Nepodařilo se vytvořit tmp soubor'); } $xml->asXml($tmpFilename); $token = $accounts[$currency]['token']; $url = 'https://fioapi.fio.cz/v1/rest/import/'; $fields = [ 'type' => 'xml', 'token' => $token, 'file' => new \CURLFile($tmpFilename, 'application/xml'), ]; if (!isLocalDevelopment()) { $res = curl_init(); curl_setopt($res, CURLOPT_RETURNTRANSFER, true); curl_setopt($res, CURLOPT_TIMEOUT, 60); curl_setopt($res, CURLOPT_URL, $url); curl_setopt($res, CURLOPT_HTTPHEADER, ['Content-Type: multipart/form-data']); curl_setopt($res, CURLOPT_POST, true); curl_setopt($res, CURLOPT_POSTFIELDS, $fields); $result = curl_exec($res); $statusCode = curl_getinfo($res, CURLINFO_RESPONSE_CODE); curl_close($res); } else { continue; } if ($statusCode != 200) { addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_SYNC, 'FIO API: Nepodařilo se odeslat platby do Fio.', tags: [BankAutoPaymentBundle::LOG_TAG_FIO]); throw new FioPaymentExeption('Nepodařilo se nahrát platby do Fio.'); } addActivityLog(ActivityLog::SEVERITY_NOTICE, ActivityLog::TYPE_SYNC, 'FIO API: Odeslány Platby do Fio', [ 'request' => $xml->asXML(), 'response' => $result, ], tags: [BankAutoPaymentBundle::LOG_TAG_FIO]); $responseXml = simplexml_load_string($result); $ids = []; $index = 0; foreach ($values as $id => $item) { $detail = $responseXml->ordersDetails->detail[$index++]; $success = true; $msg = []; foreach ($detail->messages->message as $message) { if ($message['errorCode']->__toString() != 0) { $success = false; $msg[] = $message->__toString(); $ids[] = $id; } } $return[$types[$type]][$id] = [ 'success' => $success, 'message' => $msg, ]; } if (!empty($ids)) { addActivityLog(ActivityLog::SEVERITY_WARNING, ActivityLog::TYPE_SYNC, 'FIO API: při zpracování některých plateb došlo k chybě.', [implode(', ', $ids)], [BankAutoPaymentBundle::LOG_TAG_FIO]); } @unlink($tmpFilename); } return $return; } protected function getAccountForCurrency($currency) { $dbcfg = \Settings::getDefault(); $account = $dbcfg['returns']['fio'][$currency]['account']; if (empty($account)) { throw new FioPaymentExeption("Měna {$currency} nemá nastavený účet."); } return explode('/', $account)[0]; } protected function getTokenForCurrency($currency) { $dbcfg = \Settings::getDefault(); $token = $dbcfg['returns']['fio'][$currency]['token']; if (empty($token)) { throw new FioPaymentExeption("Měna {$currency} nemá nastavený token."); } return $token; } }