318 lines
12 KiB
PHP
318 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\EverifinPaymentException;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Component\Routing\Router;
|
|
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;
|
|
use Symfony\Contracts\HttpClient\ResponseInterface;
|
|
use Symfony\Contracts\Service\Attribute\Required;
|
|
|
|
class EverifinApi extends AbstractPaymentSource
|
|
{
|
|
protected static string $name = 'Everifin';
|
|
protected static string $logTag = BankAutoPaymentBundle::LOG_TAG_EVERIFIN;
|
|
|
|
// testovací údaje
|
|
public const clientId = 'wpj-cz-test';
|
|
public const clientSecret = '11asXDRO8egAchUftnOCx0BThM9IgY3n';
|
|
private const TRANSACTION_URL = 'https://api.everifin.com/v1/transactions';
|
|
private const REFRESH_TRANSACTION_URL = 'https://api.everifin.com/v1/transactions/refresh';
|
|
private const LOGOUT_URL = 'https://app.everifin.com/auth/realms/everifin_app/protocol/openid-connect/logout';
|
|
private const LOGIN_EXPIRATION_DIFF = 1209600; // 14 days
|
|
protected array $allowedPaymentTypes = [
|
|
// 'PAYMENT',
|
|
// 'INSTANT_PAYMENT',
|
|
];
|
|
|
|
protected LoggerInterface $logger;
|
|
|
|
public function getRedirectUrl(): string
|
|
{
|
|
// https://app.everifin.com/auth/realms/everifin_app/protocol/openid-connect/auth?client_id=wpj-cz-test&redirect_uri=http://www.kupshop.local/admin/_everifin&response_type=code&scope=ais
|
|
$redirect_uri = path('kupshop_bankautopayment_admin_everifin_handleoauth', referenceType: Router::ABSOLUTE_URL);
|
|
|
|
// https://app.everifin.com/auth/realms/everifin_app/protocol/openid-connect/auth?client_id=everifin-api-test&redirect_uri=http%3A%2F%2Fmyweb.example&response_type=code&scope=ais
|
|
return 'https://app.everifin.com/auth/realms/everifin_app/protocol/openid-connect/auth?client_id='.$this->getClientId()."&redirect_uri={$redirect_uri}&response_type=code&scope=ais";
|
|
}
|
|
|
|
public function checkPayments(?\DateTime $forceDate = null): bool
|
|
{
|
|
if (!$this->loadConfig()) {
|
|
return false;
|
|
}
|
|
|
|
$settings = \Settings::getDefault();
|
|
$startTimestamp = null;
|
|
|
|
$everifinSettings = $settings->loadValue('everifin');
|
|
|
|
if ($forceDate) {
|
|
$dateFrom = $forceDate;
|
|
} elseif ($everifinSettings['last_check'] ?? false) {
|
|
$dateFrom = \DateTime::createFromFormat('Y-m-d\TH:i:s.vP', $everifinSettings['last_check']);
|
|
} else {
|
|
$dateFrom = new \DateTime();
|
|
}
|
|
|
|
$dateFrom->modify('+1 ms');
|
|
$dateFrom = $dateFrom->format('Y-m-d\TH:i:s.vP');
|
|
|
|
$currentPage = 1;
|
|
$maxPage = 100;
|
|
$transactions = [];
|
|
|
|
// Refresh token if it's expired or expires soon
|
|
if (time() + 30 >= $this->config['expires_in']) {
|
|
if (isProduction()) {
|
|
$this->refreshToken();
|
|
}
|
|
}
|
|
|
|
// download paymentsl
|
|
try {
|
|
$downloadResponse = $this->curlUtil->getClient(['Authorization' => "Bearer {$this->config['access_token']}"], ['max_duration' => 30])
|
|
->request('GET', self::REFRESH_TRANSACTION_URL);
|
|
$downloaded = $downloadResponse->toArray();
|
|
|
|
$this->logger->notice('Everifin API: refresh payments', ['data' => $downloaded]);
|
|
|
|
if (($downloaded['meta']['status'] ?? false) != 'SUCCESS') {
|
|
addActivityLog(ActivityLog::SEVERITY_ERROR,
|
|
ActivityLog::TYPE_SYNC,
|
|
'Everifin API: Nepodařilo se aktualizovat platby',
|
|
['error' => 'Refresh token expired'],
|
|
[BankAutoPaymentBundle::LOG_TAG_EVERIFIN]);
|
|
}
|
|
|
|
if (isset($downloaded['data']) && $downloaded['data'] == []) {
|
|
addActivityLog(ActivityLog::SEVERITY_ERROR,
|
|
ActivityLog::TYPE_SYNC,
|
|
'Everifin API: S Everifinem není propojen žádný bankovní účet. Obnovte připojení mezi Everifinem a bankou.',
|
|
['error' => 'No bank account connected'], [BankAutoPaymentBundle::LOG_TAG_EVERIFIN]
|
|
);
|
|
}
|
|
} catch (ClientExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface|TransportExceptionInterface|DecodingExceptionInterface $e) {
|
|
$this->logger->notice('Everifin API: refresh payments error', ['error' => $e->getMessage()]);
|
|
}
|
|
|
|
foreach (range(1, $maxPage) as $page) {
|
|
if ($page > $maxPage) {
|
|
break;
|
|
}
|
|
|
|
try {
|
|
$request = $this->makePaymentsRequest($dateFrom, $page);
|
|
$payments = $request->toArray();
|
|
$this->logger->notice('Everifin API: payments', ['data' => $payments]);
|
|
} catch (ClientExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface|TransportExceptionInterface|DecodingExceptionInterface $e) {
|
|
// Unauthorized, probably expired token
|
|
if ($e->getCode() == 401) {
|
|
if (isProduction()) {
|
|
$this->refreshToken();
|
|
}
|
|
// TODO: fix
|
|
// try current page again
|
|
// $page--;
|
|
continue;
|
|
}
|
|
|
|
$this->logger->notice('Everifin API: refresh payments error', ['error' => $e->getMessage()]);
|
|
|
|
return false;
|
|
}
|
|
|
|
$maxPage = $payments['meta']['last'];
|
|
$transactions = [...$transactions, ...$payments['data']];
|
|
}
|
|
|
|
$this->logger->notice('Everifin API: payments downloaded', ['data' => $transactions]);
|
|
|
|
foreach ($transactions as $transaction) {
|
|
$bankTransaction = (new BankTransaction())
|
|
->setType($transaction['type'])
|
|
->setPrice($transaction['amount'])
|
|
->setCurrency($transaction['currency'])
|
|
->setId($transaction['id'])
|
|
->setIban($transaction['userIban'])
|
|
->setVariableSymbol($transaction['variableSymbol'])
|
|
->setRef($transaction['reference'])
|
|
->setMsg($transaction['description']);
|
|
|
|
if ($startTimestamp === null) {
|
|
$startTimestamp = \DateTime::createFromFormat('Y-m-d\TH:i:s.vP', $transaction['createdAt']);
|
|
} else {
|
|
$currentTimestamp = \DateTime::createFromFormat('Y-m-d\TH:i:s.vP', $transaction['createdAt']);
|
|
if ($currentTimestamp > $startTimestamp) {
|
|
$startTimestamp = $currentTimestamp;
|
|
}
|
|
}
|
|
|
|
$this->checkPayment($bankTransaction);
|
|
}
|
|
|
|
if ($startTimestamp !== null) {
|
|
$this->saveSettings(['last_check' => $startTimestamp->format('Y-m-d\TH:i:s.vP')]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected function loadConfig()
|
|
{
|
|
if (!empty($this->config)) {
|
|
return $this->config;
|
|
}
|
|
|
|
$settings = \Settings::getDefault();
|
|
|
|
return $this->config = array_merge($settings->payments['Everifin'] ?? [], $settings->loadValue('everifin') ?? []);
|
|
}
|
|
|
|
protected function refreshToken()
|
|
{
|
|
if (time() >= $this->config['refresh_expires_in']) {
|
|
addActivityLog(ActivityLog::SEVERITY_ERROR,
|
|
ActivityLog::TYPE_SYNC,
|
|
'Everifin API: Vypršel přihlašovací token, obnovte přihlášení v nastavení eshopu',
|
|
['error' => 'Refresh token expired'],
|
|
[BankAutoPaymentBundle::LOG_TAG_EVERIFIN]);
|
|
|
|
throw new EverifinPaymentException('Refresh token expired');
|
|
}
|
|
|
|
$refreshBody = [
|
|
'grant_type' => 'refresh_token',
|
|
'client_id' => $this->getClientId(),
|
|
'client_secret' => $this->getClientSecret(),
|
|
'refresh_token' => $this->config['refresh_token'],
|
|
];
|
|
|
|
$this->logger->notice('Everifin API: refreshing token with body', ['data' => $refreshBody, 'config' => $this->config]);
|
|
|
|
$curlResponse = $this->curlUtil->getClient([
|
|
'Content-Type' => 'application/x-www-form-urlencoded',
|
|
])->request('POST', 'https://app.everifin.com/auth/realms/everifin_app/protocol/openid-connect/token', [
|
|
'body' => $refreshBody,
|
|
]);
|
|
|
|
$curlResponse = $curlResponse->toArray();
|
|
|
|
$this->logger->notice('Everifin API: refresh token', ['data' => $curlResponse]);
|
|
|
|
$this->saveSettings($curlResponse, true);
|
|
|
|
$this->loadConfig();
|
|
}
|
|
|
|
public function saveSettings(array $data, bool $updateTimestamp = false): void
|
|
{
|
|
$settings = \Settings::getDefault();
|
|
$oldState = $settings->loadValue('everifin') ?? [];
|
|
|
|
// Set new timestamps only when saving new tokens
|
|
if ($updateTimestamp) {
|
|
// Calculate expiration time
|
|
$data['expires_in'] = time() + $data['expires_in'];
|
|
$data['refresh_expires_in'] = time() + $data['refresh_expires_in'];
|
|
}
|
|
|
|
$data = array_merge($oldState, $data);
|
|
|
|
$settings->saveValue('everifin', $data, false);
|
|
}
|
|
|
|
protected function makePaymentsRequest($dateFrom, $page = 1): ResponseInterface
|
|
{
|
|
return $this->curlUtil->getClient(['Authorization' => "Bearer {$this->config['access_token']}"])
|
|
->request('GET', self::TRANSACTION_URL, [
|
|
'query' => [
|
|
'sort' => 'createdAt:desc',
|
|
'countPerPage' => 100,
|
|
'direction' => 'IN',
|
|
'createdFrom' => $dateFrom,
|
|
'page' => $page,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function logout()
|
|
{
|
|
$this->loadConfig();
|
|
|
|
$response = $this->curlUtil->getClient([
|
|
'Content-Type' => 'application/x-www-form-urlencoded',
|
|
])->request('POST', self::LOGOUT_URL, [
|
|
'body' => [
|
|
'client_id' => $this->getClientId(),
|
|
'client_secret' => $this->getClientSecret(),
|
|
'refresh_token' => $this->config['refresh_token'],
|
|
],
|
|
]);
|
|
|
|
if ($response->getStatusCode() == 204) {
|
|
$settings = \Settings::getDefault();
|
|
$settings->saveValue('everifin', [], false);
|
|
} else {
|
|
throw new EverifinPaymentException('Logout failed');
|
|
}
|
|
}
|
|
|
|
public function checkEverifinLogin(): void
|
|
{
|
|
if (!$this->loadConfig()) {
|
|
return;
|
|
}
|
|
|
|
// Nothing to do
|
|
if (empty($this->config['refresh_expires_in'])) {
|
|
return;
|
|
}
|
|
|
|
$timeDiff = $this->config['refresh_expires_in'] - time();
|
|
|
|
// Less than X days remaining to relogin
|
|
if ($timeDiff < self::LOGIN_EXPIRATION_DIFF) {
|
|
// Calculate remaining days
|
|
$days = (int) floor($timeDiff / 86400);
|
|
|
|
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_SYNC,
|
|
"Everifin: k vypršení přihlášení zbývá {$days} dní", ['timeDiff' => $timeDiff], [$this::getLogTag()]);
|
|
}
|
|
}
|
|
|
|
public function getClientId(): ?string
|
|
{
|
|
if (!$this->loadConfig()) {
|
|
return null;
|
|
}
|
|
|
|
return $this->config['clientId'] ?? null;
|
|
}
|
|
|
|
public function getClientSecret(): ?string
|
|
{
|
|
if (!$this->loadConfig()) {
|
|
return null;
|
|
}
|
|
|
|
return $this->config['clientSecret'] ?? null;
|
|
}
|
|
|
|
#[Required]
|
|
public function setLogger(LoggerInterface $logger): void
|
|
{
|
|
$this->logger = $logger;
|
|
}
|
|
}
|