first commit
This commit is contained in:
399
class/payments/class.Adyen.php
Normal file
399
class/payments/class.Adyen.php
Normal file
@@ -0,0 +1,399 @@
|
||||
<?php
|
||||
|
||||
use Adyen\Webhook\Exception\AuthenticationException;
|
||||
use Adyen\Webhook\Receiver\HmacSignature;
|
||||
use Adyen\Webhook\Receiver\NotificationReceiver;
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\BankAutoPaymentBundle\BankAutoPaymentBundle;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\OrderingBundle\Exception\PaymentException;
|
||||
use KupShop\OrderingBundle\Util\Order\PaymentOrderSubMethodTrait;
|
||||
|
||||
/**
|
||||
* "adyen/php-api-library": "^15.2.0",
|
||||
* "adyen/php-webhook-module": "^0.8.0".
|
||||
*/
|
||||
class Adyen extends Payment
|
||||
{
|
||||
use PaymentOrderSubMethodTrait;
|
||||
|
||||
public static $name = 'Adyen platební brána';
|
||||
|
||||
public static bool $canAutoReturn = true;
|
||||
|
||||
protected $templateCart = 'payment.Adyen.cart.tpl';
|
||||
|
||||
protected $templateOrderView = 'payment.Adyen.orderView.tpl';
|
||||
|
||||
public $class = 'Adyen';
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
protected $method;
|
||||
|
||||
public function createAdyenSession()
|
||||
{
|
||||
$session = \KupShop\KupShopBundle\Util\Compat\ServiceContainer::getService('session');
|
||||
$adyenSession = $session->get('adyen_'.$this->orderId);
|
||||
// Aby když mě to přesměruje na detail a nestihl přijit webhook, naukázal se mi znova platební widget
|
||||
if ($adyenSession['paymentResult'] == \Adyen\Model\Checkout\SessionResultResponse::STATUS_COMPLETED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Když chci změnit metodu, tak tohle přeskočit
|
||||
if (!getVal('rpm')) {
|
||||
// Pokud mám sessionId a není starší pěti minut, použiji ho.
|
||||
if ($adyenSession['sessionId'] && $adyenSession['expired'] > time() - (5 * 60)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$service = new \Adyen\Service\Checkout\PaymentsApi($this->getApiClient());
|
||||
|
||||
$createCheckoutSessionRequest = new \Adyen\Model\Checkout\CreateCheckoutSessionRequest();
|
||||
$createCheckoutSessionRequest->setCountryCode(Contexts::get(\KupShop\KupShopBundle\Context\CountryContext::class)->getActiveId())
|
||||
->setMerchantAccount($this->config['merchantAccount'])
|
||||
->setReturnUrl($this->getGenericPaymentUrl(5))
|
||||
->setReference($this->order->order_no.'_'.time())
|
||||
->setShopperEmail($this->order->getUserEmail())
|
||||
->setShopperLocale($this->getLocale());
|
||||
|
||||
$amount = new \Adyen\Model\Checkout\Amount();
|
||||
$amount->setValue($this->order->getRemainingPayment(true)->mul(DecimalConstants::hundred())->asInteger())
|
||||
->setCurrency($this->order->getCurrency());
|
||||
|
||||
$createCheckoutSessionRequest->setAmount($amount);
|
||||
|
||||
$this->kibanaLogger->notice('[Adyen] Create session', [
|
||||
'order' => $this->order->order_no,
|
||||
'amount' => $amount->getValue(),
|
||||
]);
|
||||
|
||||
if ($this->method && !getVal('rpm')) {
|
||||
$adyenMethods = [$this->method];
|
||||
} else {
|
||||
$methods = $this->getAvailableMethods($amount);
|
||||
$adyenMethods = array_values(array_map(fn ($val) => $val['type'], $methods));
|
||||
}
|
||||
$createCheckoutSessionRequest->setAllowedPaymentMethods($adyenMethods);
|
||||
|
||||
$items = [];
|
||||
foreach ($this->order->fetchItems() as $item) {
|
||||
$items[] = (new \Adyen\Model\Checkout\LineItem())
|
||||
->setQuantity($item['pieces'])
|
||||
->setTaxPercentage($item['vat'])
|
||||
->setDescription($item['descr'])
|
||||
->setAmountIncludingTax($item['total_price']['value_with_vat']->div(toDecimal($item['pieces']))->mul(DecimalConstants::hundred())->asInteger());
|
||||
}
|
||||
$createCheckoutSessionRequest->setLineItems($items);
|
||||
|
||||
$adyenSessionRequestResult = $service->sessions($createCheckoutSessionRequest);
|
||||
$session->set('adyen_'.$this->orderId, [
|
||||
'sessionId' => $adyenSessionRequestResult->getId(),
|
||||
'sessionData' => $adyenSessionRequestResult->getSessionData(),
|
||||
'expired' => time(),
|
||||
'orderId' => $this->orderId,
|
||||
]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Return from gateway */
|
||||
public function processStep_5()
|
||||
{
|
||||
$sessionId = $this->request->get('sessionId');
|
||||
$sessionResult = $this->request->get('sessionResult');
|
||||
|
||||
if ($sessionResult) {
|
||||
$result = $this->getSessionStatus($sessionId, $sessionResult);
|
||||
}
|
||||
|
||||
// Z informace redirectResult vubec nic nedostanu. Takze proste zakaznikovi zakazu znova zaplatit a bude se cekat az vysledek dorazi pres webhook.
|
||||
if (($result ?? false) == \Adyen\Model\Checkout\SessionResultResponse::STATUS_COMPLETED || $this->request->get('redirectResult')) {
|
||||
$adyenSession = $this->request->getSession()->get('adyen_'.$this->orderId) ?: [];
|
||||
$this->request->getSession()->set('adyen_'.$this->orderId, $adyenSession + ['paymentResult' => \Adyen\Model\Checkout\SessionResultResponse::STATUS_COMPLETED]);
|
||||
}
|
||||
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
}
|
||||
|
||||
public function getSessionStatus($sessionId, $sessionResult)
|
||||
{
|
||||
$paymentsApi = new \Adyen\Service\Checkout\PaymentsApi($this->getApiClient());
|
||||
$sessionResultResponse = $paymentsApi->getResultOfPaymentSession($sessionId, ['queryParams' => ['sessionResult' => $sessionResult]]);
|
||||
|
||||
return $sessionResultResponse->getStatus();
|
||||
}
|
||||
|
||||
public function startPayment()
|
||||
{
|
||||
$pathData = ['id' => $this->order->id];
|
||||
if (!\User::getCurrentUser()) {
|
||||
$pathData['cf'] = $this->order->getSecurityCode();
|
||||
}
|
||||
redirection(path('payment-redirect', $pathData));
|
||||
}
|
||||
|
||||
public function processStep_1()
|
||||
{
|
||||
redirection($this->order->getDetailUrl());
|
||||
}
|
||||
|
||||
/** Return from gateway */
|
||||
public function processStep_10()
|
||||
{
|
||||
$request = json_decode($this->request->getContent() ?? '', true);
|
||||
|
||||
if (empty($request['notificationItems'])) {
|
||||
$this->error('Not found !!');
|
||||
}
|
||||
|
||||
// Setup NotificationReceiver with dependency injection or create an instance as follows
|
||||
$notificationReceiver = new NotificationReceiver(new HmacSignature());
|
||||
|
||||
$_SERVER['PHP_AUTH_USER'] = $this->request->headers->get('php-auth-user');
|
||||
$_SERVER['PHP_AUTH_PW'] = $this->request->headers->get('php-auth-pw');
|
||||
|
||||
if (!$notificationReceiver->isAuthenticated(
|
||||
$request['notificationItems'][0]['NotificationRequestItem'],
|
||||
$this->config['merchantAccount'],
|
||||
$this->config['basic_name'],
|
||||
$this->config['basic_pass']
|
||||
)) {
|
||||
throw new AuthenticationException('Incoming webhook wasn\'t authenticated!');
|
||||
}
|
||||
|
||||
foreach ($request['notificationItems'] as $notificationItem) {
|
||||
$notifItem = $notificationItem['NotificationRequestItem'];
|
||||
|
||||
if ($notificationReceiver->validateHmac($notifItem, $this->config['hmac']) && in_array($notifItem['eventCode'], ['AUTHORISATION', 'REFUND'])) {
|
||||
$isRefund = ($notifItem['eventCode'] == 'REFUND');
|
||||
$message = ($isRefund ? '[Adyen] Refund payment' : '[Adyen] Incoming payment');
|
||||
$this->kibanaLogger->notice($message, [
|
||||
'notificationItem' => $notifItem,
|
||||
]);
|
||||
|
||||
$merchantReference = $notifItem['merchantReference'];
|
||||
$ref = explode('_', $merchantReference);
|
||||
$id_order = ($ref[0] ?? false);
|
||||
|
||||
if ($notifItem['success'] == 'true') {
|
||||
if ($id_order) {
|
||||
try {
|
||||
$order = Order::createFromDbOrderNo($id_order);
|
||||
$payment_data = [
|
||||
'paymentClass' => self::class,
|
||||
'session' => $notifItem['additionalData']['checkoutSessionId'] ?? null,
|
||||
'pspReference' => $notifItem['pspReference'] ?? null,
|
||||
];
|
||||
$price = $notifItem['amount']['value'] / 100;
|
||||
$note = "Platba modulu {$this->class}";
|
||||
if ($isRefund) {
|
||||
$price *= -1;
|
||||
$note = "Vrácení platby: objednávka {$order->order_no}, ID platby: {$notifItem['originalReference']}";
|
||||
$payment_data['originalReference'] = $notifItem['originalReference'];
|
||||
}
|
||||
$order?->insertPayment($price, $note, method: self::METHOD_ONLINE, payment_data: json_encode($payment_data));
|
||||
|
||||
if ($order && !empty($notifItem['paymentMethod'])) {
|
||||
$this->setPaymentSubMethod($notifItem['paymentMethod'], $order);
|
||||
}
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
getRaven()->captureException($exception);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION,
|
||||
"Objednávka {$id_order}: {$message} error: {$notifItem['reason']}", $notifItem, [BankAutoPaymentBundle::LOG_TAG_ADYEN]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->sendNotificationResponse(200, '[accepted]');
|
||||
}
|
||||
|
||||
public function doReturnPayment(array $payment, float $amount)
|
||||
{
|
||||
if ($paymentPspReference = $payment['payment_data']['pspReference'] ?? null) {
|
||||
$class = new \Adyen\Service\Checkout\ModificationsApi($this->getApiClient());
|
||||
$paymentRefundRequest = new \Adyen\Model\Checkout\PaymentRefundRequest();
|
||||
$paymentRefundRequest->setMerchantAccount($this->config['merchantAccount']);
|
||||
$paymentRefundRequest->setReference($this->order->order_no.'_'.time());
|
||||
$amountCents = (int) floor($amount * -100); // musí být integer v centech
|
||||
$amount = new \Adyen\Model\Checkout\Amount();
|
||||
$amount->setValue($amountCents)->setCurrency($this->order->getCurrency());
|
||||
$paymentRefundRequest->setAmount($amount);
|
||||
$paymentRefundResponse = $class->refundCapturedPayment($paymentPspReference, $paymentRefundRequest);
|
||||
if ($paymentRefundResponse->getStatus() == 'received') {
|
||||
// the refund request was successfully received by Adyen - no result yet,
|
||||
// because you receive the outcome of the refund request asynchronously, in a REFUND webhook.
|
||||
|
||||
// Vrácení platby bylo odesláno na platební bránu.
|
||||
throw new PaymentException(translate('returnSucceed', 'orderPayment'), 'Adyen');
|
||||
}
|
||||
}
|
||||
|
||||
// Vrácení platby se nezdařilo, vyřešte, prosím, na platební bráně.);
|
||||
throw new PaymentException(translate('returnFailed', 'orderPayment'));
|
||||
}
|
||||
|
||||
public function getApiClient(): Adyen\Client
|
||||
{
|
||||
if (!isset($this->apiClient)) {
|
||||
$config = new \Adyen\Config();
|
||||
$testEnv = (isDevelopment() || ($this->config['test'] ?? '0') == '1');
|
||||
|
||||
if (!$testEnv) {
|
||||
$config->set('prefix', $this->config['api_urls_prefix']);
|
||||
}
|
||||
|
||||
$client = new \Adyen\Client($config);
|
||||
|
||||
$client->setXApiKey($this->config['apikey']);
|
||||
$client->setEnvironment($testEnv ? \Adyen\Environment::TEST : \Adyen\Environment::LIVE);
|
||||
$client->setTimeout(5);
|
||||
$this->apiClient = $client;
|
||||
}
|
||||
|
||||
return $this->apiClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Adyen\AdyenException
|
||||
*/
|
||||
public function getAvailableMethods(Adyen\Model\Checkout\Amount|Decimal|null $amount = null)
|
||||
{
|
||||
$countryContext = Contexts::get(\KupShop\KupShopBundle\Context\CountryContext::class);
|
||||
$currencyContext = Contexts::get(\KupShop\KupShopBundle\Context\CurrencyContext::class);
|
||||
$currency = $currencyContext->getActiveId();
|
||||
$country = $countryContext->getActiveId();
|
||||
$cacheKey = "adyen-methods-{$currency}-{$country}-{$this->getLocale()}";
|
||||
if (!$amount) {
|
||||
if ($paymentMethods = getCache($cacheKey)) {
|
||||
return $paymentMethods;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$class = new \Adyen\Service\Checkout\PaymentsApi($this->getApiClient());
|
||||
|
||||
$paymentMethodsRequest = new \Adyen\Model\Checkout\PaymentMethodsRequest();
|
||||
$paymentMethodsRequest->setMerchantAccount($this->config['merchantAccount']);
|
||||
$paymentMethodsRequest->setCountryCode($countryContext->getActiveId());
|
||||
$paymentMethodsRequest->setShopperLocale($this->getLocale());
|
||||
|
||||
if ($amount instanceof Decimal) {
|
||||
$amount = (new \Adyen\Model\Checkout\Amount())->setValue($amount->mul(DecimalConstants::hundred())->asInteger())->setCurrency($currencyContext->getActiveId());
|
||||
}
|
||||
|
||||
if ($amount) {
|
||||
$paymentMethodsRequest->setAmount($amount);
|
||||
}
|
||||
|
||||
$paymentMethodsResponse = $class->paymentMethods($paymentMethodsRequest);
|
||||
|
||||
$paymentMethods = [];
|
||||
foreach ($paymentMethodsResponse->getPaymentMethods() as $method) {
|
||||
if ($method->getType() == 'scheme') {
|
||||
$images = array_map(function ($brand) {
|
||||
return "https://checkoutshopper-live.adyen.com/checkoutshopper/images/logos/{$brand}.svg";
|
||||
}, $method->getBrands());
|
||||
} else {
|
||||
$images = ["https://checkoutshopper-live.adyen.com/checkoutshopper/images/logos/{$method->getType()}.svg"];
|
||||
}
|
||||
|
||||
$paymentMethods[$method->getType()] = [
|
||||
'name' => $method->getName(),
|
||||
'type' => $method->getType(),
|
||||
'images' => $images,
|
||||
];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION,
|
||||
'Chyba Adyen při načítání platebních metod: '.$e->getMessage(),
|
||||
['key' => $cacheKey, 'config' => $this->config], [BankAutoPaymentBundle::LOG_TAG_ADYEN]);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!$amount) {
|
||||
setCache($cacheKey, $paymentMethods);
|
||||
}
|
||||
|
||||
return $paymentMethods;
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getSettingsConfiguration(): array
|
||||
{
|
||||
return ['fields' => [
|
||||
'apikey' => [
|
||||
'title' => 'API klíč',
|
||||
'type' => 'text',
|
||||
],
|
||||
'merchantAccount' => [
|
||||
'title' => 'Merchant Account',
|
||||
'type' => 'text',
|
||||
],
|
||||
'clientKey' => [
|
||||
'title' => 'Client Key',
|
||||
'type' => 'text',
|
||||
],
|
||||
'hmac' => [
|
||||
'title' => 'HMAC key',
|
||||
'type' => 'text',
|
||||
],
|
||||
'api_urls_prefix' => [
|
||||
'title' => 'API URLs prefix',
|
||||
'type' => 'text',
|
||||
],
|
||||
'basic_name' => [
|
||||
'title' => 'Basic auth name',
|
||||
'type' => 'text',
|
||||
],
|
||||
'basic_pass' => [
|
||||
'title' => 'Basic auth pass',
|
||||
'type' => 'text',
|
||||
],
|
||||
'test' => [
|
||||
'title' => 'Testovací režim',
|
||||
'type' => 'toggle',
|
||||
],
|
||||
]];
|
||||
}
|
||||
|
||||
public function getLocale()
|
||||
{
|
||||
$languageContext = Contexts::get(\KupShop\KupShopBundle\Context\LanguageContext::class);
|
||||
|
||||
return $languageContext->getActive()->getLocale();
|
||||
}
|
||||
|
||||
// private function checkWebhooks()
|
||||
// {
|
||||
// Maybe later alligator
|
||||
//
|
||||
// $merchantAccount = $this->config['merchantAccount'];
|
||||
// $webhooksApi = new \Adyen\Service\Management\WebhooksMerchantLevelApi($this->getApiClient());
|
||||
// $allWebhooks = $webhooksApi->listAllWebhooks($merchantAccount);
|
||||
//
|
||||
// $webhooks = $allWebhooks->getData();
|
||||
//
|
||||
// if ($webhooks) {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// $createMerchantWebhookRequest = new \Adyen\Model\Management\CreateMerchantWebhookRequest();
|
||||
// $createMerchantWebhookRequest->setUrl(path('kupshop_ordering_payment_legacypayment', ['class' => 'Adyen', 'step' => 10], \Symfony\Component\Routing\Router::ABSOLUTE_URL))
|
||||
// ->setDescription('API - wpjshop')
|
||||
// ->setType('standard')
|
||||
// ->setCommunicationFormat('json');
|
||||
//
|
||||
// $webhooksApi->setUpWebhook($merchantAccount, $createMerchantWebhookRequest);
|
||||
// }
|
||||
}
|
||||
431
class/payments/class.CSOB.php
Normal file
431
class/payments/class.CSOB.php
Normal file
@@ -0,0 +1,431 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
10
class/payments/class.DefaultPayment.php
Normal file
10
class/payments/class.DefaultPayment.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
class DefaultPayment extends Payment
|
||||
{
|
||||
public static $name = 'Platba';
|
||||
|
||||
public $class = 'DefaultPayment';
|
||||
|
||||
protected $pay_method = Payment::METHOD_UNKNOWN;
|
||||
}
|
||||
36
class/payments/class.Dobirka.php
Normal file
36
class/payments/class.Dobirka.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use KupShop\OrderingBundle\Exception\PaymentException;
|
||||
|
||||
class Dobirka extends Payment
|
||||
{
|
||||
public static $name = 'Platba na dobírku';
|
||||
protected ?string $defaultIcon = '../../common/static/payments/dobirka.svg';
|
||||
|
||||
public $class = 'Dobirka';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_COD;
|
||||
|
||||
public function requiresEET()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $cart \Cart
|
||||
*/
|
||||
public function check(CartBase $cart)
|
||||
{
|
||||
if ($cart->hasVirtualProducts()) {
|
||||
$this->exception = new PaymentException(translate_shop('errorDobirkaVirtualProducts', 'payment'), translate_shop('errorDobirkaVirtualProducts_short', 'payment'));
|
||||
}
|
||||
|
||||
if (!empty($this->exception) && $cart->max_step != 0) {
|
||||
throw $this->exception;
|
||||
}
|
||||
|
||||
return parent::check($cart);
|
||||
}
|
||||
}
|
||||
138
class/payments/class.Essox.php
Normal file
138
class/payments/class.Essox.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
use KupShop\KupShopBundle\Config;
|
||||
|
||||
class Essox extends Payment
|
||||
{
|
||||
public static $name = 'Essox';
|
||||
|
||||
protected $templateOrderView = 'payment.Essox.orderView.tpl';
|
||||
// protected $templateCart = 'payment.Essox.cart.tpl';
|
||||
|
||||
public $class = 'Essox';
|
||||
|
||||
protected $pay_method = Payment::METHOD_INSTALLMENTS;
|
||||
|
||||
// Config
|
||||
public $url = 'https://e-smlouvy.essox.cz';
|
||||
public $userName;
|
||||
public $encryptKey;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->readConfig();
|
||||
}
|
||||
|
||||
public function readConfig()
|
||||
{
|
||||
foreach ($this->config as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function getEssoxOrderUrl()
|
||||
{
|
||||
return $this->getRequestUrl('NewContract', $this->order->total_price->asInteger(), ['OrderId' => $this->order->order_no]);
|
||||
}
|
||||
|
||||
protected function base64_url_encode($input)
|
||||
{
|
||||
return strtr(base64_encode($input), '+/', '-_');
|
||||
}
|
||||
|
||||
protected function getRequestUrl($method, $price, $customData)
|
||||
{
|
||||
$Timestamp = date('YmdHis', time()); // aktualni cas
|
||||
$UserName = $this->userName; // vase prihlasovaci jmeno
|
||||
$Password = $this->encryptKey; // vase heslo
|
||||
|
||||
$HashKey = $UserName.'#'.$Password.'#'.$price.'#'.$Timestamp;
|
||||
$HashKey = sha1($HashKey);
|
||||
|
||||
$extendedParameters = join('', array_map(function ($key, $value) {
|
||||
return "<{$key}>{$value}</{$key}>";
|
||||
}, array_keys($customData), $customData));
|
||||
|
||||
$xml = "<FinitServiceRequest>
|
||||
<Version>1.0</Version>
|
||||
<ServiceName>{$method}</ServiceName>
|
||||
<BaseParameters>
|
||||
<UserName>{$UserName}</UserName>
|
||||
<Price>{$price}</Price>
|
||||
<Timestamp>{$Timestamp}</Timestamp>
|
||||
<HashKey>{$HashKey}</HashKey>
|
||||
</BaseParameters>
|
||||
<ExtendedParameters>
|
||||
{$extendedParameters}
|
||||
</ExtendedParameters>
|
||||
|
||||
</FinitServiceRequest>";
|
||||
|
||||
return $this->url.'?ESXCode=5&ESXAuth='.$this->base64_url_encode(trim($xml));
|
||||
}
|
||||
|
||||
public function accept($totalPrice, $freeDelivery)
|
||||
{
|
||||
$totalPrice = $totalPrice->getPriceWithVat()->asFloat();
|
||||
if ($totalPrice <= 0 && $this->order) {
|
||||
$totalPrice = $this->order->total_price;
|
||||
}
|
||||
|
||||
return parent::accept($totalPrice, $freeDelivery) && $totalPrice >= 2000;
|
||||
}
|
||||
|
||||
public function getEssoxCalcUrl(Decimal $price)
|
||||
{
|
||||
$price = roundPrice($price, -1, 'DB', 0)->asInteger();
|
||||
|
||||
if ($price < 2000) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return path('kupshop_ordering_payment_legacypayment', [
|
||||
'step' => 5,
|
||||
'class' => $this->class,
|
||||
'price' => $price,
|
||||
]);
|
||||
}
|
||||
|
||||
public function processStep_1()
|
||||
{
|
||||
redirection($this->getEssoxOrderUrl());
|
||||
}
|
||||
|
||||
public function processStep_5()
|
||||
{
|
||||
$price = roundPrice(getVal('price'), -1, 'DB', 0)->asInteger();
|
||||
|
||||
if ($price < 2000) {
|
||||
return null;
|
||||
}
|
||||
|
||||
redirection($this->getRequestUrl('Calculation', $price, []));
|
||||
}
|
||||
|
||||
public function startPayment()
|
||||
{
|
||||
// Zakázat automatický redirect na bránu, už se z ní nikdy nevrátí
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
69
class/payments/class.Essox2Rozdeleni.php
Normal file
69
class/payments/class.Essox2Rozdeleni.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
if (!class_exists('Essox2Splatky')) {
|
||||
require_once 'class.Essox2Splatky.php';
|
||||
}
|
||||
|
||||
class Essox2Rozdeleni extends Essox2Splatky
|
||||
{
|
||||
public static $name = 'Essox - rozdělení platby';
|
||||
|
||||
public $class = 'Essox2Rozdeleni';
|
||||
|
||||
protected int $splitParts = 4;
|
||||
|
||||
protected $templateOrderView = 'payment.Essox2Rozdeleni.orderView.tpl';
|
||||
|
||||
protected function getSpreadedInstalments(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function processStep_5()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getEssoxCalcDetail(Decimal $price): ?array
|
||||
{
|
||||
$priceVal = roundPrice($price, -1, 'DB', 0)->asFloat();
|
||||
|
||||
if (!($this->config['productDetail'] ?? false) || $priceVal < ($this->config['minPrice'] ?? 2000) || $priceVal > ($this->config['maxPrice'] ?? 30000)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->splitPrice($price);
|
||||
}
|
||||
|
||||
protected function splitPrice(Decimal $price): array
|
||||
{
|
||||
$part = $price->div(toDecimal($this->splitParts))->floor();
|
||||
|
||||
return [
|
||||
'parts' => $this->splitParts,
|
||||
'price' => $part->asInteger(),
|
||||
];
|
||||
}
|
||||
|
||||
public function accept($totalPrice, $freeDelivery)
|
||||
{
|
||||
$price = $totalPrice->getPriceWithVat()->asFloat();
|
||||
if ($price <= 0 && $this->order) {
|
||||
$price = $this->order->total_price;
|
||||
}
|
||||
|
||||
// price has to be lower than 30000 Kč according to the documentation
|
||||
return parent::accept($totalPrice, $freeDelivery) && $price <= 30000;
|
||||
}
|
||||
|
||||
public static function getSettingsConfiguration(): array
|
||||
{
|
||||
$essoxConfig = parent::getSettingsConfiguration();
|
||||
$essoxConfig['fields']['maxPrice']['tooltip'] = 'Maximální částka u které tuto možnost zobrazit na detailu produktu. (max. 30000 Kč)';
|
||||
$essoxConfig['fields']['maxPrice']['placeholder'] = '30000';
|
||||
|
||||
return $essoxConfig;
|
||||
}
|
||||
}
|
||||
424
class/payments/class.Essox2Splatky.php
Normal file
424
class/payments/class.Essox2Splatky.php
Normal file
@@ -0,0 +1,424 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\KupShopBundle\Config;
|
||||
use KupShop\KupShopBundle\Query\JsonOperator;
|
||||
use KupShop\KupShopBundle\Util\StringUtil;
|
||||
use KupShop\OrderingBundle\Exception\PaymentException;
|
||||
use Query\Operator;
|
||||
|
||||
// Dokumentace ESSOX API: https://drive.google.com/drive/folders/1j7OFhsrUl1F3ZQt3Lo7d8yOyvLW7ioN5?usp=sharing
|
||||
|
||||
class Essox2Splatky extends Payment
|
||||
{
|
||||
public static $name = 'Essox - splátky';
|
||||
|
||||
public $class = 'Essox2Splatky';
|
||||
|
||||
protected string $apiUrl = 'https://apiv32.essox.cz';
|
||||
protected string $apiTestUrl = 'https://testapiv32.essox.cz';
|
||||
|
||||
protected $templateOrderView = 'payment.Essox2Splatky.orderView.tpl';
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
public function getPaymentUrl()
|
||||
{
|
||||
return createScriptURL([
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => 1,
|
||||
'class' => $this->class,
|
||||
'absolute' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getSpreadedInstalments(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function processStep_1()
|
||||
{
|
||||
$proposalData = $this->getProposalData();
|
||||
|
||||
$redirectData = $this->getProposalRedirect($proposalData);
|
||||
|
||||
$contractId = $redirectData['contractId'];
|
||||
|
||||
$this->createPayment($contractId, $proposalData['price'], ['paymentClass' => $this->class]);
|
||||
|
||||
redirection($redirectData['redirectionUrl']);
|
||||
}
|
||||
|
||||
/** Return from gateway */
|
||||
public function processStep_2()
|
||||
{
|
||||
$this->info(translate('payment_waiting_for_confirmation', 'payment'));
|
||||
}
|
||||
|
||||
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(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']);
|
||||
$contractId = (int) $paymentData['session'];
|
||||
|
||||
$response = $this->getPaymentStatus($contractId);
|
||||
|
||||
if (!empty($response['errorCollection'])) {
|
||||
foreach ($response['errorCollection'] as $error) {
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR,
|
||||
ActivityLog::TYPE_COMMUNICATION,
|
||||
translate('returnFailedMessage', 'orderPayment'),
|
||||
$error);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($response['businessCases'] as $businessCase) {
|
||||
$businessCaseStatus = (int) $businessCase['contractStatusId'];
|
||||
|
||||
switch ($businessCaseStatus) {
|
||||
case 1: // Zakaz. opustil před odesláním
|
||||
case 2: // Čeká na posouzení
|
||||
case 3: // Posuzuje se
|
||||
case 4: // Odložen
|
||||
case 18: // Čeká na nahrání dokumentů
|
||||
case 19: // Čeká na platbu
|
||||
case 9:
|
||||
case 13: // Ke kontrole 9,13
|
||||
$this->setStatus(self::STATUS_PENDING, $contractId);
|
||||
break;
|
||||
case 5:
|
||||
case 10:
|
||||
case 11: // Reklamace 5,10,11
|
||||
case 7: // Zamítnuto / Neschválený návrh
|
||||
$this->setStatus(self::STATUS_STORNO, $contractId);
|
||||
break;
|
||||
case 12: // V pořádku doručeno do ESSOXu proplaceno / Zkontrolováno
|
||||
$newStatus = ($this->config['setPaymentCompletedManually'] ?? false) ? self::STATUS_PENDING : self::STATUS_FINISHED;
|
||||
$this->setStatus($newStatus, $contractId);
|
||||
break;
|
||||
default:
|
||||
$this->setStatus(self::STATUS_UNKNOWN, $contractId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getPaymentStatus(int $contractId)
|
||||
{
|
||||
$response = $this->requestEssoxCurl('/consumergoods/v1/api/consumergoods/status?ContractId='.$contractId, [
|
||||
'Accept: application/json',
|
||||
'Authorization: Bearer '.$this->getAccessToken(),
|
||||
], '', false);
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
protected function getAccessToken()
|
||||
{
|
||||
$loginHash = md5(($this->config['clientKey'] ?? '').':'.($this->config['clientSecret'] ?? '').':'.($this->config['test'] ?? ''));
|
||||
$tokenCacheKey = 'payment_essox_access_token'.$loginHash;
|
||||
|
||||
$token = getCache($tokenCacheKey);
|
||||
|
||||
if (!$token) {
|
||||
$retrievedToken = $this->retrieveAccessToken();
|
||||
$token = $retrievedToken['access_token'];
|
||||
setCache($tokenCacheKey, $token, $retrievedToken['expires_in'] - 60);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
protected function retrieveAccessToken(): array
|
||||
{
|
||||
$data = [
|
||||
'grant_type' => 'client_credentials',
|
||||
'scope' => 'scopeFinit.consumerGoods.eshop',
|
||||
];
|
||||
|
||||
$response = $this->requestEssoxCurl('/token', [
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'accept: application/json',
|
||||
'Authorization: Basic '.base64_encode($this->config['clientKey'].':'.$this->config['clientSecret']),
|
||||
], http_build_query($data));
|
||||
|
||||
$decodedData = json_decode($response, true);
|
||||
|
||||
return ['access_token' => (string) $decodedData['access_token'], 'expires_in' => (int) $decodedData['expires_in']];
|
||||
}
|
||||
|
||||
public function getProposalData(): array
|
||||
{
|
||||
$phoneNumber = $this->order->invoice_phone;
|
||||
$checkPhonePrefixes = ['+420'];
|
||||
$phonePrefix = '';
|
||||
|
||||
foreach ($checkPhonePrefixes as $checkPrefix) {
|
||||
if (StringUtil::startsWith($phoneNumber, $checkPrefix)) {
|
||||
$phoneNumber = str_replace($checkPrefix, '', $phoneNumber);
|
||||
$phonePrefix = $checkPrefix;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'firstName' => $this->order->invoice_name,
|
||||
'surname' => $this->order->invoice_surname,
|
||||
'mobilePhonePrefix' => $phonePrefix,
|
||||
'mobilePhoneNumber' => $phoneNumber,
|
||||
'email' => $this->order->invoice_email,
|
||||
'price' => $this->order->getTotalPrice()->getPriceWithVat()->asFloat(),
|
||||
'orderId' => $this->order->order_no,
|
||||
'customerId' => $this->order->id_user,
|
||||
'transactionId' => 1,
|
||||
'shippingAddress' => [
|
||||
'street' => $this->order->delivery_street,
|
||||
'houseNumber' => '',
|
||||
'city' => $this->order->delivery_city,
|
||||
'zip' => $this->order->delivery_zip,
|
||||
],
|
||||
'callbackUrl' => $this->getGenericPaymentUrl(2),
|
||||
'spreadedInstalments' => $this->getSpreadedInstalments(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getProposalRedirect($proposalData)
|
||||
{
|
||||
$response = $this->requestEssoxCurl('/consumergoods/v1/api/consumergoods/proposal', [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer '.$this->getAccessToken(),
|
||||
], json_encode($proposalData));
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
public function getEssoxCalcDetail(Decimal $price)
|
||||
{
|
||||
$price = roundPrice($price, -1, 'DB', 0)->asInteger();
|
||||
|
||||
if (!($this->config['productDetail'] ?? false) || $price < ($this->config['minPrice'] ?? 2000) || (!empty($this->config['maxPrice']) && $price > $this->config['maxPrice'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return path('kupshop_ordering_payment_legacypayment', [
|
||||
'step' => 5,
|
||||
'class' => $this->class,
|
||||
'price' => $price,
|
||||
]);
|
||||
}
|
||||
|
||||
public function processStep_5()
|
||||
{
|
||||
$price = roundPrice(getVal('price'), -1, 'DB', 0)->asInteger();
|
||||
|
||||
if ($price < ($this->config['minPrice'] ?? 2000) || (!empty($this->config['maxPrice']) && $price > $this->config['maxPrice'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
redirection($this->getCalculatorUrl($price));
|
||||
}
|
||||
|
||||
protected function getCalculatorUrl($price)
|
||||
{
|
||||
$body = [
|
||||
'price' => $price,
|
||||
'productId' => 0,
|
||||
];
|
||||
|
||||
$response = $this->requestEssoxCurl('/consumergoods/v1/api/consumergoods/calculator', [
|
||||
'Content-Type: application/json',
|
||||
'accept: application/json',
|
||||
'Authorization: Bearer '.$this->getAccessToken(),
|
||||
], json_encode($body));
|
||||
|
||||
$decodedData = json_decode($response, true);
|
||||
|
||||
return $decodedData['redirectionUrl'];
|
||||
}
|
||||
|
||||
protected function requestEssoxCurl(string $path, array $headers, string $encodedBody, $post = true)
|
||||
{
|
||||
$url = $this->getApiUrl().$path;
|
||||
$initTimeout = 30;
|
||||
$step = 1;
|
||||
$ch = curl_init();
|
||||
if ($post) {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedBody);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1);
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
|
||||
while ($step <= 2) {
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, $initTimeout);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$responseInfo = curl_getinfo($ch);
|
||||
$curl_errno = curl_errno($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($responseInfo['http_code'] === 0 && $step === 1) {
|
||||
$initTimeout = 10;
|
||||
$step++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($responseInfo['http_code'] != 200) {
|
||||
$debugInfo = ['url' => $url, 'responseInfo' => $responseInfo, 'curl_errno' => $curl_errno, 'RESPONSE' => $response];
|
||||
$this->handleError($debugInfo);
|
||||
throw new PaymentException('Komunikace s platební bránou selhala. Zkuste prosím znovu později.');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function accept($totalPrice, $freeDelivery)
|
||||
{
|
||||
$price = $totalPrice->getPriceWithVat()->asFloat();
|
||||
if ($price <= 0 && $this->order) {
|
||||
$price = $this->order->total_price;
|
||||
}
|
||||
|
||||
// price has to be greater than 2000 Kč according to the documentation
|
||||
return parent::accept($totalPrice, $freeDelivery) && $price >= 2000;
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getApiUrl(): string
|
||||
{
|
||||
return ($this->config['test'] ?? false) ? $this->apiTestUrl : $this->apiUrl;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getSettingsConfiguration(): array
|
||||
{
|
||||
return [
|
||||
'fields' => [
|
||||
'clientKey' => [
|
||||
'title' => 'ID klienta (client_id)',
|
||||
'type' => 'text',
|
||||
],
|
||||
'clientSecret' => [
|
||||
'title' => 'Secret (clientSecret)',
|
||||
'type' => 'text',
|
||||
],
|
||||
'setPaymentCompletedManually' => [
|
||||
'title' => 'Manuální potvrzení platby',
|
||||
'tooltip' => 'Pokud je vypnuto, platba objednávky se nastaví jako uhrazená automaticky po schválení úvěru. Pokud je zapnuto, přepnutí platby na uhrazenou je potřeba udělat manuálně.',
|
||||
'type' => 'toggle',
|
||||
],
|
||||
'minPrice' => [
|
||||
'title' => 'Minimální částka',
|
||||
'type' => 'number',
|
||||
'placeholder' => 2000,
|
||||
'tooltip' => 'Minimální částka u které tuto možnost zobrazit na detailu produktu. (min. 2000 Kč)',
|
||||
],
|
||||
'maxPrice' => [
|
||||
'title' => 'Maximální částka',
|
||||
'type' => 'number',
|
||||
'tooltip' => 'Maximální částka u které tuto možnost zobrazit na detailu produktu.',
|
||||
],
|
||||
'productDetail' => [
|
||||
'title' => 'Zobrazit na detailu produktu',
|
||||
'type' => 'toggle',
|
||||
],
|
||||
'test' => [
|
||||
'title' => 'Testovací režim',
|
||||
'type' => 'toggle',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function handleError($debugInfo)
|
||||
{
|
||||
$message = $this->getName().': admin info - Komunikace s platební bránou selhala';
|
||||
|
||||
$sentry = false;
|
||||
$csobFault = false;
|
||||
switch ($debugInfo['responseInfo']['http_code'] ?? -1) {
|
||||
case 400:
|
||||
$reason = 'Nevalidní request. V dotazu chybí povinné pole nebo je v
|
||||
nevhodném / nevalidním formátu.
|
||||
Nevalidní uživatel.
|
||||
Neplatné pověření.
|
||||
Uživatel není oprávněný používat autorizační typ';
|
||||
$sentry = true;
|
||||
break;
|
||||
case 401:
|
||||
unset($debugInfo['responseInfo']);
|
||||
$reason = 'Přístup odepřen (špatné přihlašovací údaje)';
|
||||
$message .= ' - '.$reason;
|
||||
break;
|
||||
case 403:
|
||||
$reason = 'Klient není oprávněný provádět tento dotaz.';
|
||||
$sentry = true;
|
||||
break;
|
||||
case 500:
|
||||
$reason = 'Chyba na straně CSOB serveru';
|
||||
$csobFault = true;
|
||||
break;
|
||||
case 503:
|
||||
$reason = 'CSOB služba je dočasně nedostupná. Může být způsobena přetížením serveru nebo z důvodu údržby';
|
||||
$csobFault = true;
|
||||
break;
|
||||
case 0:
|
||||
$reason = 'CSOB API timeout';
|
||||
$csobFault = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$debugInfo = array_merge(['reason' => $reason ?? 'Neznámá chyba'], $debugInfo);
|
||||
|
||||
if ($sentry) {
|
||||
getRaven()->captureMessage('Essox API error', [], ['extra' => $debugInfo]);
|
||||
}
|
||||
if ($csobFault) {
|
||||
$message .= ' - chyba na straně CSOB';
|
||||
}
|
||||
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, $message, $debugInfo);
|
||||
}
|
||||
}
|
||||
14
class/payments/class.GiroCheckoutCrCard.php
Normal file
14
class/payments/class.GiroCheckoutCrCard.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('GiroCheckoutGiropay')) {
|
||||
require_once 'class.GiroCheckoutGiropay.php';
|
||||
}
|
||||
|
||||
class GiroCheckoutCrCard extends GiroCheckoutGiropay
|
||||
{
|
||||
public static $name = 'GiroCheckout Credit Card';
|
||||
|
||||
public $class = 'GiroCheckoutCrCard';
|
||||
|
||||
protected $methodCode = 'creditCardTransaction';
|
||||
}
|
||||
14
class/payments/class.GiroCheckoutEps.php
Normal file
14
class/payments/class.GiroCheckoutEps.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('GiroCheckoutGiropay')) {
|
||||
require_once 'class.GiroCheckoutGiropay.php';
|
||||
}
|
||||
|
||||
class GiroCheckoutEps extends GiroCheckoutGiropay
|
||||
{
|
||||
public static $name = 'GiroCheckout EPS';
|
||||
|
||||
public $class = 'GiroCheckoutEps';
|
||||
|
||||
protected $methodCode = 'epsTransaction';
|
||||
}
|
||||
283
class/payments/class.GiroCheckoutGiropay.php
Normal file
283
class/payments/class.GiroCheckoutGiropay.php
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
use girosolution\GiroCheckout_SDK\GiroCheckout_SDK_Notify;
|
||||
use girosolution\GiroCheckout_SDK\GiroCheckout_SDK_Request;
|
||||
use KupShop\KupShopBundle\Config;
|
||||
|
||||
/**
|
||||
* composer require girosolution/girocheckout-sdk.
|
||||
*
|
||||
* https://github.com/girosolution/girocheckout_sdk
|
||||
* http://api.girocheckout.de/en:girocheckout:giropay:start
|
||||
*/
|
||||
class GiroCheckoutGiropay extends Payment
|
||||
{
|
||||
public static $name = 'GiroCheckout Giropay';
|
||||
|
||||
public $template = 'payment.OmnipayNestpay.tpl';
|
||||
// protected $templateCart = 'payment.GiroCheckout.cart.tpl';
|
||||
|
||||
public $class = 'GiroCheckoutGiropay';
|
||||
|
||||
protected $methodCode = 'giropayTransaction';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
// public function storePaymentInfo()
|
||||
// {
|
||||
// $data = parent::storePaymentInfo();
|
||||
// $data['method'] = explode('-', getVal('payment_id'))[1];
|
||||
//
|
||||
// return $data;
|
||||
// }
|
||||
|
||||
public function getPaymentUrl()
|
||||
{
|
||||
return createScriptURL([
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => 1,
|
||||
'class' => $this->class,
|
||||
]);
|
||||
}
|
||||
|
||||
/* Payment steps */
|
||||
public function processStep_1()
|
||||
{
|
||||
$returnUrl = createScriptURL([
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => 5,
|
||||
'class' => $this->class,
|
||||
'absolute' => true,
|
||||
]);
|
||||
$webhookUrl = createScriptURL([
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => 10,
|
||||
'class' => $this->class,
|
||||
'absolute' => true,
|
||||
]);
|
||||
|
||||
$method = $this->determinePaymentMethod();
|
||||
$methodSpecificConfig = $this->getMethodSpecificConfig($method);
|
||||
|
||||
$amount = roundPrice($this->order->getRemainingPayment())->asFloat();
|
||||
$amountInCents = roundPrice($this->order->getRemainingPayment())->mul(DecimalConstants::hundred())->asInteger();
|
||||
$paymentIDSuffix = uniqid('', true);
|
||||
$this->createPayment(
|
||||
null,
|
||||
$amount,
|
||||
['paymentClass' => static::class, 'method' => $method, 'IDSuffix' => $paymentIDSuffix]
|
||||
);
|
||||
|
||||
// save session (use our identifier that we send as merchantTxId)
|
||||
$session = $this->paymentId.'-'.$paymentIDSuffix;
|
||||
$this->updateCustomPaymentData('session', $session);
|
||||
|
||||
// create request of selected type
|
||||
$request = new GiroCheckout_SDK_Request($method);
|
||||
$request->setSecret($methodSpecificConfig['projectPassphrase']); // String
|
||||
$request->addParam('merchantId', $methodSpecificConfig['merchantID']) // Integer merchant ID of a giropay project
|
||||
->addParam('projectId', $methodSpecificConfig['projectID']) // integer project ID of a giropay project
|
||||
->addParam('merchantTxId', $session) // String(255) unique transaction id of the merchant
|
||||
->addParam('amount', $amountInCents) // Integer if a decimal currency is used, the amount has to be in the smallest unit of value, eg. Cent, Penny
|
||||
->addParam('currency', 'EUR') // String(3)
|
||||
->addParam('purpose', 'Bestellung '.$this->orderId) // String(27)
|
||||
// bic - This parameter must not be used anymore. All giropay transactions now use an external bank selection form!
|
||||
->addParam('urlRedirect', $returnUrl) // URL, where the buyer has to be sent after payment
|
||||
->addParam('urlNotify', $webhookUrl); // URL, where the notification has to be sent after payment
|
||||
|
||||
$request = $this->addRequestParams($request);
|
||||
|
||||
// the hash field is auto generated by the SDK
|
||||
$request->submit();
|
||||
|
||||
if ($request->requestHasSucceeded()) {
|
||||
// save gcReference (giropay request param "reference")
|
||||
$this->updateCustomPaymentData(
|
||||
'gcReference',
|
||||
$request->getResponseParam('reference')
|
||||
);
|
||||
|
||||
$request->redirectCustomerToPaymentProvider();
|
||||
} else {
|
||||
// if the transaction did not succeed, update your local system, get the responsecode and notify the customer
|
||||
throw new Exception($request->getResponseMessage($request->getResponseParam('rc'), 'DE'));
|
||||
}
|
||||
}
|
||||
|
||||
protected function addRequestParams(GiroCheckout_SDK_Request $request): GiroCheckout_SDK_Request
|
||||
{
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $paymentIdentifier kupshopPaymentID-uniqueSuffix
|
||||
*/
|
||||
private function getPaymentByUniqueIdentifier(string $paymentIdentifier, bool $ignoreStatus = false): ?array
|
||||
{
|
||||
foreach ($this->order->getPaymentsArray() as $payment) {
|
||||
$paymentData = json_decode($payment['payment_data']);
|
||||
if (($payment['status'] == static::STATUS_CREATED || $payment['status'] == static::STATUS_PENDING || $ignoreStatus)
|
||||
&& isset($paymentData->paymentClass)
|
||||
&& $paymentData->paymentClass === static::class
|
||||
&& $payment['id'] == explode('-', $paymentIdentifier)[0]
|
||||
&& $paymentData->IDSuffix == (explode('-', $paymentIdentifier)[1] ?? null)
|
||||
) {
|
||||
$payment['decoded_data'] = $paymentData;
|
||||
|
||||
return $payment;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function updateCustomPaymentData($keyName, $value)
|
||||
{
|
||||
$paymentRow = sqlQueryBuilder()->select('*')->from('order_payments')
|
||||
->where(\Query\Operator::equals(['id' => $this->paymentId]))->execute()->fetch();
|
||||
$data = json_decode($paymentRow['payment_data'] ?? '{}');
|
||||
$data->$keyName = $value;
|
||||
sqlQueryBuilder()->update('order_payments')
|
||||
->directValues(['payment_data' => json_encode($data)])
|
||||
->where(\Query\Operator::equals(['id' => $this->paymentId]))->execute();
|
||||
}
|
||||
|
||||
public function processStep_5()
|
||||
{
|
||||
$method = $this->determinePaymentMethod();
|
||||
$methodSpecificConfig = $this->getMethodSpecificConfig($method);
|
||||
|
||||
$notify = new GiroCheckout_SDK_Notify($method);
|
||||
$notify->setSecret($methodSpecificConfig['projectPassphrase']);
|
||||
$notify->parseNotification($_GET);
|
||||
|
||||
$payment = $this->getPaymentByUniqueIdentifier($notify->getResponseParam('gcMerchantTxId'), true);
|
||||
if (is_null($payment) || $payment['status'] == static::STATUS_STORNO) {
|
||||
$this->step(-3, 'storno');
|
||||
} elseif ($payment['status'] == static::STATUS_FINISHED) {
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
}
|
||||
|
||||
$this->paymentId = $payment['id'];
|
||||
$this->saveTransactionData($notify, $payment);
|
||||
|
||||
if ($notify->paymentSuccessful()) {
|
||||
// change payment status to finished
|
||||
if (!$this->setStatus(Payment::STATUS_FINISHED, $payment['decoded_data']->session)) {
|
||||
throw new Exception('GiroCheckout::setStatus failed!');
|
||||
}
|
||||
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
} else {
|
||||
if (!$this->setStatus(Payment::STATUS_STORNO, $payment['decoded_data']->session)) {
|
||||
throw new Exception('GiroCheckout::setStatus failed!');
|
||||
}
|
||||
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GiroPurchase webhook handler - check for approved payment.
|
||||
*/
|
||||
public function processStep_10()
|
||||
{
|
||||
$this->setIsNotification(true);
|
||||
$method = $this->determinePaymentMethod();
|
||||
$methodSpecificConfig = $this->getMethodSpecificConfig($method);
|
||||
|
||||
$notify = new GiroCheckout_SDK_Notify($method);
|
||||
$notify->setSecret($methodSpecificConfig['projectPassphrase']);
|
||||
$notify->parseNotification($_GET);
|
||||
|
||||
$payment = $this->getPaymentByUniqueIdentifier($notify->getResponseParam('gcMerchantTxId'));
|
||||
if (is_null($payment)) {
|
||||
// send #400: The merchant did not process the notification and does not wish to be notified again.
|
||||
$notify->sendBadRequestStatus();
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->paymentId = $payment['id'];
|
||||
$this->saveTransactionData($notify, $payment);
|
||||
|
||||
if ($notify->paymentSuccessful()) {
|
||||
// change payment status to finished
|
||||
if (!$this->setStatus(Payment::STATUS_FINISHED, $payment['decoded_data']->session)) {
|
||||
throw new Exception('GiroCheckout::setStatus failed!');
|
||||
}
|
||||
|
||||
// send #200: The notification was processed correctly.
|
||||
$notify->sendOkStatus();
|
||||
exit;
|
||||
} else {
|
||||
if (!$this->setStatus(Payment::STATUS_STORNO, $payment['decoded_data']->session)) {
|
||||
throw new Exception('GiroCheckout::setStatus failed!');
|
||||
}
|
||||
|
||||
// send #200: The notification was processed correctly.
|
||||
$notify->sendOkStatus();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// public function getAvailableMethods(): array
|
||||
// {
|
||||
// $availableMethods = [];
|
||||
// foreach ($this->config['methods'] as $requestType => $methodConfig) {
|
||||
// $availableMethods[$requestType] = ['name' => $methodConfig['name'] ?? $requestType];
|
||||
// }
|
||||
//
|
||||
// return $availableMethods;
|
||||
// }
|
||||
|
||||
/**
|
||||
* determine payment method (request type) with fallback to first available method.
|
||||
*/
|
||||
private function determinePaymentMethod(): string
|
||||
{
|
||||
// $method = $this->order->getDataAll()['payment_data']['method'] ?? null;
|
||||
// $methods = $this->getAvailableMethods();
|
||||
// reset($methods);
|
||||
//
|
||||
// return isset($methods[$method]) ? $method : key($methods);
|
||||
return $this->methodCode;
|
||||
}
|
||||
|
||||
private function getMethodSpecificConfig(?string $method = null): array
|
||||
{
|
||||
// return $this->config['methods'][$method ?? $this->determinePaymentMethod()];
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
private function saveTransactionData(GiroCheckout_SDK_Notify $notify, array $payment): void
|
||||
{
|
||||
// save transaction reference ID and result code
|
||||
$json = $payment['decoded_data'];
|
||||
$json->resultCode = $notify->getResponseParam('gcResultPayment'); // http://api.girocheckout.de/en:girocheckout:resultcodes
|
||||
$this->updateSQL('order_payments', ['payment_data' => json_encode($json)], ['id' => $payment['id']]);
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
40
class/payments/class.GiroCheckoutPaydirek.php
Normal file
40
class/payments/class.GiroCheckoutPaydirek.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use girosolution\GiroCheckout_SDK\GiroCheckout_SDK_Request;
|
||||
|
||||
if (!class_exists('GiroCheckoutGiropay')) {
|
||||
require_once 'class.GiroCheckoutGiropay.php';
|
||||
}
|
||||
|
||||
class GiroCheckoutPaydirek extends GiroCheckoutGiropay
|
||||
{
|
||||
public static $name = 'GiroCheckout Paydirekt';
|
||||
|
||||
public $class = 'GiroCheckoutPaydirek';
|
||||
|
||||
protected $methodCode = 'paydirektTransaction';
|
||||
|
||||
public function accept($totalPrice, $freeDelivery)
|
||||
{
|
||||
$totalPrice = $totalPrice->getPriceWithVat()->asFloat();
|
||||
if ($totalPrice <= 0 && $this->order) {
|
||||
$totalPrice = $this->order->total_price;
|
||||
}
|
||||
|
||||
// max 50000 EUR (http://api.girocheckout.de/girocheckout:paydirekt:start#initialisierung_einer_paydirekt_zahlung)
|
||||
return parent::accept($totalPrice, $freeDelivery) && $totalPrice < 50000;
|
||||
}
|
||||
|
||||
protected function addRequestParams(GiroCheckout_SDK_Request $request): GiroCheckout_SDK_Request
|
||||
{
|
||||
$request->addParam('shippingAddresseFirstName', $this->order->delivery_name) // Vorname des Addressaten, Pflicht bei Warenkorbtypen PHYSICAL, DIGITAL und MIXED, optional bei ANONYMOUS_DONATION und AUTHORITIES_PAYMENT.
|
||||
->addParam('shippingAddresseLastName', $this->order->delivery_surname) // Nachname des Addressaten, Pflicht bei Warenkorbtypen PHYSICAL, DIGITAL und MIXED, optional bei ANONYMOUS_DONATION und AUTHORITIES_PAYMENT.
|
||||
->addParam('shippingZipCode', $this->order->delivery_zip) // PLZ des Addressaten. Dies ist Pflicht bei Warenkörben der Typen PHYSICAL und MIXED, optional bei DIGITAL, ANONYMOUS_DONATION und AUTHORITIES_PAYMENT.
|
||||
->addParam('shippingCity', $this->order->delivery_city) // Ort des Addressaten. Dies ist Pflicht bei Warenkörben der Typen PHYSICAL und MIXED, optional bei DIGITAL, ANONYMOUS_DONATION und AUTHORITIES_PAYMENT.
|
||||
->addParam('shippingCountry', $this->order->delivery_country) // Ländercode (ISO 3166-1). Dies ist Pflicht bei Warenkörben der Typen PHYSICAL und MIXED, optional bei DIGITAL, ANONYMOUS_DONATION und AUTHORITIES_PAYMENT.
|
||||
->addParam('shippingEmail', $this->order->invoice_email) // Email-Adresse des Käufers. Dies ist Pflicht bei digitalen Warenkörben (DIGITAL), bei allen anderen optional.
|
||||
->addParam('orderId', $this->orderId); // Bestellnummer, zulässige Zeichen: A-Z a-z 0-9 + ? / - : ( ) . , ' (KEINE Blanks), maximale Länge: 20
|
||||
|
||||
return $request;
|
||||
}
|
||||
}
|
||||
596
class/payments/class.GoPay.php
Normal file
596
class/payments/class.GoPay.php
Normal file
@@ -0,0 +1,596 @@
|
||||
<?php
|
||||
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\KupShopBundle\Config;
|
||||
use KupShop\KupShopBundle\Context\CurrencyContext;
|
||||
use KupShop\KupShopBundle\Context\DomainContext;
|
||||
use KupShop\KupShopBundle\Context\LanguageContext;
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
use KupShop\KupShopBundle\Util\EANValidator;
|
||||
use KupShop\OrderingBundle\Exception\PaymentException;
|
||||
use KupShop\OrderingBundle\Util\Order\PaymentOrderSubMethodTrait;
|
||||
|
||||
/**
|
||||
* !!!! POZOR !!!!
|
||||
* Pokud se nasazuje na betu testovací GoPay, je potřeba mít v configu test => 1, aby to na nasazené betě fungovalo.
|
||||
* Jinak v košíku neuvidíte žádné jejich platby - gopay odmítne spojení ostrého gopay serveru s testovacími údaji.
|
||||
* A Nedivit se, na lokale ať je cokoliv nastavené, přetluče to config_db, kde jsou testovací údaje z KOZA. *
|
||||
* !!!! POZOR !!!!
|
||||
*/
|
||||
class GoPay extends Payment
|
||||
{
|
||||
use PaymentOrderSubMethodTrait;
|
||||
|
||||
public const BAD_STATE_CODE = '303';
|
||||
|
||||
public static $name = 'GoPay platební brána';
|
||||
|
||||
public static bool $canAutoReturn = true;
|
||||
|
||||
public $template = 'payment.GoPay.tpl';
|
||||
|
||||
protected $templateCart = 'payment.GoPay.cart.tpl';
|
||||
|
||||
protected $templateOrderView = 'payment.GoPay.orderView.tpl';
|
||||
|
||||
public $class = 'GoPay';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
private $apiContext;
|
||||
|
||||
private $gatewayUrl;
|
||||
|
||||
public static function getSettingsConfiguration(): array
|
||||
{
|
||||
return [
|
||||
'fields' => [
|
||||
'goid' => [
|
||||
'title' => 'GoID',
|
||||
'type' => 'text',
|
||||
],
|
||||
'clientId' => [
|
||||
'title' => 'ClientID',
|
||||
'type' => 'text',
|
||||
],
|
||||
'clientSecret' => [
|
||||
'title' => 'ClientSecret',
|
||||
'type' => 'text',
|
||||
],
|
||||
'test' => [
|
||||
'title' => 'Testovací režim',
|
||||
'type' => 'toggle',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/** Get payment icon url and name.
|
||||
* @return array|null
|
||||
*/
|
||||
public function getIcon()
|
||||
{
|
||||
$method = getVal($this->method, $this->getAvailableMethods());
|
||||
if ($method) {
|
||||
return [
|
||||
'url' => $method['image'],
|
||||
'name' => $method['name'],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getPaymentUrl(int $step = 1): string
|
||||
{
|
||||
return path('kupshop_ordering_payment_payment', [
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => $step,
|
||||
'class' => $this->class,
|
||||
], \Symfony\Component\Routing\Router::ABSOLUTE_URL);
|
||||
}
|
||||
|
||||
public function getGatewayUrl(): string
|
||||
{
|
||||
if (!isset($this->gatewayUrl)) {
|
||||
$this->processStep_1(false, false);
|
||||
}
|
||||
|
||||
return $this->gatewayUrl;
|
||||
}
|
||||
|
||||
/* Payment steps */
|
||||
public function processStep_1($enableWaitRedirect = true, $enableRedirects = true)
|
||||
{
|
||||
$apiContext = $this->getApiContext();
|
||||
|
||||
if (empty($this->method)) {
|
||||
$paymentData = $this->order->getData('payment_data');
|
||||
if (!$paymentData) {
|
||||
$paymentData = ['method' => 'PAYMENT_CARD'];
|
||||
}
|
||||
$this->loadPaymentInfo($paymentData);
|
||||
}
|
||||
$tmpMethod = explode('__', $this->method);
|
||||
$defaultPaymentInstrument = $tmpMethod[0];
|
||||
$defaultSwift = isset($tmpMethod[1]) ? $tmpMethod[1] : null;
|
||||
$availableInstruments = [];
|
||||
$availableSwifts = [];
|
||||
|
||||
// test gopay (maybe live too?) might return swifts that triggers error 111
|
||||
// 'BACXCZPP', 'AGBACZPP', 'AIRACZPP', 'EQBKCZPP', 'CITICZPX', 'INGBCZPP', 'EXPNCZPP', 'OBKLCZ2X', 'VBOECZ2X', 'SUBACZPP'
|
||||
// maybe check against $usableSwifts = array_values(
|
||||
// (new ReflectionClass(\GoPay\Definition\Payment\BankSwiftCode::class))
|
||||
// ->getConstants()
|
||||
// );
|
||||
|
||||
foreach ($this->getAvailableMethods() as $methodID => $method) {
|
||||
$tmpMethod = explode('__', $methodID);
|
||||
if (!in_array($tmpMethod[0], $availableInstruments)) {
|
||||
$availableInstruments[] = $tmpMethod[0];
|
||||
}
|
||||
if (isset($tmpMethod[1]) && !in_array($tmpMethod[1], $availableSwifts)) {
|
||||
$availableSwifts[] = $tmpMethod[1];
|
||||
}
|
||||
}
|
||||
|
||||
$rawPaymentRequest = [
|
||||
'payer' => [
|
||||
'default_payment_instrument' => $defaultPaymentInstrument,
|
||||
'default_swift' => $defaultSwift,
|
||||
'contact' => [
|
||||
'first_name' => $this->order->invoice_name,
|
||||
'last_name' => $this->order->invoice_surname,
|
||||
'email' => $this->order->invoice_email,
|
||||
'phone_number' => $this->order->invoice_phone,
|
||||
'city' => $this->order->invoice_city,
|
||||
'street' => $this->order->invoice_street,
|
||||
'postal_code' => $this->order->invoice_zip,
|
||||
'country_code' => (new \League\ISO3166\ISO3166())->alpha2(
|
||||
!empty($this->order->invoice_country)
|
||||
? $this->order->invoice_country
|
||||
: (!empty($this->order->delivery_country) ? $this->order->delivery_country : 'CZ')
|
||||
)['alpha3'],
|
||||
],
|
||||
],
|
||||
'amount' => $amount = toDecimal($this->order->getRemainingPayment())->mul(DecimalConstants::hundred())->asFloat(),
|
||||
'currency' => $this->order->getCurrency(),
|
||||
'order_number' => $this->getOrderNumber(),
|
||||
'items' => [],
|
||||
'callback' => [
|
||||
'return_url' => $this->getPaymentUrl(2),
|
||||
'notification_url' => $this->getPaymentUrl(10),
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($this->order->fetchItems() as $item) {
|
||||
$tmpItem = [
|
||||
'type' => is_null($item['id_product']) ? 'DELIVERY' : 'ITEM',
|
||||
'name' => $item['descr'],
|
||||
'amount' => $item['total_price']['value_with_vat']->mul(DecimalConstants::hundred())->asInteger(),
|
||||
'count' => (int) $item['pieces'],
|
||||
];
|
||||
if (isset($item['ean']) && EANValidator::checkEAN($item['ean'])) {
|
||||
$tmpItem['ean'] = $item['ean'];
|
||||
}
|
||||
if (isset($item['product'])) {
|
||||
$tmpItem['product_url'] = createScriptURL([
|
||||
'absolute' => true,
|
||||
's' => 'product',
|
||||
'IDproduct' => $item['id_product'],
|
||||
'TITLE' => $item['product']->title,
|
||||
]).(!empty($item['id_variation']) ? '#'.$item['id_variation'] : '');
|
||||
}
|
||||
$rawPaymentRequest['items'][] = $tmpItem;
|
||||
}
|
||||
|
||||
$response = $apiContext->createPayment($rawPaymentRequest);
|
||||
|
||||
if ($response->hasSucceed()) {
|
||||
$this->createPayment(
|
||||
$response->json['id'],
|
||||
Decimal::fromInteger($response->json['amount'])
|
||||
->mul(Decimal::fromFloat(0.01))->asFloat(),
|
||||
['paymentClass' => self::class]
|
||||
);
|
||||
$this->gatewayUrl = $response->json['gw_url'];
|
||||
} else {
|
||||
$jsonErrors = json_decode($response->rawBody, true);
|
||||
$errorMessage = [];
|
||||
foreach ($jsonErrors['errors'] ?? [] as $error) {
|
||||
$errorMessage[] = $error['message'];
|
||||
}
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION,
|
||||
'GoPay chyba: '.implode(', ', $errorMessage).' Objednávka '.$this->order->order_no,
|
||||
['order' => "Kód objednávky {$this->order->order_no} (ID: {$this->order->id})",
|
||||
'request' => json_encode($rawPaymentRequest),
|
||||
'response' => (string) $response]);
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GoPay return from gateway.
|
||||
*/
|
||||
public function processStep_2()
|
||||
{
|
||||
$this->checkPaymentStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waiting for approval - current payment status is created.
|
||||
*/
|
||||
public function processStep_3()
|
||||
{
|
||||
$this->checkPaymentStatus(false);
|
||||
|
||||
$this->info(translate('payment_waiting_for_confirmation', 'payment'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change GoPay payment method.
|
||||
*/
|
||||
public function processStep_4()
|
||||
{
|
||||
$kupshopPayment = $this->getPendingPayment();
|
||||
if ($kupshopPayment) {
|
||||
// storno pending payment so new one can be created
|
||||
$this->setStatus(Payment::STATUS_STORNO, $kupshopPayment['decoded_data']->session);
|
||||
}
|
||||
$this->step(1, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* GoPay webhook handler.
|
||||
*/
|
||||
public function processStep_10()
|
||||
{
|
||||
$this->setIsNotification(true);
|
||||
$this->checkPaymentStatus();
|
||||
$this->sendNotificationResponse(200, 'OK');
|
||||
}
|
||||
|
||||
private function checkPaymentStatus(
|
||||
$enableWaitRedirect = true,
|
||||
$goPayPaymentID = false,
|
||||
$enableStepOne = false,
|
||||
) {
|
||||
if (!$goPayPaymentID) {
|
||||
$goPayPaymentID = getVal('id', null, false);
|
||||
}
|
||||
if (!$goPayPaymentID) {
|
||||
$this->error(translate('payment_id_missing', 'payment'));
|
||||
}
|
||||
|
||||
$response = $this->getApiContext()->getStatus($goPayPaymentID);
|
||||
if (!$response->hasSucceed()) {
|
||||
$this->error(translate('payment_status_check_error', 'payment'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$goPayPaymentState = $response->json['state'];
|
||||
|
||||
// determine kupshop unified payment state from GoPay payment state
|
||||
if (in_array($goPayPaymentState, [
|
||||
'CREATED', 'AUTHORIZED',
|
||||
])
|
||||
) {
|
||||
$this->status = $unifiedState = Payment::STATUS_CREATED;
|
||||
if ($enableStepOne) {
|
||||
$this->gatewayUrl = $response->json['gw_url'];
|
||||
$this->step = 1;
|
||||
|
||||
return;
|
||||
}
|
||||
} elseif ($goPayPaymentState === 'PAYMENT_METHOD_CHOSEN') {
|
||||
$this->status = $unifiedState = Payment::STATUS_PENDING;
|
||||
// customer might have returned from the gateway without success/failure
|
||||
// set gatewayUrl so it can be used in order detail
|
||||
$this->gatewayUrl = $response->json['gw_url'];
|
||||
} elseif (in_array($goPayPaymentState, ['PAID'])) {
|
||||
$this->status = $unifiedState = Payment::STATUS_FINISHED;
|
||||
} elseif (in_array($goPayPaymentState, [
|
||||
'CANCELED', 'TIMEOUTED', 'REFUNDED', 'PARTIALLY_REFUNDED',
|
||||
])
|
||||
) {
|
||||
$this->status = $unifiedState = Payment::STATUS_STORNO;
|
||||
} else {
|
||||
logError(__FILE__, __LINE__, 'GoPay unexpected payment state "'.$goPayPaymentState.'"');
|
||||
$this->error(translate('payment_unexpected_status', 'payment'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// change payment status
|
||||
if (!$this->setStatus($unifiedState, $goPayPaymentID)) {
|
||||
logError(__FILE__, __LINE__, 'PayPal::updatePaymentStatus: setStatus failed!');
|
||||
throw new \Exception('Set status failed');
|
||||
}
|
||||
|
||||
$paymentInstrument = $response->json['payment_instrument'] ?? null;
|
||||
if ($paymentInstrument && $paymentInstrument != ($response->json['payer']['default_payment_instrument'] ?? null)) {
|
||||
$this->setPaymentSubMethod($paymentInstrument);
|
||||
}
|
||||
|
||||
switch ($unifiedState) {
|
||||
case Payment::STATUS_FINISHED:
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
break;
|
||||
case Payment::STATUS_STORNO:
|
||||
$this->info(translate('payment_storno', 'payment'));
|
||||
break;
|
||||
case Payment::STATUS_PENDING:
|
||||
case Payment::STATUS_CREATED:
|
||||
if ($enableWaitRedirect) {
|
||||
$this->step(3, 'wait', ['id' => $goPayPaymentID]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return GoPay\Payments
|
||||
*/
|
||||
public function getApiContext()
|
||||
{
|
||||
if (!isset($this->apiContext)) {
|
||||
if (isset($this->config['language'])) {
|
||||
$language = $this->config['language'];
|
||||
} else {
|
||||
$languageContext = ServiceContainer::getService(LanguageContext::class);
|
||||
// see \GoPay\Definition\Language
|
||||
$availableLangs = [
|
||||
'cs' => 'CS', 'en' => 'EN', 'sk' => 'SK', 'de' => 'DE', 'ru' => 'RU', 'pl' => 'PL',
|
||||
'hu' => 'HU', 'fr' => 'FR', 'ro' => 'RO', 'bg' => 'BG', 'hr' => 'HR', 'it' => 'IT',
|
||||
'es' => 'ES', 'at' => 'DE',
|
||||
];
|
||||
$language = $availableLangs[$languageContext->getActiveId()] ?? \GoPay\Definition\Language::CZECH;
|
||||
}
|
||||
$apiContext = GoPay\payments([
|
||||
'goid' => $this->config['goid'],
|
||||
'clientId' => $this->config['clientId'],
|
||||
'clientSecret' => $this->config['clientSecret'],
|
||||
'isProductionMode' => empty($this->config['test']),
|
||||
'scope' => GoPay\Definition\TokenScope::ALL,
|
||||
'language' => $language,
|
||||
'timeout' => isset($this->config['timeout']) ? $this->config['timeout'] : 30,
|
||||
'gatewayUrl' => empty($this->config['test']) ? 'https://gate.gopay.cz/api/' : 'https://gw.sandbox.gopay.com/api/',
|
||||
]);
|
||||
$this->apiContext = $apiContext;
|
||||
}
|
||||
|
||||
return $this->apiContext;
|
||||
}
|
||||
|
||||
public function getAvailableMethods()
|
||||
{
|
||||
$currencyContext = ServiceContainer::getService(CurrencyContext::class);
|
||||
$currency = $currencyContext->getActiveId();
|
||||
$domainContext = ServiceContainer::getService(DomainContext::class);
|
||||
$domain = $domainContext->getActiveId();
|
||||
$languageContext = ServiceContainer::getService(LanguageContext::class);
|
||||
$language = $languageContext->getActiveId();
|
||||
$cacheKey = "gopay-methods-{$currency}-{$domain}-{$language}";
|
||||
if (!($methods = getCache($cacheKey))) {
|
||||
$methods = $this->fetchAvailableMethods($currency);
|
||||
setCache($cacheKey, $methods);
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
public function fetchAvailableMethods($currency)
|
||||
{
|
||||
/**
|
||||
* Sračka GoPay vrací rychlý převody společně s offline platbama (obyč převod, kde se jen napíše číslo účtu a vs)
|
||||
* To jestli je metoda rychlej převod nebo offline se rozlišuje přes "isOnline"
|
||||
* My zobrazujeme na eshopu jen ty isOnline=true, protože jinak tam je 15 zbytečnejch metod úplně k prdu
|
||||
* Metoda "Rychlý bankovní převod" je kec, jsou tam tedy i pomalý offline převody
|
||||
* Když nejsou povolený offline převody, můžu "Rychlý bankovní převod" smazat, není k ničemu, protože online se zobrazí samostatně
|
||||
* V Adminu to maj úplně dementní. Nedaj se vypínat jednotlivý metody, jen jestli je daná banka online nebo offline.
|
||||
* Naprosto nepoužitelnou metody GoPay která nejde vypnout se pokouším hodit dozadu, stejně tak "Offline" bankovní převody, protože jsou k ničemu.
|
||||
*/
|
||||
$apiContext = $this->getApiContext();
|
||||
$response = $apiContext->getPaymentInstruments($this->config['goid'], $currency);
|
||||
if (!$response->hasSucceed()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$methods = [];
|
||||
$offlinePayment = false;
|
||||
|
||||
foreach ((array) $response->json['enabledPaymentInstruments'] as $method) {
|
||||
if (in_array($method['paymentInstrument'], $this->config['ignoreInstruments'] ?? [])) {
|
||||
continue;
|
||||
}
|
||||
$methods[$method['paymentInstrument']] = [
|
||||
'name' => translate_shop($method['paymentInstrument'], 'gopay', true) ?: $method['label']['cs'],
|
||||
'id' => $method['paymentInstrument'],
|
||||
'image' => $method['image']['large'],
|
||||
];
|
||||
if (empty($method['enabledSwifts']) || !is_array($method['enabledSwifts'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($method['enabledSwifts'] as $swift) {
|
||||
if ($currency === 'PLN' && $swift['swift'] === 'DNBANOKK') {
|
||||
continue; // skip (GoPay did not accept this swift)
|
||||
}
|
||||
if (!$swift['isOnline']) {
|
||||
$offlinePayment = $method['paymentInstrument'];
|
||||
continue; // skip offline payments, show one method instead of 15
|
||||
}
|
||||
$methods[$method['paymentInstrument'].'__'.$swift['swift']] = [
|
||||
'name' => $swift['label']['cs'],
|
||||
'id' => $method['paymentInstrument'].'__'.$swift['swift'],
|
||||
'image' => $swift['image']['large'],
|
||||
];
|
||||
}
|
||||
|
||||
if (!$offlinePayment) {
|
||||
unset($methods[$method['paymentInstrument']]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($offlinePayment) {
|
||||
$method = $methods[$offlinePayment];
|
||||
unset($methods[$offlinePayment]);
|
||||
$methods[$offlinePayment] = $method;
|
||||
}
|
||||
|
||||
if (isset($methods['GOPAY'])) {
|
||||
$method = $methods['GOPAY'];
|
||||
unset($methods['GOPAY']);
|
||||
$methods['GOPAY'] = $method;
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
public function getEmbedScriptUrl()
|
||||
{
|
||||
return $this->getApiContext()->urlToEmbedJs();
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PaymentException
|
||||
*/
|
||||
public function doReturnPayment(array $payment, float $amount): array
|
||||
{
|
||||
$amountCents = (int) floor($amount * -100); // musí být integer v centech
|
||||
$paymentId = $payment['payment_data']['session'];
|
||||
|
||||
$refundResponse = $this->getApiContext()->refundPayment($paymentId, $amountCents);
|
||||
$this->kibanaLogger->notice('[GoPay] doReturnPayment response', [
|
||||
'order_id' => $payment['id_order'],
|
||||
'payment_id' => $paymentId,
|
||||
'amountCents' => $amountCents,
|
||||
'refund_response_status' => $refundResponse->statusCode,
|
||||
'refund_response' => $refundResponse->__toString(),
|
||||
]);
|
||||
if (!$refundResponse->hasSucceed()) {
|
||||
$message = translate('returnFailed', 'orderPayment');
|
||||
$errors = $refundResponse->json['errors'] ?? [];
|
||||
$error = reset($errors);
|
||||
|
||||
if ($error['error_code'] == self::BAD_STATE_CODE && $this->isPaymentRefunded($paymentId)) {
|
||||
$result = [
|
||||
'id' => $paymentId,
|
||||
'result' => 'FINISHED',
|
||||
];
|
||||
}
|
||||
|
||||
if (!isset($result)) {
|
||||
if (!empty($error['message'])) {
|
||||
$message .= translate('returnFailedMessage', 'orderPayment').$error['message'];
|
||||
}
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, $message,
|
||||
['amount' => $amount, 'payment_id' => $paymentId, 'id_order' => $payment['id_order'], 'RESPONSE' => $refundResponse]);
|
||||
|
||||
throw new PaymentException($message);
|
||||
}
|
||||
} else {
|
||||
$result = (array) $refundResponse->json;
|
||||
}
|
||||
|
||||
switch ($result['result']) {
|
||||
case 'FAILED':
|
||||
$message = translate('returnFailed', 'orderPayment');
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, $message,
|
||||
['amount' => $amount, 'payment_id' => $paymentId, 'id_order' => $payment['id_order'], 'RESPONSE' => $refundResponse]);
|
||||
|
||||
throw new PaymentException($message);
|
||||
case 'ACCEPT':
|
||||
case 'FINISHED':
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check paid orders (and update orders.status_payed).
|
||||
*/
|
||||
public function checkPaidOrders()
|
||||
{
|
||||
if (getVal('test', $this->config)) {
|
||||
return false;
|
||||
}
|
||||
$context = $this->getApiContext();
|
||||
$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))')
|
||||
->execute();
|
||||
foreach ($orderPayments as $orderPayment) {
|
||||
$paymentData = json_decode($orderPayment['payment_data'], true);
|
||||
if ($paymentData['paymentClass'] !== 'GoPay' || empty($paymentData['session'])) {
|
||||
continue;
|
||||
}
|
||||
// https://doc.gopay.com/cs/?lang=php#stav-platby
|
||||
$response = $context->getStatus($paymentData['session']);
|
||||
if (!$response->hasSucceed() || empty($response->json['state'])) {
|
||||
continue;
|
||||
}
|
||||
if ($response->json['state'] === 'PAID') {
|
||||
$this->setOrder($orderPayment['id_order']);
|
||||
try {
|
||||
$this->setStatus(Payment::STATUS_FINISHED, $paymentData['session']);
|
||||
} catch (\KupShop\KupShopBundle\Exception\RedirectException $e) {
|
||||
throw $e;
|
||||
} catch (Exception $e) {
|
||||
getRaven()->captureException($e);
|
||||
}
|
||||
} elseif ($response->json['state'] === 'CANCELED' || $response->json['state'] === 'TIMEOUTED') {
|
||||
$this->setOrder($orderPayment['id_order']);
|
||||
try {
|
||||
$this->setStatus(Payment::STATUS_STORNO, $paymentData['session']);
|
||||
} catch (\KupShop\KupShopBundle\Exception\RedirectException $e) {
|
||||
throw $e;
|
||||
} catch (Exception $e) {
|
||||
getRaven()->captureException($e);
|
||||
}
|
||||
}
|
||||
// print_r(['id' => $paymentData['session'], 'state' => $response->json['state']]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function isPaymentRefunded($paymentId): bool
|
||||
{
|
||||
$statusResponse = $this->getApiContext()->getStatus($paymentId);
|
||||
|
||||
if (!$statusResponse->hasSucceed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$status = $statusResponse->json;
|
||||
|
||||
return $status['state'] == 'REFUNDED';
|
||||
}
|
||||
}
|
||||
154
class/payments/class.HelloBank.php
Normal file
154
class/payments/class.HelloBank.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
use KupShop\KupShopBundle\Config;
|
||||
|
||||
class HelloBank extends Payment
|
||||
{
|
||||
public static $name = 'HelloBank';
|
||||
|
||||
public $template = 'payment.HelloBank.tpl';
|
||||
|
||||
protected $templateOrderView = 'payment.HelloBank.orderView.tpl';
|
||||
|
||||
public $class = 'HelloBank';
|
||||
|
||||
protected $pay_method = Payment::METHOD_INSTALLMENTS;
|
||||
|
||||
protected $url = 'https://www.cetelem.cz/cetelem2_webshop.php/zadost-o-pujcku/on-line-zadost-o-pujcku';
|
||||
|
||||
public function getCalcUrl(Decimal $price)
|
||||
{
|
||||
$price = roundPrice($price, -1, 'DB', 0)->asInteger();
|
||||
|
||||
if ($price < 2000) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return path('hellobank_calc', ['price' => $price]);
|
||||
}
|
||||
|
||||
public function getPaymentUrl(int $step = 1): string
|
||||
{
|
||||
return createScriptURL([
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => $step,
|
||||
'class' => $this->class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getGatewayUrl()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
return [
|
||||
'kodProdejce' => $this->config['kodProdejce'],
|
||||
'cenaZbozi' => roundPrice($this->order->getRemainingPaymentInCZK(), -1, 'DB', 0)->asInteger(),
|
||||
'calc' => '1',
|
||||
'url_back_ok' => createScriptURL([
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => 5,
|
||||
'class' => $this->class,
|
||||
'absolute' => true,
|
||||
]),
|
||||
'url_back_ko' => createScriptURL([
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => 6,
|
||||
'class' => $this->class,
|
||||
'absolute' => true,
|
||||
]),
|
||||
'obj' => $this->orderId,
|
||||
'numklient' => $this->order->id_user,
|
||||
'doprava' => '1', // $this->order->getDeliveryType()->isInPerson() ? '0' : '1',
|
||||
];
|
||||
}
|
||||
|
||||
public function accept($totalPrice, $freeDelivery)
|
||||
{
|
||||
$totalPrice = $totalPrice->getPriceWithVat()->asFloat();
|
||||
if ($totalPrice <= 0 && $this->order) {
|
||||
$totalPrice = $this->order->total_price;
|
||||
}
|
||||
|
||||
return parent::accept($totalPrice, $freeDelivery) && $totalPrice >= 2000;
|
||||
}
|
||||
|
||||
public function processStep_1()
|
||||
{
|
||||
}
|
||||
|
||||
public function processStep_5()
|
||||
{
|
||||
// success - authorized
|
||||
$this->processResult(true);
|
||||
$this->success('Žádost o úvěr byla schválena');
|
||||
}
|
||||
|
||||
public function processStep_6()
|
||||
{
|
||||
// not authorized yet
|
||||
$this->processResult(false);
|
||||
$this->error('Žádost o úvěr zatím nebyla autorizována');
|
||||
}
|
||||
|
||||
protected function processResult(bool $ok = false)
|
||||
{
|
||||
$remainingPayment = roundPrice($this->order->getRemainingPayment())->asFloat();
|
||||
if ($remainingPayment > 0.00) {
|
||||
$session = getVal('numwrk').'_'.uniqid();
|
||||
$this->createPayment(
|
||||
$session,
|
||||
$remainingPayment,
|
||||
['paymentClass' => self::class]
|
||||
);
|
||||
|
||||
// save return params
|
||||
$returnParams = ['stav', 'numaut', 'vdr', 'numwrk', 'jmeno', 'prijmeni', 'splatka', 'numklient', 'obj'];
|
||||
$paymentRow = sqlQueryBuilder()->select('*')->from('order_payments')
|
||||
->where(\Query\Operator::equals(['id' => $this->paymentId]))->execute()->fetch();
|
||||
$data = json_decode($paymentRow['payment_data'] ?? '{}');
|
||||
$data->returnParams = [];
|
||||
foreach ($returnParams as $param) {
|
||||
$data->returnParams[$param] = getVal($param);
|
||||
}
|
||||
sqlQueryBuilder()->update('order_payments')->directValues(['payment_data' => json_encode($data)])
|
||||
->where(\Query\Operator::equals(['id' => $this->paymentId]))->execute();
|
||||
|
||||
$paymentStatus = $ok ? Payment::STATUS_FINISHED : Payment::STATUS_PENDING;
|
||||
// change payment status
|
||||
if (!$this->setStatus($paymentStatus, $session)) {
|
||||
logError(__FILE__, __LINE__, 'PayPal::updatePaymentStatus: setStatus failed!');
|
||||
throw new \Exception('Set status failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function startPayment()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
21
class/payments/class.HeurekaPayment.php
Normal file
21
class/payments/class.HeurekaPayment.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
class HeurekaPayment extends Payment
|
||||
{
|
||||
public static $name = 'Platba přes Heureku';
|
||||
|
||||
public $class = 'HeurekaPayment';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
if (findModule(Modules::HEUREKA_CART) || findModule(Modules::DROPSHIP)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
18
class/payments/class.Hotovost.php
Normal file
18
class/payments/class.Hotovost.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class Hotovost extends Payment
|
||||
{
|
||||
public static $name = 'Platba v hotovosti';
|
||||
protected ?string $defaultIcon = '../../common/static/payments/prodejna_hotove.svg';
|
||||
|
||||
public $class = 'Hotovost';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_CASH;
|
||||
|
||||
public function requiresEET()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
227
class/payments/class.Intrum.php
Normal file
227
class/payments/class.Intrum.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
use Diglin\Intrum\CreditDecision\Request;
|
||||
use Diglin\Intrum\CreditDecision\Response;
|
||||
use Diglin\Intrum\CreditDecision\TransportV0017;
|
||||
use KupShop\KupShopBundle\Config;
|
||||
use KupShop\KupShopBundle\Util\Compat\SymfonyBridge;
|
||||
|
||||
class Intrum extends Payment
|
||||
{
|
||||
public static $name = 'Intrum';
|
||||
|
||||
public $template = 'payment.Intrum.tpl';
|
||||
|
||||
public $class = self::class;
|
||||
|
||||
protected $pay_method = Payment::METHOD_INVOICE;
|
||||
|
||||
public static $REQUEST_ENQUIRY_CREDIT_ASSESSMENT = 0;
|
||||
public static $REQUEST_ENQUIRY_BUSINESS_TRANSACTION = 1;
|
||||
public static $REQUEST_SEND_PAYMENT_TRANSACTION_RECEIPT = 3;
|
||||
public static $REQUEST_SEND_PAYMENT_TRANSACTION_CANCEL = 6;
|
||||
|
||||
public $states = [
|
||||
1 => 'There are serious negative indicators',
|
||||
2 => 'All payment methods',
|
||||
3 => 'Manual post-processing (currently not yet in use)',
|
||||
4 => 'Postal address is incorrect',
|
||||
5 => 'Enquiry exceeds the credit limit (the credit limit is specified in the cooperation agreement)',
|
||||
6 => 'Customer specifications not met (currently not yet in use)',
|
||||
7 => 'Enquiry exceeds the net credit limit (enquiry amount plus open items exceeds credit limit)',
|
||||
8 => 'Person queried is not of creditworthy age',
|
||||
9 => 'Delivery address does not match invoice address (for payment guarantee only)',
|
||||
10 => 'Household cannot be identified at this address',
|
||||
11 => 'Country is not supported',
|
||||
12 => 'Party queried is not a natural person',
|
||||
13 => 'System is in maintenance mode',
|
||||
14 => 'Address with high fraud risk',
|
||||
15 => 'Allowance is too low',
|
||||
16 => 'Application data incomplete',
|
||||
17 => 'Send contract documents for external credit check',
|
||||
18 => 'External credit check in progress',
|
||||
19 => 'Customer is on client blacklist',
|
||||
20 => 'Customer is on client whitelist',
|
||||
21 => 'Customer is on Intrum blacklist',
|
||||
22 => 'Address is a P.O. box',
|
||||
23 => 'Address not in residential area',
|
||||
24 => 'Ordering person not legitimated',
|
||||
25 => 'IP Address temporarily blacklisted',
|
||||
50 => 'Blacklist WSNP (NL only)',
|
||||
51 => 'Blacklist Bankruptcy (NL only)',
|
||||
52 => 'Blacklist Fraud (NL only)',
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function check(CartBase $cart)
|
||||
{
|
||||
// skip if not last step
|
||||
$step = end($cart->steps);
|
||||
if (!($step ?? false) || !($step['selected'] ?? false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($cart->invoice['birthdate'])
|
||||
|| DateTime::createFromFormat('d-m-Y', $cart->invoice['birthdate']) === false
|
||||
) {
|
||||
addUserMessage(replacePlaceholders(translate('checkDataOrChangeDelivery', 'payment'), ['URL' => '/'.$cart->steps['user']['url']]), 'danger');
|
||||
redirection($cart->steps['delivery']['url']);
|
||||
}
|
||||
|
||||
$hash = $this->createHash(array_merge($cart->invoice, [$cart->totalPricePay->printValue()]));
|
||||
$oldHash = $cart->getData($this->class.'_hash');
|
||||
$counter = $cart->getData($this->class.'_counter');
|
||||
try {
|
||||
if (isset($oldHash) && $oldHash === $hash) {
|
||||
$requestID = $cart->getData($this->class.'_enquiryCreditRequest');
|
||||
$requestResponse = $this->selectSQL('intrum_requests', ['id' => $requestID])->fetch()['response'];
|
||||
if ($requestResponse) {
|
||||
$this->checkRawResponse($requestResponse, $requestID);
|
||||
} else {
|
||||
$cart->setData($this->class.'_hash', null);
|
||||
$cart->setData($this->class.'_enquiryCreditRequest', null);
|
||||
$cart->setData($this->class.'_counter', null);
|
||||
$cart->setData($this->class.'_counter', 1);
|
||||
$this->enquiryCreditAssessment($cart, $hash);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($counter) || $counter <= 2) {
|
||||
$cart->setData($this->class.'_counter', ($counter ?? 0) + 1);
|
||||
$this->enquiryCreditAssessment($cart, $hash);
|
||||
} else {
|
||||
addUserMessage(mb_ucfirst(translate('changePaymentMethod', 'payment')), 'danger');
|
||||
redirection($cart->steps['delivery']['url']);
|
||||
}
|
||||
} catch (RuntimeException $e) {
|
||||
addUserMessage(replacePlaceholders(translate('checkDataOrChangeDelivery', 'payment'), ['URL' => '/'.$cart->steps['user']['url']]), 'danger');
|
||||
redirection($cart->steps['delivery']['url']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool TRUE if response status is 2 (All payment methods)
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function checkRawResponse(string $rawResponse, int $requestID): bool
|
||||
{
|
||||
$response = new Response();
|
||||
$response->setRawResponse($rawResponse);
|
||||
$response->processResponse();
|
||||
if ($response->getCustomerRequestStatus() !== 2) {
|
||||
throw new RuntimeException($this->states[$response->getCustomerRequestStatus()] ?? 'Unknown Intrum error in request '.$requestID.'.');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function enquiryCreditAssessment(CartBase $cart, string $hash)
|
||||
{
|
||||
$streetArray = explode(' ', $cart->invoice['street']);
|
||||
$houseNumber = count($streetArray) > 1 ? array_pop($streetArray) : ' ';
|
||||
$firstLine = join(' ', $streetArray);
|
||||
$birthdate = DateTime::createFromFormat('d-m-Y', $cart->invoice['birthdate'])->format('Y-m-d');
|
||||
$data = [
|
||||
'customer_reference' => 'uid_'.uniqid(),
|
||||
'person' => [
|
||||
'first_name' => $cart->invoice['name'],
|
||||
'last_name' => $cart->invoice['surname'],
|
||||
'gender' => $cart->invoice['gender'] === 'female' ? 2 : 1,
|
||||
'date_of_birth' => $birthdate, // YYYY-MM-DD
|
||||
'current_address' => [
|
||||
'first_line' => $firstLine,
|
||||
'house_number' => $houseNumber,
|
||||
'post_code' => $cart->invoice['zip'],
|
||||
'country_code' => $cart->invoice['country'],
|
||||
'town' => $cart->invoice['city'],
|
||||
],
|
||||
'communication_numbers' => [
|
||||
'mobile' => $cart->invoice['phone'],
|
||||
'email' => $cart->invoice['email'],
|
||||
],
|
||||
'extra_info' => [
|
||||
[
|
||||
'name' => 'ORDERCLOSED',
|
||||
'value' => 'NO',
|
||||
],
|
||||
[
|
||||
'name' => 'ORDERAMOUNT',
|
||||
'value' => $cart->totalPricePay->printValue(2),
|
||||
],
|
||||
[
|
||||
'name' => 'ORDERCURRENCY',
|
||||
'value' => $cart->invoice['currency'],
|
||||
],
|
||||
[
|
||||
'name' => 'IP',
|
||||
// 'value' => $_SERVER['REMOTE_ADDR'],
|
||||
'value' => explode(',', SymfonyBridge::getCurrentRequest()->getClientIp())[0],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$uid = uniqid(null, true);
|
||||
$this->insertSQL('intrum_requests', ['uid' => $uid, 'type' => static::$REQUEST_ENQUIRY_CREDIT_ASSESSMENT]);
|
||||
$requestID = sqlInsertId();
|
||||
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
|
||||
/* @var $request Request */
|
||||
$request = $dom->appendChild(new Request());
|
||||
|
||||
$request->setVersion('1.00');
|
||||
$request->setClientId($this->config['clientId']);
|
||||
$request->setUserID($this->config['userID']);
|
||||
$request->setPassword($this->config['password']);
|
||||
$request->setEmail($this->config['email']);
|
||||
$request->setRequestId($uid);
|
||||
|
||||
$request->createRequest($data);
|
||||
$requestXML = $dom->saveXML();
|
||||
$this->updateSQL('intrum_requests', ['request' => $requestXML], ['id' => $requestID]);
|
||||
|
||||
$transport = new TransportV0017();
|
||||
$transport->setMode($this->config['mode'] ?? 'test');
|
||||
$responseRequest = $transport->sendRequest($requestXML);
|
||||
|
||||
$successful = false;
|
||||
try {
|
||||
$successful = $this->checkRawResponse($responseRequest, $requestID);
|
||||
} finally {
|
||||
$this->updateSQL('intrum_requests', [
|
||||
'response' => $responseRequest,
|
||||
'successful' => $successful,
|
||||
], ['id' => $requestID]);
|
||||
|
||||
$cart->setData($this->class.'_enquiryCreditRequest', $requestID);
|
||||
$cart->setData($this->class.'_hash', $hash);
|
||||
}
|
||||
}
|
||||
|
||||
private function createHash(array $data)
|
||||
{
|
||||
array_multisort($data);
|
||||
|
||||
return md5(json_encode($data));
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
62
class/payments/class.OmnipayNestpay.php
Normal file
62
class/payments/class.OmnipayNestpay.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
class OmnipayNestpay extends \KupShop\OrderingBundle\OmniPay
|
||||
{
|
||||
public static $name = 'Nestpay platební brána';
|
||||
|
||||
public $template = 'payment.OmnipayNestpay.tpl';
|
||||
// protected $templateCart = 'payment.OmnipayNestpay.cart.tpl';
|
||||
|
||||
public $class = 'OmnipayNestpay';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
public static function getOmnipayName(): string
|
||||
{
|
||||
return 'Nestpay';
|
||||
}
|
||||
|
||||
public function configureGateway(Omnipay\Common\GatewayInterface $gateway, Order $order): Omnipay\Common\GatewayInterface
|
||||
{
|
||||
$gateway->setBank($this->config['bank']);
|
||||
$gateway->setClientId($this->config['clientId']);
|
||||
$gateway->setStoreKey($this->config['storeKey']);
|
||||
|
||||
return $gateway;
|
||||
}
|
||||
|
||||
public function getGatewayOptions(Order $order): array
|
||||
{
|
||||
$returnUrl = $this->getGenericPaymentUrl(5);
|
||||
|
||||
$options = [
|
||||
'amount' => $this->order->convertPriceToEUR(roundPrice($this->order->getRemainingPayment())->asFloat()),
|
||||
'currency' => $this->config['currency'],
|
||||
'orderid' => $this->order->order_no,
|
||||
'returnUrl' => $returnUrl,
|
||||
'cancelUrl' => $returnUrl,
|
||||
'shopUrl' => $returnUrl,
|
||||
'lang' => $this->config['lang'],
|
||||
];
|
||||
|
||||
if (!empty($this->order->invoice_name)) {
|
||||
$options['billToName'] = $this->order->invoice_name.' '.$this->order->invoice_surname;
|
||||
}
|
||||
if (!empty($this->order->invoice_firm)) {
|
||||
$options['billToCompany'] = $this->order->invoice_firm;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
protected function createPaymentFromResponse(Omnipay\Common\Message\ResponseInterface $response)
|
||||
{
|
||||
$this->createPayment(
|
||||
$response->getTransactionReference(),
|
||||
$this->order->convertPriceFromEUR($response->getData()['amount']),
|
||||
[]
|
||||
);
|
||||
}
|
||||
}
|
||||
168
class/payments/class.PagOnline.php
Normal file
168
class/payments/class.PagOnline.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
use KupShop\KupShopBundle\Config;
|
||||
|
||||
/**
|
||||
* Requires composer package "mattiabasone/pagonline: ^1.0".
|
||||
*/
|
||||
class PagOnline extends Payment
|
||||
{
|
||||
public static $name = 'PagOnline Imprese platební brána';
|
||||
|
||||
public $template = 'payment.OmnipayNestpay.tpl';
|
||||
|
||||
public $class = 'PagOnline';
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
public function getPaymentUrl($step = 1)
|
||||
{
|
||||
return createScriptURL([
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => $step,
|
||||
'class' => $this->class,
|
||||
'paymentId' => $this->paymentId,
|
||||
'absolute' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
private function getPayment(bool $new = false)
|
||||
{
|
||||
$kupshopPayment = $this->getPendingPayment();
|
||||
if ($kupshopPayment && $new) {
|
||||
$status = Payment::STATUS_UNKNOWN;
|
||||
sqlQuery('UPDATE '.getTableName('order_payments')." SET status={$status}, date=NOW() WHERE id={$kupshopPayment['id']}");
|
||||
$kupshopPayment = false;
|
||||
}
|
||||
if ($kupshopPayment && !$new) {
|
||||
return $kupshopPayment;
|
||||
} else {
|
||||
$this->createPayment(
|
||||
null,
|
||||
$amount = roundPrice($this->order->getRemainingPayment())->asFloat(),
|
||||
['paymentClass' => self::class]
|
||||
);
|
||||
$paymentRow = $this->selectSQL('order_payments', ['id' => $this->paymentId])->fetch();
|
||||
$json = json_decode($paymentRow['payment_data'], true);
|
||||
$json['session'] = $this->paymentId;
|
||||
$json['shopID'] = $this->order->id.'_'.$paymentRow['id'].'_'.uniqid();
|
||||
$paymentRow['payment_data'] = json_encode($json);
|
||||
$this->updateSQL('order_payments', $paymentRow, ['id' => $this->paymentId]);
|
||||
|
||||
return $this->getPendingPayment();
|
||||
}
|
||||
}
|
||||
|
||||
public function savePaymentData(array $data, array $payment)
|
||||
{
|
||||
$paymentRow = $this->selectSQL('order_payments', ['id' => $payment['id']])->fetch();
|
||||
$json = json_decode($paymentRow['payment_data'], true);
|
||||
$json = array_merge($json, $data);
|
||||
$paymentRow['payment_data'] = json_encode($json);
|
||||
$this->updateSQL('order_payments', $paymentRow, ['id' => $payment['id']]);
|
||||
}
|
||||
|
||||
/* Payment steps */
|
||||
public function processStep_1()
|
||||
{
|
||||
$merchangConfig = $this->getMerchantConfig();
|
||||
$amount = roundPrice($this->order->getRemainingPayment())->asFloat();
|
||||
if ($amount <= (float) 0) {
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
$payment = $this->getPayment(true);
|
||||
|
||||
// Initialize the payment page
|
||||
// See https://github.com/mattiabasone/PagOnline
|
||||
$init = new \PagOnline\Init\IgfsCgInit();
|
||||
$init->serverURL = $merchangConfig['url'];
|
||||
$init->tid = $merchangConfig['tid'];
|
||||
$init->kSig = $merchangConfig['kSig'];
|
||||
$init->shopID = $payment['decoded_data']->shopID;
|
||||
$init->shopUserRef = $this->order->invoice_email;
|
||||
$init->shopUserName = $this->order->invoice_surname.','.$this->order->invoice_name;
|
||||
$init->trType = 'AUTH';
|
||||
$init->currencyCode = 'EUR';
|
||||
$init->amount = (int) ($amount * 100); // Amount without comma (500 = 5,00)
|
||||
$init->langID = 'IT';
|
||||
$init->notifyURL = $this->getPaymentUrl(2);
|
||||
$init->errorURL = $this->getPaymentUrl(-3);
|
||||
|
||||
// $init->addInfo1 = 'myFirstAddintionalInfo';
|
||||
|
||||
if (!$init->execute()) {
|
||||
// Something went wrong, save error
|
||||
$this->savePaymentData([
|
||||
'error' => $init->errorDesc,
|
||||
'rc' => $init->rc,
|
||||
], $payment);
|
||||
$this->step(-3, 'storno');
|
||||
} else {
|
||||
// save PagOnline PaymentID
|
||||
$this->savePaymentData(['session' => $init->paymentID], $payment);
|
||||
|
||||
// Redirect user to payment gateway
|
||||
redirection($init->redirectURL);
|
||||
}
|
||||
}
|
||||
|
||||
public function processStep_2()
|
||||
{
|
||||
$merchangConfig = $this->getMerchantConfig();
|
||||
$payment = $this->getPayment();
|
||||
|
||||
$verify = new \PagOnline\Init\IgfsCgVerify();
|
||||
$verify->setRequestTimeout(15);
|
||||
|
||||
$verify->serverURL = $merchangConfig['url'];
|
||||
$verify->tid = $merchangConfig['tid']; // per servizio MyBank usare UNI_MYBK
|
||||
$verify->kSig = $merchangConfig['kSig'];
|
||||
$verify->shopID = $payment['decoded_data']->shopID;
|
||||
$verify->paymentID = $payment['decoded_data']->session;
|
||||
|
||||
if (!$verify->execute()) {
|
||||
// save error
|
||||
$this->savePaymentData([
|
||||
'error' => $verify->errorDesc,
|
||||
'rc' => $verify->rc,
|
||||
], $payment);
|
||||
|
||||
$this->step(-3, 'storno');
|
||||
|
||||
return;
|
||||
}
|
||||
if (!$this->setStatus(Payment::STATUS_FINISHED, $verify->paymentID)) {
|
||||
throw new Exception('PagOnline::updatePaymentStatus: setStatus failed!');
|
||||
}
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getMerchantConfig(): array
|
||||
{
|
||||
$defaultTestConfig = [
|
||||
'url' => 'https://testeps.netswgroup.it/UNI_CG_SERVICES/services',
|
||||
'tid' => 'UNI_ECOM',
|
||||
'kSig' => 'UNI_TESTKEY',
|
||||
];
|
||||
|
||||
return array_merge($defaultTestConfig, $this->config);
|
||||
}
|
||||
}
|
||||
600
class/payments/class.PayPal.php
Normal file
600
class/payments/class.PayPal.php
Normal file
@@ -0,0 +1,600 @@
|
||||
<?php
|
||||
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\KupShopBundle\Config;
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
use KupShop\OrderingBundle\OrderList\OrderList;
|
||||
use PayPal\Core\PayPalConstants;
|
||||
use Query\Operator;
|
||||
use Symfony\Component\Routing\Router;
|
||||
|
||||
/**
|
||||
* Dependencies: `composer require paypal/rest-api-sdk-php=^1.14`
|
||||
* Example config: $cfg['Modules']['payments']['PayPal'] = [
|
||||
* 'clientID' => 'client id',
|
||||
* 'secret' => 'client secret',
|
||||
* webProfileID - create temporary (3 hours): symfony kupshop:paypal-create-web-profile
|
||||
* or permanent profile: symfony kupshop:paypal-create-web-profile --permanent
|
||||
* optional image path as an argument: symfony kupshop:paypal-create-web-profile --permanent templates/images/logo.png
|
||||
* 'webProfileID' => 'web profile id',
|
||||
* 'mode' => 'sandbox' OR 'live',
|
||||
* 'enableLog' => false,
|
||||
* ].
|
||||
*/
|
||||
class PayPal extends Payment
|
||||
{
|
||||
public static $name = 'PayPal platební brána';
|
||||
|
||||
public $template = 'payment.PayPal.tpl';
|
||||
|
||||
public $class = 'PayPal';
|
||||
|
||||
public $tp_id_payment;
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
private $apiContext;
|
||||
|
||||
protected $allowedCurrencies = ['AUD', 'BRL', 'CAD', 'CZK', 'DKK', 'EUR', 'HKD',
|
||||
'HUF', 'ILS', 'JPY', 'MYR', 'MXN', 'TWD', 'NZD', 'NOK', 'PHP', 'PLN', 'GBP',
|
||||
'RUB', 'SGD', 'SEK', 'CHF', 'THB', 'USD', ];
|
||||
|
||||
public static function getSettingsConfiguration(): array
|
||||
{
|
||||
return [
|
||||
'fields' => [
|
||||
'clientID' => [
|
||||
'title' => 'ID klienta (clientID)',
|
||||
'type' => 'text',
|
||||
],
|
||||
'secret' => [
|
||||
'title' => 'Secret',
|
||||
'type' => 'text',
|
||||
],
|
||||
'mode' => [
|
||||
'title' => 'Režim',
|
||||
'type' => 'select',
|
||||
'options' => ['live' => 'Produkční režim', 'sandbox' => 'Testovaci režim'],
|
||||
],
|
||||
'tracking' => [
|
||||
'title' => 'Tracking info',
|
||||
'type' => 'toggle',
|
||||
'tooltip' => 'Odesílat číslo balíku z e-shopu k platbě do PayPalu.',
|
||||
],
|
||||
'webhook' => [
|
||||
'title' => 'Webhook URL',
|
||||
'text' => path('kupshop_ordering_payment_legacypayment', ['class' => 'PayPal', 'step' => 10], Router::ABSOLUTE_URL),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/* Payment steps */
|
||||
public function processStep_1()
|
||||
{
|
||||
$apiContext = $this->getApiContext();
|
||||
|
||||
// always create new paypal payment in step 1
|
||||
// $kupshopPayment = $this->getPendingPayment();
|
||||
$kupshopPayment = false;
|
||||
if ($kupshopPayment) {
|
||||
// use already created payment
|
||||
$payment = $this->getPayPalPayment($apiContext, $kupshopPayment['decoded_data']->session);
|
||||
} else {
|
||||
// create new payment
|
||||
$dbcfg = Settings::getDefault();
|
||||
if ($this->order->getRemainingPayment() <= 0) {
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
}
|
||||
|
||||
$payerInfoAddress = new \PayPal\Api\Address();
|
||||
$payerInfoAddress->setState($this->order->invoice_state ?? '')
|
||||
->setCountryCode($this->order->invoice_country)
|
||||
->setCity($this->order->invoice_city)
|
||||
->setLine1($this->order->invoice_street)
|
||||
->setLine2($this->order->invoice_custom_address ?? '')
|
||||
->setPostalCode($this->order->invoice_zip);
|
||||
|
||||
$payerInfo = new \PayPal\Api\PayerInfo();
|
||||
$payerInfo->setEmail($this->order->getUserEmail())
|
||||
->setBillingAddress($payerInfoAddress)
|
||||
->setFirstName($this->order->invoice_name)
|
||||
->setLastName($this->order->invoice_surname);
|
||||
|
||||
$payer = new \PayPal\Api\Payer();
|
||||
$payer->setPaymentMethod('paypal')
|
||||
->setPayerInfo($payerInfo);
|
||||
|
||||
if ($useOriginalCurrency = in_array($this->order->currency, $this->allowedCurrencies)) {
|
||||
$totalPrice = $this->order->getRemainingPayment();
|
||||
} else {
|
||||
$totalPrice = $this->convertPriceToEUR(
|
||||
(float) $this->order->getRemainingPayment()
|
||||
);
|
||||
}
|
||||
|
||||
// shipping address
|
||||
$shippingAddress = new \PayPal\Api\ShippingAddress();
|
||||
$shippingAddress->setState($this->order->delivery_state ?? '')
|
||||
->setRecipientName(implode(' ', [$this->order->delivery_name, $this->order->delivery_surname]))
|
||||
->setCountryCode($this->order->delivery_country)
|
||||
->setCity($this->order->delivery_city)
|
||||
->setLine1($this->order->delivery_street)
|
||||
->setLine2($this->order->delivery_custom_address ?? '')
|
||||
->setPostalCode($this->order->delivery_zip);
|
||||
|
||||
// Set item list
|
||||
$item1 = new \PayPal\Api\Item();
|
||||
$item1->setName('Payment for order '.$this->getOrderNumber().' on '.$dbcfg->shop_firm_name)
|
||||
->setCurrency($useOriginalCurrency ? $this->order->currency : 'EUR')
|
||||
->setQuantity(1)
|
||||
->setPrice($totalPrice);
|
||||
|
||||
$itemList = new \PayPal\Api\ItemList();
|
||||
$itemList->setItems([$item1])
|
||||
->setShippingAddress($shippingAddress);
|
||||
|
||||
// to set payment details see https://developer.paypal.com/docs/api/quickstart/create-process-order/
|
||||
|
||||
$amount = new \PayPal\Api\Amount();
|
||||
$amount->setCurrency($useOriginalCurrency ? $this->order->currency : 'EUR')
|
||||
->setTotal($totalPrice);
|
||||
|
||||
$transaction = new \PayPal\Api\Transaction();
|
||||
$transaction->setAmount($amount)
|
||||
->setDescription('Payment for order '.$this->getOrderNumber().' on '.$dbcfg->shop_firm_name)
|
||||
->setInvoiceNumber($this->getOrderNumber())
|
||||
->setItemList($itemList);
|
||||
|
||||
$redirectUrls = new \PayPal\Api\RedirectUrls();
|
||||
$redirectUrls->setReturnUrl(
|
||||
createScriptURL(
|
||||
[
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => 2,
|
||||
'class' => $this->class,
|
||||
'absolute' => true,
|
||||
]
|
||||
)
|
||||
)
|
||||
->setCancelUrl(
|
||||
createScriptURL(
|
||||
[
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => 4,
|
||||
'class' => $this->class,
|
||||
'absolute' => true,
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$payment = new \PayPal\Api\Payment();
|
||||
$payment->setIntent('sale')
|
||||
->setPayer($payer)
|
||||
->setRedirectUrls($redirectUrls)
|
||||
->setTransactions([$transaction]);
|
||||
|
||||
if (isset($this->config['webProfileID'])) {
|
||||
$payment->setExperienceProfileId($this->config['webProfileID']);
|
||||
}
|
||||
|
||||
// nechci umoznit nastavit pri platbe dorucovaci adresu, protoze oni si ji pak nastavi a ocekavaji, ze jim
|
||||
// to prijde na tu adresu co nastavili v PayPalu. Takze bysme museli tu adresu tahat zpatky do e-shopu...
|
||||
// a jednodusi je jim tu zmenu neumoznit
|
||||
// bohuzel to musim udelat takhle osklive, protoze ta knihovna tu optionu neumoznuje nastavit nejakym setterem
|
||||
$payment->application_context = [
|
||||
// If available, uses the merchant-provided shipping address, which the customer cannot change on the PayPal pages.
|
||||
// If the merchant does not provide an address, the customer can enter the address on PayPal pages.
|
||||
'shipping_preference' => 'SET_PROVIDED_ADDRESS',
|
||||
];
|
||||
|
||||
try {
|
||||
$payment->create($apiContext);
|
||||
} catch (PayPal\Exception\PayPalConnectionException $e) {
|
||||
$data = json_decode($e->getData(), true);
|
||||
if (($data['name'] ?? false) === 'VALIDATION_ERROR') {
|
||||
logError(__FILE__, __LINE__, 'PayPal exception: '.print_r($e->getData(), true));
|
||||
$this->error(translate('PayPalConnectionException', 'paypal'));
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// save payment
|
||||
$transaction = $payment->getTransactions()[0];
|
||||
if ($useOriginalCurrency) {
|
||||
$paymentAmount = $transaction->getAmount()->getTotal();
|
||||
} else {
|
||||
$paymentAmount = $this->order->convertPriceFromEUR($transaction->getAmount()->getTotal());
|
||||
}
|
||||
$this->createPayment(
|
||||
$payment->getId(),
|
||||
$paymentAmount,
|
||||
['paymentClass' => self::class]
|
||||
);
|
||||
}
|
||||
|
||||
// redirect user to PayPal for the payment approval
|
||||
$approvalUrl = $payment->getApprovalLink();
|
||||
redirection($approvalUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Success return from paypal - execute payment.
|
||||
*/
|
||||
public function processStep_2()
|
||||
{
|
||||
logError(__FILE__, __LINE__, 'PayPal response: '.print_r([$_GET, $_POST, $_SERVER], true));
|
||||
|
||||
$apiContext = $this->getApiContext();
|
||||
|
||||
$payPalPaymentID = getVal('paymentId', null, false);
|
||||
$this->processPayment($this->getPayPalPayment($apiContext, $payPalPaymentID), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waiting for approval - current payment status is created.
|
||||
*/
|
||||
public function processStep_3()
|
||||
{
|
||||
$apiContext = $this->getApiContext();
|
||||
$payPalPaymentID = getVal('paymentId', null, false);
|
||||
if (!$payPalPaymentID) {
|
||||
$kupshopPayment = $this->getPendingPayment();
|
||||
if ($kupshopPayment) {
|
||||
// use already created payment
|
||||
$payPalPaymentID = $kupshopPayment['decoded_data']->session;
|
||||
} else {
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
}
|
||||
$this->processPayment($this->getPayPalPayment($apiContext, $payPalPaymentID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Storno.
|
||||
*/
|
||||
public function processStep_4()
|
||||
{
|
||||
$apiContext = $this->getApiContext();
|
||||
$payPalPaymentID = getVal('paymentId', null, false);
|
||||
if (!$payPalPaymentID) {
|
||||
$kupshopPayment = $this->getPendingPayment();
|
||||
if ($kupshopPayment) {
|
||||
// use already created payment
|
||||
$payPalPaymentID = $kupshopPayment['decoded_data']->session;
|
||||
} else {
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
}
|
||||
$payment = $this->getPayPalPayment($apiContext, $payPalPaymentID);
|
||||
$payment->setState('failed');
|
||||
$this->processPayment($payment);
|
||||
}
|
||||
|
||||
/**
|
||||
* PayPal webhook handler - check for approved payment.
|
||||
*/
|
||||
public function processStep_10()
|
||||
{
|
||||
$logger = ServiceContainer::getService('logger');
|
||||
$logger->notice('PayPal: webhook', [
|
||||
'data' => $this->request->getContent(),
|
||||
]);
|
||||
|
||||
$this->isNotification = true;
|
||||
|
||||
$data = json_decode($this->request->getContent(), true);
|
||||
// only process PAYMENT.SALE.COMPLETED and payment must already exist
|
||||
if ($data['event_type'] !== 'PAYMENT.SALE.COMPLETED'
|
||||
|| empty($data['resource']['parent_payment'])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
$apiContext = $this->getApiContext();
|
||||
$payPalPaymentID = $data['resource']['parent_payment'];
|
||||
$payment = $this->getPayPalPayment($apiContext, $payPalPaymentID, false);
|
||||
if (!$payment) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!($orderId = $this->getOrderId($payPalPaymentID))) {
|
||||
$this->sendNotificationResponse(400, 'Order not found!');
|
||||
}
|
||||
|
||||
// set order - getStatus method needs it
|
||||
$this->setOrder($orderId);
|
||||
// only proceed if payment already exists
|
||||
if (!$this->getStatus($payPalPaymentID)) {
|
||||
return;
|
||||
}
|
||||
$this->processPayment(
|
||||
$payment,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
$this->sendNotificationResponse(200, 'OK');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process payment procedure
|
||||
* (used in step 2 - after return from PayPal, step 3 - waiting for approval, step 4 - storno and step 10 - webhook).
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function processPayment(
|
||||
PayPal\Api\Payment $payment,
|
||||
bool $enableRedirectToStep3 = false,
|
||||
bool $enableRedirects = true,
|
||||
) {
|
||||
$this->tp_id_payment = $payment->getId();
|
||||
$paymentState = $payment->getState();
|
||||
|
||||
if ($paymentState === 'created') {
|
||||
$apiContext = $this->getApiContext();
|
||||
|
||||
// Execute payment with payer id
|
||||
$execution = new \PayPal\Api\PaymentExecution();
|
||||
$execution->setPayerId($payment->getPayer()->getPayerInfo()->getPayerId());
|
||||
|
||||
try {
|
||||
// Execute payment
|
||||
$payment->execute($execution, $apiContext);
|
||||
} catch (Exception $ex) {
|
||||
logError(__FILE__, __LINE__, 'PayPal exception: '.$ex->getMessage());
|
||||
|
||||
if ($ex instanceof \PayPal\Exception\PayPalConnectionException) {
|
||||
logError(__FILE__, __LINE__, 'PayPal exception: '.print_r($ex->getData(), true));
|
||||
}
|
||||
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
}
|
||||
|
||||
// determine kupshop unified payment state from paypal payment state
|
||||
switch ($paymentState) {
|
||||
case 'created':
|
||||
$this->status = $unifiedState = Payment::STATUS_CREATED;
|
||||
break;
|
||||
case 'approved':
|
||||
$this->status = $unifiedState = Payment::STATUS_FINISHED;
|
||||
break;
|
||||
case 'failed':
|
||||
$this->status = $unifiedState = Payment::STATUS_STORNO;
|
||||
break;
|
||||
default:
|
||||
logError(__FILE__, __LINE__, 'PayPal\Api\Payment invalid state "'.$paymentState.'"');
|
||||
if ($enableRedirects) {
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// change payment status
|
||||
if (!$this->setStatus($unifiedState, $this->tp_id_payment)) {
|
||||
logError(__FILE__, __LINE__, 'PayPal::updatePaymentStatus: setStatus failed!');
|
||||
throw new \Exception('Set status failed');
|
||||
}
|
||||
|
||||
if ($enableRedirects) {
|
||||
switch ($unifiedState) {
|
||||
case Payment::STATUS_FINISHED:
|
||||
if ($sale = $payment->getTransactions()[0]->getRelatedResources()[0]->getSale()) {
|
||||
if ($transactionID = $sale->getId()) {
|
||||
$payment_data = $this->selectSQL('order_payments', ['id' => $this->paymentId], ['payment_data'])->fetchColumn();
|
||||
$payment_data = json_decode($payment_data, true);
|
||||
$payment_data['transactionID'] = $transactionID;
|
||||
$this->updateSQL('order_payments', ['payment_data' => json_encode($payment_data)], ['id' => $this->paymentId]);
|
||||
}
|
||||
}
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
break;
|
||||
case Payment::STATUS_STORNO:
|
||||
$this->step(-3, 'storno');
|
||||
break;
|
||||
case Payment::STATUS_CREATED:
|
||||
if ($enableRedirectToStep3) {
|
||||
$this->step(3, 'wait', ['paymentId' => $payment->getId()]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return false|\PayPal\Api\Payment
|
||||
*/
|
||||
protected function getPayPalPayment(
|
||||
PayPal\Rest\ApiContext $apiContext,
|
||||
$payPalPaymentID,
|
||||
bool $enableRedirects = true,
|
||||
) {
|
||||
try {
|
||||
$payment = \PayPal\Api\Payment::get($payPalPaymentID, $apiContext);
|
||||
} catch (Exception $ex) {
|
||||
logError(__FILE__, __LINE__, 'PayPal\Api\Payment::get exception');
|
||||
if ($enableRedirects) {
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \PayPal\Rest\ApiContext
|
||||
*/
|
||||
public function getApiContext()
|
||||
{
|
||||
if (!isset($this->apiContext)) {
|
||||
$apiContext = new PayPal\Rest\ApiContext(
|
||||
new \PayPal\Auth\OAuthTokenCredential($this->config['clientID'], $this->config['secret'])
|
||||
);
|
||||
$apiConfig = [
|
||||
'mode' => isset($this->config['mode']) ? $this->config['mode'] : 'sandbox',
|
||||
'cache.enabled' => true,
|
||||
// 'http.CURLOPT_CONNECTTIMEOUT' => 30
|
||||
// 'http.headers.PayPal-Partner-Attribution-Id' => '123123123'
|
||||
// 'log.AdapterFactory' => '\PayPal\Log\DefaultLogFactory' // Factory class implementing \PayPal\Log\PayPalLogFactory
|
||||
];
|
||||
|
||||
if (isset($this->config['enableLog']) && $this->config['enableLog']) {
|
||||
$apiConfig['log.LogEnabled'] = true;
|
||||
$apiConfig['log.FileName'] = './PayPal.log';
|
||||
$apiConfig['log.LogLevel'] = ($apiConfig['mode'] == 'sandbox') ? 'DEBUG' : 'INFO'; // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
|
||||
}
|
||||
|
||||
$apiContext->setConfig($apiConfig);
|
||||
$this->apiContext = $apiContext;
|
||||
}
|
||||
|
||||
return $this->apiContext;
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function sendOrderTrackings(): void
|
||||
{
|
||||
if (($this->config['tracking'] ?? 0) != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$orderList = ServiceContainer::getService(OrderList::class);
|
||||
$orderList->andSpec(function (Query\QueryBuilder $qb) {
|
||||
$qb->join('o', 'delivery_type', 'dt', 'dt.id = o.id_delivery')
|
||||
->join('dt', 'delivery_type_payment', 'dtp_paypal_filter', 'dtp_paypal_filter.id = dt.id_payment');
|
||||
|
||||
return Operator::andX(
|
||||
// musi byt vyplnene tracking cislo
|
||||
'o.package_id IS NOT NULL',
|
||||
// filtruju objednavky, u kterych nebyl tracking jeste odeslany
|
||||
'JSON_VALUE(o.note_admin, "$.paypalTrackingSent") IS NULL',
|
||||
// filtruju pouze objednavky s PayPal platbou
|
||||
Operator::equals(['dtp_paypal_filter.class' => $this->class]),
|
||||
// beru v potaz objednavky za max. posledni tyden
|
||||
'o.date_handle IS NOT NULL AND o.date_handle >= DATE(NOW() - INTERVAL 7 DAY)',
|
||||
// objednavka musi byt vyrizena
|
||||
Operator::inIntArray(getStatuses('handled'), 'o.status'),
|
||||
// objednavka musi byt zaplacena
|
||||
Operator::equals(['o.status_payed' => 1]),
|
||||
// objednavka nesmi byt osobni odber
|
||||
Operator::not(\Query\Order::byInPersonDelivery())
|
||||
);
|
||||
});
|
||||
|
||||
foreach ($orderList->getOrders() as $order) {
|
||||
$this->sendOrderTrackingToPayPal($order);
|
||||
}
|
||||
}
|
||||
|
||||
public function sendOrderTrackingToPayPal(Order $order): bool
|
||||
{
|
||||
$apiContext = $this->getApiContext();
|
||||
$token = $apiContext->getCredential()->getAccessToken($apiContext->getConfig());
|
||||
$url = $this->config['mode'] === 'sandbox' ? PayPalConstants::REST_SANDBOX_ENDPOINT : PayPalConstants::REST_LIVE_ENDPOINT;
|
||||
|
||||
[$carrier, $carrierOtherName] = $this->getOrderCarrier($order);
|
||||
|
||||
$orderData = [
|
||||
'status' => 'SHIPPED',
|
||||
'carrier' => $carrier,
|
||||
'carrier_other_name' => $carrierOtherName,
|
||||
'last_update_time' => $order->date_handle->format('c'),
|
||||
'tracking_number' => $order->package_id,
|
||||
];
|
||||
|
||||
$trackers = [];
|
||||
|
||||
$payments = array_filter($order->getPaymentsArray(), fn ($x) => $x['status'] == Payment::STATUS_FINISHED);
|
||||
foreach ($payments as $payment) {
|
||||
$paymentData = json_decode($payment['payment_data'] ?: '', true) ?: [];
|
||||
if (empty($paymentData['transactionID'])) {
|
||||
throw new RuntimeException('PayPal payment is missing "transactionID"! Order: '.$order->order_no);
|
||||
}
|
||||
|
||||
$trackers[] = array_merge($orderData, ['transaction_id' => $paymentData['transactionID']]);
|
||||
}
|
||||
|
||||
if (empty($trackers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
|
||||
curl_setopt($curl, CURLOPT_URL, $url.'v1/shipping/trackers-batch');
|
||||
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer '.$token,
|
||||
]);
|
||||
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode(['trackers' => $trackers]));
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$responseCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
$result = json_decode($response ?: '', true) ?: [];
|
||||
|
||||
if (!in_array($responseCode, [200, 201])) {
|
||||
addActivityLog(
|
||||
ActivityLog::SEVERITY_ERROR,
|
||||
ActivityLog::TYPE_COMMUNICATION,
|
||||
'[PayPal] Nepodařila se odeslat informace o číslu balíku! Objednávka: '.$order->order_no,
|
||||
['errors' => $result['errors'] ?? []]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$order->setData('paypalTrackingSent', true);
|
||||
$order->logHistory('[PayPal] Byla odeslána informace o číslu balíku z e-shopu do PayPalu.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getOrderCarrier(Order $order): array
|
||||
{
|
||||
$delivery = null;
|
||||
if ($deliveryType = $order->getDeliveryType()) {
|
||||
$delivery = $deliveryType->getDelivery();
|
||||
}
|
||||
|
||||
if ($delivery instanceof PPL) {
|
||||
return ['PPL', null];
|
||||
} elseif ($delivery instanceof DHL) {
|
||||
return ['DHL', null];
|
||||
} elseif ($delivery instanceof BalikDoRuky) {
|
||||
return ['CESKA_CZ', null];
|
||||
} elseif ($delivery instanceof GLS && $order->delivery_country === 'CZ') {
|
||||
return ['GLS_CZ', null];
|
||||
}
|
||||
|
||||
return ['OTHER', $delivery->name];
|
||||
}
|
||||
}
|
||||
30
class/payments/class.PayPalCheckout.php
Normal file
30
class/payments/class.PayPalCheckout.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
if (!class_exists('PayPal')) {
|
||||
require_once 'class.PayPal.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependencies: `composer require paypal/paypal-checkout-sdk`
|
||||
* enable PayPalCheckoutBundle (payments submodule Modules::SUB_PAYPAL_CHECKOUT)
|
||||
* $cfg['Modules']['payments']['PayPalCheckout'] = [
|
||||
* 'delivery_type_id' => 3,
|
||||
* 'clientID' => 'clientID',
|
||||
* 'secret' => 'secret',
|
||||
* 'mode' => 'sandbox',
|
||||
* ];.
|
||||
*/
|
||||
class PayPalCheckout extends PayPal
|
||||
{
|
||||
public static $name = 'PayPal Checkout platební brána';
|
||||
|
||||
public $template = 'payment.PayPal.tpl';
|
||||
|
||||
public $class = 'PayPalCheckout';
|
||||
|
||||
public $tp_id_payment;
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
}
|
||||
392
class/payments/class.PayPalPlus.php
Normal file
392
class/payments/class.PayPalPlus.php
Normal file
@@ -0,0 +1,392 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
use Symfony\Component\Routing\Router;
|
||||
|
||||
require_once 'class.PayPal.php';
|
||||
|
||||
/**
|
||||
* Dependencies: `composer require paypal/rest-api-sdk-php=^1.14`
|
||||
* Example config: $cfg['Modules']['payments']['PayPal'] = [
|
||||
* 'clientID' => 'client id',
|
||||
* 'secret' => 'client secret',
|
||||
* webProfileID - create temporary (3 hours): symfony kupshop:paypal-create-web-profile
|
||||
* or permanent profile: symfony kupshop:paypal-create-web-profile --permanent
|
||||
* optional image path as an argument: symfony kupshop:paypal-create-web-profile --permanent templates/images/logo.png
|
||||
* 'webProfileID' => 'web profile id',
|
||||
* 'mode' => 'sandbox' OR 'live',
|
||||
* 'enableLog' => false,
|
||||
* ].
|
||||
*/
|
||||
class PayPalPlus extends PayPal
|
||||
{
|
||||
public static $name = 'PayPal Plus platební brána';
|
||||
|
||||
protected $templateCart = 'payment.PayPalPlus.cart.tpl';
|
||||
protected $templateOrderView = 'payment.PayPalPlus.orderView.tpl';
|
||||
protected $templateInit = 'payment.PayPalPlus.init.tpl';
|
||||
|
||||
public $class = 'PayPalPlus';
|
||||
|
||||
/** Vytáhne existující, nebo vytvoří novou platbu */
|
||||
public function getPayment()
|
||||
{
|
||||
$cart = ServiceContainer::getService(\KupShop\OrderingBundle\Cart::class);
|
||||
// Preferuj ID z objednavky
|
||||
if (isset($this->order) && ($this->order->getData('paypalplus')['id'] ?? false)) {
|
||||
$payment = $this->getPayPalPayment($this->getApiContext(), $this->order->getData('paypalplus')['id']);
|
||||
} elseif ($cart->getData('paypalplus') && $cart->getData('paypalplus')['id']) {
|
||||
$payment = $this->getPayPalPayment($this->getApiContext(), $cart->getData('paypalplus')['id']);
|
||||
} else {
|
||||
$payment = $this->createPayPalPayment();
|
||||
$cart->setData('paypalplus', ['id' => $payment->getId(), 'token' => $payment->getToken()]);
|
||||
}
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
public function getPaymentUrl()
|
||||
{
|
||||
if (empty($this->order)) {
|
||||
throw new \KupShop\OrderingBundle\Exception\PaymentException(translate('sameDevicePayment', 'payment'));
|
||||
}
|
||||
|
||||
return path('kupshop_content_orders_order',
|
||||
['id' => $this->order->id, 'cf' => $this->order->getSecurityCode(), 'status' => 1, 'immediate_pay' => 1]);
|
||||
}
|
||||
|
||||
/** Pošle patch request pokud není platba aktualizovana - nema security code */
|
||||
public function makeSurePaymentIsUpdated()
|
||||
{
|
||||
$payment = $this->getPayment();
|
||||
if (empty($payment->getTransactions()[0]->getCustom())) {
|
||||
return $this->updatePayment();
|
||||
}
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
/** Vytvoří payment s dummy daty - protože se musí tvořit už v košíku */
|
||||
protected function createPayPalPayment($useOriginalCurrency = true, $totalPrice = 0): PayPal\Api\Payment
|
||||
{
|
||||
$apiContext = $this->getApiContext();
|
||||
$dbcfg = Settings::getDefault();
|
||||
if ($this->order && $remainingPayment = roundPrice($this->order->getRemainingPayment())->asFloat() <= 0) {
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
}
|
||||
|
||||
$payer = new \PayPal\Api\Payer();
|
||||
$payer->setPaymentMethod('paypal');
|
||||
|
||||
$amount = new \PayPal\Api\Amount();
|
||||
$amount->setCurrency('EUR')->setTotal('2.00');
|
||||
|
||||
$item1 = new \PayPal\Api\Item();
|
||||
$item1->setName('Payment for order '.($this->order ? $this->order->order_no : '').' on '.$dbcfg->shop_firm_name)
|
||||
->setCurrency('EUR')
|
||||
->setQuantity(1)
|
||||
->setPrice($totalPrice);
|
||||
|
||||
$itemList = new \PayPal\Api\ItemList();
|
||||
$itemList->setItems([$item1]);
|
||||
|
||||
$transaction = new \PayPal\Api\Transaction();
|
||||
$transaction->setAmount($amount)
|
||||
->setDescription('Payment for order')
|
||||
->setInvoiceNumber('123456')
|
||||
->setItemList($itemList);
|
||||
|
||||
$redirectUrls = new \PayPal\Api\RedirectUrls();
|
||||
$redirectUrls->setReturnUrl(
|
||||
path('kupshop_ordering_payment_legacypayment',
|
||||
array_merge([
|
||||
'step' => 2,
|
||||
'class' => $this->class,
|
||||
], []),
|
||||
Router::ABSOLUTE_URL)
|
||||
)->setCancelUrl(
|
||||
path('kupshop_ordering_payment_legacypayment',
|
||||
array_merge([
|
||||
'step' => 4,
|
||||
'class' => $this->class,
|
||||
], []),
|
||||
Router::ABSOLUTE_URL)
|
||||
);
|
||||
|
||||
$payment = new \PayPal\Api\Payment();
|
||||
$payment->setIntent('sale')
|
||||
->setPayer($payer)
|
||||
->setTransactions([$transaction])
|
||||
->setRedirectUrls($redirectUrls);
|
||||
|
||||
try {
|
||||
$payment->create($apiContext);
|
||||
} catch (PayPal\Exception\PayPalConnectionException $e) {
|
||||
$data = $e->getData();
|
||||
if ($data) {
|
||||
$data = json_decode($data, true);
|
||||
}
|
||||
|
||||
if (($data['name'] ?? false) === 'VALIDATION_ERROR') {
|
||||
$this->error(translate('PayPalConnectionException', 'paypal'));
|
||||
ServiceContainer::getService('logger')->error('PayPal exception: '.print_r($e->getData(), true),
|
||||
['file' => __FILE__, 'line' => __LINE__]);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
/** Funkce pro patch paymentu reálnými daty */
|
||||
public function updatePayment(): PayPal\Api\Payment
|
||||
{
|
||||
$payment = $this->getPayment();
|
||||
|
||||
if ($useOriginalCurrency = in_array($this->order->currency, $this->allowedCurrencies)) {
|
||||
$totalPrice = roundPrice($this->order->getRemainingPayment())->asFloat();
|
||||
} else {
|
||||
$totalPrice = $this->order->convertPriceToEUR(
|
||||
roundPrice($this->order->getRemainingPayment())->asFloat()
|
||||
);
|
||||
}
|
||||
|
||||
$patchAmount = new \PayPal\Api\Patch();
|
||||
$patchAddress = new \PayPal\Api\Patch();
|
||||
$patchPayer = new \PayPal\Api\Patch();
|
||||
$patchItems = new \PayPal\Api\Patch();
|
||||
$patchCustom = new \PayPal\Api\Patch();
|
||||
$patchInvoiceNo = new \PayPal\Api\Patch();
|
||||
|
||||
$amount = new \PayPal\Api\Amount();
|
||||
$amount->setCurrency($useOriginalCurrency ? ($this->order ? $this->order->currency : '') : 'EUR')
|
||||
->setTotal($totalPrice);
|
||||
|
||||
$patchAmount->setOp('replace')
|
||||
->setPath('/transactions/0/amount')
|
||||
->setValue($amount);
|
||||
|
||||
$payerDeliveryAddress = new \PayPal\Api\ShippingAddress();
|
||||
$payerDeliveryAddress->setState($this->order->delivery_state ?? '')
|
||||
->setCountryCode($this->order->delivery_country ?? '')
|
||||
->setCity($this->order->delivery_city ?? '')
|
||||
->setLine1($this->order->delivery_street ?? '')
|
||||
->setLine2($this->order->delivery_custom_address ?? '')
|
||||
->setPostalCode($this->order->delivery_zip ?? '')
|
||||
->setRecipientName($this->order->delivery_name.' '.$this->order->delivery_surname);
|
||||
|
||||
$patchAddress->setOp('add')
|
||||
->setPath('/transactions/0/item_list/shipping_address')
|
||||
->setValue($payerDeliveryAddress);
|
||||
|
||||
$payerInfoAddress = new \PayPal\Api\Address();
|
||||
$payerInfoAddress->setState($this->order->invoice_state ?? '')
|
||||
->setCountryCode($this->order->invoice_country ?? '')
|
||||
->setCity($this->order->invoice_city ?? '')
|
||||
->setLine1($this->order->invoice_street ?? '')
|
||||
->setLine2($this->order->invoice_custom_address ?? '')
|
||||
->setPostalCode($this->order->invoice_zip ?? '');
|
||||
|
||||
$payerInfo = new \PayPal\Api\PayerInfo();
|
||||
$payerInfo->setEmail($this->order ? $this->order->getUserEmail() : '')
|
||||
->setBillingAddress($payerInfoAddress)
|
||||
->setFirstName($this->order->invoice_name ?? '')
|
||||
->setLastName($this->order->invoice_surname ?? '');
|
||||
|
||||
$patchPayer->setOp('add')
|
||||
->setPath('/payer/payer_info')
|
||||
->setValue($payerInfo);
|
||||
|
||||
$items = [];
|
||||
$itemList = new \PayPal\Api\ItemList();
|
||||
|
||||
foreach ($this->order->fetchItems() as $orderItem) {
|
||||
$item = new \PayPal\Api\Item();
|
||||
$item->setName($orderItem['descr'])
|
||||
->setCurrency($useOriginalCurrency ? $this->order->currency ?? '' : 'EUR')
|
||||
->setQuantity($orderItem['pieces'])
|
||||
->setPrice($orderItem['piece_price']['value_with_vat']->asFloat());
|
||||
$items[] = $item;
|
||||
}
|
||||
$itemList->setItems($items);
|
||||
|
||||
$patchItems->setOp('replace')
|
||||
->setPath('/transactions/0/item_list')
|
||||
->setValue($itemList);
|
||||
|
||||
$patchCustom->setOp('add')
|
||||
->setPath('/transactions/0/custom')
|
||||
->setValue($this->order->getSecurityCode());
|
||||
|
||||
$patchInvoiceNo->setOp('add')
|
||||
->setPath('/transactions/0/invoice_number')
|
||||
->setValue($this->order->order_no);
|
||||
|
||||
$patchRequest = new \PayPal\Api\PatchRequest();
|
||||
$patchRequest->setPatches([
|
||||
$patchAddress,
|
||||
$patchAmount,
|
||||
$patchPayer,
|
||||
$patchItems,
|
||||
$patchCustom,
|
||||
$patchInvoiceNo,
|
||||
]);
|
||||
|
||||
try {
|
||||
$payment->update($patchRequest, $this->getApiContext());
|
||||
} catch (Exception $ex) {
|
||||
ServiceContainer::getService('logger')->error('PayPal exception: '.$ex->getMessage(), ['file' => __FILE__, 'line' => __LINE__]);
|
||||
|
||||
if ($ex instanceof \PayPal\Exception\PayPalConnectionException) {
|
||||
ServiceContainer::getService('logger')->error('PayPal exception: '.print_r($ex->getData(), true),
|
||||
['file' => __FILE__, 'line' => __LINE__]);
|
||||
}
|
||||
|
||||
$this->error('storno');
|
||||
}
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
public function processStep_1()
|
||||
{
|
||||
// $this->error('');
|
||||
}
|
||||
|
||||
public function processStep_2()
|
||||
{
|
||||
ServiceContainer::getService('logger')->error('PayPal response: '.print_r([$_GET, $_POST, $_SERVER], true),
|
||||
['file' => __FILE__, 'line' => __LINE__]);
|
||||
$payPalPaymentID = getVal('paymentId', null, false);
|
||||
$apiContext = $this->getApiContext();
|
||||
$payment = $this->getPayPalPayment($apiContext, $payPalPaymentID);
|
||||
$error = false;
|
||||
if ($payment) {
|
||||
$this->orderId = $this->selectSQL('orders',
|
||||
['order_no' => $payment->getTransactions()[0]->getInvoiceNumber()],
|
||||
['id'])->fetchOne();
|
||||
$this->order = new Order();
|
||||
$this->order->createFromDB($this->orderId);
|
||||
if ($this->order->getSecurityCode() == $payment->getTransactions()[0]->getCustom()) {
|
||||
// Při platbě paypal umožňuje změnit adresu doručení....
|
||||
|
||||
// $address = $payment->getTransactions()[0]->getItemList()->getShippingAddress()->toArray();
|
||||
// $address = $payment->toArray();
|
||||
// if (isset($address['payer']['payer_info']['first_name']) && isset($address['payer']['payer_info']['last_name'])) {
|
||||
// // $this->order->updateSQL('orders', [
|
||||
// // 'delivery_name' => $address['payer']['payer_info']['first_name'],
|
||||
// // 'delivery_surname' => $address['payer']['payer_info']['last_name'],
|
||||
// // 'delivery_street' => $address['payer']['payer_info']['shipping_address']['line1'],
|
||||
// // 'delivery_city' => $address['payer']['payer_info']['shipping_address']['city'],
|
||||
// // 'delivery_zip' => $address['payer']['payer_info']['shipping_address']['postal_code'],
|
||||
// // 'delivery_country' => $address['payer']['payer_info']['shipping_address']['country_code'],
|
||||
// // 'delivery_phone' => $address['payer']['payer_info']['phone'] ?? '',
|
||||
// // 'delivery_state' => $address['payer']['payer_info']['shipping_address']['state'],
|
||||
// // ], ['id' => $this->orderId]);
|
||||
//
|
||||
// $name = explode(' ', $address['recipient_name']);
|
||||
// $firstname = $name[0] ?? '';
|
||||
// unset($name[0]);
|
||||
// $lastname = implode(' ', $name);
|
||||
// $this->order->updateSQL('orders', [
|
||||
// 'delivery_name' => $firstname,
|
||||
// 'delivery_surname' => $lastname ?? '',
|
||||
// 'delivery_street' => $address['line1'],
|
||||
// 'delivery_city' => $address['city'],
|
||||
// 'delivery_zip' => $address['postal_code'],
|
||||
// 'delivery_country' => $address['country_code'],
|
||||
// 'delivery_phone' => $address['payer']['payer_info']['phone'] ?? '',
|
||||
// 'delivery_state' => $address['payer']['payer_info']['shipping_address']['state'],
|
||||
// ], ['id' => $this->orderId]);
|
||||
// }
|
||||
|
||||
$this->createPayment(
|
||||
$payment->getId(),
|
||||
$payment->getTransactions()[0]->getAmount()->getTotal(),
|
||||
['paymentClass' => self::class]
|
||||
);
|
||||
$this->processPayment($payment);
|
||||
|
||||
$paymentInstructions = $payment->getPaymentInstruction();
|
||||
if ($paymentInstructions) {
|
||||
$paypalData = $this->order->getData('paypalplus');
|
||||
$paypalData['payment_instruction'] = $paymentInstructions->toArray();
|
||||
$this->order->setData('paypalplus', $paypalData);
|
||||
}
|
||||
|
||||
$this->step(3, 'wait', ['paymentId' => $payment->getId()]);
|
||||
} else {
|
||||
$error = true;
|
||||
}
|
||||
} else {
|
||||
$error = true;
|
||||
}
|
||||
|
||||
if ($error) {
|
||||
$this->error(translate('payment_status_check_error', 'payment'));
|
||||
}
|
||||
}
|
||||
|
||||
public function processStep_3()
|
||||
{
|
||||
$apiContext = $this->getApiContext();
|
||||
$payPalPaymentID = getVal('paymentId', null, false);
|
||||
if (!$payPalPaymentID) {
|
||||
$kupshopPayment = $this->getPendingPayment();
|
||||
if ($kupshopPayment) {
|
||||
// use already created payment
|
||||
$payPalPaymentID = $kupshopPayment['decoded_data']->session;
|
||||
} else {
|
||||
$this->error('storno');
|
||||
}
|
||||
}
|
||||
$this->processPayment($this->getPayPalPayment($apiContext, $payPalPaymentID));
|
||||
}
|
||||
|
||||
public function processStep_4()
|
||||
{
|
||||
$token = getVal('token', null, false);
|
||||
if (!$token) {
|
||||
$this->error('storno');
|
||||
}
|
||||
$payPalId = sqlQueryBuilder()
|
||||
->select("id, json_extract(note_admin, '$.paypalplus.id') as paypalID")
|
||||
->from('orders')
|
||||
->where("json_extract(note_admin, '$.paypalplus.token') = :token")
|
||||
->setParameter('token', $token)
|
||||
->execute()
|
||||
->fetchAssociative();
|
||||
|
||||
if (!$payPalId) {
|
||||
$this->error('storno');
|
||||
}
|
||||
$this->orderId = $payPalId['id'];
|
||||
$this->order = new \Order();
|
||||
$this->order->createFromDB($this->orderId);
|
||||
$this->error(translate('payment_storno', 'payment'));
|
||||
}
|
||||
|
||||
public static function getSettingsConfiguration(): array
|
||||
{
|
||||
return [
|
||||
'fields' => [
|
||||
'clientID' => [
|
||||
'title' => 'clientID',
|
||||
'type' => 'text',
|
||||
],
|
||||
'secret' => [
|
||||
'title' => 'secret',
|
||||
'type' => 'text',
|
||||
],
|
||||
'mode' => [
|
||||
'title' => 'Režim',
|
||||
'type' => 'select',
|
||||
'options' => ['live' => 'Produkční režim', 'sandbox' => 'Testovaci režim'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
354
class/payments/class.PayU.php
Normal file
354
class/payments/class.PayU.php
Normal file
@@ -0,0 +1,354 @@
|
||||
<?php
|
||||
|
||||
use KupShop\KupShopBundle\Config;
|
||||
|
||||
class PayU extends Payment
|
||||
{
|
||||
public static $name = 'PayU platební brána';
|
||||
|
||||
public $template = 'payment.PayU.tpl';
|
||||
protected $templateCart = 'payment.PayU.cart.tpl';
|
||||
|
||||
public $class = 'PayU';
|
||||
|
||||
public $url = 'https://secure.payu.com/paygw/';
|
||||
|
||||
// Session
|
||||
public $ts;
|
||||
public $session_id;
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
public function setOrder($order)
|
||||
{
|
||||
if (empty($order)) {
|
||||
$this->readParams();
|
||||
|
||||
$order = $this->getOrderId($this->session_id);
|
||||
}
|
||||
|
||||
if (empty($order)) {
|
||||
$payu_response = $this->makeStatusRequest();
|
||||
|
||||
$trans = $this->readResponse($payu_response);
|
||||
|
||||
$result = $this->readStatus($trans);
|
||||
|
||||
logError(__FILE__, __LINE__, 'Not Found: result:'.print_r($result, true));
|
||||
|
||||
if ($result['code'] == 2 || $result['code'] == 1) {
|
||||
// If payment canceled or not paid yet, confirm
|
||||
echo 'OK';
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($result['code'] == 99) {
|
||||
logError(__FILE__, __LINE__, 'Missed payment!! Creating ...');
|
||||
$order = intval($trans->order_id);
|
||||
}
|
||||
|
||||
logError(__FILE__, __LINE__, 'Not Found: payu_response:'.$payu_response);
|
||||
}
|
||||
|
||||
return parent::setOrder($order);
|
||||
}
|
||||
|
||||
public function storePaymentInfo()
|
||||
{
|
||||
$data = parent::storePaymentInfo();
|
||||
|
||||
$data['method'] = explode('-', getVal('payment_id'))[1];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function checkAuth()
|
||||
{
|
||||
$this->readParams();
|
||||
|
||||
if ($this->session_id && !getVal('cf')) {
|
||||
$parts = explode('_', $this->session_id);
|
||||
$_GET['cf'] = getVal(0, $parts);
|
||||
}
|
||||
}
|
||||
|
||||
public function readParams()
|
||||
{
|
||||
$this->session_id = getVal('session_id');
|
||||
$this->ts = time();
|
||||
}
|
||||
|
||||
public function readResponse($payu_response)
|
||||
{
|
||||
$xml = simplexml_load_string($payu_response);
|
||||
|
||||
if (!$xml) {
|
||||
logError(__FILE__, __LINE__, "Chyba parsovani xml: {$payu_response}");
|
||||
}
|
||||
|
||||
if (strval($xml->status) != 'OK') {
|
||||
logError(__FILE__, __LINE__, 'Transakce vratila chybu');
|
||||
}
|
||||
|
||||
return $xml->trans;
|
||||
}
|
||||
|
||||
public function readStatus($trans)
|
||||
{
|
||||
// incorrect POS ID number specified in response
|
||||
if ($trans->pos_id != $this->config['pos']) {
|
||||
return ['code' => false, 'message' => 'incorrect POS number'];
|
||||
}
|
||||
|
||||
// calculating signature for comparison with sig sent by PayU
|
||||
$sig = md5($trans->pos_id.$trans->session_id.$trans->order_id.$trans->status.$trans->amount.$trans->desc.$trans->ts.$this->config['key2']);
|
||||
|
||||
// incorrect signature in response in comparison to locally calculated one
|
||||
if ($trans->sig != $sig) {
|
||||
return ['code' => false, 'message' => 'incorrect signature'];
|
||||
}
|
||||
|
||||
// different messages depending on transaction status. For status description, see documentation
|
||||
switch ($trans->status) {
|
||||
case 1:
|
||||
return ['code' => $trans->status, 'message' => 'new'];
|
||||
case 2:
|
||||
return ['code' => $trans->status, 'message' => 'cancelled'];
|
||||
case 3:
|
||||
return ['code' => $trans->status, 'message' => 'rejected'];
|
||||
case 4:
|
||||
return ['code' => $trans->status, 'message' => 'started'];
|
||||
case 5:
|
||||
return ['code' => $trans->status, 'message' => 'awaiting receipt'];
|
||||
case 6:
|
||||
return ['code' => $trans->status, 'message' => 'no authorization'];
|
||||
case 7:
|
||||
return ['code' => $trans->status, 'message' => 'payment rejected'];
|
||||
case 99:
|
||||
return ['code' => $trans->status, 'message' => 'payment received - ended'];
|
||||
case 888:
|
||||
return ['code' => $trans->status, 'message' => 'incorrect status'];
|
||||
default:
|
||||
return ['code' => false, 'message' => 'no status'];
|
||||
}
|
||||
}
|
||||
|
||||
public function checkReceivedSignature()
|
||||
{
|
||||
// some parameters are missing
|
||||
if (!isset($_POST['pos_id']) || !isset($_POST['session_id']) || !isset($_POST['ts']) || !isset($_POST['sig'])) {
|
||||
logError(__FILE__, __LINE__, 'ERROR: EMPTY PARAMETERS');
|
||||
}
|
||||
|
||||
// received POS ID is different than expected
|
||||
if ($_POST['pos_id'] != $this->config['pos']) {
|
||||
logError(__FILE__, __LINE__, 'ERROR: INCORRECT POS ID');
|
||||
}
|
||||
|
||||
// verification of received signature
|
||||
$sig = md5($_POST['pos_id'].$_POST['session_id'].$_POST['ts'].$this->config['key2']);
|
||||
|
||||
// incorrect signature
|
||||
if ($_POST['sig'] != $sig) {
|
||||
logError(__FILE__, __LINE__, 'ERROR: INCORRECT SIGNATURE');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function makeStatusRequest()
|
||||
{
|
||||
// signature that will be sent to PayU with request
|
||||
$sig = md5($this->config['pos'].$this->session_id.$this->ts.$this->config['key1']);
|
||||
|
||||
// preparing parameters string to be sent to PayU
|
||||
$parameters = 'pos_id='.$this->config['pos'].'&session_id='.$this->session_id.'&ts='.$this->ts.'&sig='.$sig;
|
||||
|
||||
// sending request via CURL
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $this->url.'UTF/Payment/get');
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $parameters);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
|
||||
$payu_response = curl_exec($ch);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $payu_response;
|
||||
}
|
||||
|
||||
public function step($index, $message, $data = [])
|
||||
{
|
||||
if ($index == 1) {
|
||||
if (!empty($this->method)) {
|
||||
$data['method'] = $this->method;
|
||||
} else {
|
||||
$data['method'] = getVal('payu_method');
|
||||
}
|
||||
}
|
||||
|
||||
parent::step($index, $message, $data);
|
||||
}
|
||||
|
||||
public function processStep_1()
|
||||
{
|
||||
$this->ts = strval(time());
|
||||
$this->session_id = "{$this->order->getSecurityCode()}_{$this->ts}";
|
||||
$this->method = getVal('method');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function processStep_5()
|
||||
{
|
||||
$this->error('Platba byla zrušena.');
|
||||
}
|
||||
|
||||
public function processStep_6()
|
||||
{
|
||||
$this->updatePaymentStatus();
|
||||
|
||||
if ($this->status == Payment::STATUS_FINISHED) {
|
||||
$this->success('Platba proběhla v pořádku.');
|
||||
} else {
|
||||
$this->step(2, 'Platba byla zaznamenána, čeká se na zaplacení. O přijetí platby Vám zašleme email.');
|
||||
}
|
||||
}
|
||||
|
||||
public function processStep_7()
|
||||
{
|
||||
$this->checkReceivedSignature();
|
||||
|
||||
if ($this->updatePaymentStatus()) {
|
||||
logError(__FILE__, __LINE__, 'PayU step 7 - OK');
|
||||
echo 'OK';
|
||||
} else {
|
||||
logError(__FILE__, __LINE__, 'PayU step 7 - KO');
|
||||
echo 'KO';
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
public function ensurePaymentExists($trans)
|
||||
{
|
||||
if (!$this->getStatus($this->session_id)) {
|
||||
$this->createPayment($this->session_id, intval($trans->amount) / 100, ['id_payu' => $trans->id]);
|
||||
}
|
||||
}
|
||||
|
||||
public function updatePaymentStatus()
|
||||
{
|
||||
$payu_response = $this->makeStatusRequest();
|
||||
|
||||
logError(__FILE__, __LINE__, 'updatePaymentStatus: payu_response:'.$payu_response);
|
||||
|
||||
$trans = $this->readResponse($payu_response);
|
||||
|
||||
$result = $this->readStatus($trans);
|
||||
logError(__FILE__, __LINE__, 'updatePaymentStatus: response:'.print_r($result, true));
|
||||
|
||||
if ($result['code']) {
|
||||
if ($result['code'] == '2') {
|
||||
// transaction canceled
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->ensurePaymentExists($trans);
|
||||
|
||||
// change of transaction status in system of the shop
|
||||
if ($result['code'] == '99') {
|
||||
// payment sucessful so we send back OK
|
||||
if (!$this->setStatus(Payment::STATUS_FINISHED, $this->session_id)) {
|
||||
logError(__FILE__, __LINE__, 'Payment::updatePaymentStatus: setStatus failed!');
|
||||
}
|
||||
|
||||
return true;
|
||||
} elseif ($result['code'] == '4') {
|
||||
// transaction pending
|
||||
if (!$this->setStatus(Payment::STATUS_PENDING, $this->session_id)) {
|
||||
logError(__FILE__, __LINE__, 'Payment::updatePaymentStatus: setStatus failed!');
|
||||
}
|
||||
|
||||
return true;
|
||||
} elseif ($result['code'] == '1') {
|
||||
// transaction started
|
||||
if (!$this->setStatus(Payment::STATUS_CREATED, $this->session_id)) {
|
||||
logError(__FILE__, __LINE__, 'Payment::updatePaymentStatus: setStatus failed!');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
logError(__FILE__, __LINE__, 'updatePaymentStatus: not handled');
|
||||
|
||||
return false;
|
||||
} else {
|
||||
logError(__FILE__, __LINE__, 'updatePaymentStatus: error: code='.$result['code'].' message='.$result['message']."\n{$payu_response}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$methods = $this->getAvailableMethods();
|
||||
|
||||
return parent::getName().' - '.getVal($this->method, $methods, ['name' => 'Neznámý typ PayU platby'])['name'];
|
||||
}
|
||||
|
||||
public function getAvailableMethods()
|
||||
{
|
||||
if (!($methods = getCache('payu-methods'))) {
|
||||
$methods = $this->fetchAvailableMethods();
|
||||
setCache('payu-methods', $methods);
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
public function fetchAvailableMethods()
|
||||
{
|
||||
$key = substr($this->config['key1'], 0, 2);
|
||||
|
||||
$xml = new SimpleXMLElement($this->url."UTF/xml/{$this->config['pos']}/{$key}/paytype.xml", 0, true);
|
||||
$payTypes = $xml->xpath('//paytype');
|
||||
|
||||
$methods = [];
|
||||
|
||||
foreach ($payTypes as $payment) {
|
||||
$payment = (array) $payment;
|
||||
$payment['enable'] = $payment['enable'] == 'true';
|
||||
$methods[$payment['type']] = $payment;
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
18
class/payments/class.PlatebniKarta.php
Normal file
18
class/payments/class.PlatebniKarta.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
class PlatebniKarta extends Payment
|
||||
{
|
||||
public static $name = 'Platba kartou';
|
||||
protected ?string $defaultIcon = '../../common/static/payments/prodejna_kartou.svg';
|
||||
|
||||
public $class = 'PlatebniKarta';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_CARD;
|
||||
|
||||
public function requiresEET()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
45
class/payments/class.PosCustom.php
Normal file
45
class/payments/class.PosCustom.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
use KupShop\KupShopBundle\Context\PosContext;
|
||||
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\POSBundle\Event\PosPaymentEvent;
|
||||
use KupShop\POSBundle\Util\PosPaymentUtil;
|
||||
use Query\Operator;
|
||||
|
||||
class PosCustom extends Payment
|
||||
{
|
||||
public static $name = '[Pokladna] Nastavitelná platba';
|
||||
protected ?string $defaultIcon = '../../common/static/payments/prodejna_hotove.svg';
|
||||
|
||||
public $class = 'PosCustom';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_UNKNOWN;
|
||||
|
||||
public function createPayment($session, $price = null, $data = [])
|
||||
{
|
||||
$pos = Contexts::get(PosContext::class)->getActive();
|
||||
|
||||
$posPaymentUtil = ServiceContainer::getService(PosPaymentUtil::class);
|
||||
$posPaymentUtil->changeOrderDelivery($pos->getId(), $this->order, 'CUSTOM');
|
||||
|
||||
$idPayment = sqlQueryBuilder()
|
||||
->select('id_payment')
|
||||
->from('delivery_type', 'dt')
|
||||
->andWhere(Operator::equals(['dt.id' => $pos->getCustomDeliveryType()]))
|
||||
->execute()
|
||||
->fetchOne();
|
||||
|
||||
$eventDispatcher = ServiceContainer::getService('event_dispatcher');
|
||||
$eventDispatcher->dispatch(new PosPaymentEvent($pos, $price, $idPayment, $this->order), PosPaymentEvent::POS_CUSTOM_PAYMENT);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
return findModule(Modules::NEW_POS);
|
||||
}
|
||||
}
|
||||
31
class/payments/class.PosInvoice.php
Normal file
31
class/payments/class.PosInvoice.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use KupShop\POSBundle\Util\PosOrderUtil;
|
||||
|
||||
class PosInvoice extends Payment
|
||||
{
|
||||
public static $name = '[Pokladna] Platba fakturou';
|
||||
protected ?string $defaultIcon = '../../common/static/payments/prodejna_hotove.svg';
|
||||
|
||||
public $class = 'PosInvoice';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_INVOICE;
|
||||
|
||||
public function createPayment($session, $price = null, $data = [])
|
||||
{
|
||||
$this->order->changeStatus(
|
||||
PosOrderUtil::getHandledOrderStatus(),
|
||||
'Vytvořeno v pokladně '.date(\Settings::getDateFormat().' '.\Settings::getTimeFormat(), time()),
|
||||
false,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
return findModule(Modules::NEW_POS);
|
||||
}
|
||||
}
|
||||
43
class/payments/class.Prevodem.php
Normal file
43
class/payments/class.Prevodem.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
use KupShop\BankAutoPaymentBundle\PaymentSources\FioBankApi;
|
||||
|
||||
class Prevodem extends Payment
|
||||
{
|
||||
public static $name = 'Platba převodem';
|
||||
protected ?string $defaultIcon = '../../common/static/payments/prevodem.svg';
|
||||
|
||||
protected $templateOrderView = 'payment.Prevod.orderView.tpl';
|
||||
|
||||
public $class = 'Převod';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_TRANSFER;
|
||||
|
||||
public function hasPaymentDescription()
|
||||
{
|
||||
// Potřebujeme umožnit editaci v adminu i když to je vyplé v nastavení
|
||||
return (Settings::getDefault()['payment_config']['show_payment_instruction'] ?? 0) == 1
|
||||
|| (getVal('FORCE_QR') == 1 && !empty(getAdminUser()));
|
||||
}
|
||||
|
||||
public function requiresEET()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function loadPaymentDbConfig($dbcfg): array
|
||||
{
|
||||
$fioSetting = null;
|
||||
|
||||
if (findModule(\Modules::BANK_AUTO_PAYMENTS, 'fio_bank') && isset($dbcfg->payments[FioBankApi::getName()])) {
|
||||
$fioSetting = array_filter($dbcfg->payments[FioBankApi::getName()],
|
||||
function ($value) {
|
||||
return $value !== null && $value !== false && $value !== '';
|
||||
});
|
||||
}
|
||||
|
||||
return is_array($fioSetting) ? $fioSetting : [];
|
||||
}
|
||||
}
|
||||
175
class/payments/class.Quatro.php
Normal file
175
class/payments/class.Quatro.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use KupShop\KupShopBundle\Config;
|
||||
|
||||
/**
|
||||
* Dependencies: `composer require firebase/php-jwt=^5.4.0`
|
||||
* Class Quatro.
|
||||
*/
|
||||
class Quatro extends Payment
|
||||
{
|
||||
public static $name = 'Quatro';
|
||||
public $class = 'Quatro';
|
||||
protected $pay_method = Payment::METHOD_INSTALLMENTS;
|
||||
protected $templateOrderView = 'payment.Quatro.orderView.tpl';
|
||||
|
||||
public function getCalcUrl(Decimal $price): ?string
|
||||
{
|
||||
$price = roundPrice($price, -1, 'DB', 0)->asInteger();
|
||||
if (empty($this->config['seller']) || $price > 10000 || $price < 100) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "https://quatro.vub.sk/kalkulacka/{$this->config['seller']}?cenaTovaru={$price}";
|
||||
}
|
||||
|
||||
public function getGatewayUrl(): ?string
|
||||
{
|
||||
if (empty($this->config['seller'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "https://quatroapi.vub.sk/stores/{$this->config['seller']}/create-application";
|
||||
}
|
||||
|
||||
public function processStep_1()
|
||||
{
|
||||
}
|
||||
|
||||
public function processStep_2()
|
||||
{
|
||||
// hack protože natvrdo lepěj ? ke callbacku
|
||||
$cn = str_replace('?cn=', '', getVal('h'));
|
||||
$id = getVal('id');
|
||||
$state = getVal('state');
|
||||
$sign = getVal('hmacSign');
|
||||
|
||||
if (hash_hmac('sha1', "cn={$cn}&id={$id}&state={$state}", base64_decode($this->config['key'])) != strtolower($sign) && !isDevelopment()) {
|
||||
throw new \KupShop\OrderingBundle\Exception\PaymentException('Chyba ověření podpisu');
|
||||
}
|
||||
|
||||
$remainingPayment = roundPrice($this->order->getRemainingPayment())->asFloat();
|
||||
if ($remainingPayment > 0.00) {
|
||||
if (!$this->getPendingPayment()) {
|
||||
$this->createPayment(
|
||||
$id,
|
||||
$remainingPayment,
|
||||
['paymentClass' => self::class]
|
||||
);
|
||||
}
|
||||
|
||||
if (getVal('state') == 'signed') {
|
||||
$paymentStatus = Payment::STATUS_FINISHED;
|
||||
} elseif (getVal('state') == 'canceled') {
|
||||
$paymentStatus = Payment::STATUS_STORNO;
|
||||
} else {
|
||||
$paymentStatus = Payment::STATUS_PENDING;
|
||||
}
|
||||
|
||||
// change payment status
|
||||
if (!$this->setStatus($paymentStatus, $id)) {
|
||||
logError(__FILE__, __LINE__, 'Payment::updatePaymentStatus: setStatus failed!');
|
||||
throw new \Exception('Set status failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getSubject()
|
||||
{
|
||||
$subject = '';
|
||||
foreach ($this->order->fetchItems() as $item) {
|
||||
if (!$item['id_product']) {
|
||||
continue;
|
||||
}
|
||||
/** @var Product $product */
|
||||
$product = $item['product'];
|
||||
$subject .= "{$product->fetchSections()[0]->getName()} - {$product->fetchProducer()['name']} - {$product->title},";
|
||||
}
|
||||
$subject = substr($subject, 0, -1);
|
||||
|
||||
if (strlen($subject) > 250) {
|
||||
$subject = substr($subject, 0, 247).'...';
|
||||
}
|
||||
|
||||
return $subject;
|
||||
}
|
||||
|
||||
public function getPayload()
|
||||
{
|
||||
$payload = [
|
||||
'application' => [
|
||||
'orderNumber' => $this->order->order_no,
|
||||
'applicant' => [
|
||||
'firstName' => $this->order->invoice_name,
|
||||
'lastName' => $this->order->invoice_surname,
|
||||
'email' => $this->order->invoice_email,
|
||||
'mobile' => $this->order->invoice_phone,
|
||||
'permanentAddress' => [
|
||||
'addressLine' => $this->order->invoice_street,
|
||||
'city' => $this->order->invoice_city,
|
||||
'zipCode' => $this->order->invoice_zip,
|
||||
'country' => $this->order->invoice_country,
|
||||
],
|
||||
],
|
||||
'subject' => $this->getSubject(),
|
||||
'totalAmount' => $this->order->total_price->asFloat(),
|
||||
'goodsAction' => null,
|
||||
'callback' => $this->getGenericPaymentUrl(2, ['h' => '']),
|
||||
],
|
||||
'iat' => time(),
|
||||
];
|
||||
$jwt = JWT::encode($payload, base64_decode($this->config['key']), 'HS256');
|
||||
|
||||
return $jwt;
|
||||
}
|
||||
|
||||
// https://www.kupshop.local/platby/Quatro/1/49698/?cf=2171114784fcbceb29f9b6bdc6f07e48&h=?cn=1000018425&id=0514186c-3eb6-4150-ac31-048eb330507d&state=canceled&hmacSign=95F512D8D7F14A02376A78CE94382A3F2301DA5E
|
||||
|
||||
public function accept($totalPrice, $freeDelivery)
|
||||
{
|
||||
$totalPrice = $totalPrice->getPriceWithVat()->asFloat();
|
||||
if ($totalPrice <= 0 && $this->order) {
|
||||
$totalPrice = $this->order->total_price;
|
||||
}
|
||||
|
||||
return parent::accept($totalPrice, $freeDelivery) && $totalPrice >= 100 && $totalPrice <= 10000;
|
||||
}
|
||||
|
||||
public static function getSettingsConfiguration(): array
|
||||
{
|
||||
return [
|
||||
'fields' => [
|
||||
'key' => [
|
||||
'title' => 'Bezpečnostní klíč',
|
||||
'type' => 'text',
|
||||
],
|
||||
'seller' => [
|
||||
'title' => 'Kód prodejny',
|
||||
'type' => 'text',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function startPayment()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
292
class/payments/class.Saferpay.php
Normal file
292
class/payments/class.Saferpay.php
Normal file
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
use KupShop\KupShopBundle\Config;
|
||||
use Ticketpark\SaferpayJson\Request\Container;
|
||||
use Ticketpark\SaferpayJson\Request\Exception\HttpRequestException;
|
||||
use Ticketpark\SaferpayJson\Request\Exception\SaferpayErrorException;
|
||||
use Ticketpark\SaferpayJson\Request\PaymentPage\AssertRequest;
|
||||
use Ticketpark\SaferpayJson\Request\PaymentPage\InitializeRequest;
|
||||
use Ticketpark\SaferpayJson\Request\RequestConfig;
|
||||
use Ticketpark\SaferpayJson\Request\Transaction\CaptureRequest;
|
||||
|
||||
/**
|
||||
* Requires composer package "ticketpark/saferpay-json-api: ~4.4".
|
||||
*/
|
||||
class Saferpay extends Payment
|
||||
{
|
||||
public static $name = 'Saferpay platební brána';
|
||||
|
||||
public $template = 'payment.Saferpay.tpl';
|
||||
|
||||
public $class = 'Saferpay';
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
public function getPaymentUrl($step = 1)
|
||||
{
|
||||
return createScriptURL([
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => $step,
|
||||
'class' => $this->class,
|
||||
'paymentId' => $this->paymentId,
|
||||
'absolute' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
private function getPayment($id = null)
|
||||
{
|
||||
if (isset($id)) {
|
||||
$payment = $this->selectSQL('order_payments', ['id' => $id])
|
||||
->fetch();
|
||||
if (!$payment) {
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
$payment['decoded_data'] = json_decode($payment['payment_data']);
|
||||
|
||||
return $payment;
|
||||
}
|
||||
$kupshopPayment = $this->getPendingPayment();
|
||||
if (!$kupshopPayment) {
|
||||
$kupshopPayment = $this->initializeSaferpayPayment();
|
||||
}
|
||||
$this->paymentId = $kupshopPayment['id'];
|
||||
|
||||
return $kupshopPayment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array $kupshopPayment
|
||||
*/
|
||||
private function initializeSaferpayPayment(): array
|
||||
{
|
||||
$amount = roundPrice($this->order->getRemainingPayment())->asFloat();
|
||||
$this->createPayment(
|
||||
null,
|
||||
$amount,
|
||||
['paymentClass' => self::class]
|
||||
);
|
||||
|
||||
// -----------------------------
|
||||
// Step 1:
|
||||
// Initialize the required payment page data
|
||||
// See https://saferpay.github.io/jsonapi/#Payment_v1_PaymentPage_Initialize
|
||||
$requestConfig = new RequestConfig(
|
||||
$this->config['apiKey'],
|
||||
$this->config['apiSecret'],
|
||||
$this->config['customerID'],
|
||||
getVal('test', $this->config, false)
|
||||
);
|
||||
|
||||
$containerAmount = new Container\Amount(
|
||||
$amount * 100, // amount in cents
|
||||
$this->order->currency
|
||||
);
|
||||
|
||||
$containerPayment = new Container\Payment($containerAmount);
|
||||
$containerPayment
|
||||
->setOrderId($this->order->order_no)
|
||||
->setDescription('Order No. '.$this->order->order_no);
|
||||
|
||||
$returnUrls = new Container\ReturnUrls(
|
||||
$this->getPaymentUrl(2),
|
||||
$this->getPaymentUrl(3),
|
||||
$this->getPaymentUrl(3)
|
||||
);
|
||||
|
||||
// -----------------------------
|
||||
// Step 2:
|
||||
// Create the request with required data
|
||||
$initializeRequest = new InitializeRequest(
|
||||
$requestConfig,
|
||||
$this->config['terminalID'],
|
||||
$containerPayment,
|
||||
$returnUrls
|
||||
);
|
||||
|
||||
$containerAddress = (new Container\Address())
|
||||
->setFirstName($this->order->invoice_name)
|
||||
->setLastName($this->order->invoice_surname)
|
||||
->setStreet($this->order->invoice_street)
|
||||
->setZip($this->order->invoice_zip)
|
||||
->setCity($this->order->invoice_city)
|
||||
->setCountryCode($this->order->invoice_country);
|
||||
$containerPayer = (new Container\Payer())
|
||||
->setLanguageCode('de')
|
||||
->setBillingAddress($containerAddress);
|
||||
$initializeRequest->setPayer($containerPayer);
|
||||
|
||||
// Note: More data can be provided to InitializeRequest with setters,
|
||||
// for example: $initializeRequest->setPayer()
|
||||
// See Saferpay documentation for available options.
|
||||
|
||||
// -----------------------------
|
||||
// Step 3:
|
||||
// Execute and check for successful response
|
||||
try {
|
||||
$response = $initializeRequest->execute();
|
||||
} catch (SaferpayErrorException $e) {
|
||||
logError(__FILE__, __LINE__, 'Saferpay::initializeSaferpayPayment: ErrorResponse! ErrorName: '.$e->getErrorResponse()->getErrorName().', errorDetail: '.json_encode($e->getErrorResponse()->getErrorDetail()));
|
||||
if (!$this->setStatus(Payment::STATUS_STORNO, $this->paymentId)) {
|
||||
throw new Exception('Saferpay::updatePaymentStatus: setStatus failed!');
|
||||
}
|
||||
$this->step(-3, 'storno');
|
||||
} catch (HttpRequestException $e) {
|
||||
$this->deletePayment();
|
||||
$this->step(-1, 'Server error. Try again please.');
|
||||
}
|
||||
|
||||
// Save the response token, you will need it later to verify the payment
|
||||
$token = $response->getToken();
|
||||
// Redirect to the payment page
|
||||
$redirectURL = $response->getRedirectUrl();
|
||||
|
||||
$paymentRow = $this->selectSQL('order_payments', ['id' => $this->paymentId])->fetch();
|
||||
$json = json_decode($paymentRow['payment_data']);
|
||||
$json->session = $this->paymentId;
|
||||
$json->token = $token;
|
||||
$json->redirectURL = $redirectURL;
|
||||
$paymentRow['payment_data'] = json_encode($json);
|
||||
$logger = \KupShop\KupShopBundle\Util\Compat\ServiceContainer::getService('logger');
|
||||
$logger->notice('Saferpay:initializeSaferpayPayment(): '.$paymentRow['id_order'], ['response' => $response, 'paymentRow' => $paymentRow]);
|
||||
$this->updateSQL('order_payments', $paymentRow, ['id' => $this->paymentId]);
|
||||
$paymentRow['decoded_data'] = $json;
|
||||
|
||||
return $paymentRow;
|
||||
}
|
||||
|
||||
/* Payment steps */
|
||||
public function processStep_1()
|
||||
{
|
||||
$amount = roundPrice($this->order->getRemainingPayment())->asFloat();
|
||||
if ($amount <= (float) 0) {
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
|
||||
$payment = $this->getPayment();
|
||||
if (!isset($payment['decoded_data']->redirectURL)) {
|
||||
$this->updateSQL('order_payments', ['status' => Payment::STATUS_STORNO], ['id' => $payment['id']]);
|
||||
$payment = $this->getPayment();
|
||||
}
|
||||
redirection($payment['decoded_data']->redirectURL);
|
||||
}
|
||||
|
||||
public function processStep_2()
|
||||
{
|
||||
$payment = $this->getPayment(getVal('paymentId', null, false));
|
||||
|
||||
// Return success if payment already finished
|
||||
if ($payment['status'] === strval(Payment::STATUS_FINISHED)) {
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
}
|
||||
$token = $payment['decoded_data']->token;
|
||||
|
||||
// -----------------------------
|
||||
// Step 1:
|
||||
// Prepare the assert request
|
||||
// See http://saferpay.github.io/jsonapi/#Payment_v1_PaymentPage_Assert
|
||||
$requestConfig = new RequestConfig(
|
||||
$this->config['apiKey'],
|
||||
$this->config['apiSecret'],
|
||||
$this->config['customerID'],
|
||||
getVal('test', $this->config, false)
|
||||
);
|
||||
|
||||
// -----------------------------
|
||||
// Step 2:
|
||||
// Create the request with required data
|
||||
$assertRequest = new AssertRequest(
|
||||
$requestConfig,
|
||||
$token,
|
||||
);
|
||||
|
||||
// -----------------------------
|
||||
// Step 3:
|
||||
// Execute and check for successful response
|
||||
try {
|
||||
$response = $assertRequest->execute();
|
||||
} catch (SaferpayErrorException $e) {
|
||||
$logger = \KupShop\KupShopBundle\Util\Compat\ServiceContainer::getService('logger');
|
||||
$logger->notice('Saferpay:processStep_2() ErrorResponse: '.$payment['id_order'], ['response' => $e->getErrorResponse(), 'payment' => $payment]);
|
||||
if (!$this->setStatus(Payment::STATUS_STORNO, $payment['id'])) {
|
||||
throw new Exception('Saferpay::updatePaymentStatus: setStatus failed! (ErrorResponse: '.$e->getErrorResponse()->getErrorMessage().')');
|
||||
}
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
|
||||
// A transaction id you received with a successful assert request (see ../PaymentPage/example-assert.php)
|
||||
$transactionId = $response->getTransaction()->getId();
|
||||
|
||||
// -----------------------------
|
||||
// Step 1:
|
||||
// Prepare the capture request
|
||||
// https://saferpay.github.io/jsonapi/#Payment_v1_Transaction_Capture
|
||||
$transactionReference = (new Container\TransactionReference())
|
||||
->setTransactionId($transactionId);
|
||||
|
||||
// -----------------------------
|
||||
// Step 2:
|
||||
// Create the request with required data
|
||||
$captureRequest = new CaptureRequest(
|
||||
$requestConfig,
|
||||
$transactionReference
|
||||
);
|
||||
|
||||
// -----------------------------
|
||||
// Step 3:
|
||||
// Execute and check for successful response
|
||||
try {
|
||||
$captureResponse = $captureRequest->execute();
|
||||
} catch (SaferpayErrorException $e) {
|
||||
/** @var $sentryLogger \KupShop\KupShopBundle\Util\Logging\SentryLogger */
|
||||
$sentryLogger = \KupShop\KupShopBundle\Util\Compat\ServiceContainer::getService(\KupShop\KupShopBundle\Util\Logging\SentryLogger::class);
|
||||
$sentryLogger->captureMessage(
|
||||
$e->getErrorResponse()->getErrorMessage(),
|
||||
[],
|
||||
['transaction_id' => $transactionId],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// save transactionID
|
||||
$paymentRow = $this->selectSQL('order_payments', ['id' => $payment['id']])->fetch();
|
||||
$json = json_decode($paymentRow['payment_data']);
|
||||
$json->transactionID = $transactionId;
|
||||
$paymentRow['payment_data'] = json_encode($json);
|
||||
$this->updateSQL('order_payments', $paymentRow, ['id' => $payment['id']]);
|
||||
$logger = \KupShop\KupShopBundle\Util\Compat\ServiceContainer::getService('logger');
|
||||
$logger->notice('Saferpay:processStep_2() finished: '.$paymentRow['id_order'], ['response' => $response, 'captureResponse' => $captureResponse, 'paymentRow' => $paymentRow]);
|
||||
// change payment status to finished
|
||||
if (!$this->setStatus(Payment::STATUS_FINISHED, $payment['id'])) {
|
||||
throw new Exception('Saferpay::updatePaymentStatus: setStatus failed!');
|
||||
}
|
||||
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
}
|
||||
|
||||
public function processStep_3()
|
||||
{
|
||||
$payment = $this->getPayment(getVal('paymentId', null, false));
|
||||
if (!$this->setStatus(Payment::STATUS_STORNO, $payment['id'])) {
|
||||
throw new Exception('Saferpay::updatePaymentStatus: setStatus failed!');
|
||||
}
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
449
class/payments/class.ThePay.php
Normal file
449
class/payments/class.ThePay.php
Normal file
@@ -0,0 +1,449 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
||||
395
class/payments/class.ThePay20.php
Normal file
395
class/payments/class.ThePay20.php
Normal file
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
use KupShop\AdminBundle\Util\ActivityLog;
|
||||
use KupShop\KupShopBundle\Context\CurrencyContext;
|
||||
use KupShop\KupShopBundle\Context\DomainContext;
|
||||
use KupShop\KupShopBundle\Context\LanguageContext;
|
||||
use KupShop\KupShopBundle\Util\Contexts;
|
||||
use KupShop\KupShopBundle\Util\EANValidator;
|
||||
use KupShop\OrderingBundle\Exception\PaymentException;
|
||||
use KupShop\OrderingBundle\Util\Order\PaymentOrderSubMethodTrait;
|
||||
use ThePay\ApiClient\Exception\ApiException;
|
||||
use ThePay\ApiClient\Exception\MissingExtensionException;
|
||||
use ThePay\ApiClient\Filter\PaymentMethodFilter;
|
||||
use ThePay\ApiClient\Model\CreatePaymentCustomer;
|
||||
use ThePay\ApiClient\Model\CreatePaymentItem;
|
||||
use ThePay\ApiClient\ValueObject\LanguageCode;
|
||||
|
||||
/**
|
||||
* composer require "thepay/api-client":"1.2.4".
|
||||
*/
|
||||
class ThePay20 extends Payment
|
||||
{
|
||||
use PaymentOrderSubMethodTrait;
|
||||
|
||||
public static $name = 'ThePay 2.0 platební brána';
|
||||
|
||||
public static bool $canAutoReturn = true;
|
||||
|
||||
protected $templateCart = 'payment.ThePay20.cart.tpl';
|
||||
|
||||
public $class = 'ThePay20';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
protected $apiContext;
|
||||
|
||||
protected $gatewayUrl;
|
||||
|
||||
/** Get payment icon url and name.
|
||||
* @return array|null
|
||||
*/
|
||||
public function getIcon()
|
||||
{
|
||||
$method = getVal($this->method, $this->getAvailableMethods());
|
||||
if ($method) {
|
||||
return [
|
||||
'url' => $method['image'],
|
||||
'name' => $method['name'],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getGatewayUrl(): string
|
||||
{
|
||||
if (!isset($this->gatewayUrl)) {
|
||||
$this->processStep_1(false);
|
||||
}
|
||||
|
||||
return $this->gatewayUrl;
|
||||
}
|
||||
|
||||
public function processStep_1($redirectToGateway = true)
|
||||
{
|
||||
$apiContext = $this->getApiContext();
|
||||
|
||||
if (empty($this->method)) {
|
||||
$paymentData = $this->order->getData('payment_data');
|
||||
if ($paymentData) {
|
||||
$this->loadPaymentInfo($paymentData);
|
||||
}
|
||||
}
|
||||
|
||||
$paymentUid = uniqid($this->order->id.'_', true);
|
||||
$currencyContext = Contexts::get(CurrencyContext::class);
|
||||
$params = new \ThePay\ApiClient\Model\CreatePaymentParams(
|
||||
$amount = toDecimal($this->order->getRemainingPayment())
|
||||
->mul(DecimalConstants::hundred())->asInteger(),
|
||||
$currencyContext->getActiveId(),
|
||||
$paymentUid
|
||||
);
|
||||
$params->setReturnUrl($this->getGenericPaymentUrl(2));
|
||||
$params->setNotifUrl($this->getGenericPaymentUrl(10));
|
||||
$params->setOrderId($this->order->order_no);
|
||||
// $params->setDescriptionForCustomer($this->order->order_no);
|
||||
// $params->setDescriptionForMerchant($this->order->order_no);
|
||||
$params->setCustomer(new CreatePaymentCustomer(
|
||||
$this->order->invoice_name,
|
||||
$this->order->invoice_surname,
|
||||
$this->order->invoice_email,
|
||||
$this->order->invoice_phone,
|
||||
($this->order->invoice_country != '' && $this->order->invoice_city != '' && $this->order->invoice_zip != '' && $this->order->invoice_street != '') ?
|
||||
new \ThePay\ApiClient\Model\Address(
|
||||
$this->order->invoice_country,
|
||||
$this->order->invoice_city,
|
||||
$this->order->invoice_zip,
|
||||
$this->order->invoice_street
|
||||
) : null
|
||||
));
|
||||
|
||||
foreach ($this->order->fetchItems() as $item) {
|
||||
if ($item['pieces'] < 1) {
|
||||
continue;
|
||||
}
|
||||
$tmpItem = [
|
||||
'type' => is_null($item['id_product']) ? 'delivery' : 'item',
|
||||
'name' => $item['descr'],
|
||||
'amount' => $item['total_price']['value_with_vat']->mul(DecimalConstants::hundred())->asInteger(),
|
||||
'count' => (int) $item['pieces'],
|
||||
];
|
||||
if (isset($item['ean']) && EANValidator::checkEAN($item['ean'])) {
|
||||
$tmpItem['ean'] = $item['ean'];
|
||||
}
|
||||
$params->addItem(
|
||||
new CreatePaymentItem($tmpItem['type'], $tmpItem['name'], $tmpItem['amount'], $tmpItem['count'], $tmpItem['ean'] ?? null)
|
||||
);
|
||||
}
|
||||
|
||||
// check PaymentMethod beforehand: this should eliminate error "21 in not valid value"
|
||||
// when the order was created with the old ThePay and then the eshop switched to ThePay20
|
||||
try {
|
||||
$tmp = new \ThePay\ApiClient\ValueObject\PaymentMethodCode($this->method);
|
||||
$method = $this->method;
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$method = null;
|
||||
}
|
||||
|
||||
$response = $apiContext->createPayment($params, $method);
|
||||
$this->gatewayUrl = $response->getPayUrl();
|
||||
|
||||
$this->createPayment(
|
||||
$paymentUid,
|
||||
Decimal::fromInteger($amount)->mul(Decimal::fromFloat(0.01))->asFloat(),
|
||||
['paymentClass' => self::class]
|
||||
);
|
||||
if ($redirectToGateway) {
|
||||
redirection($this->gatewayUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return from gateway */
|
||||
public function processStep_2()
|
||||
{
|
||||
$this->checkPaymentStatus();
|
||||
}
|
||||
|
||||
/** Webhook handler */
|
||||
public function processStep_10()
|
||||
{
|
||||
$this->setIsNotification(true);
|
||||
$this->checkPaymentStatus();
|
||||
$this->sendNotificationResponse(200, 'OK');
|
||||
}
|
||||
|
||||
private function checkPaymentStatus()
|
||||
{
|
||||
$paymentID = getVal('payment_uid', null, false);
|
||||
if (!$paymentID) {
|
||||
$this->error(translate('payment_id_missing', 'payment'));
|
||||
}
|
||||
|
||||
$response = $this->getApiContext()->getPayment($paymentID);
|
||||
$thirdPartyPaymentState = $response->getState(); // https://dataapi21.docs.apiary.io/#paymentStatesEnum
|
||||
|
||||
// determine kupshop unified payment state from third party payment state
|
||||
if (in_array($thirdPartyPaymentState, ['waiting_for_payment', 'waiting_for_confirmation'])) {
|
||||
$this->status = $unifiedState = Payment::STATUS_CREATED;
|
||||
} elseif (in_array($thirdPartyPaymentState, ['paid'])) {
|
||||
$this->status = $unifiedState = Payment::STATUS_FINISHED;
|
||||
} elseif (in_array($thirdPartyPaymentState, [
|
||||
'expired', 'preauth_cancelled', 'preauth_expired', 'partially_refunded',
|
||||
])
|
||||
) {
|
||||
$this->status = $unifiedState = Payment::STATUS_STORNO;
|
||||
} else {
|
||||
logError(__FILE__, __LINE__, 'ThePay20 unexpected payment state "'.$thirdPartyPaymentState.'"');
|
||||
$this->error(translate('payment_unexpected_status', 'payment'));
|
||||
}
|
||||
|
||||
// change payment status
|
||||
if (!$this->setStatus($unifiedState, $paymentID)) {
|
||||
logError(__FILE__, __LINE__, 'ThePay20::updatePaymentStatus: setStatus failed!');
|
||||
throw new \Exception('Set status failed');
|
||||
}
|
||||
|
||||
$this->setPaymentSubMethod($response->getPaymentMethod());
|
||||
|
||||
switch ($unifiedState) {
|
||||
case Payment::STATUS_FINISHED:
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
break;
|
||||
case Payment::STATUS_STORNO:
|
||||
$this->info(translate('payment_storno', 'payment'));
|
||||
break;
|
||||
case Payment::STATUS_PENDING:
|
||||
case Payment::STATUS_CREATED:
|
||||
$this->info(translate('payment_waiting_for_confirmation', 'payment'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function getApiContext(): ThePay\ApiClient\TheClient
|
||||
{
|
||||
if (!isset($this->apiContext)) {
|
||||
$languageContext = Contexts::get(LanguageContext::class);
|
||||
$apiContext = new \ThePay\ApiClient\TheClient(new \ThePay\ApiClient\TheConfig(
|
||||
$this->config['merchantId'],
|
||||
$this->config['projectId'],
|
||||
$this->config['apiPassword'],
|
||||
!empty($this->config['test']) ? 'https://demo.api.thepay.cz/' : 'https://api.thepay.cz/',
|
||||
!empty($this->config['test']) ? 'https://demo.gate.thepay.cz/' : 'https://gate.thepay.cz/',
|
||||
$this->getPreferredLanguage($this->config['language'] ?? $languageContext->getActiveId())
|
||||
));
|
||||
$this->apiContext = $apiContext;
|
||||
}
|
||||
|
||||
return $this->apiContext;
|
||||
}
|
||||
|
||||
public function getAvailableMethods()
|
||||
{
|
||||
$currencyContext = Contexts::get(CurrencyContext::class);
|
||||
$currency = $currencyContext->getActiveId();
|
||||
$domainContext = Contexts::get(DomainContext::class);
|
||||
$domain = $domainContext->getActiveId();
|
||||
$languageContext = Contexts::get(LanguageContext::class);
|
||||
$language = $languageContext->getActiveId();
|
||||
$cacheKey = "thepay20-methods-{$currency}-{$domain}-{$language}";
|
||||
$methods = getCache($cacheKey);
|
||||
if (!isset($methods) || $methods === false || !is_array($methods)) {
|
||||
$methods = $this->fetchAvailableMethods($currency);
|
||||
setCache($cacheKey, $methods);
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
public function fetchAvailableMethods($currency)
|
||||
{
|
||||
// check config presence
|
||||
if (empty($this->config['merchantId'])
|
||||
|| empty($this->config['projectId'])
|
||||
|| empty($this->config['apiPassword'])
|
||||
) {
|
||||
addActivityLog(
|
||||
ActivityLog::SEVERITY_ERROR,
|
||||
ActivityLog::TYPE_COMMUNICATION,
|
||||
'ThePay: Chybí údaje pro požadovaný jazyk v nastavení eshopu, případně využijte omezení platby/dopravy, aby se způsob doručení v tomto jazyce nenabízel.',
|
||||
[
|
||||
...ActivityLog::addObjectData([$this->getId() => $this->getName()], 'deliveryPayment'),
|
||||
...['context-languageID' => Contexts::get(LanguageContext::class)->getActiveId()],
|
||||
]
|
||||
);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$overwriteMethodNames = $this->config['overwriteMethodNames'] ?? [];
|
||||
$methods = [];
|
||||
try {
|
||||
$preferredLang = $this->getPreferredLanguage(
|
||||
Contexts::get(LanguageContext::class)->getActiveId()
|
||||
);
|
||||
|
||||
$overrideNamePerLang = [
|
||||
'de' => ['card' => 'Kartenzahlung'], // preferred lang falls back to 'de' for 'at' too
|
||||
'pl' => ['card' => 'Płatność kartą'],
|
||||
];
|
||||
foreach ($this->getApiContext()->getActivePaymentMethods(new PaymentMethodFilter([$currency], [], []), new LanguageCode($preferredLang)) as $method) {
|
||||
$methods[$method->getCode()] = [
|
||||
'name' => $overwriteMethodNames[$method->getCode()] ?? $overrideNamePerLang[$preferredLang][$method->getCode()] ?? $method->getTitle(),
|
||||
'id' => $method->getCode(),
|
||||
'image' => $method->getImageUrl()->getValue(),
|
||||
];
|
||||
}
|
||||
} catch (ApiException|MissingExtensionException|\RuntimeException $e) {
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, 'Chyba ThePay při načítání platebních metod: '.$e->getMessage());
|
||||
$sentry = getRaven();
|
||||
$sentry->captureException($e);
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function doReturnPayment(array $payment, float $amount)
|
||||
{
|
||||
$amountCents = (int) floor($amount * -100); // musí být integer v centech
|
||||
$full = (($payment['price'] + $amount) == 0);
|
||||
try {
|
||||
$this->getApiContext()->createPaymentRefund($payment['payment_data']['session'], $amountCents, 'vrácení '.(($full) ? 'platby' : 'přeplatku'));
|
||||
$result = ['id' => $payment['payment_data']['session']];
|
||||
|
||||
return $result;
|
||||
} catch (ApiException|InvalidArgumentException|RuntimeException $e) {
|
||||
$message = translate('returnFailed', 'orderPayment');
|
||||
$message .= translate('returnFailedMessage', 'orderPayment').$e->getMessage();
|
||||
|
||||
throw new PaymentException($message);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Check paid orders */
|
||||
public function checkPaidOrders()
|
||||
{
|
||||
if (getVal('test', $this->config)) {
|
||||
return false;
|
||||
}
|
||||
if (empty($this->config['merchantId']) || empty($this->config['projectId']) || empty($this->config['apiPassword'])) {
|
||||
return false;
|
||||
}
|
||||
$context = $this->getApiContext();
|
||||
$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))')
|
||||
->execute();
|
||||
foreach ($orderPayments as $orderPayment) {
|
||||
$paymentData = json_decode($orderPayment['payment_data'], true);
|
||||
if ($paymentData['paymentClass'] !== 'ThePay20' || empty($paymentData['session'])) {
|
||||
continue;
|
||||
}
|
||||
$thirdPartyPaymentState = false;
|
||||
try {
|
||||
$response = $context->getPayment($paymentData['session']);
|
||||
$thirdPartyPaymentState = $response->getState(); // https://dataapi21.docs.apiary.io/#paymentStatesEnum
|
||||
} catch (ApiException|InvalidArgumentException $e) {
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, 'ThePay error: '.$e->getMessage());
|
||||
continue;
|
||||
}
|
||||
if ($thirdPartyPaymentState === 'paid') {
|
||||
$this->setOrder($orderPayment['id_order']);
|
||||
try {
|
||||
$this->setStatus(Payment::STATUS_FINISHED, $paymentData['session']);
|
||||
} catch (\KupShop\KupShopBundle\Exception\RedirectException $e) {
|
||||
throw $e;
|
||||
} catch (Exception $e) {
|
||||
getRaven()->captureException($e);
|
||||
}
|
||||
} elseif ($thirdPartyPaymentState === 'expired' || $thirdPartyPaymentState === 'partially_refunded' || $thirdPartyPaymentState === 'preauth_cancelled') {
|
||||
$this->setOrder($orderPayment['id_order']);
|
||||
try {
|
||||
$this->setStatus(Payment::STATUS_STORNO, $paymentData['session']);
|
||||
} catch (\KupShop\KupShopBundle\Exception\RedirectException $e) {
|
||||
throw $e;
|
||||
} catch (Exception $e) {
|
||||
getRaven()->captureException($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getPreferredLanguage(string $language): string
|
||||
{
|
||||
$thePaySupportedLanguages = [
|
||||
'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo',
|
||||
'br', 'bs', 'ca', 'ce', 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee', 'el', 'en', 'eo', 'es',
|
||||
'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',
|
||||
'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj',
|
||||
'kk', 'kl', 'km', 'kn', 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv',
|
||||
'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mo', 'mr', 'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr',
|
||||
'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc',
|
||||
'sd', 'se', 'sg', 'sh', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta', 'te', 'tg',
|
||||
'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo',
|
||||
'xh', 'yi', 'yo', 'za', 'zh', 'zu',
|
||||
];
|
||||
|
||||
$preferredLang = $language;
|
||||
|
||||
if (!in_array($preferredLang, $thePaySupportedLanguages)) {
|
||||
if ($preferredLang === 'at') {
|
||||
$preferredLang = 'de'; // use german language for at - austria
|
||||
} else {
|
||||
$preferredLang = 'en'; // if not supported fallback to english
|
||||
}
|
||||
}
|
||||
|
||||
return $preferredLang;
|
||||
}
|
||||
}
|
||||
509
class/payments/class.TwistoPay.php
Normal file
509
class/payments/class.TwistoPay.php
Normal file
@@ -0,0 +1,509 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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 Query\Operator;
|
||||
|
||||
/**
|
||||
* Implementace pouze pro "Immediate Capture"
|
||||
* Klient musí mít v twisto portalu povoleno "Okamžité odbavení" (Immediate Capture).
|
||||
*/
|
||||
class TwistoPay extends Payment
|
||||
{
|
||||
public static $name = 'Twisto Pay';
|
||||
|
||||
public $class = 'TwistoPay';
|
||||
|
||||
protected ?string $defaultIcon = '../../common/static/payments/twisto_pay.svg';
|
||||
|
||||
public static bool $canAutoReturn = true;
|
||||
|
||||
protected string $apiUrl = 'https://api.twisto.cz/psp/smi';
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
public function getPaymentUrl()
|
||||
{
|
||||
return createScriptURL([
|
||||
's' => 'payment',
|
||||
'IDo' => $this->order->id,
|
||||
'cf' => $this->order->getSecurityCode(),
|
||||
'step' => 1,
|
||||
'class' => $this->class,
|
||||
'absolute' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function processStep_1()
|
||||
{
|
||||
if ($paymentExists = $this->checkPaymentAlreadyExists()) {
|
||||
if (empty($paymentExists['sessionId'])) {
|
||||
$this->info(translate('payment_already_exists', 'payment'));
|
||||
}
|
||||
$redirectExists = $this->requestCheckoutData($paymentExists['sessionId']);
|
||||
if (empty($redirectExists['uri'])) {
|
||||
$this->info(translate('payment_already_exists', 'payment'));
|
||||
}
|
||||
redirection($redirectExists['uri']);
|
||||
}
|
||||
|
||||
$checkoutData = $this->getCheckoutData();
|
||||
|
||||
$redirectData = $this->getCheckoutRedirect($checkoutData);
|
||||
|
||||
if (empty($redirectData['id']) || empty($redirectData['uri'])) {
|
||||
$this->createApiException(['message' => 'ID or URI not received', 'id' => $redirectData['id'], 'uri' => $redirectData['uri']]);
|
||||
}
|
||||
|
||||
$this->createPayment($redirectData['id'], $redirectData['order']['amount'], ['paymentClass' => $this->class]);
|
||||
|
||||
redirection($redirectData['uri']);
|
||||
}
|
||||
|
||||
protected function checkPaymentAlreadyExists()
|
||||
{
|
||||
return 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(Operator::equals(['o.order_no' => $this->order->order_no]))
|
||||
->andWhere(Operator::orX(
|
||||
JsonOperator::contains('op.payment_data', 'paymentClass', 'TwistoPay'),
|
||||
JsonOperator::contains('op.payment_data', 'paymentClass', 'TwistoPayIn3')
|
||||
))->execute()->fetchAssociative();
|
||||
}
|
||||
|
||||
public function processStep_2()
|
||||
{
|
||||
$checkoutId = getVal('checkout_id');
|
||||
$status = getVal('status');
|
||||
|
||||
switch ($status) {
|
||||
case 'captured':
|
||||
case 'authorized':
|
||||
$this->setStatus(Payment::STATUS_FINISHED, $checkoutId);
|
||||
$this->info(translate('paymentSuccess', 'payment'));
|
||||
break;
|
||||
case 'rejected':
|
||||
$this->setStatus(Payment::STATUS_STORNO, $checkoutId);
|
||||
$this->info(translate('payment_rejected_status', 'payment'));
|
||||
// no break
|
||||
case 'error':
|
||||
$this->setStatus(Payment::STATUS_STORNO, $checkoutId);
|
||||
$this->info(translate('payment_unexpected_status', 'payment'));
|
||||
}
|
||||
}
|
||||
|
||||
protected function getCheckoutRedirect($checkoutData)
|
||||
{
|
||||
$response = $this->requestCurl('/checkouts', json_encode($checkoutData));
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
protected function getCheckoutData()
|
||||
{
|
||||
$items = [];
|
||||
foreach ($this->order->fetchItems() as $item) {
|
||||
$items[] = $this->getCheckoutItem($item);
|
||||
}
|
||||
|
||||
$this->setItemsRoundingDiscount($items);
|
||||
|
||||
$requestBody = [
|
||||
'type' => $this->getCheckoutType(),
|
||||
'config' => [
|
||||
'redirect_uri' => $this->getGenericPaymentUrl(2),
|
||||
],
|
||||
'shopper' => $this->removeEmptyValues([
|
||||
'first_name' => $this->order->invoice_name,
|
||||
'last_name' => $this->order->invoice_surname,
|
||||
'phone' => $this->order->invoice_phone,
|
||||
'billing_address' => $this->removeEmptyValues([
|
||||
'line1' => $this->order->invoice_street,
|
||||
'line2' => $this->order->invoice_custom_address,
|
||||
'city' => $this->order->invoice_city,
|
||||
'state' => $this->order->invoice_state,
|
||||
'postal_code' => $this->order->invoice_zip,
|
||||
'country' => $this->order->invoice_country,
|
||||
]),
|
||||
'statistics' => $this->getCheckoutUserStatistics($this->order->id_user, $this->order->invoice_email),
|
||||
// "personal_id" => $this->order->id_user ?? '',
|
||||
'email' => $this->order->invoice_email,
|
||||
]),
|
||||
'order' => $this->removeEmptyValues([
|
||||
'reference' => $this->order->order_no,
|
||||
'amount' => $this->getOrderPrice(),
|
||||
'currency' => $this->order->getTotalPrice()->getCurrency()->getId(),
|
||||
'items' => $items,
|
||||
]),
|
||||
];
|
||||
|
||||
/** @var LoggerInterface $logger */
|
||||
$logger = ServiceContainer::getService('logger');
|
||||
$logger->notice('Twisto - objednavka', [
|
||||
'Request' => $requestBody,
|
||||
]);
|
||||
|
||||
return $requestBody;
|
||||
}
|
||||
|
||||
protected function removeEmptyValues(array $array): array
|
||||
{
|
||||
foreach ($array as $key => $value) {
|
||||
if ((is_string($value) && !$value) || is_null($value)) {
|
||||
unset($array[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
protected function getCheckoutUserStatistics($idUser, string $email): array
|
||||
{
|
||||
$userStats = [];
|
||||
|
||||
if ($idUser) {
|
||||
$user = sqlQueryBuilder()->select('*')->from('users')->where(Operator::equals(['id' => $this->order->id_user]))
|
||||
->execute()->fetchAssociative();
|
||||
|
||||
if ($user['date_reg'] && $user['date_reg'] !== '0000-00-00 00:00:00') {
|
||||
$userStats['account_created'] = (new DateTime($user['date_reg']))->format('Y-m-d\TH:i:s\Z');
|
||||
}
|
||||
$userStats['currency'] = $user['currency'];
|
||||
}
|
||||
|
||||
$prevOrdersStats = sqlQueryBuilder()->select('AVG(o.total_price) avg, SUM(o.total_price) sum, MAX(o.total_price) max, COUNT(*) count')->from('orders',
|
||||
'o')
|
||||
->where(Operator::equals($idUser ? ['id_user' => $idUser] : ['invoice_email' => $email]))
|
||||
->andWhere('status_storno = 0')
|
||||
->andWhere(Operator::inIntArray(getStatuses('handled'), 'o.status'))
|
||||
->groupBy($idUser ? 'id_user' : 'invoice_email')
|
||||
->execute()->fetchAssociative() ?: [];
|
||||
|
||||
$stats = ($prevOrdersStats['count'] ?? false) ? [
|
||||
'sales_total_count' => $prevOrdersStats['count'] ?? 0,
|
||||
'sales_total_amount' => $prevOrdersStats['sum'] ?? 0,
|
||||
'sales_avg_amount' => $prevOrdersStats['avg'] ?? 0,
|
||||
'sales_max_amount' => $prevOrdersStats['max'] ?? 0,
|
||||
'has_previous_purchases' => true,
|
||||
] : ['has_previous_purchases' => false];
|
||||
|
||||
// skipped fields
|
||||
// "refunds_total_amount" => "string",
|
||||
// "previous_chargeback" => true,
|
||||
// "last_login" => "2019-08-24T14:15:22Z",
|
||||
return array_merge($userStats, $stats);
|
||||
}
|
||||
|
||||
protected function getCheckoutItem(array $item): array
|
||||
{
|
||||
if (($item['note']['item_type'] ?? '') == 'delivery') {
|
||||
$type = 'shipping';
|
||||
} elseif (($item['note']['item_type'] ?? '') == 'discount') {
|
||||
$type = 'discount';
|
||||
} elseif (!empty($item['note']['bonus_points'])) {
|
||||
$type = 'store_credit';
|
||||
} else {
|
||||
$type = 'sku';
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => $item['descr'],
|
||||
'amount' => $item['piece_price']['value_with_vat']->asFloat(),
|
||||
'quantity' => (int) $item['pieces'],
|
||||
'type' => $type,
|
||||
];
|
||||
}
|
||||
|
||||
protected function setItemsRoundingDiscount(&$items)
|
||||
{
|
||||
$itemsPrice = array_sum(array_map(function ($item) {
|
||||
return $item['amount'] * $item['quantity'];
|
||||
}, $items));
|
||||
|
||||
$difference = $this->getOrderPrice() - $itemsPrice;
|
||||
if (abs($difference) > PHP_FLOAT_EPSILON && abs($difference) < 10) {
|
||||
$items[] = [
|
||||
'name' => 'Rounding discount',
|
||||
'amount' => round($difference, 3),
|
||||
'quantity' => 1,
|
||||
'type' => ($difference < 0) ? 'discount' : 'sku',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
protected function getOrderPrice(): float
|
||||
{
|
||||
return $this->order->getTotalPrice()->getPriceWithVat()->asFloat();
|
||||
}
|
||||
|
||||
public function createPayment($session, $price = null, $data = [])
|
||||
{
|
||||
$fields = [
|
||||
'id_order' => $this->orderId,
|
||||
'price' => $price,
|
||||
'note' => "Platba modulu {$this->class}",
|
||||
'status' => self::STATUS_CREATED,
|
||||
'payment_data' => json_encode(array_merge($data, ['session' => $session])),
|
||||
'date' => date('Y-m-d H:i:s'),
|
||||
'method' => self::METHOD_ONLINE,
|
||||
];
|
||||
|
||||
$this->insertSQL('order_payments', $fields);
|
||||
|
||||
$this->status = self::STATUS_CREATED;
|
||||
$this->paymentId = (int) sqlInsertId();
|
||||
|
||||
$this->order->updatePayments();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function requestCheckoutData($checkoutId)
|
||||
{
|
||||
$response = $this->requestCurl('/checkouts/'.$checkoutId, '', false);
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
protected function requestRefund($checkoutId, $amount, $reference)
|
||||
{
|
||||
$body = $this->removeEmptyValues([
|
||||
'checkout_id' => $checkoutId,
|
||||
'amount' => abs($amount),
|
||||
'reference' => $reference,
|
||||
]);
|
||||
|
||||
return $this->requestCurl('/refunds', json_encode($body));
|
||||
}
|
||||
|
||||
protected function requestCheckoutCancel($checkoutId)
|
||||
{
|
||||
return $this->requestCurl("/checkouts/{$checkoutId}/cancel", '');
|
||||
}
|
||||
|
||||
protected function requestCurl(string $path, string $encodedBody, $post = true)
|
||||
{
|
||||
$url = $this->getApiUrl().$path;
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
if ($post) {
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedBody);
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Authorization: Bearer '.$this->getAccessToken(),
|
||||
'Content-Type: application/json',
|
||||
]);
|
||||
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$responseInfo = curl_getinfo($ch);
|
||||
$curl_errno = curl_errno($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($responseInfo['http_code'] >= 300 || $responseInfo['http_code'] === 0) {
|
||||
$debugInfo = ['url' => $url, 'responseInfo' => $responseInfo, 'curl_errno' => $curl_errno, 'RESPONSE' => $response];
|
||||
$this->createApiException($debugInfo);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function createApiException($debugInfo)
|
||||
{
|
||||
$errMessage = 'Komunikace s platební bránou Twisto selhala. Zkuste prosím znovu později.';
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, $errMessage, $debugInfo);
|
||||
getRaven()->captureMessage('Twisto API error', [], ['extra' => $debugInfo]);
|
||||
throw new PaymentException($errMessage);
|
||||
}
|
||||
|
||||
protected function createWebhookError($httpCode, $debugInfo, $responseText = '')
|
||||
{
|
||||
$debugInfo = is_array($debugInfo) ? $debugInfo : [$debugInfo];
|
||||
$errMessage = 'Twisto webhook se nepodařilo zpracovat.';
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, $errMessage, $debugInfo);
|
||||
getRaven()->captureMessage('Twisto webhook error', [], ['extra' => $debugInfo]);
|
||||
$this->sendNotificationResponse($httpCode, $responseText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhook handler.
|
||||
*/
|
||||
public function processStep_10()
|
||||
{
|
||||
if (!$this->request->isMethod('POST')) {
|
||||
$this->sendNotificationResponse(405, '');
|
||||
}
|
||||
|
||||
try {
|
||||
$requestBody = $this->request->toArray();
|
||||
} catch (Exception $e) {
|
||||
$this->createWebhookError(422, $this->request->getContent(), 'Unreadable JSON');
|
||||
}
|
||||
|
||||
$checkoutId = getVal('checkout_id', $requestBody);
|
||||
$state = getVal('state', $requestBody);
|
||||
|
||||
if (!$checkoutId) {
|
||||
$this->createWebhookError(400, array_merge($requestBody, ['err' => 'missing checkout id']), '');
|
||||
}
|
||||
|
||||
$idOrder = $this->getOrderId($checkoutId);
|
||||
|
||||
if (!$idOrder) {
|
||||
$this->sendNotificationResponse(400, 'Order not found');
|
||||
}
|
||||
|
||||
$this->setOrder($idOrder);
|
||||
$this->getStatus($checkoutId);
|
||||
$this->updateStatus($state, $checkoutId);
|
||||
|
||||
$this->sendNotificationResponse(200, 'OK');
|
||||
}
|
||||
|
||||
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,
|
||||
], 'op.status'))
|
||||
->andWhere('op.date > (DATE_SUB(CURDATE(), INTERVAL 1 MONTH))')
|
||||
->andWhere(Operator::inStringArray(['TwistoPay', 'TwistoPayIn3'],
|
||||
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']);
|
||||
$checkoutId = $paymentData['session'];
|
||||
|
||||
$response = $this->requestCheckoutData($checkoutId);
|
||||
|
||||
$this->updateStatus($response['state'], $checkoutId);
|
||||
}
|
||||
}
|
||||
|
||||
protected function updateStatus($state, $checkoutId)
|
||||
{
|
||||
switch ($state) {
|
||||
case 'created':
|
||||
$this->setStatus(Payment::STATUS_PENDING, $checkoutId);
|
||||
break;
|
||||
case 'approved':
|
||||
case 'completed':
|
||||
$this->setStatus(Payment::STATUS_FINISHED, $checkoutId);
|
||||
break;
|
||||
case 'cancelled':
|
||||
case 'error':
|
||||
$this->setStatus(Payment::STATUS_STORNO, $checkoutId);
|
||||
}
|
||||
}
|
||||
|
||||
public function doReturnPayment(array $payment, float $amount)
|
||||
{
|
||||
if ($payment['status'] != Payment::STATUS_FINISHED) {
|
||||
throw new PaymentException(translate('returnFailed', 'orderPayment'));
|
||||
}
|
||||
|
||||
$checkoutId = $payment['payment_data']['session'] ?? null;
|
||||
|
||||
if (!$checkoutId) {
|
||||
throw new PaymentException('Payment does not have assigned checkout_id');
|
||||
}
|
||||
|
||||
$checkoutData = $this->requestCheckoutData($checkoutId);
|
||||
|
||||
switch ($checkoutData['state']) {
|
||||
case 'completed':
|
||||
$reference = $checkoutData['order']['reference'].'_'.$payment['id'].'_'.rand();
|
||||
$response = $this->requestRefund($checkoutId, $amount, $reference);
|
||||
break;
|
||||
case 'created':
|
||||
case 'approved':
|
||||
if ($checkoutData['order']['amount'] + $amount > PHP_FLOAT_EPSILON) {
|
||||
$message = translate('returnFailedOnlyFullAmount', 'orderPayment');
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR,
|
||||
ActivityLog::TYPE_COMMUNICATION,
|
||||
$message,
|
||||
['amount' => $amount, 'payment_id' => $checkoutData, 'RESPONSE' => $checkoutData['state']]);
|
||||
throw new PaymentException($message);
|
||||
}
|
||||
$response = $this->requestCheckoutCancel($checkoutId);
|
||||
break;
|
||||
default:
|
||||
$message = translate('returnFailedInvalidState', 'orderPayment').": {$checkoutData['state']}";
|
||||
addActivityLog(ActivityLog::SEVERITY_ERROR,
|
||||
ActivityLog::TYPE_COMMUNICATION,
|
||||
$message,
|
||||
['amount' => $amount, 'payment_id' => $checkoutData, 'RESPONSE' => $checkoutData['state']]);
|
||||
throw new PaymentException($message);
|
||||
}
|
||||
|
||||
return ['response' => $response];
|
||||
}
|
||||
|
||||
public static function getSettingsConfiguration(): array
|
||||
{
|
||||
return [
|
||||
'fields' => [
|
||||
'token' => [
|
||||
'title' => 'Token',
|
||||
'type' => 'text',
|
||||
],
|
||||
'webhook' => [
|
||||
'title' => 'Webhook url',
|
||||
'text' => path('kupshop_ordering_payment_legacypayment', ['class' => 'TwistoPay', 'step' => 10], \Symfony\Component\Routing\Router::ABSOLUTE_URL),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getCheckoutType(): string
|
||||
{
|
||||
return 'standard';
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getApiUrl(): string
|
||||
{
|
||||
return $this->apiUrl;
|
||||
}
|
||||
|
||||
protected function getAccessToken(): string
|
||||
{
|
||||
return $this->config['token'] ?? '';
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
31
class/payments/class.TwistoPayIn3.php
Normal file
31
class/payments/class.TwistoPayIn3.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
if (!class_exists('TwistoPay')) {
|
||||
require_once 'class.TwistoPay.php';
|
||||
}
|
||||
|
||||
class TwistoPayIn3 extends TwistoPay
|
||||
{
|
||||
public static $name = 'Twisto Pay in three';
|
||||
|
||||
public $class = 'TwistoPayIn3';
|
||||
|
||||
protected ?string $defaultIcon = '../../common/static/payments/twisto_payin3.svg';
|
||||
|
||||
protected function getCheckoutType(): string
|
||||
{
|
||||
return 'pay-in-three';
|
||||
}
|
||||
|
||||
public function accept($totalPrice, $freeDelivery)
|
||||
{
|
||||
$price = $totalPrice->getPriceWithVat()->asFloat();
|
||||
if ($price <= 0 && $this->order) {
|
||||
$price = $this->order->total_price;
|
||||
}
|
||||
|
||||
return parent::accept($totalPrice, $freeDelivery) && $price >= 1500;
|
||||
}
|
||||
}
|
||||
251
class/payments/class.WebPay.php
Normal file
251
class/payments/class.WebPay.php
Normal file
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
use KupShop\KupShopBundle\Config;
|
||||
|
||||
/**
|
||||
* Requires composer packages alcohol/iso3166, alcohol/iso4217 and adamstipak/webpay-php:~1.1.3 (https://github.com/wpj-cz/gp-webpay-php-sdk).
|
||||
*/
|
||||
class WebPay extends Payment
|
||||
{
|
||||
public static $name = 'WebPay platební brána';
|
||||
|
||||
// public $template = 'payment.WebPay.tpl';
|
||||
|
||||
public $class = 'WebPay';
|
||||
|
||||
public $tp_id_payment;
|
||||
|
||||
/** @var \AdamStipak\Webpay\Api */
|
||||
public $apiContext;
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_ONLINE;
|
||||
|
||||
public function getPaymentUrl()
|
||||
{
|
||||
return $this->getGenericPaymentUrl(1);
|
||||
}
|
||||
|
||||
public function getGatewayUrl()
|
||||
{
|
||||
return $this->config['webpayUrl'];
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
$request = $this->createPaymentRequest();
|
||||
|
||||
return $this->getApiContext()->createPaymentParam($request);
|
||||
}
|
||||
|
||||
private function getPayment(bool $alwaysNew = true)
|
||||
{
|
||||
$kupshopPayment = $this->getPendingPayment();
|
||||
if (!$alwaysNew && $kupshopPayment) {
|
||||
return $kupshopPayment;
|
||||
} else {
|
||||
$this->createPayment(
|
||||
null,
|
||||
$this->order->getRemainingPayment(),
|
||||
['paymentClass' => self::class]
|
||||
);
|
||||
$paymentRow = $this->selectSQL('order_payments', ['id' => $this->paymentId])->fetch();
|
||||
$json = json_decode($paymentRow['payment_data'], true);
|
||||
$json['session'] = $this->paymentId;
|
||||
$paymentRow['payment_data'] = json_encode($json);
|
||||
$this->updateSQL('order_payments', $paymentRow, ['id' => $this->paymentId]);
|
||||
|
||||
$paymentRow['decoded_data'] = $json;
|
||||
|
||||
return $paymentRow;
|
||||
}
|
||||
}
|
||||
|
||||
private function createPaymentRequest(): AdamStipak\Webpay\PaymentRequest
|
||||
{
|
||||
$iso4217 = new Alcohol\ISO4217();
|
||||
if (class_exists('Alcohol\ISO3166\ISO3166')) {
|
||||
$iso3166 = new Alcohol\ISO3166\ISO3166();
|
||||
}
|
||||
|
||||
$amount = $this->order->getRemainingPayment();
|
||||
if ($amount <= (float) 0) {
|
||||
$this->step(-3, 'storno');
|
||||
}
|
||||
|
||||
$addInfo = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><additionalInfoRequest xmlns="http://gpe.cz/gpwebpay/additionalInfo/request" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>');
|
||||
$addInfo['version'] = '4.0';
|
||||
|
||||
$cardHolderInfo = $addInfo->addChild('cardholderInfo');
|
||||
$cardHolderDetails = $cardHolderInfo->addChild('cardholderDetails');
|
||||
$cardHolderDetails->name = mb_substr($this->order->invoice_name.' '.$this->order->invoice_surname, 0, 255);
|
||||
$cardHolderDetails->email = mb_substr($this->order->invoice_email, 0, 255);
|
||||
|
||||
if (!empty($this->order->invoice_street) && isset($iso3166)
|
||||
&& !empty($this->order->invoice_name)
|
||||
&& !empty($this->order->invoice_city)
|
||||
&& !empty($this->order->invoice_zip)
|
||||
&& !empty($this->order->invoice_country)
|
||||
&& !empty($this->order->invoice_email)
|
||||
) {
|
||||
$billingDetails = $cardHolderInfo->addChild('billingDetails');
|
||||
$billingDetails->name = mb_substr($this->order->invoice_name.' '.$this->order->invoice_surname, 0, 255);
|
||||
$billingDetails->address1 = mb_substr($this->order->invoice_street, 0, 50);
|
||||
if (!empty($this->order->invoice_custom_address)) {
|
||||
$billingDetails->address2 = mb_substr($this->order->invoice_custom_address, 0, 50);
|
||||
}
|
||||
$billingDetails->city = mb_substr($this->order->invoice_city, 0, 50);
|
||||
$billingDetails->postalCode = mb_substr($this->order->invoice_zip, 0, 16);
|
||||
$billingDetails->country = (int) $iso3166->getByAlpha2($this->order->invoice_country)['numeric'];
|
||||
// phone has weird validation rules: cvc-pattern-valid: Value '+420777852147' is not facet-valid with respect to pattern '\d{1,15}' for type 'phoneValue'.
|
||||
if (!empty($this->order->invoice_phone)) {
|
||||
$billingDetails->phone = mb_substr(preg_replace('/([^0-9]+)/', '', $this->order->invoice_phone), 0, 20);
|
||||
}
|
||||
$billingDetails->email = mb_substr($this->order->invoice_email, 0, 255);
|
||||
}
|
||||
|
||||
if (!empty($this->order->delivery_street) && isset($iso3166)
|
||||
&& !empty($this->order->delivery_name)
|
||||
&& !empty($this->order->delivery_city)
|
||||
&& !empty($this->order->delivery_zip)
|
||||
&& !empty($this->order->delivery_country)
|
||||
) {
|
||||
$shippingDetails = $cardHolderInfo->addChild('shippingDetails');
|
||||
$shippingDetails->name = mb_substr($this->order->delivery_name.' '.$this->order->delivery_surname, 0, 255);
|
||||
$shippingDetails->address1 = mb_substr($this->order->delivery_street, 0, 50);
|
||||
if (!empty($this->order->delivery_custom_address)) {
|
||||
$shippingDetails->address2 = mb_substr($this->order->delivery_custom_address, 0, 50);
|
||||
}
|
||||
$shippingDetails->city = mb_substr($this->order->delivery_city, 0, 50);
|
||||
$shippingDetails->postalCode = mb_substr($this->order->delivery_zip, 0, 16);
|
||||
$shippingDetails->country = (int) $iso3166->getByAlpha2($this->order->delivery_country)['numeric'];
|
||||
// phone has weird validation rules: cvc-pattern-valid: Value '+420777852147' is not facet-valid with respect to pattern '\d{1,15}' for type 'phoneValue'.
|
||||
if (!empty($this->order->delivery_phone)) {
|
||||
$shippingDetails->phone = mb_substr(preg_replace('/([^0-9]+)/', '', $this->order->delivery_phone), 0, 20);
|
||||
}
|
||||
}
|
||||
|
||||
$paymentInfo = $addInfo->addChild('paymentInfo');
|
||||
$paymentInfo->transactionType = '01';
|
||||
|
||||
$shoppingCartInfo = $addInfo->addChild('shoppingCartInfo');
|
||||
$shoppingCartItems = $shoppingCartInfo->addChild('shoppingCartItems');
|
||||
foreach ($this->order->fetchItems() as $item) {
|
||||
if ($item['piece_price']['value_without_vat']->asInteger() < 0) {
|
||||
continue; // skip items with negative price (must be unsignedLong - 12 digits max)
|
||||
}
|
||||
$shoppingCartItem = $shoppingCartItems->addChild('shoppingCartItem');
|
||||
if (!empty($item['id_product'])) {
|
||||
$itemCodeValue = $item['id_product'].(!empty($item['id_variation']) ? ('_'.$item['id_variation']) : '');
|
||||
if (mb_strlen($itemCodeValue) <= 20) {
|
||||
$shoppingCartItem->itemCode = $itemCodeValue;
|
||||
}
|
||||
}
|
||||
$shoppingCartItem->itemDescription = mb_substr($item['descr'], 0, 50);
|
||||
$shoppingCartItem->itemQuantity = $item['pieces'];
|
||||
$shoppingCartItem->itemUnitPrice = $item['piece_price']['value_without_vat']->asInteger();
|
||||
}
|
||||
|
||||
$request = new \AdamStipak\Webpay\PaymentRequest(
|
||||
$this->getPayment()['id'],
|
||||
$amount,
|
||||
(int) $iso4217->getByAlpha3($this->order->currency)['numeric'],
|
||||
1,
|
||||
$this->getGenericPaymentUrl(5),
|
||||
$this->order->id
|
||||
);
|
||||
|
||||
$request->setParam('REFERENCENUMBER', $this->order->order_no);
|
||||
$request->setParam('ADDINFO', str_replace(PHP_EOL, '', $addInfo->asXML()));
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/* Payment steps */
|
||||
public function processStep_1()
|
||||
{
|
||||
$this->template = 'payment.WebPay.tpl';
|
||||
}
|
||||
|
||||
public function processStep_5()
|
||||
{
|
||||
$response = new \AdamStipak\Webpay\PaymentResponse(
|
||||
$_REQUEST['OPERATION'],
|
||||
$_REQUEST['ORDERNUMBER'],
|
||||
$_REQUEST['MERORDERNUM'],
|
||||
$_REQUEST['PRCODE'],
|
||||
$_REQUEST['SRCODE'],
|
||||
$_REQUEST['RESULTTEXT'],
|
||||
$_REQUEST['DIGEST'],
|
||||
$_REQUEST['DIGEST1']
|
||||
);
|
||||
|
||||
try {
|
||||
$this->getApiContext()->verifyPaymentResponse($response);
|
||||
} catch (\AdamStipak\Webpay\PaymentResponseException $e) {
|
||||
// change payment status to finished
|
||||
if (!$this->setStatus(Payment::STATUS_STORNO, $_REQUEST['ORDERNUMBER'])) {
|
||||
logError(__FILE__, __LINE__, 'WebPay::updatePaymentStatus: setStatus failed!');
|
||||
exit;
|
||||
}
|
||||
if ($_REQUEST['PRCODE'] != 50) { // nejedna se o "Drzitel karty zrusil platbu"
|
||||
// PaymentResponseException has $prCode, $srCode for properties for logging GP Webpay response error codes.
|
||||
logError(__FILE__, __LINE__, 'WebPay error: '.$_REQUEST['PRCODE'].' - '.$_REQUEST['RESULTTEXT'].', exception:'.$e);
|
||||
}
|
||||
$this->error(translate('payment_storno', 'payment'));
|
||||
} catch (Exception $e) {
|
||||
// Digest is not correct.
|
||||
logError(__FILE__, __LINE__, 'WebPay exception: '.$e);
|
||||
$this->error(translate('payment_storno', 'payment'));
|
||||
}
|
||||
|
||||
if ($_REQUEST['PRCODE'] == 0) {
|
||||
$this->status = Payment::STATUS_FINISHED;
|
||||
// change payment status to finished
|
||||
if (!$this->setStatus(Payment::STATUS_FINISHED, $_REQUEST['ORDERNUMBER'])) {
|
||||
logError(__FILE__, __LINE__, 'WebPay::updatePaymentStatus: setStatus failed!');
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->success(translate('paymentSuccess', 'payment'));
|
||||
}
|
||||
}
|
||||
|
||||
/* Payment methods */
|
||||
|
||||
public function getApiContext(): AdamStipak\Webpay\Api
|
||||
{
|
||||
if (!isset($this->apiContext)) {
|
||||
$signer = new \AdamStipak\Webpay\Signer(
|
||||
$this->config['privateKeyFilepath'],
|
||||
$this->config['privateKeyPassword'],
|
||||
$this->config['GPpublicKeyFilepath']
|
||||
);
|
||||
|
||||
$this->apiContext = new \AdamStipak\Webpay\Api(
|
||||
$this->config['merchantNumber'],
|
||||
$this->config['webpayUrl'],
|
||||
$signer
|
||||
);
|
||||
}
|
||||
|
||||
return $this->apiContext;
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isEnabled($className)
|
||||
{
|
||||
$cfg = Config::get();
|
||||
|
||||
if (empty($cfg['Modules']['payments'][$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
76
class/payments/class.WsPay.php
Normal file
76
class/payments/class.WsPay.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/** Dependencies: `composer require dodo-it/omnipay-wspay:dev-master */
|
||||
class WsPay extends \KupShop\OrderingBundle\OmniPay
|
||||
{
|
||||
public static $name = 'WsPay platební brána';
|
||||
|
||||
public $class = 'WsPay';
|
||||
|
||||
public $method;
|
||||
|
||||
protected $pay_method = Payment::METHOD_COD;
|
||||
|
||||
public static function getOmnipayName(): string
|
||||
{
|
||||
return 'WsPay';
|
||||
}
|
||||
|
||||
public static function getSettingsConfiguration(): array
|
||||
{
|
||||
return [
|
||||
'fields' => [
|
||||
'shopID' => [
|
||||
'title' => 'Shop ID',
|
||||
'type' => 'text',
|
||||
],
|
||||
'secretKey' => [
|
||||
'title' => 'Secret key',
|
||||
'type' => 'text',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Omnipay\WsPay\Gateway $gateway
|
||||
*/
|
||||
public function configureGateway(Omnipay\Common\GatewayInterface $gateway, Order $order): Omnipay\Common\GatewayInterface
|
||||
{
|
||||
$gateway->setTestMode(false);
|
||||
if (isDevelopment()) {
|
||||
$gateway->setTestMode(true);
|
||||
}
|
||||
|
||||
$returnUrl = $this->getGenericPaymentUrl(5);
|
||||
|
||||
$totalAmount = str_replace('.', ',', $order->total_price->printFloatValue(2));
|
||||
|
||||
$gateway
|
||||
->setShopId($this->config['shopID'])
|
||||
->setSecretKey($this->config['secretKey'])
|
||||
->setShoppingCartId($order->order_no)
|
||||
->setSignature($this->config['shopID'], $this->config['secretKey'], $order->order_no, $totalAmount)
|
||||
->setTotalAmount($totalAmount)
|
||||
->setCurrency($order->getCurrency())
|
||||
->setReturnUrl($returnUrl)
|
||||
->setReturnErrorURL($returnUrl)
|
||||
->setCancelURL($returnUrl)
|
||||
->setLang($order->getLanguage())
|
||||
->setCustomerEmail($this->order->invoice_email)
|
||||
->setCustomerFirstName($this->order->invoice_name)
|
||||
->setCustomerLastName($this->order->invoice_surname)
|
||||
->setCustomerCountry($this->order->invoice_country)
|
||||
->setCustomerCity($this->order->invoice_city)
|
||||
->setCustomerAddress($this->order->invoice_street)
|
||||
->setCustomerZIP($this->order->invoice_zip)
|
||||
->setCustomerPhone($this->order->invoice_phone);
|
||||
|
||||
return $gateway;
|
||||
}
|
||||
|
||||
public function hasOnlinePayment()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user