325 lines
12 KiB
PHP
325 lines
12 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace KupShop\BankAutoPaymentBundle\PaymentSources;
|
||
|
||
use KupShop\AdminBundle\Util\ActivityLog;
|
||
use KupShop\BankAutoPaymentBundle\BankAutoPaymentBundle;
|
||
use KupShop\BankAutoPaymentBundle\Entity\BankTransaction;
|
||
use KupShop\BankAutoPaymentBundle\Exceptions\FioPaymentExeption;
|
||
use KupShop\KupShopBundle\Config;
|
||
use KupShop\KupShopBundle\Util\StringUtil;
|
||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||
|
||
class FioBankApi extends AbstractPaymentSource
|
||
{
|
||
public const SETTINGS = 'fio_check';
|
||
|
||
protected static string $name = 'FioBank';
|
||
protected static string $logTag = BankAutoPaymentBundle::LOG_TAG_FIO;
|
||
|
||
protected array $allowedPaymentTypes = [
|
||
'Okamžitá příchozí platba',
|
||
'Bezhotovostní příjem',
|
||
'Příjem',
|
||
'Převod mezi bankovními konty (příjem)',
|
||
'Příjem převodem uvnitř banky',
|
||
'Posel – příjem',
|
||
'Neidentifikovaný příjem na bankovní konto',
|
||
'Okamžitá příchozí Europlatba',
|
||
'Vklad pokladnou',
|
||
];
|
||
|
||
protected array $lastPaymentIDs = [];
|
||
|
||
public function checkPayments(?\DateTime $forceDate = null): bool
|
||
{
|
||
if (!$this->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('<Import xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.fio.cz/schema/importIB.xsd"><Orders></Orders></Import>');
|
||
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;
|
||
}
|
||
}
|