first commit

This commit is contained in:
2024-10-28 22:14:22 +01:00
commit b65352c452
40581 changed files with 5712079 additions and 0 deletions

View File

@@ -0,0 +1,528 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowAdminFormHelper
{
/**
* @var Module
*/
private $module;
/**
* @var Context
*/
private $context;
private $translations;
public function __construct($module, $context, $translations)
{
$this->module = $module;
$this->context = $context;
$this->translations = $translations;
}
public function generate(): string
{
$helper = new HelperForm();
$helper->show_toolbar = false;
$helper->name_controller = $this->module->name;
$helper->title = $this->module->displayName;
$helper->submit_action = 'submit' . $this->module->name;
$helper->default_form_language = (new Language((int)Configuration::get('PS_LANG_DEFAULT')))->id;
$helper->allow_employee_form_lang = Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG', 0);
$helper->currentIndex = $this->context->link->getAdminLink('AdminModules', false) . '&' . http_build_query([
'configure' => $this->module->name,
'tab_module' => $this->module->tab,
'module_name' => $this->module->name
]);
$helper->token = Tools::getAdminTokenLite('AdminModules');
$helper->tpl_vars = [
'fields_value' => $this->getConfigFieldsValues(),
'languages' => $this->context->controller->getLanguages(),
'id_language' => $this->context->language->id
];
return $helper->generateForm($this->getForm());
}
private function getForm(): array
{
$form = [];
$form['pos_sandbox'] = [
'form' => [
'legend' => [
'title' => $this->translations['Sandbox configuration'],
'icon' => 'icon-cog'
],
'input' => [
[
'type' => 'switch',
'label' => $this->translations['Test mode (Sandbox)'],
'desc' => $this->translations['Enable to use test environment'],
'name' => 'PAYNOW_SANDBOX_ENABLED',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
[
'type' => 'text',
'label' => $this->translations['API Key'],
'name' => 'PAYNOW_SANDBOX_API_KEY'
],
[
'type' => 'text',
'label' => $this->translations['API Signature Key'],
'name' => 'PAYNOW_SANDBOX_API_SIGNATURE_KEY'
]
],
'submit' => [
'title' => $this->translations['Save']
]
]
];
$form['pos_prod'] = [
'form' => [
'legend' => [
'title' => $this->translations['Production configuration'],
'icon' => 'icon-cog'
],
'input' => [
[
'type' => 'text',
'label' => $this->translations['API Key'],
'name' => 'PAYNOW_PROD_API_KEY'
],
[
'type' => 'text',
'label' => $this->translations['API Signature Key'],
'name' => 'PAYNOW_PROD_API_SIGNATURE_KEY'
]
],
'submit' => [
'title' => $this->translations['Save']
]
]
];
$order_states = OrderState::getOrderStates($this->context->language->id);
$form['payment_statuses'] = [
'form' => [
'legend' => [
'title' => $this->translations['Payment status mapping'],
'icon' => 'icon-cog'
],
'input' => [
[
'type' => 'select',
'label' => $this->translations['Awaiting payment confirmation'],
'name' => 'PAYNOW_ORDER_INITIAL_STATE',
'options' => [
'query' => $order_states,
'id' => 'id_order_state',
'name' => 'name'
]
],
[
'type' => 'select',
'label' => $this->translations['Payment has been authorized by the buyer'],
'name' => 'PAYNOW_ORDER_CONFIRMED_STATE',
'options' => [
'query' => $order_states,
'id' => 'id_order_state',
'name' => 'name'
]
],
[
'type' => 'select',
'label' => $this->translations['Payment has not been authorized by the buyer'],
'name' => 'PAYNOW_ORDER_REJECTED_STATE',
'options' => [
'query' => $order_states,
'id' => 'id_order_state',
'name' => 'name'
]
],
[
'type' => 'select',
'label' => $this->translations['An error occurred during the payment process and the payment could not be completed'],
'name' => 'PAYNOW_ORDER_ERROR_STATE',
'options' => [
'query' => $order_states,
'id' => 'id_order_state',
'name' => 'name'
]
],
[
'type' => 'select',
'label' => $this->translations['Payment has been abandoned by the buyer'],
'name' => 'PAYNOW_ORDER_ABANDONED_STATE',
'options' => [
'query' => $order_states,
'id' => 'id_order_state',
'name' => 'name'
]
],
[
'type' => 'select',
'label' => $this->translations['Payment has been expired'],
'name' => 'PAYNOW_ORDER_EXPIRED_STATE',
'options' => [
'query' => $order_states,
'id' => 'id_order_state',
'name' => 'name'
]
],
[ // Show retry payment button
'type' => 'select',
'label' => $this->translations['Show retry payment button on selected statuses'],
'name' => 'PAYNOW_RETRY_BUTTON_ORDER_STATE[]',
'multiple' => true,
'options' => [
'query' => $order_states,
'id' => 'id_order_state',
'name' => 'name'
]
],
],
'submit' => [
'title' => $this->translations['Save']
]
]
];
$form['refunds'] = [
'form' => [
'legend' => [
'title' => $this->translations['Refunds'],
'icon' => 'icon-cog'
],
'input' => [
[
'type' => 'html',
'name' => '',
'html_content' => $this->module->fetchTemplate('/views/templates/admin/_partials/info.tpl')
],
[
'type' => 'switch',
'label' => $this->translations['Enable refunds'],
'name' => 'PAYNOW_REFUNDS_ENABLED',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
[
'type' => 'switch',
'label' => $this->translations['After status change'],
'name' => 'PAYNOW_REFUNDS_AFTER_STATUS_CHANGE_ENABLED',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
[
'type' => 'select',
'label' => $this->translations['On status'],
'name' => 'PAYNOW_REFUNDS_ON_STATUS',
'options' => [
'query' => $order_states,
'id' => 'id_order_state',
'name' => 'name'
]
],
[
'type' => 'switch',
'label' => $this->translations['Include shipping costs'],
'name' => 'PAYNOW_REFUNDS_WITH_SHIPPING_COSTS',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
],
'submit' => [
'title' => $this->translations['Save']
]
]
];
$logs_path = _PS_MODULE_DIR_ . $this->module->name . '/logs';
$form['additional_options'] = [
'form' => [
'legend' => [
'title' => $this->translations['Additional options'],
'icon' => 'icon-cog'
],
'input' => [
[
'type' => 'select',
'label' => $this->translations['Moment of creating order'],
'name' => 'PAYNOW_CREATE_ORDER_STATE',
'options' => [
'query' => [
[
'id_option' => 1,
'name' => $this->translations['On clicking the Place order']
],
[
'id_option' => 2,
'name' => $this->translations['After the successful Paynow payment']
],
],
'id' => 'id_option',
'name' => 'name'
]
],
[
'type' => 'switch',
'label' => $this->translations['Show separated payment methods'],
'name' => 'PAYNOW_SEPARATE_PAYMENT_METHODS',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
[
'type' => 'select',
'label' => $this->translations['Hide payment types'],
'name' => 'PAYNOW_HIDE_PAYMENT_TYPES[]',
'multiple' => true,
'options' => [
'query' => [
[
'id_option' => 'none',
'name' => $this->module->l('None')
],
[
'id_option' => Paynow\Model\PaymentMethods\Type::BLIK,
'name' => $this->module->getPaymentMethodTitle(Paynow\Model\PaymentMethods\Type::BLIK)
],
[
'id_option' => Paynow\Model\PaymentMethods\Type::PBL,
'name' => $this->module->getPaymentMethodTitle(Paynow\Model\PaymentMethods\Type::PBL)
],
[
'id_option' => Paynow\Model\PaymentMethods\Type::CARD,
'name' => $this->module->getPaymentMethodTitle(Paynow\Model\PaymentMethods\Type::CARD)
],
[
'id_option' => 'DIGITAL_WALLETS',
'name' => $this->translations['Digital wallets']
],
[
'id_option' => Paynow\Model\PaymentMethods\Type::PAYPO,
'name' => $this->module->getPaymentMethodTitle(Paynow\Model\PaymentMethods\Type::PAYPO)
]
],
'id' => 'id_option',
'name' => 'name'
]
],
[
'type' => 'switch',
'label' => $this->translations['Show retry payment button'],
'desc' => $this->translations['The button appears on the order details screen.'],
'name' => 'PAYNOW_RETRY_PAYMENT_BUTTON_ENABLED',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
[
'type' => 'switch',
'label' => $this->translations['Use order-confirmation page as shop\'s return URL'],
'desc' => $this->translations['Buyer will be redirected to order-confirmation page after payment.'],
'name' => 'PAYNOW_USE_CLASSIC_RETURN_URL',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
[
'type' => 'switch',
'label' => $this->translations['Send order items'],
'desc' => $this->translations['Enable sending ordered products information: name, categories, quantity and unit price.'],
'name' => 'PAYNOW_SEND_ORDER_ITEMS',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
[
'type' => 'switch',
'label' => $this->translations['Use payment validity time'],
'desc' => $this->translations['Enable to limit the validity of the payment.'],
'name' => 'PAYNOW_PAYMENT_VALIDITY_TIME_ENABLED',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
[
'type' => 'text',
'label' => $this->translations['Payment validity time'],
'desc' => $this->translations['Determines how long it will be possible to pay for the order from the moment the payment link is generated. The value expressed in seconds. Must be between 60 and 86400 seconds.'],
'name' => 'PAYNOW_PAYMENT_VALIDITY_TIME'
],
[
'type' => 'switch',
'label' => $this->translations['BLIK field autofocus'],
'desc' => $this->translations['Autofocus on checkout form field: BLIK code. Enabled by default. Disabling may be helpful when checkout page is visualy long (e.g. single-page checkout).'],
'name' => 'PAYNOW_BLIK_AUTOFOCUS_ENABLED',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
[
'type' => 'switch',
'label' => $this->translations['Enable logs'],
'desc' => $this->translations['Logs are available in '] . ' ' . $logs_path,
'name' => 'PAYNOW_DEBUG_LOGS_ENABLED',
'values' => [
[
'id' => 'active_on',
'value' => 1,
'label' => $this->translations['Yes']
],
[
'id' => 'active_off',
'value' => 0,
'label' => $this->translations['No']
]
],
],
],
'submit' => [
'title' => $this->translations['Save']
]
]
];
return $form;
}
private function getConfigFieldsValues(): array
{
return [
'PAYNOW_REFUNDS_ENABLED' => Configuration::get('PAYNOW_REFUNDS_ENABLED'),
'PAYNOW_REFUNDS_AFTER_STATUS_CHANGE_ENABLED' => Configuration::get('PAYNOW_REFUNDS_AFTER_STATUS_CHANGE_ENABLED'),
'PAYNOW_REFUNDS_ON_STATUS' => Configuration::get('PAYNOW_REFUNDS_ON_STATUS'),
'PAYNOW_REFUNDS_WITH_SHIPPING_COSTS' => Configuration::get('PAYNOW_REFUNDS_WITH_SHIPPING_COSTS'),
'PAYNOW_DEBUG_LOGS_ENABLED' => Configuration::get('PAYNOW_DEBUG_LOGS_ENABLED'),
'PAYNOW_SEPARATE_PAYMENT_METHODS' => Configuration::get('PAYNOW_SEPARATE_PAYMENT_METHODS'),
'PAYNOW_HIDE_PAYMENT_TYPES[]' => explode(',', Configuration::get('PAYNOW_HIDE_PAYMENT_TYPES')),
'PAYNOW_USE_CLASSIC_RETURN_URL' => Configuration::get('PAYNOW_USE_CLASSIC_RETURN_URL'),
'PAYNOW_PROD_API_KEY' => Configuration::get('PAYNOW_PROD_API_KEY'),
'PAYNOW_PROD_API_SIGNATURE_KEY' => Configuration::get('PAYNOW_PROD_API_SIGNATURE_KEY'),
'PAYNOW_SANDBOX_ENABLED' => Configuration::get('PAYNOW_SANDBOX_ENABLED'),
'PAYNOW_SANDBOX_API_KEY' => Configuration::get('PAYNOW_SANDBOX_API_KEY'),
'PAYNOW_SANDBOX_API_SIGNATURE_KEY' => Configuration::get('PAYNOW_SANDBOX_API_SIGNATURE_KEY'),
'PAYNOW_ORDER_INITIAL_STATE' => Configuration::get('PAYNOW_ORDER_INITIAL_STATE'),
'PAYNOW_ORDER_CONFIRMED_STATE' => Configuration::get('PAYNOW_ORDER_CONFIRMED_STATE'),
'PAYNOW_ORDER_REJECTED_STATE' => Configuration::get('PAYNOW_ORDER_REJECTED_STATE'),
'PAYNOW_ORDER_ERROR_STATE' => Configuration::get('PAYNOW_ORDER_ERROR_STATE'),
'PAYNOW_SEND_ORDER_ITEMS' => Configuration::get('PAYNOW_SEND_ORDER_ITEMS'),
'PAYNOW_PAYMENT_VALIDITY_TIME_ENABLED' => Configuration::get('PAYNOW_PAYMENT_VALIDITY_TIME_ENABLED'),
'PAYNOW_PAYMENT_VALIDITY_TIME' => Configuration::get('PAYNOW_PAYMENT_VALIDITY_TIME'),
'PAYNOW_ORDER_ABANDONED_STATE' => Configuration::get('PAYNOW_ORDER_ABANDONED_STATE'),
'PAYNOW_ORDER_EXPIRED_STATE' => Configuration::get('PAYNOW_ORDER_EXPIRED_STATE'),
'PAYNOW_CREATE_ORDER_STATE' => Configuration::get('PAYNOW_CREATE_ORDER_STATE'),
'PAYNOW_RETRY_PAYMENT_BUTTON_ENABLED' => Configuration::get('PAYNOW_RETRY_PAYMENT_BUTTON_ENABLED'),
'PAYNOW_RETRY_BUTTON_ORDER_STATE[]' => explode(',', Configuration::get('PAYNOW_RETRY_BUTTON_ORDER_STATE', [])),
'PAYNOW_BLIK_AUTOFOCUS_ENABLED' => Configuration::get('PAYNOW_BLIK_AUTOFOCUS_ENABLED') === '0' ? '0' : '1',
];
}
}

View File

@@ -0,0 +1,125 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowConfigurationHelper
{
public const CREATE_ORDER_BEFORE_PAYMENT = 1;
public const CREATE_ORDER_AFTER_PAYMENT = 2;
public static function update()
{
Configuration::updateValue(
'PAYNOW_DEBUG_LOGS_ENABLED',
Tools::getValue('PAYNOW_DEBUG_LOGS_ENABLED')
);
Configuration::updateValue(
'PAYNOW_USE_CLASSIC_RETURN_URL',
Tools::getValue('PAYNOW_USE_CLASSIC_RETURN_URL')
);
Configuration::updateValue(
'PAYNOW_REFUNDS_ENABLED',
Tools::getValue('PAYNOW_REFUNDS_ENABLED')
);
Configuration::updateValue(
'PAYNOW_REFUNDS_AFTER_STATUS_CHANGE_ENABLED',
Tools::getValue('PAYNOW_REFUNDS_AFTER_STATUS_CHANGE_ENABLED')
);
Configuration::updateValue(
'PAYNOW_REFUNDS_ON_STATUS',
Tools::getValue('PAYNOW_REFUNDS_ON_STATUS')
);
Configuration::updateValue(
'PAYNOW_REFUNDS_WITH_SHIPPING_COSTS',
Tools::getValue('PAYNOW_REFUNDS_WITH_SHIPPING_COSTS')
);
Configuration::updateValue(
'PAYNOW_SEPARATE_PAYMENT_METHODS',
Tools::getValue('PAYNOW_SEPARATE_PAYMENT_METHODS')
);
Configuration::updateValue(
'PAYNOW_HIDE_PAYMENT_TYPES',
join(',', Tools::getValue('PAYNOW_HIDE_PAYMENT_TYPES', []))
);
Configuration::updateValue(
'PAYNOW_PROD_API_KEY',
Tools::getValue('PAYNOW_PROD_API_KEY')
);
Configuration::updateValue(
'PAYNOW_PROD_API_SIGNATURE_KEY',
Tools::getValue('PAYNOW_PROD_API_SIGNATURE_KEY')
);
Configuration::updateValue(
'PAYNOW_SANDBOX_ENABLED',
Tools::getValue('PAYNOW_SANDBOX_ENABLED')
);
Configuration::updateValue(
'PAYNOW_SANDBOX_API_KEY',
Tools::getValue('PAYNOW_SANDBOX_API_KEY')
);
Configuration::updateValue(
'PAYNOW_SANDBOX_API_SIGNATURE_KEY',
Tools::getValue('PAYNOW_SANDBOX_API_SIGNATURE_KEY')
);
Configuration::updateValue(
'PAYNOW_ORDER_INITIAL_STATE',
Tools::getValue('PAYNOW_ORDER_INITIAL_STATE')
);
Configuration::updateValue(
'PAYNOW_ORDER_CONFIRMED_STATE',
Tools::getValue('PAYNOW_ORDER_CONFIRMED_STATE')
);
Configuration::updateValue(
'PAYNOW_ORDER_REJECTED_STATE',
Tools::getValue('PAYNOW_ORDER_REJECTED_STATE')
);
Configuration::updateValue(
'PAYNOW_ORDER_ERROR_STATE',
Tools::getValue('PAYNOW_ORDER_ERROR_STATE')
);
Configuration::updateValue(
'PAYNOW_SEND_ORDER_ITEMS',
Tools::getValue('PAYNOW_SEND_ORDER_ITEMS')
);
Configuration::updateValue(
'PAYNOW_PAYMENT_VALIDITY_TIME_ENABLED',
Tools::getValue('PAYNOW_PAYMENT_VALIDITY_TIME_ENABLED')
);
Configuration::updateValue(
'PAYNOW_PAYMENT_VALIDITY_TIME',
Tools::getValue('PAYNOW_PAYMENT_VALIDITY_TIME')
);
Configuration::updateValue(
'PAYNOW_ORDER_ABANDONED_STATE',
Tools::getValue('PAYNOW_ORDER_ABANDONED_STATE')
);
Configuration::updateValue(
'PAYNOW_ORDER_EXPIRED_STATE',
Tools::getValue('PAYNOW_ORDER_EXPIRED_STATE')
);
Configuration::updateValue(
'PAYNOW_CREATE_ORDER_STATE',
Tools::getValue('PAYNOW_CREATE_ORDER_STATE')
);
Configuration::updateValue(
'PAYNOW_RETRY_PAYMENT_BUTTON_ENABLED',
Tools::getValue('PAYNOW_RETRY_PAYMENT_BUTTON_ENABLED')
);
Configuration::updateValue(
'PAYNOW_RETRY_BUTTON_ORDER_STATE',
join(',', Tools::getValue('PAYNOW_RETRY_BUTTON_ORDER_STATE', []))
);
Configuration::updateValue(
'PAYNOW_BLIK_AUTOFOCUS_ENABLED',
Tools::getValue('PAYNOW_BLIK_AUTOFOCUS_ENABLED')
);
}
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
use Paynow\Exception\PaynowException;
use Paynow\Model\Payment\Status;
class PaynowFrontController extends ModuleFrontControllerCore
{
protected $order;
protected $payment;
/** @var Paynow */
public $module;
public function init()
{
parent::init();
PaynowHelper::$module = $this->module;
}
public function initContent()
{
$this->display_column_left = false;
parent::initContent();
}
public function generateToken(): string
{
return Tools::encrypt($this->context->customer->secure_key);
}
public function isTokenValid(): bool
{
return $this->generateToken() === Tools::getValue('token');
}
protected function renderTemplate($template_name)
{
if (version_compare(_PS_VERSION_, '1.7', 'gt')) {
$template_name = 'module:paynow/views/templates/front/1.7/' . $template_name;
}
$this->setTemplate($template_name);
}
protected function redirectToOrderHistory()
{
Tools::redirect(
'index.php?controller=history',
__PS_BASE_URI__,
null,
'HTTP/1.1 301 Moved Permanently'
);
}
protected function getPaymentStatus($paymentId, $external_id)
{
PaynowLogger::info('Retrieving payment status {paymentId={}, externalId={}}', [$paymentId, $external_id]);
$idempotencyKey = PaynowKeysGenerator::generateIdempotencyKey($external_id);
try {
$status = (new Paynow\Service\Payment($this->module->getPaynowClient()))->status($paymentId, $idempotencyKey)->getStatus();
PaynowLogger::info('Retrieved payment status {paymentId={}, status={}}', [$paymentId, $status]);
return $status;
} catch (PaynowException $exception) {
PaynowLogger::error($exception->getMessage() . ' {paymentId={}, idempotencyKey={}}', [$paymentId, $idempotencyKey]);
}
return false;
}
protected function ajaxRender($value = null, $controller = null, $method = null)
{
header('Content-Type: application/json');
if (version_compare(_PS_VERSION_, '1.7.4.4', 'gt')) {
parent::ajaxRender($value, $controller, $method);
} else {
echo $value;
}
}
protected function getOrderCurrentState($order)
{
if ($order) {
$current_state = $order->getCurrentStateFull($this->context->language->id);
return is_array($current_state) ? $current_state['name'] : $this->getDefaultOrderStatus();
}
return $this->getDefaultOrderStatus();
}
private function getDefaultOrderStatus()
{
$order_state = new OrderState(Configuration::get('PAYNOW_ORDER_INITIAL_STATE'));
return $order_state->name[$this->context->language->id];
}
}

View File

@@ -0,0 +1,108 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
use Paynow\Client;
use Paynow\Exception\PaynowException;
/**
* GdprHelper
*/
class PaynowGDPRHelper
{
private $cart;
/**
* @var Client
*/
private $client;
/**
* @param Client $client
* @param $cart
*/
public function __construct(Client $client, $cart)
{
$this->client = $client;
$this->cart = $cart;
}
/**
* @param string|null $locale
*
* @return array
*/
public function getNotices(?string $locale)
{
$configurationId = 'PAYNOW_'.$this->isSandbox() ? 'SANDBOX_' : ''.'GDPR_' . $this->cleanLocale($locale);
$configurationOption = Configuration::get($configurationId);
if (! $configurationOption) {
$gdpr_notices = $this->retrieve($locale);
if ($gdpr_notices) {
$notices = [];
foreach ($gdpr_notices as $notice) {
array_push($notices, [
'title' => base64_encode($notice->getTitle()),
'content' => base64_encode($notice->getContent()),
'locale' => $notice->getLocale()
]);
}
Configuration::updateValue($configurationId, serialize($notices));
$configurationOption = Configuration::get($configurationId);
}
}
$notices = [];
$unserialized = unserialize($configurationOption);
if ($unserialized) {
foreach ($unserialized as $notice) {
array_push($notices, [
'title' => base64_decode($notice['title']),
'content' => base64_decode($notice['content']),
'locale' => $notice['locale']
]);
}
}
return $notices;
}
private function retrieve($locale): ?array
{
try {
PaynowLogger::info("Retrieving GDPR notices");
$idempotencyKey = PaynowKeysGenerator::generateIdempotencyKey(PaynowKeysGenerator::generateExternalIdByCart($this->cart));
return (new Paynow\Service\DataProcessing($this->client))->getNotices($locale, $idempotencyKey)->getAll();
} catch (PaynowException $exception) {
PaynowLogger::error(
'An error occurred during GDPR notices retrieve {code={}, message={}}',
[
$exception->getCode(),
$exception->getPrevious()->getMessage()
]
);
}
return null;
}
private function isSandbox()
{
return (int)Configuration::get('PAYNOW_SANDBOX_ENABLED') === 1;
}
private function cleanLocale($locale)
{
return Tools::strtoupper(str_replace('-', '_', $locale));
}
}

View File

@@ -0,0 +1,49 @@
<?php
use Http\Discovery\Exception\NotFoundException;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\Psr17FactoryDiscovery;
use Http\Discovery\Psr18ClientDiscovery;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
class PaynowGithubClient
{
/** @var ClientInterface */
private $client;
/** @var RequestFactoryInterface */
protected $messageFactory;
/** @var \Psr\Http\Message\UriInterface */
private $url;
public function __construct()
{
try {
$this->client = Psr18ClientDiscovery::find();
} catch (NotFoundException $exception) {
$this->client = HttpClientDiscovery::find();
}
$this->messageFactory = Psr17FactoryDiscovery::findRequestFactory();
$this->url = Psr17FactoryDiscovery::findUrlFactory()->createUri('https://api.github.com');
}
public function latest($username, $repository)
{
$request = $this->messageFactory->createRequest(
'GET',
$this->url->withPath('/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/latest')
);
try {
return json_decode($this->client->sendRequest($request)->getBody()->getContents());
} catch (ClientExceptionInterface $exception) {
PaynowLogger::error("Error occurred during retrieving github latest release information {message={}}", [$exception->getMessage()]);
}
return null;
}
}

View File

@@ -0,0 +1,61 @@
<?php
use Paynow\Exception\PaynowException;
use Paynow\Model\Payment\Status;
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowHelper
{
/** @var Paynow */
public static $module;
/**
* @param int $id_order
* @param string $payment_status
* @param int $payment_data_locked
* @param bool $orders_exists
*
* @return bool
*/
public static function canProcessCreateOrder(int $id_order, string $payment_status, int $payment_data_locked, bool $orders_exists): bool
{
return Status::STATUS_CONFIRMED === $payment_status &&
0 === $id_order &&
0 === $payment_data_locked &&
false === $orders_exists;
}
/**
* @param $cart
* @param $external_id
* @param $payment_id
*
* @return Order|null
*/
public static function createOrder($cart, $external_id, $payment_id): ?Order
{
$order = (new PaynowOrderCreateProcessor(self::$module))->process($cart, $external_id, $payment_id);
if (! $order) {
return null;
}
PaynowPaymentData::updateOrderIdAndOrderReferenceByPaymentId(
$order->id,
$order->reference,
$payment_id
);
return $order;
}
}

View File

@@ -0,0 +1,54 @@
<?php
use Paynow\Util\ClientExternalIdCalculator;
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowKeysGenerator
{
/**
* @param string $externalId
* @return false|string
*/
public static function generateIdempotencyKey(string $externalId)
{
return substr(uniqid($externalId . '_', true), 0, 45);
}
/**
* @param $order
* @return mixed
*/
public static function generateExternalIdByOrder($order)
{
return $order->reference;
}
/**
* @param $cart
* @return string
*/
public static function generateExternalIdByCart($cart): string
{
return uniqid($cart->id . '_', false);
}
/**
* @param $customerId
* @param $module
* @return string
*/
public static function generateBuyerExternalId($customerId, $module): string
{
return ClientExternalIdCalculator::calculate("$customerId", $module->getSignatureKey());
}
}

View File

@@ -0,0 +1,95 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowLinkHelper
{
public static function getContinueUrl($id_cart, $id_module, $secure_key, $external_id = null): string
{
if (Configuration::get('PAYNOW_USE_CLASSIC_RETURN_URL')) {
$params = [
'id_cart' => $id_cart,
'id_module' => $id_module,
'key' => $secure_key
];
return Context::getContext()->link->getPageLink(
'order-confirmation',
null,
Context::getContext()->language->id,
$params
);
}
return PaynowLinkHelper::getReturnUrl($external_id, Tools::encrypt($secure_key));
}
public static function getPaymentUrl($url_params = null): string
{
return Context::getContext()->link->getModuleLink(
'paynow',
'payment',
! empty($url_params) ? $url_params : []
);
}
public static function getNotificationUrl(): string
{
return Context::getContext()->link->getModuleLink('paynow', 'notifications');
}
public static function getReturnUrl($external_id, $token = null): string
{
$params = [
'token' => $token
];
if ($external_id) {
$params['external_id'] = $external_id;
}
return Context::getContext()->link->getModuleLink(
'paynow',
'return',
$params
);
}
public static function getOrderUrl($order): string
{
if (Cart::isGuestCartByCartId($order->id_cart)) {
$customer = new Customer((int)$order->id_customer);
return Context::getContext()->link->getPageLink(
'guest-tracking',
null,
Context::getContext()->language->id,
[
'order_reference' => $order->reference,
'email' => $customer->email
]
);
}
return Context::getContext()->link->getPageLink(
'order-detail',
null,
Context::getContext()->language->id,
[
'id_order' => $order->id
]
);
}
public static function getBlikConfirmUrl($url_params): string
{
return Context::getContext()->link->getModuleLink('paynow', 'confirmBlik', $url_params);
}
}

View File

@@ -0,0 +1,134 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowLockingHelper
{
private const LOCKS_DIR = 'paynow-locks';
private const LOCKS_PREFIX = 'paynow-lock-';
private const LOCKED_TIME = 35;
/**
* @string
*/
public $locksDirPath;
/**
* @var bool
*/
public $lockEnabled = true;
/**
* Constructor of PaynowLockingHelper
*/
public function __construct() {
// Setup locks dir
try {
$lockPath = dirname(__FILE__) . '/..' . DIRECTORY_SEPARATOR . self::LOCKS_DIR;
@mkdir( $lockPath );
if ( is_dir( $lockPath ) && is_writable( $lockPath ) ) {
$this->locksDirPath = $lockPath;
} else {
$this->locksDirPath = sys_get_temp_dir();
}
} catch ( \Exception $exception ) {
PaynowLogger::error(
'Error occurred when creating locking dir.',
[
'exception' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine()
]
);
$this->locksDirPath = sys_get_temp_dir();
}
if ( ! is_writable( $this->locksDirPath ) ) {
PaynowLogger::error( 'Locking mechanism disabled, no locks path available.' );
}
$this->lockEnabled = is_writable( $this->locksDirPath );
}
/**
* @param $externalId
* @return bool
*/
public function checkAndCreate( $externalId ) {
if ( ! $this->lockEnabled ) {
return false;
}
$lockFilePath = $this->generateLockPath( $externalId );
$lockExists = file_exists( $lockFilePath );
if ( $lockExists && ( filemtime( $lockFilePath ) + self::LOCKED_TIME ) > time() ) {
return true;
} else {
$this->create( $externalId, $lockExists );
return false;
}
}
/**
* @param $externalId
* @return void
*/
public function delete( $externalId ) {
if ( empty( $externalId ) ) {
return;
}
$lockFilePath = $this->generateLockPath( $externalId );
if ( file_exists( $lockFilePath ) ) {
unlink( $lockFilePath );
}
}
/**
* @param $externalId
* @param $lockExists
* @return void
*/
private function create( $externalId, $lockExists ) {
$lockPath = $this->generateLockPath( $externalId );
if ( $lockExists ) {
touch( $lockPath );
} else {
$fileSaved = @file_put_contents($lockPath, '');
if ( false === $fileSaved ) {
PaynowLogger::error(
'Locking mechanism disabled, no locks path available.',
[
'external_id' => $externalId,
'lock_path' => $lockPath,
]
);
}
}
}
/**
* @param $externalId
* @return string
*/
private function generateLockPath( $externalId ) {
return $this->locksDirPath . DIRECTORY_SEPARATOR . self::LOCKS_PREFIX . md5( $externalId ) . '.lock';
}
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowLogger
{
const DEBUG = 'debug';
const INFO = 'info';
const WARNING = 'warning';
const ERROR = 'error';
public static function log($type, $message, $context = [])
{
if ((int)Configuration::get('PAYNOW_DEBUG_LOGS_ENABLED') === 1) {
$file_name = 'paynow-' . date('Y-m-d');
$file_path = dirname(__FILE__) . '/../log/' . $file_name . '-' . Tools::encrypt($file_name) . '.log';
file_put_contents($file_path, self::processRecord($type, $message, $context), FILE_APPEND);
}
}
private static function processRecord($type, $message, $context): string
{
$split_message = explode('{}', $message);
$message_part_count = sizeof($split_message);
$result_message = '';
for ($i = 0; $i < $message_part_count; $i++) {
if ($i > 0 && sizeof($context) >= $i) {
$paramValue = $context[$i - 1];
if (!is_array($paramValue)) {
$result_message .= $paramValue;
} else {
$result_message .= json_encode($paramValue);
}
}
$messagePart = $split_message[$i];
$result_message .= $messagePart;
}
if (strpos($message, '{}') === false && is_array($context) && !empty($context)) {
$strContext = json_encode($context);
$result_message = $message . " {$strContext}";
}
return self::getTimestamp() . ' ' . substr(hash('sha256', $_SERVER['REMOTE_ADDR']), 0, 32) . ' ' . Tools::strtoupper($type) . ' ' . $result_message . PHP_EOL;
}
public static function getTimestamp()
{
$now = microtime(true);
$micro = sprintf('%06d', ($now - floor($now)) * 1000000);
return (new DateTime(date('Y-m-d H:i:s.' . $micro, $now)))->format('Y-m-d G:i:s.u');
}
public static function info($message, $context = [])
{
self::log(self::INFO, $message, $context);
}
public static function debug($message, $context = [])
{
self::log(self::DEBUG, $message, $context);
}
public static function error($message, $context = [])
{
self::log(self::ERROR, $message, $context);
}
public static function warning($message, $context = [])
{
self::log(self::WARNING, $message, $context);
}
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowNotificationRetryProcessing extends Exception
{
public $logMessage;
public $logContext;
/**
* @param string $message
* @param array $context
*/
public function __construct($message, $context)
{
$this->logMessage = $message;
$this->logContext = $context;
parent::__construct($message);
}
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowNotificationStopProcessing extends Exception
{
public $logMessage;
public $logContext;
/**
* @param string $message
* @param array $context
*/
public function __construct($message, $context)
{
$this->logMessage = $message;
$this->logContext = $context;
parent::__construct($message);
}
}

View File

@@ -0,0 +1,171 @@
<?php
class PaynowOrderCreateProcessor
{
/** @var Module */
public $module;
public function __construct($module)
{
$this->module = $module;
}
/**
* @param $cart
* @param $external_id
* @param $payment_id
*
* @return Order|null
*/
public function process($cart, $external_id, $payment_id = null): ?Order
{
if (! $this->canProcess($cart->id, $external_id)) {
PaynowLogger::warning(
'Can\'t create an order due optimistic lock on paynow\'s payment data {cartId={}, externalId={}}',
[
$cart->id,
$external_id
]
);
return null;
}
$this->setOptimisticLock($cart->id, $external_id);
PaynowLogger::info(
'Creating an order from cart {cartId={}, externalId={}}',
[
$cart->id,
$external_id,
]
);
try {
return $this->createOrder($cart, $external_id, $payment_id);
} catch (Exception $exception) {
PaynowLogger::error(
'An order has not been created {cartId={}, externalId={}, message={}}',
[
$cart->id,
$external_id,
$exception->getMessage()
]
);
return null;
}
}
/**
* @param Cart $cart
* @param $external_id
* @param $payment_id
*
* @return Order|null
* @throws PrestaShopDatabaseException
* @throws PrestaShopException
*/
private function createOrder(Cart $cart, $external_id, $payment_id = null): ?Order
{
$order_created = $this->module->validateOrder(
(int)$cart->id,
Configuration::get('PAYNOW_ORDER_INITIAL_STATE'),
(float)$cart->getOrderTotal(),
$this->module->displayName,
null,
$payment_id ? ['transaction_id' => $payment_id] : [],
(int)$cart->id_currency,
false,
$cart->secure_key,
new Shop($cart->id_shop)
);
if (! $order_created && ! $this->module->currentOrder) {
PaynowLogger::error(
'An order has not been created {cartId={}, externalId={}, paymentId={}}',
[
$cart->id,
$external_id,
$payment_id
]
);
return null;
}
$order = new Order($this->module->currentOrder);
PaynowLogger::info(
'An order has been successfully created {cartId={}, externalId={}, orderId={}, orderReference={}, paymentId={}}',
[
$cart->id,
$external_id,
$order->id,
$order->reference,
$payment_id
]
);
$this->unsetOptimisticLock($cart->id, $external_id);
return $order;
}
/**
* @param $cart_id
* @param null $external_id
*
* @return bool
*/
private function canProcess($cart_id, $external_id = null): bool
{
try {
if ($external_id) {
$payment_data = PaynowPaymentData::findLastByExternalId($external_id);
} else {
$payment_data = PaynowPaymentData::findLastByCartId($cart_id);
}
return $payment_data === false || ($payment_data && 0 === (int)$payment_data->locked);
} catch (PrestaShopException $exception) {
PaynowLogger::error(
'An error occurred during check can process create order {cartId={}, externalId={}}',
[
$cart_id,
$external_id
]
);
return false;
}
}
private function setOptimisticLock($cart_id, $external_id = null)
{
PaynowLogger::debug(
'Setting optimistic lock on paynow data {cartId={}, externalId={}}',
[
$cart_id,
$external_id
]
);
if ($external_id) {
PaynowPaymentData::setOptimisticLockByExternalId($external_id);
} else {
PaynowPaymentData::setOptimisticLockByCartId($cart_id);
}
}
private function unsetOptimisticLock($cart_id, $external_id = null)
{
PaynowLogger::debug(
'Unsetting optimistic lock on paynow data {cartId={}, externalId={}}',
[
$cart_id,
$external_id
]
);
if ($external_id) {
PaynowPaymentData::unsetOptimisticLockByExternalId($external_id);
} else {
PaynowPaymentData::unsetOptimisticLockByCartId($cart_id);
}
}
}

View File

@@ -0,0 +1,453 @@
<?php
use Paynow\Model\Payment\Status;
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowOrderStateProcessor
{
/** @var Paynow */
public $module;
/** @var \PaynowLockingHelper */
private $lockingHelper;
public function __construct($module)
{
$this->module = $module;
$this->lockingHelper = new PaynowLockingHelper();
}
/**
* @throws \PrestaShopException
* @throws \PaynowNotificationRetryProcessing
* @throws \PaynowNotificationStopProcessing
*/
public function processNotification($data)
{
PaynowLogger::info('Lock checking...', $data);
$externalIdForLockingSystem = $data['externalId'] ?? $data['paymentId'] ?? 'unknown';
if ($this->lockingHelper->checkAndCreate($externalIdForLockingSystem)) {
for ($i = 1; $i<=3; $i++) {
sleep(1);
$isNotificationLocked = $this->lockingHelper->checkAndCreate($externalIdForLockingSystem);
if (!$isNotificationLocked) {
break;
} elseif ($i == 3) {
throw new PaynowNotificationRetryProcessing(
'Skipped processing. Previous notification is still processing.',
$data
);
}
}
}
PaynowLogger::info('Lock passed successfully, notification validation starting.', $data);
if (empty($data['modifiedAt'])) {
$data['modifiedAt'] = (new DateTime('now', new DateTimeZone(Configuration::get('PS_TIMEZONE'))))->format('Y-m-d H:i:s');
} else {
$data['modifiedAt'] = str_replace('T', ' ', $data['modifiedAt']);
}
$isNew = $data['status'] == Status::STATUS_NEW;
$isConfirmed = $data['status'] == Status::STATUS_CONFIRMED;
// Delay NEW status, in case when API sends notifications in bundle,
// status NEW should finish processing at the very end
if ($isNew) {
sleep(1);
}
/** @var \PaynowPaymentData $payment */
$payment = PaynowPaymentData::getActiveByExternalId($data['externalId'], true, $data['paymentId'] ?? 'unknown');
if (empty($payment)) {
$this->lockingHelper->delete($externalIdForLockingSystem);
throw new PaynowNotificationStopProcessing(
'Skipped processing. Payment, Order or Cart not found.',
$data
);
}
$data['activePaymentId'] = $payment->id_payment;
$data['activePaymentStatus'] = $payment->status;
$data['activePaymentDate'] = $payment->sent_at;
if ($payment->status === Status::STATUS_CONFIRMED) {
$this->lockingHelper->delete($externalIdForLockingSystem);
throw new PaynowNotificationStopProcessing(
'Skipped processing. An order already has a paid status.',
$data
);
}
if ($data['status'] == $payment->status
&& $data['paymentId'] == $payment->id_payment) {
$this->lockingHelper->delete($externalIdForLockingSystem);
throw new PaynowNotificationStopProcessing(
sprintf(
'Skipped processing. Transition status (%s) already consumed.',
$payment->status
),
$data
);
}
// proceed with creating order strategy
if ($payment->id_order == '0') {
$cart = new Cart((int)$payment->id_cart);
$canProcessCreateOrder = PaynowHelper::canProcessCreateOrder(
(int)$payment->id_order,
$data['status'],
(int)$payment->locked,
$cart->orderExists()
);
if ($canProcessCreateOrder) {
if ($payment->id_payment != $data['paymentId']) {
PaynowPaymentData::updatePaymentIdByExternalId($data['externalId'], $data['paymentId']);
}
$data['cartId'] = $payment->id_cart;
$data['paymentLocked'] = $payment->locked;
PaynowLogger::info(
'Processing notification to create new order from cart',
$data
);
if (method_exists($cart, 'getCartTotalPrice')) {
$cartTotalPrice = $cart->getCartTotalPrice();
} else {
$cartTotalPrice = $this->getCartTotalPrice($cart);
}
if ((float)$payment->total !== $cartTotalPrice) {
$data['cartTotalPrice'] = $cartTotalPrice;
$data['paymentTotal'] = (float)$payment->total;
$this->lockingHelper->delete($externalIdForLockingSystem);
throw new PaynowNotificationStopProcessing(
'Inconsistent payment and cart amount.',
$data
);
}
PaynowHelper::createOrder($cart, $data['externalId'], $data['paymentId']);
$payment = PaynowPaymentData::getActiveByExternalId($data['externalId']);
}
}
if ($data['paymentId'] != $payment->id_payment && !$isNew && !$isConfirmed) {
$this->retryProcessingNTimes(
$payment,
'Skipped processing. Order has another active payment.',
$data
);
}
if (!empty($payment->sent_at) && $payment->sent_at > $data['modifiedAt'] && !$isConfirmed) {
$this->lockingHelper->delete($externalIdForLockingSystem);
throw new PaynowNotificationStopProcessing(
'Skipped processing. Order has newer status. Time travels are prohibited.',
$data
);
}
$order = new Order($payment->id_order);
if (!Validate::isLoadedObject($order)) {
$data['payment->id_order'] = var_export($payment->id_order, true);
$this->retryProcessingNTimes(
$payment,
'Skipped processing. Order not found.',
$data
);
}
if ($order->module !== $this->module->name) {
$data['orderModule'] = $order->module;
$this->lockingHelper->delete($externalIdForLockingSystem);
throw new PaynowNotificationStopProcessing(
'Skipped processing. Order has other payment (other payment driver).',
$data
);
}
if ((int)$order->current_state === (int)Configuration::get('PAYNOW_ORDER_CONFIRMED_STATE')) {
$this->lockingHelper->delete($externalIdForLockingSystem);
throw new PaynowNotificationStopProcessing(
'Skipped processing. The order has already paid status.',
$data
);
}
if (!$this->isCorrectStatus($payment->status, $data['status']) && !$isConfirmed && !$isNew) {
$this->retryProcessingNTimes(
$payment,
sprintf(
'Skipped processing. Status transition is incorrect (%s => %s).',
$payment->status,
$data['status']
),
$data
);
}
switch ($data['status']) {
case Paynow\Model\Payment\Status::STATUS_NEW:
$this->changeState(
$order,
(int)Configuration::get('PAYNOW_ORDER_INITIAL_STATE'),
$data['status'],
$data['paymentId'],
$data['externalId']
);
break;
case Paynow\Model\Payment\Status::STATUS_REJECTED:
$this->changeState(
$order,
(int)Configuration::get('PAYNOW_ORDER_REJECTED_STATE'),
$data['status'],
$data['paymentId'],
$data['externalId']
);
break;
case Paynow\Model\Payment\Status::STATUS_CONFIRMED:
$this->changeState(
$order,
(int)Configuration::get('PAYNOW_ORDER_CONFIRMED_STATE'),
$data['status'],
$data['paymentId'],
$data['externalId']
);
$this->addOrderPayment($order, $data['paymentId'], $payment->total);
break;
case Paynow\Model\Payment\Status::STATUS_ERROR:
$this->changeState(
$order,
(int)Configuration::get('PAYNOW_ORDER_ERROR_STATE'),
$data['status'],
$data['paymentId'],
$data['externalId']
);
break;
case Paynow\Model\Payment\Status::STATUS_ABANDONED:
$this->changeState(
$order,
(int)Configuration::get('PAYNOW_ORDER_ABANDONED_STATE'),
$data['status'],
$data['paymentId'],
$data['externalId']
);
break;
case Paynow\Model\Payment\Status::STATUS_EXPIRED:
$this->changeState(
$order,
(int)Configuration::get('PAYNOW_ORDER_EXPIRED_STATE'),
$data['status'],
$data['paymentId'],
$data['externalId']
);
break;
}
if ($isNew) {
PaynowPaymentData::create(
$data['paymentId'],
$data['status'],
$order->id,
$payment->id_cart,
$payment->order_reference,
$data['externalId'],
$payment->total,
$data['modifiedAt']
);
} else {
$payment->status = $data['status'];
$payment->sent_at = $data['modifiedAt'];
$payment->save();
}
$this->lockingHelper->delete($externalIdForLockingSystem);
}
private function changeState($order, $new_order_state_id, $payment_status, $id_payment, $external_id, $withEmail = true)
{
$history = new OrderHistory();
$history->id_order = $order->id;
if ($order->current_state != $new_order_state_id) {
PaynowLogger::info(
'Adding new state to order\'s history {externalId={}, orderId={}, orderReference={}, paymentId={}, paymentStatus={}, state={}}',
[
$external_id,
$order->id,
$order->reference,
$id_payment,
$payment_status,
$new_order_state_id
]
);
$history->changeIdOrderState(
$new_order_state_id,
$order->id
);
$history->addWithemail($withEmail);
PaynowLogger::info(
'Added new state to order\'s history {externalId={}, orderId={}, orderReference={}, paymentId={}, paymentStatus={}, state={}, historyId={}}',
[
$external_id,
$order->id,
$order->reference,
$id_payment,
$payment_status,
$new_order_state_id,
$history->id
]
);
}
}
private function isCorrectStatus($previous_status, $next_status): bool
{
$payment_status_flow = [
Paynow\Model\Payment\Status::STATUS_NEW => [
Paynow\Model\Payment\Status::STATUS_NEW,
Paynow\Model\Payment\Status::STATUS_PENDING,
Paynow\Model\Payment\Status::STATUS_ERROR,
Paynow\Model\Payment\Status::STATUS_CONFIRMED,
Paynow\Model\Payment\Status::STATUS_REJECTED,
Paynow\Model\Payment\Status::STATUS_EXPIRED
],
Paynow\Model\Payment\Status::STATUS_PENDING => [
Paynow\Model\Payment\Status::STATUS_NEW,
Paynow\Model\Payment\Status::STATUS_CONFIRMED,
Paynow\Model\Payment\Status::STATUS_REJECTED,
Paynow\Model\Payment\Status::STATUS_EXPIRED,
Paynow\Model\Payment\Status::STATUS_ABANDONED
],
Paynow\Model\Payment\Status::STATUS_REJECTED => [
Paynow\Model\Payment\Status::STATUS_CONFIRMED,
Paynow\Model\Payment\Status::STATUS_ABANDONED,
Paynow\Model\Payment\Status::STATUS_NEW
],
Paynow\Model\Payment\Status::STATUS_CONFIRMED => [],
Paynow\Model\Payment\Status::STATUS_ERROR => [
Paynow\Model\Payment\Status::STATUS_CONFIRMED,
Paynow\Model\Payment\Status::STATUS_REJECTED,
Paynow\Model\Payment\Status::STATUS_ABANDONED,
Paynow\Model\Payment\Status::STATUS_NEW
],
Paynow\Model\Payment\Status::STATUS_EXPIRED => [
Paynow\Model\Payment\Status::STATUS_NEW
],
Paynow\Model\Payment\Status::STATUS_ABANDONED => [
Paynow\Model\Payment\Status::STATUS_NEW
]
];
$previous_status_exists = isset($payment_status_flow[$previous_status]);
$is_change_possible = in_array($next_status, $payment_status_flow[$previous_status]);
return $previous_status_exists && $is_change_possible;
}
/**
* @param $order
* @param $id_payment
* @param $total
*/
private function addOrderPayment($order, $id_payment, $total)
{
$payments = $order->getOrderPaymentCollection()->getResults();
if (count($payments) > 0) {
$payments[0]->transaction_id = $id_payment;
$payments[0]->update();
} else {
try {
$currentOrder = new Order($order->id);
// in case when order payment was not created
$result = $currentOrder->addOrderPayment(
$total,
$this->module->displayName,
$id_payment
);
if (!$result) {
PaynowLogger::error(
'Cannot create order payment entry',
[
$currentOrder->id,
$currentOrder->reference,
$id_payment
]
);
}
} catch (Throwable $t) {
PaynowLogger::error(
'Cannot create order payment entry due to exception: ',
[
$t->getMessage(),
$t->getLine(),
$order->id,
$order->reference,
$id_payment
]
);
}
}
}
/**
* @param PaynowPaymentData $payment
* @param $message
* @param $data
* @param int $counter
* @throws \PaynowNotificationStopProcessing
* @throws \PaynowNotificationRetryProcessing
* @throws \PrestaShopException
*/
private function retryProcessingNTimes(PaynowPaymentData $payment, $message, $data, $counter = 5)
{
$payment->counter = (int)$payment->counter + 1;
$payment->save();
$data['counter'] = $payment->counter;
$this->lockingHelper->delete($data['externalId'] ?? $data['paymentId'] ?? 'unknown');
if ($payment->counter >= $counter) {
throw new PaynowNotificationStopProcessing($message, $data);
} else {
throw new PaynowNotificationRetryProcessing($message, $data);
}
}
private function getCartTotalPrice($cart): float
{
$summary = $cart->getSummaryDetails();
if (version_compare(_PS_VERSION_, '1.7.1.0', 'ge')) {
$id_order = (int)Order::getIdByCartId($cart->id);
} else {
$id_order = (int)Order::getOrderByCartId($cart->id);
}
$order = new Order($id_order);
if (Validate::isLoadedObject($order)) {
$taxCalculationMethod = $order->getTaxCalculationMethod();
} else {
$taxCalculationMethod = Group::getPriceDisplayMethod(Group::getCurrent()->id);
}
return $taxCalculationMethod == PS_TAX_EXC ?
(float)$summary['total_price_without_tax'] :
(float)$summary['total_price'];
}
}

View File

@@ -0,0 +1,23 @@
<?php
class PaynowPaymentAuthorizeException extends Exception
{
/**
* @var string
*/
private $externalId;
public function __construct(string $message, string $externalId, Throwable $previous = null)
{
$this->externalId = $externalId;
parent::__construct($message, 0, $previous);
}
/**
* @return string
*/
public function getExternalId(): string
{
return $this->externalId;
}
}

View File

@@ -0,0 +1,229 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
class PaynowPaymentDataBuilder
{
private $context;
/**
* @var Paynow
*/
private $module;
/**
* @var array
*/
private $translations;
public function __construct($module)
{
$this->context = Context::getContext();
$this->module = $module;
$this->translations = $this->module->getTranslationsArray();
}
/**
* Returns payment request data based on cart
*
* @param $cart
* @param $external_id
*
* @return array
* @throws Exception
*/
public function fromCart($cart, $external_id): array
{
return $this->build(
$cart->id_currency,
$cart->id_customer,
$cart->getOrderTotal(),
$cart->id,
$this->translations['Order to cart: '] . $cart->id,
$external_id
);
}
/**
* Returns payment request data based on order
*
* @param $order
*
* @return array
*/
public function fromOrder($order): array
{
return $this->build(
$order->id_currency,
$order->id_customer,
$order->total_paid,
$order->id_cart,
$this->translations['Order No: '] . $order->reference,
$order->reference
);
}
/**
* Returns payments request data
*
* @param $id_currency
* @param $id_customer
* @param $total_to_paid
* @param $id_cart
* @param $description
*
* @param null $external_id
*
* @return array
*/
private function build(
$id_currency,
$id_customer,
$total_to_paid,
$id_cart,
$description,
$external_id = null
): array {
$currency = Currency::getCurrency($id_currency);
$customer = new Customer((int)$id_customer);
$paymentMethodId = Tools::getValue('paymentMethodId');
$request = [
'amount' => number_format($total_to_paid * 100, 0, '', ''),
'currency' => $currency['iso_code'],
'externalId' => $external_id,
'description' => $description,
'buyer' => [
'firstName' => $customer->firstname,
'lastName' => $customer->lastname,
'email' => $customer->email,
'locale' => $this->context->language->locale ?? $this->context->language->language_code
],
'continueUrl' => PaynowLinkHelper::getContinueUrl(
$id_cart,
$this->module->id,
$customer->secure_key,
$external_id
)
];
try {
$address = new Address($this->context->cart->id_address_delivery);
$invoiceAddress = new Address($this->context->cart->id_address_invoice);
try {
$state = new State($address->id_state);
} catch (Throwable $e) {
$state = null;
}
try {
$invoiceState = new State($invoiceAddress->id_state);
} catch (Throwable $e) {
$invoiceState = null;
}
try {
$country = Country::getIsoById($address->id_country);
} catch (Throwable $e) {
$country = null;
}
try {
$invoiceCountry = Country::getIsoById($invoiceAddress->id_country);
} catch (Throwable $e) {
$invoiceCountry = null;
}
$request['buyer']['address'] = [
'billing' => [
'street' => $invoiceAddress->address1,
'houseNumber' => $invoiceAddress->address2,
'apartmentNumber' => '',
'zipcode' => $invoiceAddress->postcode,
'city' => $invoiceAddress->city,
'county' => $invoiceState ? $invoiceState->name : '',
'country' => $invoiceCountry ?: '',
],
'shipping' => [
'street' => $address->address1,
'houseNumber' => $address->address2,
'apartmentNumber' => '',
'zipcode' => $address->postcode,
'city' => $address->city,
'county' => $state ? $state->name : '',
'country' => $country ?: '',
]
];
} catch (Throwable $exception) {
PaynowLogger::error('Cannot add addresses to payment data', ['msg' => $exception->getMessage()]);
}
if (!empty($id_customer) && $this->context->customer && $this->context->customer->is_guest === '0'){
$request['buyer']['externalId'] = PaynowKeysGenerator::generateBuyerExternalId($id_customer, $this->module);
}
if (! empty($paymentMethodId)) {
$request['paymentMethodId'] = (int)$paymentMethodId;
}
if (Configuration::get('PAYNOW_PAYMENT_VALIDITY_TIME_ENABLED')) {
$request['validityTime'] = Configuration::get('PAYNOW_PAYMENT_VALIDITY_TIME');
}
if (! empty(Tools::getValue('blikCode'))) {
$request['authorizationCode'] = (int)preg_replace('/\s+/', '', Tools::getValue('blikCode'));
}
if (!empty(Tools::getValue('paymentMethodToken'))) {
$request['paymentMethodToken'] = Tools::getValue('paymentMethodToken');
}
if (! empty(Tools::getValue('paymentMethodFingerprint'))) {
$request['buyer']['deviceFingerprint'] = Tools::getValue('paymentMethodFingerprint');
}
if (Configuration::get('PAYNOW_SEND_ORDER_ITEMS')) {
$products = $this->context->cart->getProducts(true);
$order_items = [];
foreach ($products as $product) {
$order_items[] = [
'name' => $product['name'],
'category' => $this->getCategoriesNames($product['id_category_default']),
'quantity' => $product['quantity'],
'price' => number_format($product['price'] * 100, 0, '', '')
];
}
if (! empty($order_items)) {
$request['orderItems'] = $order_items;
}
}
return $request;
}
/**
* @param $id_category_default
*
* @return string
*/
private function getCategoriesNames($id_category_default): string
{
$categoryDefault = new Category($id_category_default, $this->context->language->id);
$categoriesNames = [$categoryDefault->name];
foreach ($categoryDefault->getAllParents() as $category) {
if ($category->id_parent != 0 && !$category->is_root_category) {
array_unshift($categoriesNames, $category->name);
}
}
return implode(", ", $categoriesNames);
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
use Paynow\Client;
use Paynow\Exception\ConfigurationException;
use Paynow\Exception\PaynowException;
use Paynow\Response\PaymentMethods\PaymentMethods;
use Paynow\Service\Payment;
/**
* Class PaymentMethodsHelper
*/
class PaynowPaymentMethodsHelper
{
/**
* @var Payment
*/
private $payment_client;
/**
* @param Client $client
*
* @throws ConfigurationException
*/
public function __construct(Client $client)
{
$this->payment_client = new Paynow\Service\Payment($client);
}
/**
* @param $currency_iso_code
* @param $total
* @param $context
* @param $module
*
* @return PaymentMethods|null
*/
public function getAvailable($currency_iso_code, $total, $context, $module): ?PaymentMethods
{
$applePayEnabled = htmlspecialchars($_COOKIE['applePayEnabled'] ?? '0') === '1';
$idempotencyKey = PaynowKeysGenerator::generateIdempotencyKey(PaynowKeysGenerator::generateExternalIdByCart($context->cart));
$buyerExternalId = null;
if ($context->customer && $context->customer->isLogged()) {
$buyerExternalId = PaynowKeysGenerator::generateBuyerExternalId($context->cart->id_customer, $module);
}
try {
return $this->payment_client->getPaymentMethods($currency_iso_code, $total, $applePayEnabled, $idempotencyKey, $buyerExternalId);
} catch (PaynowException $exception) {
PaynowLogger::error(
'An error occurred during payment methods retrieve {currency={}, total={}, applePayEnabled={}, idempotencyKey={}, buyerExternalId={}, code={}, message={}, errors={}, m={}}',
[
$currency_iso_code,
$total,
$applePayEnabled,
$idempotencyKey,
$buyerExternalId,
$exception->getCode(),
$exception->getPrevious()->getMessage(),
$exception->getErrors(),
$exception->getMessage()
]
);
}
return null;
}
}

View File

@@ -0,0 +1,244 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
use Paynow\Model\PaymentMethods\PaymentMethod;
use Paynow\Response\DataProcessing\Notices;
use Paynow\Response\PaymentMethods\PaymentMethods;
use PrestaShop\PrestaShop\Core\Payment\PaymentOption;
class PaynowPaymentOptions
{
/**
* @var Context
*/
private $context;
private $module;
private $payment_methods;
/**
* @var Notices
*/
private $data_processing_notices;
/**
* @param $context
* @param $module
* @param PaymentMethods|null
* @param $data_processing_notices
*/
public function __construct($context, $module, $payment_methods, $data_processing_notices)
{
$this->context = $context;
$this->module = $module;
$this->payment_methods = $payment_methods;
$this->data_processing_notices = $data_processing_notices;
}
public function generate(): array
{
if (!Configuration::get('PAYNOW_SEPARATE_PAYMENT_METHODS') || empty($this->payment_methods)) {
return [
$this->getPaymentOption(
$this->module->getCallToActionText(),
$this->module->getLogo(),
PaynowLinkHelper::getPaymentUrl()
)
];
}
$payment_options = [];
$digital_wallets = [
Paynow\Model\PaymentMethods\Type::GOOGLE_PAY,
Paynow\Model\PaymentMethods\Type::APPLE_PAY,
];
$this->context->smarty->assign([
'action' => PaynowLinkHelper::getPaymentUrl(),
'data_processing_notices' => $this->data_processing_notices
]);
$isAnyPblEnabled = false;
/** @var PaymentMethod $pbl_payment_method */
foreach ($this->payment_methods->getOnlyPbls() as $pbl_payment_method) {
if ($pbl_payment_method->isEnabled()) {
$isAnyPblEnabled = true;
break;
}
}
$hiddenPaymentTypes = explode(',', Configuration::get('PAYNOW_HIDE_PAYMENT_TYPES'));
$digitalWalletsHidden = in_array('DIGITAL_WALLETS', $hiddenPaymentTypes);
$digitalWalletsPayments = [];
$list = [];
/** @var PaymentMethod $payment_method */
foreach ($this->payment_methods->getAll() as $payment_method) {
if (isset($list[$payment_method->getType()])) {
continue;
}
if (in_array($payment_method->getType(), $hiddenPaymentTypes)) {
continue;
}
if (Paynow\Model\PaymentMethods\Type::PBL == $payment_method->getType()) {
if (!$isAnyPblEnabled) {
continue;
}
$this->context->smarty->assign([
'paynowPbls' => $this->payment_methods->getOnlyPbls(),
]);
$payment_options[] = $this->getPaymentOption(
$this->module->getPaymentMethodTitle($payment_method->getType()),
$this->module->getLogo(),
PaynowLinkHelper::getPaymentUrl(),
'module:paynow/views/templates/front/1.7/payment_form.tpl'
);
} elseif (in_array($payment_method->getType(), $digital_wallets)) {
if (!$payment_method->isEnabled() || $digitalWalletsHidden) {
continue;
}
$digitalWalletsPayments[] = $payment_method;
} else {
if (!$payment_method->isEnabled()) {
continue;
}
$this->setUpAdditionalTemplateVariables($payment_method);
$payment_options[] = $this->getPaymentOption(
$this->module->getPaymentMethodTitle($payment_method->getType()),
$payment_method->getImage(),
PaynowLinkHelper::getPaymentUrl([
'paymentMethodId' => $payment_method->getId()
]),
$this->getForm($payment_method)
);
}
$list[$payment_method->getType()] = $payment_method->getId();
}
if (!empty($digitalWalletsPayments)) {
$this->context->smarty->assign([
'paynowDigitalWalletsPayments' => $digitalWalletsPayments,
]);
$payment_options[] = $this->getPaymentOption(
$this->module->getPaymentMethodTitle('DIGITAL_WALLETS'),
count($digitalWalletsPayments) === 1 ? $digitalWalletsPayments[0]->getImage() : $this->module->getDigitalWalletsLogo(),
PaynowLinkHelper::getPaymentUrl(),
'module:paynow/views/templates/front/1.7/payment_method_digital_wallets_form.tpl'
);
}
return $payment_options;
}
private function setUpAdditionalTemplateVariables($payment_method)
{
if (Paynow\Model\PaymentMethods\Type::BLIK == $payment_method->getType()) {
$this->context->smarty->assign([
'action_blik' => Context::getContext()->link->getModuleLink(
'paynow',
'chargeBlik',
[
'paymentMethodId' => $payment_method->getId()
]
),
'action_token' => Tools::encrypt($this->context->customer->secure_key ?? ''),
'action_token_refresh' => Context::getContext()->link->getModuleLink('paynow', 'customerToken'),
'error_message' => $this->getMessage('An error occurred during the payment process'),
'terms_message' => $this->getMessage('First accept the terms of service, then click pay.'),
'blik_autofocus' => Configuration::get('PAYNOW_BLIK_AUTOFOCUS_ENABLED') === '0' ? '0' : '1',
]);
} elseif (Paynow\Model\PaymentMethods\Type::CARD == $payment_method->getType()) {
$this->context->smarty->assign([
'action_card' => PaynowLinkHelper::getPaymentUrl([
'paymentMethodId' => $payment_method->getId()
]),
'action_remove_saved_instrument' => Context::getContext()->link->getModuleLink(
'paynow',
'removeSavedInstrument'
),
'action_remove_saved_instrument_token' => Tools::encrypt($this->context->customer->secure_key ?? ''),
'default_card_image' => Media::getMediaPath(_PS_MODULE_DIR_ . $this->module->name . '/views/img/card-default.svg'),
'paynow_card_instruments' => $payment_method->getSavedInstruments(),
]);
} elseif (Paynow\Model\PaymentMethods\Type::PAYPO == $payment_method->getType()) {
$this->context->smarty->assign([
'action_paypo' => PaynowLinkHelper::getPaymentUrl([
'paymentMethodId' => $payment_method->getId()
]),
]);
}
}
private function getForm($payment_method): ?string
{
if ($this->isWhiteLabelEnabled(Paynow\Model\PaymentMethods\Type::BLIK, $payment_method)) {
return 'module:paynow/views/templates/front/1.7/payment_method_blik_form.tpl';
}
if (Paynow\Model\PaymentMethods\Type::CARD === $payment_method->getType()) {
return 'module:paynow/views/templates/front/1.7/payment_method_card_form.tpl';
}
if (Paynow\Model\PaymentMethods\Type::PAYPO === $payment_method->getType()) {
return 'module:paynow/views/templates/front/1.7/payment_method_paypo_form.tpl';
}
return null;
}
/**
* @param string $payment_method_type
* @param PaymentMethod $payment_method
*
* @return bool
*/
private function isWhiteLabelEnabled(string $payment_method_type, PaymentMethod $payment_method): bool
{
return $payment_method_type == $payment_method->getType()
&& Paynow\Model\PaymentMethods\AuthorizationType::CODE == $payment_method->getAuthorizationType();
}
/**
* @param $title
* @param $logo
* @param $action
* @param null $form
* @param null $additional
*
* @return PaymentOption
* @throws SmartyException
*/
private function getPaymentOption($title, $logo, $action, $form = null, $additional = null): PaymentOption
{
$paymentOption = new PrestaShop\PrestaShop\Core\Payment\PaymentOption();
$paymentOption->setModuleName($this->module->name)
->setCallToActionText($title)
->setLogo($logo)
->setAdditionalInformation($additional)
->setAction($action);
if ($form) {
$paymentOption->setForm($this->context->smarty->fetch($form));
}
return $paymentOption;
}
private function getMessage($key)
{
return $this->module->getTranslationsArray()[$key];
}
}

View File

@@ -0,0 +1,190 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
use Paynow\Exception\ConfigurationException;
use Paynow\Exception\PaynowException;
use Paynow\Response\Payment\Authorize;
use Paynow\Service\Payment;
class PaynowPaymentProcessor
{
/**
* @var Context
*/
private $context;
/**
* @var Paynow
*/
private $module;
/**
* @var Payment
*/
private $paymentClient;
private $paymentDataBuilder;
private $externalId = null;
/**
* @param Context $context
* @param $module
*
* @throws ConfigurationException
*/
public function __construct(Context $context, $module)
{
$this->context = $context;
$this->module = $module;
$this->paymentClient = new Payment($module->getPaynowClient());
$this->paymentDataBuilder = new PaynowPaymentDataBuilder($this->module);
}
/**
* @return array
* @throws Exception
*/
public function process(): array
{
if (PaynowConfigurationHelper::CREATE_ORDER_BEFORE_PAYMENT === (int)Configuration::get('PAYNOW_CREATE_ORDER_STATE') && ! empty($this->module->currentOrder)) {
$order = new Order($this->module->currentOrder);
$payment = $this->processFromOrder($order, $this->getExternalId());
PaynowPaymentData::create(
$payment->getPaymentId(),
Paynow\Model\Payment\Status::STATUS_NEW,
$order->id,
$order->id_cart,
$order->reference,
$order->reference,
$order->total_paid
);
} else {
$cart = $this->context->cart;
$payment = $this->processFromCart($cart, $this->getExternalId());
PaynowPaymentData::create(
$payment->getPaymentId(),
Paynow\Model\Payment\Status::STATUS_NEW,
null,
$cart->id,
null,
$this->getExternalId(),
$cart->getOrderTotal()
);
}
PaynowLogger::info(
'Payment has been successfully created {cartId={}, externalId={}, paymentId={}, status={}}',
[
$this->context->cart->id,
$this->getExternalId(),
$payment->getPaymentId(),
$payment->getStatus()
]
);
return [
'payment_id' => $payment->getPaymentId(),
'status' => $payment->getStatus(),
'redirect_url' => $payment->getRedirectUrl() ?? null,
'external_id' => $this->getExternalId()
];
}
/**
* @return string
*/
public function getExternalId(): string
{
if (empty($this->externalId)) {
$this->generateExternalId();
}
return $this->externalId;
}
/**
* @throws PaynowPaymentAuthorizeException
*/
private function processFromOrder($order, $external_id): ?Authorize
{
PaynowLogger::info(
'Processing payment for order {cartId={}, externalId={}, orderId={}, orderReference={}}',
[
$order->id_cart,
$external_id,
$order->id,
$order->reference
]
);
$idempotency_key = PaynowKeysGenerator::generateIdempotencyKey($external_id);
$payment_request_data = $this->paymentDataBuilder->fromOrder($order);
return $this->sendPaymentRequest($payment_request_data, $idempotency_key);
}
/**
* @throws PaynowPaymentAuthorizeException
*/
private function processFromCart($cart, $external_id): ?Authorize
{
PaynowLogger::info(
'Processing payment for cart {cartId={}, externalId={}}',
[
$cart->id,
$external_id
]
);
$idempotency_key = PaynowKeysGenerator::generateIdempotencyKey($external_id);
$payment_request_data = $this->paymentDataBuilder->fromCart($cart, $external_id);
return $this->sendPaymentRequest($payment_request_data, $idempotency_key);
}
/**
* @return void
*/
private function generateExternalId(): void
{
if (PaynowConfigurationHelper::CREATE_ORDER_BEFORE_PAYMENT === (int)Configuration::get('PAYNOW_CREATE_ORDER_STATE') && ! empty($this->module->currentOrder)) {
$order = new Order($this->module->currentOrder);
$this->externalId = PaynowKeysGenerator::generateExternalIdByOrder($order);
} else {
$cart = $this->context->cart;
$this->externalId = PaynowKeysGenerator::generateExternalIdByCart($cart);
}
}
/**
* @param $payment_request_data
* @param $idempotency_key
*
* @return Authorize|null
* @throws PaynowPaymentAuthorizeException
*/
private function sendPaymentRequest($payment_request_data, $idempotency_key): ?Authorize
{
try {
return $this->paymentClient->authorize($payment_request_data, $idempotency_key);
} catch (PaynowException $exception) {
throw new PaynowPaymentAuthorizeException(
$exception->getMessage(),
$payment_request_data['externalId'],
$exception
);
}
}
}

View File

@@ -0,0 +1,129 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
use Paynow\Client;
class PaynowRefundProcessor
{
/**
* @var Client
*/
private $client;
/**
* @var string
*/
private $module_display_name;
/**
* @param Client $client
* @param $module_display_name
*/
public function __construct(Client $client, $module_display_name)
{
$this->client = $client;
$this->module_display_name = $module_display_name;
}
/**
* @param Order $order
*/
public function processFromOrderSlip(Order $order)
{
$orderSlip = $order->getOrderSlipsCollection()
->orderBy('date_upd', 'desc')
->getFirst();
$amount = $orderSlip->amount + $orderSlip->shipping_cost_amount;
$filteredPayments = $this->filterPayments(
$order->getOrderPaymentCollection()->getResults(),
$amount
);
$this->process($order, $filteredPayments, $amount);
}
/**
* @param Order $order
*/
public function processFromOrderStatusChange(Order $order)
{
$amount_to_refund = $order->total_paid;
if (!Configuration::get('PAYNOW_REFUNDS_WITH_SHIPPING_COSTS')) {
$amount_to_refund -= $order->total_shipping_tax_incl;
}
$filteredPayments = $this->filterPayments(
$order->getOrderPaymentCollection()->getResults(),
$amount_to_refund
);
$this->process($order, $filteredPayments, $amount_to_refund);
}
private function process($order, $payments, $amount)
{
if (! empty($payments)) {
PaynowLogger::info(
'Processing refund request {orderId={}, orderReference={}}',
[
$order->id,
$order->reference
]
);
$payment = $payments[0];
$refund_amount = number_format($amount * 100, 0, '', '');
try {
PaynowLogger::info(
'Found transaction to make a refund {amount={}, orderId={}, orderReference={}, paymentId={}}',
[
$refund_amount,
$order->id,
$order->reference,
$payment->transaction_id
]
);
$response = (new Paynow\Service\Refund($this->client))->create(
$payment->transaction_id,
uniqid($payment->order_reference, true),
$refund_amount
);
PaynowLogger::info(
'Refund has been created successfully {orderId={}, orderReference={}, refundId={}}',
[
$payment->id_order,
$payment->order_reference,
$response->getRefundId()
]
);
} catch (Paynow\Exception\PaynowException $exception) {
foreach ($exception->getErrors() as $error) {
PaynowLogger::error(
'An error occurred during refund request process {code={}, orderId={}, orderReference={}, paymentId={}, type={}, message={}}',
[
$exception->getCode(),
$payment->id_order,
$payment->order_reference,
$payment->transaction_id,
$error->getType(),
$error->getMessage()
]
);
}
}
}
}
private function filterPayments($payments, $amount): array
{
return array_filter($payments, function ($payment) use ($amount) {
return $this->module_display_name === $payment->payment_method &&
$amount <= $payment->amount;
});
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* NOTICE OF LICENSE
*
* This source file is subject to the MIT License (MIT)
* that is bundled with this package in the file LICENSE.md.
*
* @author mElements S.A.
* @copyright mElements S.A.
* @license MIT License
*/
use Paynow\Exception\ConfigurationException;
use Paynow\Exception\PaynowException;
use Paynow\Service\Payment;
/**
* Class PaynowSavedInstrumentHelper
*/
class PaynowSavedInstrumentHelper
{
/**
* @var Context
*/
private $context;
/**
* @var Paynow
*/
private $module;
/**
* @var Payment
*/
private $payment_client;
/**
* @param Context $context
* @param $module
* @throws ConfigurationException
*/
public function __construct(Context $context, $module)
{
$this->context = $context;
$this->module = $module;
$this->payment_client = new Paynow\Service\Payment($module->getPaynowClient());
}
/**
* @param $token
*
* @return void
*/
public function remove($token): void
{
try {
$idempotencyKey = PaynowKeysGenerator::generateIdempotencyKey(PaynowKeysGenerator::generateExternalIdByCart($this->context->cart));
$buyerExternalId = PaynowKeysGenerator::generateBuyerExternalId($this->context->cart->id_customer, $this->module);
$this->payment_client->removeSavedInstrument($buyerExternalId, $token, $idempotencyKey);
} catch (PaynowException $exception) {
PaynowLogger::error(
'An error occurred during saved instrument removal {code={}, message={}, errors={}, m={}}',
[
$exception->getCode(),
$exception->getPrevious()->getMessage(),
$exception->getErrors(),
$exception->getMessage()
]
);
}
}
}

View File

@@ -0,0 +1,11 @@
<?php
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../../../');
exit;