432 lines
16 KiB
PHP
432 lines
16 KiB
PHP
<?php
|
|
|
|
use KupShop\AdminBundle\Util\ActivityLog;
|
|
use KupShop\KupShopBundle\Config;
|
|
use KupShop\KupShopBundle\Query\JsonOperator;
|
|
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
|
use KupShop\OrderingBundle\Exception\PaymentException;
|
|
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* Requires composer package ondrakoupil/csob-eapi-paygate.
|
|
*
|
|
* V configu je nutno mít cestu privátnímu klíč merchanta (privateKeyPath), který je vygenerován CSOBckem. merchantId je taky od CSOBcka
|
|
*/
|
|
class CSOB extends Payment
|
|
{
|
|
public static $name = 'ČSOB platební brána';
|
|
|
|
protected ?string $defaultIcon = '../../common/static/payments/csob.svg';
|
|
|
|
public $class = 'CSOB';
|
|
|
|
protected $pay_method = Payment::METHOD_ONLINE;
|
|
public static bool $canAutoReturn = true;
|
|
|
|
protected ?\OndraKoupil\Csob\Client $client = null;
|
|
|
|
protected LoggerInterface $logger;
|
|
protected OrderItemInfo $orderItemInfo;
|
|
|
|
protected string $publicKeyIntegration = 'bundles/KupShop/KupShopBundle/Resources/payments/mips_iplatebnibrana.csob.cz.pub';
|
|
protected string $publicKeyProd = 'bundles/KupShop/KupShopBundle/Resources/payments/mips_platebnibrana.csob.cz.pub';
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->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;
|
|
}
|
|
}
|