450 lines
15 KiB
PHP
450 lines
15 KiB
PHP
<?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 wasn’t completed
|
||
// 2 – payment was successfully completed
|
||
// 3 – payment wasn’t 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 customer’s 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));
|
||
}
|
||
}
|