Files
kupshop/bundles/KupShop/BankAutoPaymentBundle/PaymentSources/AbstractPaymentSource.php
2025-08-02 16:30:27 +02:00

269 lines
10 KiB
PHP

<?php
declare(strict_types=1);
namespace KupShop\BankAutoPaymentBundle\PaymentSources;
use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\BankAutoPaymentBundle\Entity\BankTransaction;
use KupShop\I18nBundle\Util\PriceConverter;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Database\QueryHint;
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
use KupShop\KupShopBundle\Util\StringUtil;
use KupShop\KupShopBundle\Util\System\CurlUtil;
use Query\Operator;
use Symfony\Contracts\Service\Attribute\Required;
abstract class AbstractPaymentSource
{
protected static string $name;
protected static string $logTag;
protected CurlUtil $curlUtil;
protected SentryLogger $sentryLogger;
protected array $config = [];
protected array $allowedPaymentTypes = [];
abstract public function checkPayments(?\DateTime $forceDate = null): bool;
abstract protected function loadConfig();
public static function getName(): string
{
return static::$name;
}
public static function getLogTag(): string
{
return static::$logTag;
}
protected function checkPayment(BankTransaction $transaction): void
{
$type = $transaction->getType() ?? false;
$price = $transaction->getPrice() ?? false;
$transactionCurrency = $transaction->getCurrency() ?? false;
$transactionId = $transaction->getId() ?? false;
$transactionComment = $transaction->getComment() ?? false;
$vs = $transaction->getVariableSymbol() ?? false;
$transactionAccount = $transaction->getAccount();
$orderIdentifiers = $transaction->getOrderIdentifier();
$transactionData = [
'type' => $type,
'orderIdentifiers' => $orderIdentifiers,
'price' => $price,
'transactionCurrency' => $transactionCurrency,
'transactionAccount' => $transactionAccount,
];
// TMP: Log all payments - #15184
ServiceContainer::getService('logger')->notice('checkPayment', [...$transactionData, 'transactionId' => $transactionId]);
if (empty($orderIdentifiers) || !$type || !$transactionCurrency || $price === false
|| (!empty($this->allowedPaymentTypes) && !in_array($type, $this->allowedPaymentTypes))) {
if ($price && ($price > 0)) {
addActivityLog(ActivityLog::SEVERITY_WARNING,
ActivityLog::TYPE_SYNC,
sprintf($this::getName().': '.translate('orderNotFound', 'autoPayment', false, true), $transactionId, $vs),
$transactionData,
[$this::getLogTag()]);
}
return;
}
if (!$orderID = $this->findPaymentOrder($transactionId, $orderIdentifiers, $transactionData)) {
return;
}
$order = new \Order();
$order->createFromDB($orderID);
if ($order->getCurrency() != $transactionCurrency) {
$currencyContext = Contexts::get(CurrencyContext::class);
if (array_key_exists($transactionCurrency, $currencyContext->getSupported())) {
$priceConverter = ServiceContainer::getService(PriceConverter::class);
$price = $priceConverter->convert($transactionCurrency, $order->getCurrency(), $price)->asFloat();
} else {
$data = ['transactionCurrency' => $transactionCurrency];
addActivityLog(ActivityLog::SEVERITY_ERROR,
ActivityLog::TYPE_SYNC,
sprintf($this::getName().': '.translate('paymentInvalidCurrency', 'autoPayment', false, true), $order->order_no, $transactionCurrency),
$data + ActivityLog::addObjectData([$order->id => $order->order_no], 'orders'),
[$this::getLogTag()]);
return;
}
}
// "číslo protiúčtu"
$accountNo = $transaction->getAccountNumber() ?? '';
// "kód banky"
$bankCode = $transaction->getBankCode();
$accountNo .= !empty($bankCode) ? "/{$bankCode}" : '';
$paymentId = $this->createPayment($orderID, $price, $transactionId, $accountNo);
if ($paymentId) {
// v updatePayments() se nastavuje date_handle a následně se neodeslal email sendPaymentReceipt
$dateHandleBeforeUpdate = $order->date_handle;
$order->updatePayments();
$transactionComment = StringUtil::unicode_trim($transactionComment);
$logComment = 'Modul '.$this::getName().' - '.sprintf(translate('orderPaidComment', 'autoPayment', false, true), $transactionId)
.((empty($transactionComment)) ? '' : ' '.sprintf(translate('paymentComment', 'autoPayment', false, true),
$transactionComment));
if ($order->status_payed == 1 && is_numeric($this->config['newState']) && empty($dateHandleBeforeUpdate)) {
$order->changeStatus($this->config['newState'], $logComment, false);
} else {
$order->logHistory($logComment);
}
$activityMessage = 'orderPaidActivityFail';
if ($order->status_payed == 1) {
$doNotSendEmail = $this->config['doNotSendEmail'] ?? false;
if (empty($dateHandleBeforeUpdate) && empty($doNotSendEmail)) {
$order->sendPaymentReceipt($paymentId);
}
$activityMessage = 'orderPaidActivitySuccess';
}
$data = ['paymentId' => $paymentId];
addActivityLog(($activityMessage == 'orderPaidActivityFail') ? ActivityLog::SEVERITY_ERROR : ActivityLog::SEVERITY_SUCCESS,
ActivityLog::TYPE_SYNC,
$this::getName().': '.sprintf(translate($activityMessage, 'autoPayment', false, true), $order->order_no),
$data + ActivityLog::addObjectData([$order->id => $order->order_no], 'orders'),
[$this::getLogTag()]);
}
}
public function findPaymentOrder($transactionId, $orderIdentifiers, array $transactionData = [])
{
$qb = $this->getOrderQuery();
$qb->andWhere(Operator::inStringArray($orderIdentifiers, 'order_no'));
$orderID = $qb->execute()->fetchColumn();
if (!$orderID) {
$qb = $this->getOrderQuery();
$qb->andWhere(Operator::inStringArray($orderIdentifiers, "TRIM(LEADING '0' FROM order_no)"));
$orderID = $qb->execute()->fetchColumn();
}
if (!$orderID) {
$allInOne = join('', $orderIdentifiers);
preg_match_all('/(\d{5,})/', $allInOne, $agreements);
$qb = $this->getOrderQuery();
$qb->andWhere(Operator::inStringArray($agreements[0], 'order_no'));
$orderID = $qb->execute()->fetchColumn();
}
if (!$orderID) {
$data = $transactionData ?: ['orderIdentifiers' => $orderIdentifiers];
addActivityLog(ActivityLog::SEVERITY_ERROR,
ActivityLog::TYPE_SYNC,
sprintf($this::getName().': '.translate('orderNotFound', 'autoPayment', false, true), $transactionId, json_encode($orderIdentifiers)),
$data,
[$this::getLogTag()]);
return null;
}
return $orderID;
}
public function getOrderQuery()
{
return sqlQueryBuilder()->select('id')
->from('orders')
->andWhere(
Operator::orX(
Operator::inIntArray(getStatuses('nothandled'), 'status'),
'TIMESTAMPDIFF(MONTH, date_handle, NOW()) <= 6'
)
)
->orderBy('id', 'DESC')
->sendToMaster();
}
protected function createPayment($orderId, $price, $transactionId, string $accountNo = ''): false|int|string
{
$fields = [
'id_order' => $orderId,
'price' => $price,
'note' => "Platba č. {$transactionId} modulu ".$this::getName().' '.(!empty($accountNo) ? " č.ú.: {$accountNo}" : ''),
'date' => (new \DateTime())->format('Y-m-d H:i:s'),
'payment_data' => json_encode(['transactionId' => $transactionId]),
'method' => \Payment::METHOD_TRANSFER,
];
$paymentExists = sqlQueryBuilder()->select('id')->from('order_payments')
->where(Operator::equals(['id_order' => $fields['id_order']]))
->andWhere(
Operator::orX(
Operator::like(['payment_data' => "%\"transactionId\":{$transactionId}%"]),
Operator::like(['payment_data' => "%\"transactionId\":\"{$transactionId}\"%"])
))
->sendToMaster()
->execute()->fetch();
if (!$paymentExists) {
return QueryHint::withRouteToMaster(function () use ($fields) {
sqlQueryBuilder()->insert('order_payments')->directValues($fields)->execute();
return sqlInsertId();
});
}
return false;
}
public function runCheckPaymentsCron(): bool
{
if (!$this->isEnabled() || !($this->config['enableCron'] ?? false)) {
return true;
}
return $this->checkPayments();
}
public function runCheckPayments(?\DateTime $date = null)
{
if (!$this->isEnabled()) {
return true;
}
addActivityLog(ActivityLog::SEVERITY_NOTICE,
ActivityLog::TYPE_SYNC,
'Kontrola plateb '.$this->getName().' API (manuálně)',
tags: [$this::getLogTag()]);
return $this->checkPayments(forceDate: $date);
}
protected function isEnabled(): bool
{
return $this->loadConfig() && !($this->config['test'] ?? false);
}
#[Required]
public function setCurlUtil(CurlUtil $curlUtil): void
{
$this->curlUtil = $curlUtil;
}
#[Required]
public function setSentryLogger(SentryLogger $sentryLogger): void
{
$this->sentryLogger = $sentryLogger;
}
}