logger = ServiceContainer::getService('logger'); $this->orderItemInfo = ServiceContainer::getService(OrderItemInfo::class); } public static function getSettingsConfiguration(): array { return [ 'fields' => [ 'test' => [ 'title' => 'Testovací režim', 'type' => 'toggle', ], ], ]; } public function processStep_1() { $this->checkActivePaymentAlreadyExists(); $payment = $this->initPayment(); $url = $this->getClient()->getPaymentProcessUrl($payment); redirection($url); } public function initPayment() { $payment = $this->getInitPayment(); try { $response = $this->getClient()->paymentInit($payment); } catch (Exception $e) { $errMessage = translate('payment_exception_communication', 'payment'); addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, 'Chyba v komunikaci s CSOB platební bránou', ['message' => $e->getMessage()]); $this->logger->error('CSOB communication error', ['exception' => $e->getMessage()]); throw new PaymentException($errMessage); } $payId = $payment->getPayId(); $this->createPayment($payId, $this->order->getRemainingPayment(), ['paymentClass' => $this->class]); return $payment; } protected function checkActivePaymentAlreadyExists() { $activePaymentExists = sqlQueryBuilder()->select("op.id, JSON_UNQUOTE(JSON_EXTRACT(op.payment_data, '$.session')) sessionId")->from('order_payments', 'op') ->innerJoin('op', 'orders', 'o', 'o.id = op.id_order') ->where(\Query\Operator::equals(['o.order_no' => $this->order->order_no])) ->andWhere(\Query\Operator::inIntArray([ static::STATUS_CREATED, static::STATUS_PENDING, ], 'op.status')) ->andWhere(JsonOperator::contains('op.payment_data', 'paymentClass', $this->class)) ->orderBy('date', 'DESC') ->execute()->fetchAssociative(); $payId = $activePaymentExists['sessionId'] ?? false; if (empty($activePaymentExists) || !$payId) { return; } $client = $this->getClient(); $paymentStatus = $client->paymentStatus($payId); $this->paymentChangeStatus($paymentStatus, $payId); if (in_array($paymentStatus, [1, 2], true)) { // if payment already exists and is active, dont create new payment and redirect user to the existing one $url = $client->getPaymentProcessUrl($payId); redirection($url); } } // Vrácení z platební brány public function processStep_2() { $client = $this->getClient(); $response = $client->receiveReturningCustomer(); $payId = $response['payId']; $this->paymentChangeStatus($response['paymentStatus'], $payId); switch ($this->status) { case self::STATUS_FINISHED: $this->info(translate('paymentSuccess', 'payment')); break; case self::STATUS_PENDING: $this->info(translate('payment_unexpected_status', 'payment')); break; case self::STATUS_STORNO: $this->info(translate('payment_rejected_status', 'payment')); break; case self::STATUS_UNKNOWN: $this->info(translate('payment_unexpected_status', 'payment')); } } /** * @return void * * https://github.com/csob/platebnibrana/wiki/Pr%C5%AFb%C4%9Bh-platby#user-content-%C5%BDivotn%C3%AD-cyklus-transakce- */ protected function paymentChangeStatus($status, $payId) { switch ($status) { case 1: case 2: $this->setStatus(Payment::STATUS_PENDING, $payId); break; case 4: case 7: case 8: $this->setStatus(Payment::STATUS_FINISHED, $payId); break; case 3: case 5: case 6: $this->setStatus(Payment::STATUS_STORNO, $payId); break; case 9: case 10: // Vrácení platby; break; default: $this->setStatus(Payment::STATUS_UNKNOWN, $payId); } } protected function getInitPayment(): OndraKoupil\Csob\Payment { $payment = new \OndraKoupil\Csob\Payment($this->order->order_no); $payment->orderNo = $this->getPaymentReference(); $payment->currency = $this->order->currency; $payment->customerId = $this->order->id_user; $payment->language = $this->order->id_language; // ///// CUSTOMER $customer = new \OndraKoupil\Csob\Metadata\Customer(); $customer->name = $this->order->invoice_name.' '.$this->order->invoice_surname; $customer->email = $this->order->invoice_email; $customer->mobilePhone = $this->order->invoice_phone; $customerLogin = new \OndraKoupil\Csob\Metadata\Login(); $customerLogin->auth = $this->order->id_user ? 'account' : 'guest'; $userAccountData = $this->getUserAccountData($this->order->id_user, $this->order->invoice_email); $customerAccount = new \OndraKoupil\Csob\Metadata\Account(); if ($userAccountData['createdAt'] ?? false) { $customerAccount->setCreatedAt($userAccountData['createdAt']); } if ($userAccountData['changedAt'] ?? false) { $customerAccount->setChangedAt($userAccountData['changedAt']); } $customerAccount->paymentsDay = $userAccountData['paymentsDay'] ?? 0; $customerAccount->paymentsYear = $userAccountData['paymentsYear'] ?? 0; $customer->setLogin($customerLogin); $customer->setAccount($customerAccount); $payment->setCustomer($customer); // ///// $order = new \OndraKoupil\Csob\Metadata\Order(); $order->type = 'purchase'; $order->availability = 'now'; $order->deliveryMode = 3; $order->nameMatch = ($this->order->invoice_name.$this->order->invoice_surname) == ($this->order->delivery_name.$this->order->delivery_surname); if ($this->order->delivery_street && $this->order->delivery_city && $this->order->delivery_zip && $this->order->delivery_country) { $order->setShipping(new \OndraKoupil\Csob\Metadata\Address($this->order->delivery_street, $this->order->delivery_city, $this->order->delivery_zip, $this->order->delivery_country)); } if ($this->order->invoice_street && $this->order->invoice_city && $this->order->invoice_zip && $this->order->invoice_country) { $order->setBilling(new \OndraKoupil\Csob\Metadata\Address($this->order->invoice_street, $this->order->invoice_city, $this->order->invoice_zip, $this->order->invoice_country)); } $payment->setOrder($order); $itemsPrice = DecimalConstants::zero(); $deliveryPrice = DecimalConstants::zero(); foreach ($this->order->fetchItems() as $item) { $price = $item['total_price']['value_with_vat']; if ($this->orderItemInfo->getItemType($item) == OrderItemInfo::TYPE_DELIVERY) { $deliveryPrice = $deliveryPrice->add($price); } else { $itemsPrice = $itemsPrice->add($price); } } if (!$itemsPrice->isZero()) { $payment->addCartItem(translate('purchaseAtShop', 'payment'), 1, $this->getAmountHundreds($itemsPrice)); } if (!$deliveryPrice->isZero()) { $payment->addCartItem(translate('shipping', 'payment'), 1, $this->getAmountHundreds($deliveryPrice)); } return $payment; } public function getPaymentReference() { $orderNo = $this->order->order_no; if (isLocalDevelopment()) { return '0'.substr($orderNo, -9); } return substr($orderNo, -10); } protected function getUserAccountData($idUser, string $email): array { $userStats = []; if ($idUser) { $user = sqlQueryBuilder()->select('*')->from('users')->where(\Query\Operator::equals(['id' => $this->order->id_user])) ->execute()->fetchAssociative(); $dateTimeReg = new DateTime($user['date_reg']); $dateTimeUpdated = new DateTime($user['date_updated']); if ($dateTimeReg > (new DateTime('1990-01-01'))) { $userStats['createdAt'] = $dateTimeReg; } if ($dateTimeUpdated > (new DateTime('1990-01-01'))) { $userStats['changedAt'] = $dateTimeUpdated; } } $prevOrdersSql = sqlQueryBuilder()->select('COUNT(*)') ->from('order_payments', 'op') ->innerJoin('op', 'orders', 'o', 'o.id = op.id_order') ->andWhere(\Query\Operator::equals($idUser ? ['id_user' => $idUser] : ['invoice_email' => $email])) ->groupBy($idUser ? 'id_user' : 'invoice_email'); $userStats['paymentsDay'] = $prevOrdersSql->andWhere('op.date >= now() - INTERVAL 1 DAY') ->execute()->fetchOne(); $userStats['paymentsYear'] = $prevOrdersSql->andWhere('op.date >= now() - INTERVAL 1 YEAR') ->execute()->fetchOne(); return $userStats; } public function getClient($skipCache = false): OndraKoupil\Csob\Client { if (!$skipCache && $this->client) { return $this->client; } $pathFinder = \KupShop\KupShopBundle\Util\System\PathFinder::getService(); if ($this->config['test'] ?? false) { $publicKeyPath = $pathFinder->enginePath($this->publicKeyIntegration); $gatewayUrl = \OndraKoupil\Csob\GatewayUrl::TEST_1_9; } else { $publicKeyPath = $pathFinder->enginePath($this->publicKeyProd); $gatewayUrl = \OndraKoupil\Csob\GatewayUrl::PRODUCTION_1_9; } $domainContext = \KupShop\KupShopBundle\Util\Contexts::get(\KupShop\KupShopBundle\Context\DomainContext::class); $config = new \OndraKoupil\Csob\Config( $this->config['merchantId'], $this->config['privateKeyPath'], $publicKeyPath, $this->config['merchantName'] ?? $domainContext->getActiveWithScheme(), // Adresa, kam se mají zákazníci vracet poté, co zaplatí $this->getGenericPaymentUrl(2), // URL adresa API - výchozí je adresa testovacího (integračního) prostředí, // až budete připraveni přepnout se na ostré rozhraní, sem zadáte // adresu ostrého API. Nezapomeňte také na ostrý veřejný klíč banky. $gatewayUrl ); return $this->client = new \OndraKoupil\Csob\Client($config); } public function doReturnPayment(array $payment, float $amount) { if ($payment['status'] != Payment::STATUS_FINISHED) { throw new PaymentException(translate('returnFailed', 'orderPayment')); } $payId = $payment['payment_data']['session'] ?? null; if (!$payId) { throw new PaymentException('Payment does not have assigned checkout_id'); } $client = $this->getClient(); $paymentStatus = $client->paymentStatus($payId); switch ($paymentStatus) { case 4: case 7: if ($payment['price'] + $amount > PHP_FLOAT_EPSILON) { $message = translate('returnFailedOnlyFullAmountWhenNotCharged', 'orderPayment'); throw new PaymentException($message); } $response = $client->paymentReverse($payId); break; case 8: $amountDecimals = ($payment['price'] + $amount > PHP_FLOAT_EPSILON) ? $this->getAmountHundreds(toDecimal($amount)) : null; $response = $client->paymentRefund($payId, false, $amountDecimals); break; } if (($response['resultCode'] ?? 1) != 0) { $message = translate('returnFailed', 'orderPayment'); addActivityLog(\KupShop\AdminBundle\Util\ActivityLog::SEVERITY_ERROR, \KupShop\AdminBundle\Util\ActivityLog::TYPE_COMMUNICATION, $message, ['amount' => $amount, 'payment_id' => $payment['payment_data']['session'], 'RESPONSE' => $response]); throw new PaymentException($message); } return $response; } public function checkPaidOrders() { $orderPayments = sqlQueryBuilder()->select('op.id, op.id_order, op.payment_data') ->from('order_payments', 'op') ->where(\Query\Operator::inIntArray([ static::STATUS_CREATED, static::STATUS_PENDING, static::STATUS_UNKNOWN, ], 'op.status')) ->andWhere('op.date > (DATE_SUB(CURDATE(), INTERVAL 1 MONTH))') ->andWhere(\Query\Operator::inStringArray([$this->class], JsonOperator::value('op.payment_data', 'paymentClass'))) ->andWhere(JsonOperator::exists('op.payment_data', 'session')) ->execute(); foreach ($orderPayments as $orderPayment) { $paymentData = json_decode($orderPayment['payment_data'], true); if (empty($paymentData['session'])) { continue; } $this->setOrder($orderPayment['id_order']); $client = $this->getClient(true); $payId = $paymentData['session']; $paymentStatus = $client->paymentStatus($payId); $this->paymentChangeStatus($paymentStatus, $payId); } } protected function getAmountHundreds(Decimal $amount) { return $amount->mul(DecimalConstants::hundred())->abs()->asFloat(); } public function hasOnlinePayment() { return true; } public static function isEnabled($className) { $cfg = Config::get(); if (empty($cfg['Modules']['payments'][$className])) { return false; } return true; } }