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)); } }