Files
kupshop/class/payments/class.ThePay.php
2025-08-02 16:30:27 +02:00

450 lines
15 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\KupShopBundle\Config;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
class ThePay extends Payment
{
public static $name = 'ThePay platební brána';
public $template = 'payment.ThePay.tpl';
protected $templateCart = 'payment.ThePay.cart.tpl';
public $class = 'ThePay';
public $tp_id_payment;
/**
* @var TpDataApiPayment
*/
public $tp_payment;
public $method;
protected $pay_method = Payment::METHOD_ONLINE;
/** Get payment icon url and name.
* @return array|null
*/
public function getIcon()
{
$method = getVal($this->method, $this->getAvailableMethods());
if ($method) {
return [
'url' => "https://www.thepay.cz/gate/images/logos/public/209x127/{$method['id']}.png",
'name' => $method['name'],
];
}
return null;
}
public function storePaymentInfo()
{
$data = parent::storePaymentInfo();
$data['method'] = explode('-', getVal('payment_id'))[1];
return $data;
}
/* Payment creation */
public function getPayment()
{
$payment = new TpPayment($this->getMerchantConfig());
$payment->setValue(roundPrice($this->order->getRemainingPayment())->asFloat());
if (findModule(\Modules::CURRENCIES)) {
$payment->setCurrency($this->order->currency ?: 'CZK');
}
$payment->setCustomerEmail($this->order->invoice_email);
$payment->setDescription('Platba objednávky '.$this->order->order_no);
$payment->setReturnUrl(createScriptURL([
's' => 'payment',
'IDo' => $this->order->id,
'cf' => $this->order->getSecurityCode(),
'step' => 5,
'class' => $this->class,
'absolute' => true,
]));
$payment->setMerchantData($this->order->order_no);
if (empty($this->method)) {
$paymentData = $this->order->getData('payment_data');
if (!$paymentData) {
$paymentData = ['methodSelectionAllowed' => 1];
}
$this->loadPaymentInfo($paymentData);
}
$payment->setMethodId($this->method);
return $payment;
}
public function getPaymentUrl()
{
try {
return $this->getMerchantConfig()->gateUrl.'?'.$this->buildQuery();
} catch (Exception $e) {
return '/';
}
}
/* Payment status change */
public function getPaymentStatus()
{
$payment = TpDataApiHelper::getPayment($this->getMerchantConfig(), $this->tp_id_payment)->getPayment();
if ($this->orderId != $payment->getMerchantData() && $this->order->order_no != $payment->getMerchantData()) {
$this->error('Neplatné číslo objednávky: '.$this->orderId.' != '.$payment->getMerchantData());
}
$payment->setId($this->tp_id_payment);
$payment->setMerchantData($this->orderId);
$this->tp_payment = $payment;
return true;
}
public function checkReceivedSignature()
{
try {
$payment = new TpReturnedPayment($this->getMerchantConfig());
$payment->verifySignature();
} catch (TpMissingParameterException $e) {
return $this->error('Chybějící položky v odpověďi platební brány');
} catch (TpInvalidSignatureException $e) {
logError(__FILE__, __LINE__, 'ThePay: Neplatná odpověď platební brány! '.print_r([$payment, $this->getMerchantConfig()], true));
return $this->error('Neplatná odpověď platební brány');
}
$this->tp_id_payment = $payment->getPaymentId();
return $payment;
}
public function updatePaymentStatus()
{
// Ensure payment exists
if (!$this->getStatus($this->tp_id_payment)) {
$this->createPayment($this->tp_id_payment, $this->tp_payment->getValue(), []);
}
switch ($this->tp_payment->getState()) {
case TpReturnedPayment::STATUS_OK: // platba je zaplacena, můžeme si ji uložit jako zaplacenou a dále zpracovat (vyřídit objednávku atp.).
if (!$this->setStatus(Payment::STATUS_FINISHED, $this->tp_id_payment)) {
logError(__FILE__, __LINE__, 'ThePay::updatePaymentStatus: setStatus failed!');
}
return true;
case TpReturnedPayment::STATUS_CANCELED: // zákazník platbu stornoval
case TpReturnedPayment::STATUS_ERROR: // při zpracování platby došlo k chybě
if (!$this->setStatus(Payment::STATUS_STORNO, $this->tp_id_payment)) {
logError(__FILE__, __LINE__, 'ThePay::updatePaymentStatus: setStatus failed!');
}
break;
case TpReturnedPayment::STATUS_UNDERPAID: // zákazník zaplatil pouze část platby
// Modify paid value
$this->updateSQL('order_payments', ['price' => $this->tp_payment->getReceivedValue()], ['id' => $this->paymentId]);
if (!$this->setStatus(Payment::STATUS_FINISHED, $this->tp_id_payment)) {
logError(__FILE__, __LINE__, 'ThePay::updatePaymentStatus: setStatus failed!');
}
return true;
case TpReturnedPayment::STATUS_CARD_DEPOSIT: // částka byla zablokována na účtu zákazníka - pouze pro platbu kartou
case TpReturnedPayment::STATUS_WAITING: // platba proběhla úspěšně, ale čeká na potvrzení od poskytovatele platební metody. S vyřízením objednávky je nutno počkat na potvrzovací request se statusem TpReturnedPayment:: STATUS_OK, pokud nepřijde, platba nebyla dokončena.
if (!$this->setStatus(Payment::STATUS_PENDING, $this->tp_id_payment)) {
logError(__FILE__, __LINE__, 'ThePay::updatePaymentStatus: setStatus failed!');
}
break;
}
return false;
}
/* Payment steps */
public function processStep_1()
{
redirection($this->getPaymentUrl());
}
public function processStep_5()
{
$this->checkReceivedSignature();
try {
$this->getPaymentStatus();
$this->updatePaymentStatus();
} catch (\KupShop\KupShopBundle\Exception\RedirectException $e) {
throw $e;
} catch (Exception $e) {
return $this->error($e->getMessage());
}
switch ($this->status) {
case Payment::STATUS_FINISHED:
$this->success(translate('paymentSuccess', 'payment'));
break;
case Payment::STATUS_STORNO:
$this->step(-3, 'storno');
break;
default:
$this->success('Platba byla zaznamenána a čeká se na její potvrzení. O přijetí platby Vám zašleme email.');
}
}
/* Payment description */
public function getName()
{
if (is_null($this->method)) {
return parent::getName();
}
$methods = $this->getAvailableMethods();
return parent::getName().' - '.getVal($this->method, $methods, ['name' => 'Neznámý typ ThePay platby'])['name'];
}
public function getAvailableMethods()
{
$domainContextID = ServiceContainer::getService(\KupShop\KupShopBundle\Context\DomainContext::class)->getActiveId();
if (!($methods = getCache('thepay-methods-'.$domainContextID))) {
$methods = $this->fetchAvailableMethods();
setCache('thepay-methods-'.$domainContextID, $methods);
}
$currencyContext = ServiceContainer::getService(CurrencyContext::class);
if ($currencyContext->getActiveId() == 'EUR') {
if (isset($methods[21])) {
$method = $methods[21];
$methods = [];
$methods[21] = $method;
} elseif (isset($methods[31])) {
$method = $methods[31];
$methods = [];
$methods[31] = $method;
} else {
$methods = [];
}
}
return $methods;
}
/* Payment methods */
public function getMerchantConfig()
{
static $config = null;
if (!$config) {
$config = new TpMerchantConfig();
if (getVal('test', $this->config)) {
$defaults = [
'gateUrl' => 'https://www.thepay.cz/demo-gate/',
'webServicesWsdl' => 'https://www.thepay.cz/demo-gate/api/api-demo.wsdl',
'dataWebServicesWsdl' => 'https://www.thepay.cz/demo-gate/api/data-demo.wsdl',
];
unset($this->config['merchantId']);
unset($this->config['accountId']);
unset($this->config['password']);
unset($this->config['dataApiPassword']);
} else {
$defaults = [
'gateUrl' => 'https://www.thepay.cz/gate/',
'webServicesWsdl' => 'https://www.thepay.cz/gate/api/gate-api.wsdl',
'dataWebServicesWsdl' => 'https://www.thepay.cz/gate/api/data.wsdl',
];
}
$this->config = array_merge($defaults, $this->config);
foreach ($this->config as $key => $value) {
$config->$key = $value;
}
}
return $config;
}
public function fetchAvailableMethods()
{
try {
/** @var TpDataApiGetPaymentMethodsResponse $response */
$response = TpDataApiHelper::getPaymentMethods($this->getMerchantConfig());
} catch (TpSoapException $e) {
return [];
}
$overwriteMethodNames = $this->config['overwriteMethodNames'] ?? [
21 => translate('creditCardMethodTitle', 'payment'),
31 => translate('creditCardMethodTitle', 'payment'),
];
$methods = [];
foreach ($response->getMethods() as $method) {
$methods[$method->getId()] = [
'name' => $overwriteMethodNames[$method->getId()] ?? $method->getName(),
'id' => $method->getId(),
];
}
return $methods;
}
/* Utils */
public function buildQuery($args = [])
{
$payment = $this->getPayment();
if (isset($this->methodSelectionAllowed)) {
$args['methodSelectionAllowed'] = $this->methodSelectionAllowed;
}
$out = array_merge(
$payment->getArgs(), // Arguments of the payment
$args, // Optional helper arguments
['signature' => $payment->getSignature()] // Signature
);
$str = [];
foreach ($out as $key => $val) {
$str[] = rawurlencode($key).'='.rawurlencode($val);
}
return implode('&', $str);
}
public function requiresEET()
{
return true;
}
public function hasOnlinePayment()
{
return true;
}
public static function isEnabled($className)
{
$cfg = Config::get();
if (empty($cfg['Modules']['payments'][$className])) {
return false;
}
return true;
}
/**
* Check paid orders (and update orders.status_payed).
*/
public function checkPaidOrders()
{
if (getVal('test', $this->config)) {
return false;
}
$searchParams = new TpDataApiGetPaymentsSearchParams();
// select only paid payments
// 1 waiting for payment, payment wasnt completed
// 2 payment was successfully completed
// 3 payment wasnt completed, payment was canceled by customer
// 4 some error occurred
// 6 payment is partly paid
// 7 payment is waiting for confirmation from payment system
// 8 payment was cancelled and the money has been returned to customer
// 9 amount is blocked on customers account (in case of payments by card)
$searchParams->setState([2]);
// filter payments by accountId
$searchParams->setAccountId($this->getAccountIds());
// finishedOnFrom datetime - date when payment was paid
$dateTimeFrom = new DateTime();
$dateTimeFrom->modify('-1 day');
$searchParams->setFinishedOnFrom($dateTimeFrom);
// set ordering (default order is DESC, use setOrderHow('ASC') to ascending)
$ordering = new TpDataApiOrdering();
$ordering->setOrderBy('finishedOn');
$ordering->setOrderHow('ASC');
// set pagination
$pagination = new TpDataApiPaginationRequest();
// default 50
$pagination->setItemsOnPage(200);
$pagination->setPage(0);
$totalPages = 1;
do {
$response = TpDataApiHelper::getPayments($this->getMerchantConfig(), $searchParams, $pagination, $ordering);
foreach ($response->getPayments() as $payment) {
$order = returnSQLResult('SELECT id FROM orders WHERE order_no=:order_no', [
'order_no' => $payment->getMerchantData(),
]);
if ($order) {
$this->setOrder($order);
$this->tp_id_payment = $payment->getId();
$this->tp_payment = $payment;
try {
$this->updatePaymentStatus();
} catch (\KupShop\KupShopBundle\Exception\RedirectException $e) {
throw $e;
} catch (Exception $e) {
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, 'ThePay error: '.$e->getMessage());
}
}
}
// use pagesCount received on first request only, otherwise it could (theoretically) loop forever
if ($response->getPagination()->getPage() <= 0) {
$totalPages = $response->getPagination()->getTotalPages();
}
// default = 0
$pagination->setPage($response->getPagination()->getPage() + 1);
} while ($response->getPagination()->getPage() + 1 < $totalPages);
return 0;
}
private function getAccountIds(): array
{
$ids = [$this->config['accountId']];
// load all account ids from local config
foreach ($this->config['domain_config'] ?? [] as $domain => $config) {
$ids[] = $config['accountId'];
}
// load all account ids from db config
$languageContext = Contexts::get(LanguageContext::class);
foreach ($languageContext->getSupported() as $language) {
$settings = Settings::getFromCache($language->getId());
$dbConfig = array_filter($settings->payments[$this->class] ?? []);
if ($dbConfig['accountId'] ?? false) {
$ids[] = $dbConfig['accountId'];
}
}
return array_filter(array_unique($ids));
}
}