Не работает оформление заказа
PakoGonsalezZ
8 ноября 2023 г.
Есть сайт на шаблоне http://hmp-delivery-water.hostcms.ru/
Пытаюсь доработать корзину, чтобы доставка добавлялась и выбор формы оплаты, добавил выбор способа оплаты
Обработчик взял отсюдаhttps://yookassa.ru/docs/support/payments/onboarding/integration/cms-module/hostcms#hostcms-01
Но при оформлении заказа вижу ошибку
Exception: Call to a member function orderParams() on null
Код оформления заказа
Код обработчика ЮKassa
Пытаюсь доработать корзину, чтобы доставка добавлялась и выбор формы оплаты, добавил выбор способа оплаты
Обработчик взял отсюда
Но при оформлении заказа вижу ошибку
Exception: Call to a member function orderParams() on null
Код оформления заказа
// Оформление заказа
if (Core_Array::getGet('checkout')){
// Ajax
$_SESSION['last_order_id'] = 0;
ob_start();
$ShopCartController = Shop_Cart_Controller::instance();
$aCart = $ShopCartController->getAll($oShop);
// TODO: Сделать правильное оформление заказа
// Должны обрабатываться поля: surname, name, patronymic, email, phone, address (см. оригинал оформления)
//
// TODO: !!!!!!!! Форма не должна отправлятся с перезагрузкой страницы
$userPhone = '7'.strval(Core_Array::getGet('phone'));
$_SESSION['hostcmsOrder']['orderAdminNotificationXsl'] = Core_Array::get(Core_Page::instance()->libParams, 'orderAdminNotificationXsl');
$_SESSION['hostcmsOrder']['orderUserNotificationXsl'] = Core_Array::get(Core_Page::instance()->libParams, 'orderUserNotificationXsl');
$_SESSION['hostcmsOrder']['checkoutXsl'] = Core_Array::get(Core_Page::instance()->libParams, 'checkoutXsl');
$_SESSION['hostcmsOrder']['shop_country_id'] = 0;
$_SESSION['hostcmsOrder']['shop_country_location_id'] = intval(Core_Array::getGet('shop_country_location_id', 0));
$_SESSION['hostcmsOrder']['shop_country_location_city_id'] = intval(Core_Array::getGet('shop_country_location_city_id', 0));
$_SESSION['hostcmsOrder']['shop_country_location_city_area_id'] = intval(Core_Array::getGet('shop_country_location_city_area_id', 0));
$_SESSION['hostcmsOrder']['shop_country_location_city_area_id'] = intval(Core_Array::getGet('shop_country_location_city_area_id', 0));
$_SESSION['hostcmsOrder']['surname'] = strval(Core_Array::getGet('surname'));
$_SESSION['hostcmsOrder']['name'] = strval(Core_Array::getGet('name'));
$_SESSION['hostcmsOrder']['patronymic'] = strval(Core_Array::getGet('patronymic'));
$_SESSION['hostcmsOrder']['email'] = strval(Core_Array::getGet('email'));
$_SESSION['hostcmsOrder']['address'] = strval(Core_Array::getGet('address'));
$_SESSION['hostcmsOrder']['phone'] = $userPhone;
$_SESSION['hostcmsOrder']['shop_delivery_condition_id'] = intval(Core_Array::getGet('shop_delivery_condition_id', 0));
$_SESSION['hostcmsOrder']['shop_delivery_id'] = intval(Core_Array::getGet('shop_delivery_condition_id', 0));
$shop_payment_system_id = $_SESSION['hostcmsOrder']['shop_payment_system_id'] = intval(Core_Array::getPost('shop_payment_system_id'));
Shop_Payment_System_Handler::factory(
Core_Entity::factory('Shop_Payment_System', $shop_payment_system_id)
)
->orderParams($_SESSION['hostcmsOrder'])
->execute();
echo json_encode(ob_get_clean());
exit();
}
if (Core_Array::getGet('checkout')){
// Ajax
$_SESSION['last_order_id'] = 0;
ob_start();
$ShopCartController = Shop_Cart_Controller::instance();
$aCart = $ShopCartController->getAll($oShop);
// TODO: Сделать правильное оформление заказа
// Должны обрабатываться поля: surname, name, patronymic, email, phone, address (см. оригинал оформления)
//
// TODO: !!!!!!!! Форма не должна отправлятся с перезагрузкой страницы
$userPhone = '7'.strval(Core_Array::getGet('phone'));
$_SESSION['hostcmsOrder']['orderAdminNotificationXsl'] = Core_Array::get(Core_Page::instance()->libParams, 'orderAdminNotificationXsl');
$_SESSION['hostcmsOrder']['orderUserNotificationXsl'] = Core_Array::get(Core_Page::instance()->libParams, 'orderUserNotificationXsl');
$_SESSION['hostcmsOrder']['checkoutXsl'] = Core_Array::get(Core_Page::instance()->libParams, 'checkoutXsl');
$_SESSION['hostcmsOrder']['shop_country_id'] = 0;
$_SESSION['hostcmsOrder']['shop_country_location_id'] = intval(Core_Array::getGet('shop_country_location_id', 0));
$_SESSION['hostcmsOrder']['shop_country_location_city_id'] = intval(Core_Array::getGet('shop_country_location_city_id', 0));
$_SESSION['hostcmsOrder']['shop_country_location_city_area_id'] = intval(Core_Array::getGet('shop_country_location_city_area_id', 0));
$_SESSION['hostcmsOrder']['shop_country_location_city_area_id'] = intval(Core_Array::getGet('shop_country_location_city_area_id', 0));
$_SESSION['hostcmsOrder']['surname'] = strval(Core_Array::getGet('surname'));
$_SESSION['hostcmsOrder']['name'] = strval(Core_Array::getGet('name'));
$_SESSION['hostcmsOrder']['patronymic'] = strval(Core_Array::getGet('patronymic'));
$_SESSION['hostcmsOrder']['email'] = strval(Core_Array::getGet('email'));
$_SESSION['hostcmsOrder']['address'] = strval(Core_Array::getGet('address'));
$_SESSION['hostcmsOrder']['phone'] = $userPhone;
$_SESSION['hostcmsOrder']['shop_delivery_condition_id'] = intval(Core_Array::getGet('shop_delivery_condition_id', 0));
$_SESSION['hostcmsOrder']['shop_delivery_id'] = intval(Core_Array::getGet('shop_delivery_condition_id', 0));
$shop_payment_system_id = $_SESSION['hostcmsOrder']['shop_payment_system_id'] = intval(Core_Array::getPost('shop_payment_system_id'));
Shop_Payment_System_Handler::factory(
Core_Entity::factory('Shop_Payment_System', $shop_payment_system_id)
)
->orderParams($_SESSION['hostcmsOrder'])
->execute();
echo json_encode(ob_get_clean());
exit();
}
Код обработчика ЮKassa
<?php
/**
* ЮKassa
* Версия 2.3.2
*
* Лицензионный договор:
* Любое использование Вами программы означает полное и безоговорочное принятие Вами условий лицензионного договора,
* размещенного по адресуhttps://yoomoney.ru/doc.xml?id=527132 (далее – «Лицензионный договор»).
* Если Вы не принимаете условия Лицензионного договора в полном объёме,
* Вы не имеете права использовать программу в каких-либо целях.
*/
use YooKassa\Client;
use YooKassa\Common\Exceptions\ApiException;
use YooKassa\Common\Exceptions\BadApiRequestException;
use YooKassa\Common\Exceptions\ExtensionNotFoundException;
use YooKassa\Common\Exceptions\ForbiddenException;
use YooKassa\Common\Exceptions\InternalServerError;
use YooKassa\Common\Exceptions\NotFoundException;
use YooKassa\Common\Exceptions\ResponseProcessingException;
use YooKassa\Common\Exceptions\TooManyRequestsException;
use YooKassa\Common\Exceptions\UnauthorizedException;
use YooKassa\Model\ConfirmationType;
use YooKassa\Model\Notification\NotificationFactory;
use YooKassa\Model\Payment;
use YooKassa\Model\PaymentStatus;
use YooKassa\Model\Receipt;
use YooKassa\Model\Receipt\PaymentMode;
use YooKassa\Model\Receipt\PaymentSubject;
use YooKassa\Model\ReceiptCustomer;
use YooKassa\Model\ReceiptItem;
use YooKassa\Model\ReceiptType;
use YooKassa\Model\Settlement;
use YooKassa\Request\Payments\CreatePaymentRequest;
use YooKassa\Request\Payments\CreatePaymentResponse;
use YooKassa\Request\Payments\Payment\CreateCaptureRequest;
use YooKassa\Request\Receipts\CreatePostReceiptRequest;
use YooKassa\Request\Receipts\ReceiptResponseInterface;
use YooKassa\Request\Receipts\ReceiptResponseItemInterface;
require_once CMS_FOLDER.'yoomoney'.DIRECTORY_SEPARATOR.'autoload.php';
class Shop_Payment_System_Handler4 extends Shop_Payment_System_Handler
{
/**
* Адрес для уведомлений: https://[ваш-сайт]/shop/cart/?action=notify
*
* Этот адрес необходимо указать на сайте ЮKassa
* в «Настройках магазина» в разделе «Параметры для платежей»
*/
const YOOMONEY_MODULE_VERSION = '2.3.2';
/**
* @var int ЮKassa
*/
const MODE_YOOKASSA = 1;
/**
* @var int ЮMoney
*/
const MODE_MONEY = 2;
/**
* @var int Через что вы будете принимать платежи: ЮKassa или ЮMoney?
* Укажите нужный MODE из списка выше:
*/
protected $mode = self::MODE_YOOKASSA;
protected $apiClient = null;
/**
* Только для платежей через ЮMoney: укажите номер кошелька на ЮMoney, в который нужно зачислять платежи
* @var string Номер кошелька
*/
protected $yoo_account = '';
/**
* @var int Только для ЮKassa: укажите shopId из личного кабинета ЮKassa
*/
protected $yoo_shopid = 277277;
/**
* Только для ЮKassa: укажите «Секретный ключ» из личного кабинета ЮKassa
* @var string Секретный ключ
*/
protected $yoo_password = 'test_TK479mnabAnRyO62iCwekaau4xE0jLGMs9dMgbBILBA';
/**
* Только для ЮKassa: укажите описание платежа.
* Это описание транзакции, которое пользователь увидит при оплате,
* а вы — в личном кабинете ЮKassa. Например, «Оплата заказа №72».
* Чтобы в описание подставлялся номер заказа (как в примере),
* поставьте на его месте %id% (Оплата заказа №%id%).
* Ограничение для описания — 128 символов.
* @var string Описание платежа
*/
protected $yoo_description = 'Оплата заказа №%id%';
/**
* Только для ЮKassa: отправлять в ЮKassa данные для чеков (54-ФЗ)?
* @var bool True — если нужно, false — если не нужно
*/
protected $sendCheck = true;
/**
* Только для ЮKassa: отправлять в ЮKassa данные для закрывающих чеков (54-ФЗ)?
* @var bool True — если нужно, false — если не нужно
*/
protected $sendSecondCheck = true;
/**
* Только для ЮKassa: статус заказа, при переходе в который будут отправляться закрывающие чеки
* Берется из списка статусов заказов (Домой -> Интернет-магазины -> Справочники -> Справочник статусов заказа)
* @var int По умолчанию - "Доставлено"
*/
protected $orderStatusSecondCheck = 3;
/**
* Направлять пользователя на страницу оплаты сразу после выбора метода оплаты
* @var bool По умолчанию - false
*/
protected $autoRedirectToKassa = false;
/**
* @var bool Включить логирование.
*/
protected $enable_logging = true;
/**
* Ставки НДС в системе ЮKassa:
* 1 - Без НДС
* 2 - 0%
* 3 - 10%
* 4 - 20%
* 5 - Рассчётная ставка 10/110
* 6 - Рассчётная ставка 20/120
* @var int Только для ЮKassa: укажите номер из списка выше, который соответствует вашей налоговой ставке
*/
protected $kassaTaxRateDefault = 4;
/**
* Только для ЮKassa. В столбике слева представлены id налоговых ставок, которые есть в вашем магазине. Сопоставьте их с номерами ставок из этого списка:
* 1 - Без НДС
* 2 - 0%
* 3 - 10%
* 4 - 20%
* 5 - Расчётная ставка 10/110
* 6 - Расчётная ставка 20/120
* @var array Соотнесите ставки в вашем магазине со ставками в ЮKassa
*/
protected $kassaTaxRates = array(
2 => 4,
5 => 4,
19 => 3,
20 => 3,
21 => 1,
);
/**
* Код налоговой системы в ЮKassa:
* 1 - Общая система налогообложения
* 2 - Упрощенная (УСН, доходы)
* 3 - Упрощенная (УСН, доходы минус расходы)
* 4 - Единый налог на вмененный доход (ЕНВД)
* 5 - Единый сельскохозяйственный налог (ЕСН)
* 6 - Патентная система налогообложения
* @var int Только для ЮKassa: укажите номер из списка выше, который соответствует вашей системе налогооблажения
*/
protected $kassaTaxSystem = 0;
/**
* Одно из значений перечисления PaymentMode
* @var string
*/
protected $defaultPaymentMode = PaymentMode::FULL_PREPAYMENT;
/**
* Одно из значений перечисления PaymentSubject
* @var string
*/
protected $defaultPaymentSubject = PaymentSubject::COMPOSITE;
/**
* @return array
*/
public static function getValidPaymentMode()
{
return array(
PaymentMode::FULL_PREPAYMENT,
);
}
/**
* Только для ЮKassa: укажите, как уведомлять об оплате — одним письмом (после подтверждения оплаты от ЮKassa) или двумя письмами (при изменений статуса заказа и после окончательно подтверждения оплаты от Юkassa)
* @var bool True — если нужно отправлять два письма, false — если нужно отправлять одно письмо
*/
protected $sendChangeStatusEmail = true;
/**
* Id валюты, в которой будет производиться расчет суммы:
* 1 - рубли (RUB)
* 2 - евро (EUR)
* 3 - доллары (USD)
* @var int id валюты
*/
protected $yoo_currency_id = 1;
public function __construct(Shop_Payment_System_Model $oShop_Payment_System_Model)
{
$oCore_DataBase = Core_DataBase::instance()
->setQueryType(99)
->query(
'CREATE TABLE IF NOT EXISTS shop_yoo_order_payments (
`id` INT NOT NULL AUTO_INCREMENT,
`order_id` INT NOT NULL,
`payment_id` VARCHAR(256) NOT NULL,
PRIMARY KEY (`id`)
)'
);
parent::__construct($oShop_Payment_System_Model);
Core_Event::attach('Shop_Payment_System_Handler.onBeforeChangedOrder', array($this, 'onChangeOrder'));
}
/**
* @param Shop_Payment_System_Handler $object
* @param array $args
* @throws Core_Exception
*/
public function onChangeOrder($object, $args)
{
$mode = $args[0];
$oShop = $object->getShopOrder();
$logger = YooMoneyLogger::instance();
$logger->log('info', 'Mode: ' . $mode);
if (in_array($mode, array('changeStatusPaid', 'edit', 'apply')))
{
$logger->log('info', 'Status before: ' . $object->getShopOrderBeforeAction()->shop_order_status_id . ', Status after: ' . $oShop->shop_order_status_id);
// Изменился статус заказа
if ($object->getShopOrderBeforeAction()->shop_order_status_id != $oShop->shop_order_status_id)
{
$logger->log('info', 'Status changed!');
if (!$this->isNeedSecondReceipt($oShop->shop_order_status_id)) {
$logger->log('info', 'Second receipt is not need!');
return;
}
$paymentId = $this->getOrderPaymentId($oShop->id);
$logger->log('info', 'PaymentId: ' . $paymentId);
try {
if ($lastReceipt = $this->getLastReceipt($paymentId)) {
$logger->log('info', 'LastReceipt:' . PHP_EOL . json_encode($lastReceipt->jsonSerialize()));
} else {
$logger->log('info', 'LastReceipt is empty!');
return;
}
if ($receiptRequest = $this->buildSecondReceipt($lastReceipt, $paymentId, $oShop)) {
$logger->log('info', "Second receipt request data: " . PHP_EOL . json_encode($receiptRequest->jsonSerialize()));
try {
$response = $this->getClient()->createReceipt($receiptRequest);
} catch (Exception $e) {
$logger->log('error', 'Request second receipt error: ' . $e->getMessage());
return;
}
$logger->log('info', 'Request second receipt result: ' . PHP_EOL . json_encode($response->jsonSerialize()));
}
} catch (Exception $e) {
$logger->log('info', 'Error: ' . $e->getMessage());
return;
}
} else {
$logger->log('info', 'Status NOT changed!');
}
}
}
/**
* Метод, вызываемый в коде настроек ТДС через Shop_Payment_System_Handler::checkBeforeContent($oShop);
*/
public function checkPaymentBeforeContent()
{
$action = Core_Array::getGet('action');
if (isset($_POST['action']) && isset($_POST['invoiceId']) && isset($_POST['orderNumber']) || isset($_POST['sha1_hash'])) {
// Получаем ID заказа
$order_id = isset($_POST['sha1_hash'])
? intval(Core_Array::getPost('label'))
: intval(Core_Array::getPost('orderNumber'));
$oShop_Order = Core_Entity::factory('Shop_Order')->find($order_id);
if (!is_null($oShop_Order->id)) {
header("Content-type: application/xml");
// Вызов обработчика платежного сервиса
Shop_Payment_System_Handler::factory($oShop_Order->Shop_Payment_System)
->shopOrder($oShop_Order)
->paymentProcessing();
}
} elseif ($action == 'notify') {
$body = @file_get_contents('php://input');
$this->log('info', 'Notification: '.$body);
$callbackParams = json_decode($body, true);
if (json_last_error()) {
$this->log('error', 'Parse POST body failed');
$this->exit400();
}
try {
$fabric = new NotificationFactory();
$notificationModel = $fabric->factory($callbackParams);
} catch (\Exception $e) {
$this->log('error', 'Invalid notification object - ' . $e->getMessage());
header("HTTP/1.1 400 Bad Request");
header("Status: 400 Bad Request");
exit();
}
try {
$paymentResponse = $notificationModel->getObject();
$client = $this->getClient();
$paymentId = $paymentResponse->getId();
$paymentRow = Core_QueryBuilder::select()
->from('shop_yoo_order_payments')
->where('payment_id', '=', $paymentId)
->limit(1)
->execute()
->asAssoc()
->result();
if (is_array($paymentRow)) {
$paymentRow = $paymentRow[0];
}
$order = Core_Entity::factory('Shop_Order')->find($paymentRow['order_id']);
$this->checkValueIsNotEmpty($order, '404 Not Found',
'Order not found. OderId #'.$paymentRow['order_id']);
$paymentInfo = $client->getPaymentInfo($paymentId);
$this->checkValueIsNotEmpty($paymentInfo, '404 Not Found',
'Payment not found. PaymentId #'.$paymentId);
$this->log('info', 'Order: '.json_encode($order));
$this->log('info', 'Payment: '.json_encode($paymentInfo));
if ($paymentInfo->getStatus() === PaymentStatus::WAITING_FOR_CAPTURE) {
$captureRequest = CreateCaptureRequest::builder()->setAmount($paymentInfo->getAmount())->build();
$paymentInfo = $client->capturePayment($captureRequest, $paymentId);
}
if ($paymentInfo->getStatus() === PaymentStatus::SUCCEEDED) {
$this->completePayment($order);
$this->exit200();
} elseif ($paymentInfo->getStatus() === PaymentStatus::CANCELED) {
$this->log('info', 'Payment canceled');
$this->exit200();
} else {
$this->log('info', 'Wrong payment status: '.$paymentInfo->getStatus());
$this->exit400();
}
} catch (Exception $e) {
$this->log('error', $e->getMessage());
$this->exit400();
}
exit();
}
}
/**
* Метод, вызываемый в коде ТДС через Shop_Payment_System_Handler::checkAfterContent($oShop);
* Может быть использован как для получения информации от платежной системы о статусе платежа,
* так и о выводе информации о результатах оплаты после перенаправления пользователя
* платежной системой на корзину магазина.
*/
public function checkPaymentAfterContent()
{
$orderId = Core_Array::getGet('order_id');
$action = Core_Array::getGet('action');
if ($orderId && $action == 'return') {
$order = Core_Entity::factory('Shop_Order')->find($orderId);
$oSite_Alias = $order->Shop->Site->getCurrentAlias();
$sSiteAlias = !is_null($oSite_Alias) ? $oSite_Alias->name : '';
$sShopPath = $order->Shop->Structure->getPath();
$sHandlerUrl = 'http://'.$sSiteAlias.$sShopPath."cart/?order_id={$order->id}";
$successUrl = $sHandlerUrl."&payment=success";
$failUrl = $sHandlerUrl."&payment=fail";
$paymentRow = Core_QueryBuilder::select()
->from('shop_yoo_order_payments')
->where('order_id', '=', $orderId)
->limit(1)
->execute()
->asAssoc()
->result();
if (!$paymentRow) {
$this->log('error', 'Payment not found. OrderId: '.$orderId);
header('Location: '.$failUrl);
exit();
}
$paymentId = $paymentRow[0]['payment_id'];
$client = $this->getClient();
$paymentInfoResponse = $client->getPaymentInfo($paymentId);
$this->log('info', 'Order: '.json_encode($order));
$this->log('info', 'Payment: '.json_encode($paymentInfoResponse));
if ($paymentInfoResponse->getStatus() === PaymentStatus::WAITING_FOR_CAPTURE) {
$captureRequest = CreateCaptureRequest::builder()
->setAmount($paymentInfoResponse->getAmount())
->build();
$paymentInfoResponse = $client->capturePayment($captureRequest, $paymentId);
}
if ($paymentInfoResponse->getStatus() === PaymentStatus::SUCCEEDED) {
$this->completePayment($order);
header('Location: '.$successUrl);
} elseif (($paymentInfoResponse->status === PaymentStatus::PENDING) && $paymentInfoResponse->getPaid()) {
$this->log('info', 'Payment pending and paid');
header('Location: '.$successUrl);
} elseif ($paymentInfoResponse->status === PaymentStatus::CANCELED) {
$this->log('info', 'Payment canceled');
header('Location: '.$failUrl);
} else {
$this->log('error', 'Payment wrong status: '.$paymentInfoResponse->getStatus());
header('Location: '.$failUrl);
}
exit();
}
}
/*
* Метод, запускающий выполнение обработчика
*/
public function execute()
{
parent::execute();
$this->printNotification();
return $this;
}
/**
* Вычисление суммы товаров заказа
*/
public function getSumWithCoeff()
{
if ($this->yoo_currency_id > 0 && $this->_shopOrder->shop_currency_id > 0) {
$sum = Shop_Controller::instance()->getCurrencyCoefficientInShopCurrency(
$this->_shopOrder->Shop_Currency,
Core_Entity::factory('Shop_Currency', $this->yoo_currency_id)
);
} else {
$sum = 0;
}
return Shop_Controller::instance()->round($sum * $this->_shopOrder->getAmount());
}
/**
* Обработка ответа платёжного сирвиса
*/
public function paymentProcessing()
{
$this->processResult();
return true;
}
/**
* Печатает форму отправки запроса на сайт платёжной сервиса
*/
public function getNotification()
{
$sum = $this->getSumWithCoeff();
$oSiteuser = Core::moduleIsActive('siteuser')
? Core_Entity::factory('Siteuser')->getCurrent()
: null;
$oSite_Alias = $this->_shopOrder->Shop->Site->getCurrentAlias();
$sSiteAlias = !is_null($oSite_Alias) ? $oSite_Alias->name : '';
$sShopPath = $this->_shopOrder->Shop->Structure->getPath();
$fromUrl = $sHandlerUrl = 'http://'.$sSiteAlias.$sShopPath."cart";
$sHandlerUrl = 'http://'.$sSiteAlias.$sShopPath."cart/?order_id={$this->_shopOrder->id}";
$returnUrl = $sHandlerUrl."&action=return";
$successUrl = $sHandlerUrl."&payment=success";
$failUrl = $sHandlerUrl."&payment=fail";
$oShop_Order = Core_Entity::factory('Shop_Order', $this->_shopOrder->id);
$oShop_Order->invoice = $this->_shopOrder->id;
$oShop_Order->save();
if ($this->mode == self::MODE_YOOKASSA) {
try {
$response = $this->createPayment($sum, $returnUrl);
if ($response) {
$confirmationUrl = $response->confirmation->confirmationUrl;
$this->writePaymentId($response);
}
} catch (Exception $e) {
$this->log('error', $e->getMessage());
$errors = 'В процессе создания платежа произошла ошибка.';
}
}
?>
<form method="POST" id="frmYooMoney" action="<?php echo $this->getFormUrl() ?>">
<?php
if ($this->mode === self::MODE_YOOKASSA) {
if (isset($errors)) {
echo $errors;
} else {
?>
<table border="0" cellspacing="1" align="center" width="80%">
<tr>
<td align="center">
<a href="<?php echo $confirmationUrl?>" id="button-confirm" class="btn btn-primary">Оплатить</a>
</td>
</tr>
</table>
<?php }
} elseif ($this->mode === self::MODE_MONEY) { ?>
<input type="hidden" name="receiver" value="<?php echo htmlspecialchars($this->yoo_account); ?>">
<input type="hidden" name="formcomment" value="<?php echo htmlspecialchars($sSiteAlias); ?>">
<input type="hidden" name="short-dest" value="<?php echo htmlspecialchars($sSiteAlias); ?>">
<input type="hidden" name="writable-targets" value="false">
<input type="hidden" name="comment-needed" value="false">
<input type="hidden" name="label" value="<?php echo $this->_shopOrder->id; ?>">
<input type="hidden" name="quickpay-form" value="shop">
<input type="hidden" name="successUrl" value="<?php echo htmlspecialchars($successUrl); ?>">
<input type="hidden" name="targets" value="Заказ <?php echo $this->_shopOrder->id; ?>">
<input type="hidden" name="sum" value="<?php echo $sum; ?>" data-type="number">
<input type="hidden" name="comment"
value="<?php echo htmlspecialchars($this->_shopOrder->description); ?>">
<input type="hidden" name="need-fio" value="false">
<input type="hidden" name="need-email" value="false">
<input type="hidden" name="need-phone" value="false">
<input type="hidden" name="need-address" value="false">
<input type="submit" name="BuyButton" id="button-confirm" value="Оплатить">
<?php }?>
</form>
<script type="text/javascript">
<?php if ($this->autoRedirectToKassa) : ?>
const paymentButton = document.getElementById('button-confirm');
if (paymentButton) {
paymentButton.click();
}
<?php endif; ?>
</script>
<?php
}
public function getInvoice()
{
return $this->getNotification();
}
protected function _processOrder()
{
parent::_processOrder();
if (method_exists($this, 'setMailSubjects')) {
$this->setMailSubjects();
}
// Установка XSL-шаблонов в соответствии с настройками в узле структуры
$this->setXSLs();
// Отправка писем администраторам и пользователю
$this->send();
return $this;
}
/**
* @param $response
*
* @return Core_DataBase
*/
protected function writePaymentId($response)
{
$paymentRow = Core_QueryBuilder::select()
->from('shop_yoo_order_payments')
->where('order_id', '=', $this->_shopOrder->id)
->limit(1)
->execute()
->asAssoc()
->result();
if ($paymentRow) {
$result = Core_QueryBuilder::update('shop_yoo_order_payments')
->columns(array('payment_id' => $response->getId()))
->where('order_id', '=', $this->_shopOrder->id)
->execute();
} else {
$result = Core_QueryBuilder::insert('shop_yoo_order_payments')
->columns('order_id', 'payment_id')
->values($this->_shopOrder->id, $response->getId())
->execute();
}
return $result;
}
/**
* @return string
*/
private function getFormUrl()
{
$sUrl = 'https://';
return $this->mode === self::MODE_YOOKASSA
? ''
: $sUrl.'yoomoney.ru/quickpay/confirm';
}
/**
* @param string $tpl
* @param Shop_Order_Model $order
*
* @return string
*/
private function parsePlaceholders($tpl, $order)
{
$replace = array(
'%order_id%' => $order->id,
);
foreach ($order->toArray() as $key => $value) {
if (is_scalar($value)) {
$replace['%'.$key.'%'] = $value;
}
}
return strtr($tpl, $replace);
}
private function checkSign($callbackParams)
{
if ($this->mode === self::MODE_YOOKASSA) {
$string = $callbackParams['action'].';'.$callbackParams['orderSumAmount'].';'
.$callbackParams['orderSumCurrencyPaycash'].';'.$callbackParams['orderSumBankPaycash'].';'
.$callbackParams['shopId'].';'.$callbackParams['invoiceId'].';'
.$callbackParams['customerNumber'].';'.$this->yoo_password;
return strtoupper($callbackParams['md5']) == strtoupper(md5($string));
} else {
$string = $callbackParams['notification_type'].'&'.$callbackParams['operation_id'].'&'
.$callbackParams['amount'].'&'.$callbackParams['currency'].'&'
.$callbackParams['datetime'].'&'.$callbackParams['sender'].'&'
.$callbackParams['codepro'].'&'.$this->yoo_password.'&'.$callbackParams['label'];
$check = (sha1($string) == $callbackParams['sha1_hash']);
if (!$check) {
header('HTTP/1.0 401 Unauthorized');
return false;
}
return true;
}
}
private function sendCode($callbackParams, $code, $message = '')
{
if ($this->mode != self::MODE_YOOKASSA) {
return;
}
$invoiceId = isset($callbackParams['invoiceId']) ? $callbackParams['invoiceId'] : '';
header("Content-type: text/xml; charset=utf-8");
$xml = '<?xml version="1.0" encoding="UTF-8"?>
<'.$callbackParams['action'].'Response performedDatetime="'.date("c").'" code="'.$code
.'" invoiceId="'.$invoiceId.'" shopId="'.$this->yoo_shopid
.'" techmessage="'.$message.'"/>';
echo $xml;
die();
}
/**
* Оплачивает заказ
*/
private function processResult()
{
if ($this->checkSign($_POST)) {
if (isset($_POST['action']) || ($this->mode !== self::MODE_YOOKASSA)) {
$order_id = intval(Core_Array::getPost(isset($_POST["label"]) ? "label" : "orderNumber"));
if ($order_id > 0) {
$oShop_Order = $this->_shopOrder;
$sHostcmsSum = sprintf("%.2f", $this->getSumWithCoeff());
$sYoomoneySum = Core_Array::getRequest('orderSumAmount', '');
if ($sHostcmsSum == $sYoomoneySum) {
if ($_POST['action'] == 'paymentAviso') {
$this->shopOrder($oShop_Order)->shopOrderBeforeAction(clone $oShop_Order);
$oShop_Order->system_information = "Заказ оплачен через сервис ЮKassa.\n";
$oShop_Order->paid();
if (method_exists($this, 'setMailSubjects')) {
$this->setMailSubjects();
}
$this->setXSLs();
$this->sendEmail($this->sendChangeStatusEmail);
ob_start();
$this->changedOrder('changeStatusPaid');
ob_get_clean();
}
} else {
$this->sendCode($_POST, 100, 'Bad amount');
}
}
$this->sendCode($_POST, 0, 'Order completed.');
} else {
$this->sendCode($_POST, 0, 'Order is exist.');
}
} else {
$this->sendCode($_POST, 1, 'md5 bad');
}
}
/**
* @param bool $sendToAdmin
*
* @return Shop_Payment_System_Handler
* @throws Core_Exception
*/
protected function sendEmail($sendToAdmin)
{
if ($sendToAdmin) {
return $this->send();
}
Core_Event::notify('Shop_Payment_System_Handler.onBeforeSend', $this);
if (is_null($this->_shopOrder)) {
throw new Core_Exception('send(): shopOrder is empty.');
}
$oShopOrder = $this->_shopOrder;
$oShop = $oShopOrder->Shop;
if ($oShop->send_order_email_user) {
$oCore_Mail_Siteuser = $this->getSiteuserEmail();
$this->sendSiteuserEmail($oCore_Mail_Siteuser);
}
Core_Event::notify('Shop_Payment_System_Handler.onAfterSend', $this);
return $this;
}
/**
* @param $sum
*
* @param $returnUrl
* @return CreatePaymentResponse
* @throws ApiException
* @throws BadApiRequestException
* @throws ForbiddenException
* @throws InternalServerError
* @throws NotFoundException
* @throws ResponseProcessingException
* @throws TooManyRequestsException
* @throws UnauthorizedException
*/
protected function createPayment($sum, $returnUrl)
{
$client = $this->getClient();
$builder = CreatePaymentRequest::builder()
->setAmount($sum)
->setPaymentMethodData('')
->setCapture(true)
->setDescription($this->createDescription())
->setConfirmation(
array(
'type' => ConfirmationType::REDIRECT,
'returnUrl' => $returnUrl,
)
)
->setMetadata(array(
'cms_name' => 'yoo_api_hostcms',
'module_version' => self::YOOMONEY_MODULE_VERSION,
));
if ($this->sendCheck) {
$oShop_Order = Core_Entity::factory('Shop_Order', $this->_shopOrder->id);
$aShopOrderItems = $oShop_Order->Shop_Order_Items->findAll();
$email = isset($this->_shopOrder->email) ? $this->_shopOrder->email : '';
$phone = isset($this->_shopOrder->phone) ? $this->_shopOrder->phone : '';
if (!empty($email)) {
$builder->setReceiptEmail($email);
}
if (!empty($phone)) {
$builder->setReceiptPhone($phone);
}
$disc = 0;
$osum = 0;
foreach ($aShopOrderItems as $kk => $item) {
if ($item->price < 0) {
$disc -= $item->getAmount();
unset($aShopOrderItems[$kk]);
} else {
if ($item->shop_item_id) {
$osum += $item->getAmount();
}
}
}
unset($item);
$disc = abs($disc) / $osum;
foreach ($aShopOrderItems as $item) {
$tax_id = null;
if ($item->shop_item_id) {
$tax_id = $item->Shop_Item->shop_tax_id;
}
$tax = Core_Array::get($this->kassaTaxRates, $tax_id, $this->kassaTaxRateDefault);
$amount = $item->getPrice() * ($item->shop_item_id ? 1 - $disc : 1);
$builder->addReceiptItem(
$item->name,
$amount,
$item->quantity,
$tax,
$this->defaultPaymentMode,
$this->defaultPaymentSubject
);
}
if ($this->kassaTaxSystem) {
$builder->setTaxSystemCode($this->kassaTaxSystem);
}
}
$createPaymentRequest = $builder->build();
$receipt = $createPaymentRequest->getReceipt();
if ($receipt instanceof Receipt) {
$receipt->normalize($createPaymentRequest->getAmount());
}
return $client->createPayment($createPaymentRequest);
}
/**
* @param $order
* @throws Core_Exception
*/
private function completePayment($order)
{
$this->log('info', 'Payment completed');
$this->shopOrder($order)->shopOrderBeforeAction(clone $order);
$order->system_information = "Заказ оплачен через сервис ЮKassa.\n";
$order->paid();
if (method_exists($this, 'setMailSubjects')) {
$this->setMailSubjects();
}
$this->setXSLs();
$this->sendEmail($this->sendChangeStatusEmail);
ob_start();
$this->changedOrder('changeStatusPaid');
ob_get_clean();
}
/**
* @param string $level
* @param string $message
*/
private function log($level, $message)
{
if ($this->enable_logging) {
YooMoneyLogger::instance()->log($level, $message);
}
}
/**
* @param mixed $value
* @param string $status
* @param string $logMessage
*/
function checkValueIsNotEmpty($value, $status, $logMessage)
{
if (!$value) {
$this->log('error', $logMessage);
header('HTTP/1.1 '.$status);
header('Status: '.$status);
exit();
}
}
private function exit200()
{
header('HTTP/1.1 200 OK');
header('Status: 200 OK');
exit();
}
private function exit400()
{
header('HTTP/1.1 400 Bad Request');
header('Status: 400 Bad Request');
exit();
}
/**
* @return string
*/
private function createDescription()
{
$descriptionTemplate = $this->yoo_description;
$replace = array();
$patterns = explode('%', $descriptionTemplate);
foreach ($patterns as $pattern) {
$value = null;
if (isset($this->getShopOrder()->$pattern)) {
$value = $this->getShopOrder()->$pattern;
} else {
$method = 'get'.ucfirst($pattern);
if (method_exists($this->getShopOrder(), $method)) {
$value = $this->getShopOrder()->{$method}();
}
}
if (!is_null($value) && is_scalar($value)) {
$replace['%'.$pattern.'%'] = $value;
}
}
$description = strtr($descriptionTemplate, $replace);
return (string)mb_substr($description, 0, Payment::MAX_LENGTH_DESCRIPTION);
}
/**
* @return Client
*/
private function getClient()
{
if (!$this->apiClient) {
$this->apiClient = new Client();
$userAgent = $this->apiClient->getApiClient()->getUserAgent();
$userAgent->setCms('HostCMS', Informationsystem_Module::factory('informationsystem')->version);
$userAgent->setModule('PaymentGateway', self::YOOMONEY_MODULE_VERSION);
$this->apiClient->setAuth($this->yoo_shopid, $this->yoo_password);
if ($this->enable_logging) {
$this->apiClient->setLogger(YooMoneyLogger::instance());
}
}
return $this->apiClient;
}
/**
* @param $order_status_id
* @return bool
*/
private function isNeedSecondReceipt($order_status_id)
{
return ($this->sendCheck && $this->sendSecondCheck && $this->orderStatusSecondCheck == $order_status_id);
}
/**
* @param int $order_id
* @return string|null
* @throws Core_Exception
*/
private function getOrderPaymentId($order_id)
{
$result = null;
$paymentRow = Core_QueryBuilder::select()
->from('shop_yoo_order_payments')
->where('order_id', '=', $order_id)
->orderBy('id', 'DESC')
->limit(1)
->execute()
->asAssoc()
->result();
if (is_array($paymentRow) && !empty($paymentRow[0]['payment_id'])) {
$result = $paymentRow[0]['payment_id'];
}
return $result;
}
/**
* @param $paymentId
* @return mixed|ReceiptResponseInterface
* @throws ApiException
* @throws BadApiRequestException
* @throws ExtensionNotFoundException
* @throws ForbiddenException
* @throws InternalServerError
* @throws NotFoundException
* @throws ResponseProcessingException
* @throws TooManyRequestsException
* @throws UnauthorizedException
*/
private function getLastReceipt($paymentId)
{
$receipts = $this->getClient()->getReceipts(array('payment_id' => $paymentId))->getItems();
return array_pop($receipts);
}
/**
* @param ReceiptResponseInterface $lastReceipt
* @param string $paymentId
* @param Shop_Order_Model $order
* @return CreatePostReceiptRequest|null
*/
private function buildSecondReceipt($lastReceipt, $paymentId, $order)
{
if ($lastReceipt instanceof ReceiptResponseInterface) {
if ($lastReceipt->getType() === "refund") {
return null;
}
$resendItems = $this->getResendItems($lastReceipt->getItems());
if (count($resendItems['items']) < 1) {
$this->log('info', 'Second receipt is not required');
return null;
}
try {
$customer = $this->getReceiptCustomer($order);
if (empty($customer)) {
$this->log('error', 'Need customer phone or email for second receipt');
return null;
}
$receiptBuilder = CreatePostReceiptRequest::builder();
$receiptBuilder->setObjectId($paymentId)
->setType(ReceiptType::PAYMENT)
->setItems($resendItems['items'])
->setSettlements(array(
new Settlement(array(
'type' => 'prepayment',
'amount' => array(
'value' => $resendItems['amount'],
'currency' => 'RUB',
),
)),
))
->setCustomer($customer)
->setSend(true);
if ($lastReceipt->getTaxSystemCode()) {
$receiptBuilder->setTaxSystemCode($lastReceipt->getTaxSystemCode());
}
return $receiptBuilder->build();
} catch (Exception $e) {
$this->log('error', $e->getMessage() . '. Property name: '. $e->getProperty());
}
}
return null;
}
/**
* @param Shop_Order_Model $order
* @return bool|ReceiptCustomer
*/
private function getReceiptCustomer($order)
{
$customerData = array();
if (!empty($order->email)) {
$customerData['email'] = $order->email;
}
if (!empty($order->phone)) {
$customerData['phone'] = $order->phone;
}
if (!empty($order->tin)) {
$customerData['inn'] = $order->tin;
}
$userName = array();
if (!empty($order->surname)) $userName[] = $order->surname;
if (!empty($order->name)) $userName[] = $order->name;
if (!empty($order->patronymic)) $userName[] = $order->patronymic;
if ($userFullName = implode(' ', $userName)) {
$customerData['full_name'] = $userFullName;
}
return new ReceiptCustomer($customerData);
}
/**
* @param ReceiptResponseItemInterface[] $items
*
* @return array
*/
private function getResendItems($items)
{
$result = array(
'items' => array(),
'amount' => 0,
);
foreach ($items as $item) {
if ($this->isNeedResendItem($item->getPaymentMode())) {
$item->setPaymentMode(PaymentMode::FULL_PAYMENT);
$result['items'][] = new ReceiptItem($item->jsonSerialize());
$result['amount'] += $item->getAmount() / 100.0;
}
}
return $result;
}
/**
* @param string $paymentMode
*
* @return bool
*/
private function isNeedResendItem($paymentMode)
{
return in_array($paymentMode, self::getValidPaymentMode());
}
}
class YooMoneyLogger extends Core_Log
{
static public function instance()
{
return new self();
}
public function getLogName($date)
{
return $this->_logDir.DIRECTORY_SEPARATOR.'yoo_payment_log_'.date('d_m_Y',
Core_Date::sql2timestamp($date)).'.log';
}
public function log($level, $message, $context = null)
{
$this->clear()
->notify(false)
->status($this->convertLevelToType($level))
->write($message);
}
/**
* Convert standard log level to HostCMS type
* @param string $level
* @return int
*/
private function convertLevelToType ($level)
{
$type = self::$MESSAGE;
switch ($level) {
case 'info':
$type = self::$MESSAGE;
break;
case 'debug':
$type = self::$SUCCESS;
break;
case 'notice':
$type = self::$NOTICE;
break;
case 'warn':
$type = self::$WARNING;
break;
case 'error':
$type = self::$ERROR;
break;
}
return $type;
}
}
/**
* ЮKassa
* Версия 2.3.2
*
* Лицензионный договор:
* Любое использование Вами программы означает полное и безоговорочное принятие Вами условий лицензионного договора,
* размещенного по адресу
* Если Вы не принимаете условия Лицензионного договора в полном объёме,
* Вы не имеете права использовать программу в каких-либо целях.
*/
use YooKassa\Client;
use YooKassa\Common\Exceptions\ApiException;
use YooKassa\Common\Exceptions\BadApiRequestException;
use YooKassa\Common\Exceptions\ExtensionNotFoundException;
use YooKassa\Common\Exceptions\ForbiddenException;
use YooKassa\Common\Exceptions\InternalServerError;
use YooKassa\Common\Exceptions\NotFoundException;
use YooKassa\Common\Exceptions\ResponseProcessingException;
use YooKassa\Common\Exceptions\TooManyRequestsException;
use YooKassa\Common\Exceptions\UnauthorizedException;
use YooKassa\Model\ConfirmationType;
use YooKassa\Model\Notification\NotificationFactory;
use YooKassa\Model\Payment;
use YooKassa\Model\PaymentStatus;
use YooKassa\Model\Receipt;
use YooKassa\Model\Receipt\PaymentMode;
use YooKassa\Model\Receipt\PaymentSubject;
use YooKassa\Model\ReceiptCustomer;
use YooKassa\Model\ReceiptItem;
use YooKassa\Model\ReceiptType;
use YooKassa\Model\Settlement;
use YooKassa\Request\Payments\CreatePaymentRequest;
use YooKassa\Request\Payments\CreatePaymentResponse;
use YooKassa\Request\Payments\Payment\CreateCaptureRequest;
use YooKassa\Request\Receipts\CreatePostReceiptRequest;
use YooKassa\Request\Receipts\ReceiptResponseInterface;
use YooKassa\Request\Receipts\ReceiptResponseItemInterface;
require_once CMS_FOLDER.'yoomoney'.DIRECTORY_SEPARATOR.'autoload.php';
class Shop_Payment_System_Handler4 extends Shop_Payment_System_Handler
{
/**
* Адрес для уведомлений: https://[ваш-сайт]/shop/cart/?action=notify
*
* Этот адрес необходимо указать на сайте ЮKassa
* в «Настройках магазина» в разделе «Параметры для платежей»
*/
const YOOMONEY_MODULE_VERSION = '2.3.2';
/**
* @var int ЮKassa
*/
const MODE_YOOKASSA = 1;
/**
* @var int ЮMoney
*/
const MODE_MONEY = 2;
/**
* @var int Через что вы будете принимать платежи: ЮKassa или ЮMoney?
* Укажите нужный MODE из списка выше:
*/
protected $mode = self::MODE_YOOKASSA;
protected $apiClient = null;
/**
* Только для платежей через ЮMoney: укажите номер кошелька на ЮMoney, в который нужно зачислять платежи
* @var string Номер кошелька
*/
protected $yoo_account = '';
/**
* @var int Только для ЮKassa: укажите shopId из личного кабинета ЮKassa
*/
protected $yoo_shopid = 277277;
/**
* Только для ЮKassa: укажите «Секретный ключ» из личного кабинета ЮKassa
* @var string Секретный ключ
*/
protected $yoo_password = 'test_TK479mnabAnRyO62iCwekaau4xE0jLGMs9dMgbBILBA';
/**
* Только для ЮKassa: укажите описание платежа.
* Это описание транзакции, которое пользователь увидит при оплате,
* а вы — в личном кабинете ЮKassa. Например, «Оплата заказа №72».
* Чтобы в описание подставлялся номер заказа (как в примере),
* поставьте на его месте %id% (Оплата заказа №%id%).
* Ограничение для описания — 128 символов.
* @var string Описание платежа
*/
protected $yoo_description = 'Оплата заказа №%id%';
/**
* Только для ЮKassa: отправлять в ЮKassa данные для чеков (54-ФЗ)?
* @var bool True — если нужно, false — если не нужно
*/
protected $sendCheck = true;
/**
* Только для ЮKassa: отправлять в ЮKassa данные для закрывающих чеков (54-ФЗ)?
* @var bool True — если нужно, false — если не нужно
*/
protected $sendSecondCheck = true;
/**
* Только для ЮKassa: статус заказа, при переходе в который будут отправляться закрывающие чеки
* Берется из списка статусов заказов (Домой -> Интернет-магазины -> Справочники -> Справочник статусов заказа)
* @var int По умолчанию - "Доставлено"
*/
protected $orderStatusSecondCheck = 3;
/**
* Направлять пользователя на страницу оплаты сразу после выбора метода оплаты
* @var bool По умолчанию - false
*/
protected $autoRedirectToKassa = false;
/**
* @var bool Включить логирование.
*/
protected $enable_logging = true;
/**
* Ставки НДС в системе ЮKassa:
* 1 - Без НДС
* 2 - 0%
* 3 - 10%
* 4 - 20%
* 5 - Рассчётная ставка 10/110
* 6 - Рассчётная ставка 20/120
* @var int Только для ЮKassa: укажите номер из списка выше, который соответствует вашей налоговой ставке
*/
protected $kassaTaxRateDefault = 4;
/**
* Только для ЮKassa. В столбике слева представлены id налоговых ставок, которые есть в вашем магазине. Сопоставьте их с номерами ставок из этого списка:
* 1 - Без НДС
* 2 - 0%
* 3 - 10%
* 4 - 20%
* 5 - Расчётная ставка 10/110
* 6 - Расчётная ставка 20/120
* @var array Соотнесите ставки в вашем магазине со ставками в ЮKassa
*/
protected $kassaTaxRates = array(
2 => 4,
5 => 4,
19 => 3,
20 => 3,
21 => 1,
);
/**
* Код налоговой системы в ЮKassa:
* 1 - Общая система налогообложения
* 2 - Упрощенная (УСН, доходы)
* 3 - Упрощенная (УСН, доходы минус расходы)
* 4 - Единый налог на вмененный доход (ЕНВД)
* 5 - Единый сельскохозяйственный налог (ЕСН)
* 6 - Патентная система налогообложения
* @var int Только для ЮKassa: укажите номер из списка выше, который соответствует вашей системе налогооблажения
*/
protected $kassaTaxSystem = 0;
/**
* Одно из значений перечисления PaymentMode
* @var string
*/
protected $defaultPaymentMode = PaymentMode::FULL_PREPAYMENT;
/**
* Одно из значений перечисления PaymentSubject
* @var string
*/
protected $defaultPaymentSubject = PaymentSubject::COMPOSITE;
/**
* @return array
*/
public static function getValidPaymentMode()
{
return array(
PaymentMode::FULL_PREPAYMENT,
);
}
/**
* Только для ЮKassa: укажите, как уведомлять об оплате — одним письмом (после подтверждения оплаты от ЮKassa) или двумя письмами (при изменений статуса заказа и после окончательно подтверждения оплаты от Юkassa)
* @var bool True — если нужно отправлять два письма, false — если нужно отправлять одно письмо
*/
protected $sendChangeStatusEmail = true;
/**
* Id валюты, в которой будет производиться расчет суммы:
* 1 - рубли (RUB)
* 2 - евро (EUR)
* 3 - доллары (USD)
* @var int id валюты
*/
protected $yoo_currency_id = 1;
public function __construct(Shop_Payment_System_Model $oShop_Payment_System_Model)
{
$oCore_DataBase = Core_DataBase::instance()
->setQueryType(99)
->query(
'CREATE TABLE IF NOT EXISTS shop_yoo_order_payments (
`id` INT NOT NULL AUTO_INCREMENT,
`order_id` INT NOT NULL,
`payment_id` VARCHAR(256) NOT NULL,
PRIMARY KEY (`id`)
)'
);
parent::__construct($oShop_Payment_System_Model);
Core_Event::attach('Shop_Payment_System_Handler.onBeforeChangedOrder', array($this, 'onChangeOrder'));
}
/**
* @param Shop_Payment_System_Handler $object
* @param array $args
* @throws Core_Exception
*/
public function onChangeOrder($object, $args)
{
$mode = $args[0];
$oShop = $object->getShopOrder();
$logger = YooMoneyLogger::instance();
$logger->log('info', 'Mode: ' . $mode);
if (in_array($mode, array('changeStatusPaid', 'edit', 'apply')))
{
$logger->log('info', 'Status before: ' . $object->getShopOrderBeforeAction()->shop_order_status_id . ', Status after: ' . $oShop->shop_order_status_id);
// Изменился статус заказа
if ($object->getShopOrderBeforeAction()->shop_order_status_id != $oShop->shop_order_status_id)
{
$logger->log('info', 'Status changed!');
if (!$this->isNeedSecondReceipt($oShop->shop_order_status_id)) {
$logger->log('info', 'Second receipt is not need!');
return;
}
$paymentId = $this->getOrderPaymentId($oShop->id);
$logger->log('info', 'PaymentId: ' . $paymentId);
try {
if ($lastReceipt = $this->getLastReceipt($paymentId)) {
$logger->log('info', 'LastReceipt:' . PHP_EOL . json_encode($lastReceipt->jsonSerialize()));
} else {
$logger->log('info', 'LastReceipt is empty!');
return;
}
if ($receiptRequest = $this->buildSecondReceipt($lastReceipt, $paymentId, $oShop)) {
$logger->log('info', "Second receipt request data: " . PHP_EOL . json_encode($receiptRequest->jsonSerialize()));
try {
$response = $this->getClient()->createReceipt($receiptRequest);
} catch (Exception $e) {
$logger->log('error', 'Request second receipt error: ' . $e->getMessage());
return;
}
$logger->log('info', 'Request second receipt result: ' . PHP_EOL . json_encode($response->jsonSerialize()));
}
} catch (Exception $e) {
$logger->log('info', 'Error: ' . $e->getMessage());
return;
}
} else {
$logger->log('info', 'Status NOT changed!');
}
}
}
/**
* Метод, вызываемый в коде настроек ТДС через Shop_Payment_System_Handler::checkBeforeContent($oShop);
*/
public function checkPaymentBeforeContent()
{
$action = Core_Array::getGet('action');
if (isset($_POST['action']) && isset($_POST['invoiceId']) && isset($_POST['orderNumber']) || isset($_POST['sha1_hash'])) {
// Получаем ID заказа
$order_id = isset($_POST['sha1_hash'])
? intval(Core_Array::getPost('label'))
: intval(Core_Array::getPost('orderNumber'));
$oShop_Order = Core_Entity::factory('Shop_Order')->find($order_id);
if (!is_null($oShop_Order->id)) {
header("Content-type: application/xml");
// Вызов обработчика платежного сервиса
Shop_Payment_System_Handler::factory($oShop_Order->Shop_Payment_System)
->shopOrder($oShop_Order)
->paymentProcessing();
}
} elseif ($action == 'notify') {
$body = @file_get_contents('php://input');
$this->log('info', 'Notification: '.$body);
$callbackParams = json_decode($body, true);
if (json_last_error()) {
$this->log('error', 'Parse POST body failed');
$this->exit400();
}
try {
$fabric = new NotificationFactory();
$notificationModel = $fabric->factory($callbackParams);
} catch (\Exception $e) {
$this->log('error', 'Invalid notification object - ' . $e->getMessage());
header("HTTP/1.1 400 Bad Request");
header("Status: 400 Bad Request");
exit();
}
try {
$paymentResponse = $notificationModel->getObject();
$client = $this->getClient();
$paymentId = $paymentResponse->getId();
$paymentRow = Core_QueryBuilder::select()
->from('shop_yoo_order_payments')
->where('payment_id', '=', $paymentId)
->limit(1)
->execute()
->asAssoc()
->result();
if (is_array($paymentRow)) {
$paymentRow = $paymentRow[0];
}
$order = Core_Entity::factory('Shop_Order')->find($paymentRow['order_id']);
$this->checkValueIsNotEmpty($order, '404 Not Found',
'Order not found. OderId #'.$paymentRow['order_id']);
$paymentInfo = $client->getPaymentInfo($paymentId);
$this->checkValueIsNotEmpty($paymentInfo, '404 Not Found',
'Payment not found. PaymentId #'.$paymentId);
$this->log('info', 'Order: '.json_encode($order));
$this->log('info', 'Payment: '.json_encode($paymentInfo));
if ($paymentInfo->getStatus() === PaymentStatus::WAITING_FOR_CAPTURE) {
$captureRequest = CreateCaptureRequest::builder()->setAmount($paymentInfo->getAmount())->build();
$paymentInfo = $client->capturePayment($captureRequest, $paymentId);
}
if ($paymentInfo->getStatus() === PaymentStatus::SUCCEEDED) {
$this->completePayment($order);
$this->exit200();
} elseif ($paymentInfo->getStatus() === PaymentStatus::CANCELED) {
$this->log('info', 'Payment canceled');
$this->exit200();
} else {
$this->log('info', 'Wrong payment status: '.$paymentInfo->getStatus());
$this->exit400();
}
} catch (Exception $e) {
$this->log('error', $e->getMessage());
$this->exit400();
}
exit();
}
}
/**
* Метод, вызываемый в коде ТДС через Shop_Payment_System_Handler::checkAfterContent($oShop);
* Может быть использован как для получения информации от платежной системы о статусе платежа,
* так и о выводе информации о результатах оплаты после перенаправления пользователя
* платежной системой на корзину магазина.
*/
public function checkPaymentAfterContent()
{
$orderId = Core_Array::getGet('order_id');
$action = Core_Array::getGet('action');
if ($orderId && $action == 'return') {
$order = Core_Entity::factory('Shop_Order')->find($orderId);
$oSite_Alias = $order->Shop->Site->getCurrentAlias();
$sSiteAlias = !is_null($oSite_Alias) ? $oSite_Alias->name : '';
$sShopPath = $order->Shop->Structure->getPath();
$sHandlerUrl = 'http://'.$sSiteAlias.$sShopPath."cart/?order_id={$order->id}";
$successUrl = $sHandlerUrl."&payment=success";
$failUrl = $sHandlerUrl."&payment=fail";
$paymentRow = Core_QueryBuilder::select()
->from('shop_yoo_order_payments')
->where('order_id', '=', $orderId)
->limit(1)
->execute()
->asAssoc()
->result();
if (!$paymentRow) {
$this->log('error', 'Payment not found. OrderId: '.$orderId);
header('Location: '.$failUrl);
exit();
}
$paymentId = $paymentRow[0]['payment_id'];
$client = $this->getClient();
$paymentInfoResponse = $client->getPaymentInfo($paymentId);
$this->log('info', 'Order: '.json_encode($order));
$this->log('info', 'Payment: '.json_encode($paymentInfoResponse));
if ($paymentInfoResponse->getStatus() === PaymentStatus::WAITING_FOR_CAPTURE) {
$captureRequest = CreateCaptureRequest::builder()
->setAmount($paymentInfoResponse->getAmount())
->build();
$paymentInfoResponse = $client->capturePayment($captureRequest, $paymentId);
}
if ($paymentInfoResponse->getStatus() === PaymentStatus::SUCCEEDED) {
$this->completePayment($order);
header('Location: '.$successUrl);
} elseif (($paymentInfoResponse->status === PaymentStatus::PENDING) && $paymentInfoResponse->getPaid()) {
$this->log('info', 'Payment pending and paid');
header('Location: '.$successUrl);
} elseif ($paymentInfoResponse->status === PaymentStatus::CANCELED) {
$this->log('info', 'Payment canceled');
header('Location: '.$failUrl);
} else {
$this->log('error', 'Payment wrong status: '.$paymentInfoResponse->getStatus());
header('Location: '.$failUrl);
}
exit();
}
}
/*
* Метод, запускающий выполнение обработчика
*/
public function execute()
{
parent::execute();
$this->printNotification();
return $this;
}
/**
* Вычисление суммы товаров заказа
*/
public function getSumWithCoeff()
{
if ($this->yoo_currency_id > 0 && $this->_shopOrder->shop_currency_id > 0) {
$sum = Shop_Controller::instance()->getCurrencyCoefficientInShopCurrency(
$this->_shopOrder->Shop_Currency,
Core_Entity::factory('Shop_Currency', $this->yoo_currency_id)
);
} else {
$sum = 0;
}
return Shop_Controller::instance()->round($sum * $this->_shopOrder->getAmount());
}
/**
* Обработка ответа платёжного сирвиса
*/
public function paymentProcessing()
{
$this->processResult();
return true;
}
/**
* Печатает форму отправки запроса на сайт платёжной сервиса
*/
public function getNotification()
{
$sum = $this->getSumWithCoeff();
$oSiteuser = Core::moduleIsActive('siteuser')
? Core_Entity::factory('Siteuser')->getCurrent()
: null;
$oSite_Alias = $this->_shopOrder->Shop->Site->getCurrentAlias();
$sSiteAlias = !is_null($oSite_Alias) ? $oSite_Alias->name : '';
$sShopPath = $this->_shopOrder->Shop->Structure->getPath();
$fromUrl = $sHandlerUrl = 'http://'.$sSiteAlias.$sShopPath."cart";
$sHandlerUrl = 'http://'.$sSiteAlias.$sShopPath."cart/?order_id={$this->_shopOrder->id}";
$returnUrl = $sHandlerUrl."&action=return";
$successUrl = $sHandlerUrl."&payment=success";
$failUrl = $sHandlerUrl."&payment=fail";
$oShop_Order = Core_Entity::factory('Shop_Order', $this->_shopOrder->id);
$oShop_Order->invoice = $this->_shopOrder->id;
$oShop_Order->save();
if ($this->mode == self::MODE_YOOKASSA) {
try {
$response = $this->createPayment($sum, $returnUrl);
if ($response) {
$confirmationUrl = $response->confirmation->confirmationUrl;
$this->writePaymentId($response);
}
} catch (Exception $e) {
$this->log('error', $e->getMessage());
$errors = 'В процессе создания платежа произошла ошибка.';
}
}
?>
<form method="POST" id="frmYooMoney" action="<?php echo $this->getFormUrl() ?>">
<?php
if ($this->mode === self::MODE_YOOKASSA) {
if (isset($errors)) {
echo $errors;
} else {
?>
<table border="0" cellspacing="1" align="center" width="80%">
<tr>
<td align="center">
<a href="<?php echo $confirmationUrl?>" id="button-confirm" class="btn btn-primary">Оплатить</a>
</td>
</tr>
</table>
<?php }
} elseif ($this->mode === self::MODE_MONEY) { ?>
<input type="hidden" name="receiver" value="<?php echo htmlspecialchars($this->yoo_account); ?>">
<input type="hidden" name="formcomment" value="<?php echo htmlspecialchars($sSiteAlias); ?>">
<input type="hidden" name="short-dest" value="<?php echo htmlspecialchars($sSiteAlias); ?>">
<input type="hidden" name="writable-targets" value="false">
<input type="hidden" name="comment-needed" value="false">
<input type="hidden" name="label" value="<?php echo $this->_shopOrder->id; ?>">
<input type="hidden" name="quickpay-form" value="shop">
<input type="hidden" name="successUrl" value="<?php echo htmlspecialchars($successUrl); ?>">
<input type="hidden" name="targets" value="Заказ <?php echo $this->_shopOrder->id; ?>">
<input type="hidden" name="sum" value="<?php echo $sum; ?>" data-type="number">
<input type="hidden" name="comment"
value="<?php echo htmlspecialchars($this->_shopOrder->description); ?>">
<input type="hidden" name="need-fio" value="false">
<input type="hidden" name="need-email" value="false">
<input type="hidden" name="need-phone" value="false">
<input type="hidden" name="need-address" value="false">
<input type="submit" name="BuyButton" id="button-confirm" value="Оплатить">
<?php }?>
</form>
<script type="text/javascript">
<?php if ($this->autoRedirectToKassa) : ?>
const paymentButton = document.getElementById('button-confirm');
if (paymentButton) {
paymentButton.click();
}
<?php endif; ?>
</script>
<?php
}
public function getInvoice()
{
return $this->getNotification();
}
protected function _processOrder()
{
parent::_processOrder();
if (method_exists($this, 'setMailSubjects')) {
$this->setMailSubjects();
}
// Установка XSL-шаблонов в соответствии с настройками в узле структуры
$this->setXSLs();
// Отправка писем администраторам и пользователю
$this->send();
return $this;
}
/**
* @param $response
*
* @return Core_DataBase
*/
protected function writePaymentId($response)
{
$paymentRow = Core_QueryBuilder::select()
->from('shop_yoo_order_payments')
->where('order_id', '=', $this->_shopOrder->id)
->limit(1)
->execute()
->asAssoc()
->result();
if ($paymentRow) {
$result = Core_QueryBuilder::update('shop_yoo_order_payments')
->columns(array('payment_id' => $response->getId()))
->where('order_id', '=', $this->_shopOrder->id)
->execute();
} else {
$result = Core_QueryBuilder::insert('shop_yoo_order_payments')
->columns('order_id', 'payment_id')
->values($this->_shopOrder->id, $response->getId())
->execute();
}
return $result;
}
/**
* @return string
*/
private function getFormUrl()
{
$sUrl = 'https://';
return $this->mode === self::MODE_YOOKASSA
? ''
: $sUrl.'yoomoney.ru/quickpay/confirm';
}
/**
* @param string $tpl
* @param Shop_Order_Model $order
*
* @return string
*/
private function parsePlaceholders($tpl, $order)
{
$replace = array(
'%order_id%' => $order->id,
);
foreach ($order->toArray() as $key => $value) {
if (is_scalar($value)) {
$replace['%'.$key.'%'] = $value;
}
}
return strtr($tpl, $replace);
}
private function checkSign($callbackParams)
{
if ($this->mode === self::MODE_YOOKASSA) {
$string = $callbackParams['action'].';'.$callbackParams['orderSumAmount'].';'
.$callbackParams['orderSumCurrencyPaycash'].';'.$callbackParams['orderSumBankPaycash'].';'
.$callbackParams['shopId'].';'.$callbackParams['invoiceId'].';'
.$callbackParams['customerNumber'].';'.$this->yoo_password;
return strtoupper($callbackParams['md5']) == strtoupper(md5($string));
} else {
$string = $callbackParams['notification_type'].'&'.$callbackParams['operation_id'].'&'
.$callbackParams['amount'].'&'.$callbackParams['currency'].'&'
.$callbackParams['datetime'].'&'.$callbackParams['sender'].'&'
.$callbackParams['codepro'].'&'.$this->yoo_password.'&'.$callbackParams['label'];
$check = (sha1($string) == $callbackParams['sha1_hash']);
if (!$check) {
header('HTTP/1.0 401 Unauthorized');
return false;
}
return true;
}
}
private function sendCode($callbackParams, $code, $message = '')
{
if ($this->mode != self::MODE_YOOKASSA) {
return;
}
$invoiceId = isset($callbackParams['invoiceId']) ? $callbackParams['invoiceId'] : '';
header("Content-type: text/xml; charset=utf-8");
$xml = '<?xml version="1.0" encoding="UTF-8"?>
<'.$callbackParams['action'].'Response performedDatetime="'.date("c").'" code="'.$code
.'" invoiceId="'.$invoiceId.'" shopId="'.$this->yoo_shopid
.'" techmessage="'.$message.'"/>';
echo $xml;
die();
}
/**
* Оплачивает заказ
*/
private function processResult()
{
if ($this->checkSign($_POST)) {
if (isset($_POST['action']) || ($this->mode !== self::MODE_YOOKASSA)) {
$order_id = intval(Core_Array::getPost(isset($_POST["label"]) ? "label" : "orderNumber"));
if ($order_id > 0) {
$oShop_Order = $this->_shopOrder;
$sHostcmsSum = sprintf("%.2f", $this->getSumWithCoeff());
$sYoomoneySum = Core_Array::getRequest('orderSumAmount', '');
if ($sHostcmsSum == $sYoomoneySum) {
if ($_POST['action'] == 'paymentAviso') {
$this->shopOrder($oShop_Order)->shopOrderBeforeAction(clone $oShop_Order);
$oShop_Order->system_information = "Заказ оплачен через сервис ЮKassa.\n";
$oShop_Order->paid();
if (method_exists($this, 'setMailSubjects')) {
$this->setMailSubjects();
}
$this->setXSLs();
$this->sendEmail($this->sendChangeStatusEmail);
ob_start();
$this->changedOrder('changeStatusPaid');
ob_get_clean();
}
} else {
$this->sendCode($_POST, 100, 'Bad amount');
}
}
$this->sendCode($_POST, 0, 'Order completed.');
} else {
$this->sendCode($_POST, 0, 'Order is exist.');
}
} else {
$this->sendCode($_POST, 1, 'md5 bad');
}
}
/**
* @param bool $sendToAdmin
*
* @return Shop_Payment_System_Handler
* @throws Core_Exception
*/
protected function sendEmail($sendToAdmin)
{
if ($sendToAdmin) {
return $this->send();
}
Core_Event::notify('Shop_Payment_System_Handler.onBeforeSend', $this);
if (is_null($this->_shopOrder)) {
throw new Core_Exception('send(): shopOrder is empty.');
}
$oShopOrder = $this->_shopOrder;
$oShop = $oShopOrder->Shop;
if ($oShop->send_order_email_user) {
$oCore_Mail_Siteuser = $this->getSiteuserEmail();
$this->sendSiteuserEmail($oCore_Mail_Siteuser);
}
Core_Event::notify('Shop_Payment_System_Handler.onAfterSend', $this);
return $this;
}
/**
* @param $sum
*
* @param $returnUrl
* @return CreatePaymentResponse
* @throws ApiException
* @throws BadApiRequestException
* @throws ForbiddenException
* @throws InternalServerError
* @throws NotFoundException
* @throws ResponseProcessingException
* @throws TooManyRequestsException
* @throws UnauthorizedException
*/
protected function createPayment($sum, $returnUrl)
{
$client = $this->getClient();
$builder = CreatePaymentRequest::builder()
->setAmount($sum)
->setPaymentMethodData('')
->setCapture(true)
->setDescription($this->createDescription())
->setConfirmation(
array(
'type' => ConfirmationType::REDIRECT,
'returnUrl' => $returnUrl,
)
)
->setMetadata(array(
'cms_name' => 'yoo_api_hostcms',
'module_version' => self::YOOMONEY_MODULE_VERSION,
));
if ($this->sendCheck) {
$oShop_Order = Core_Entity::factory('Shop_Order', $this->_shopOrder->id);
$aShopOrderItems = $oShop_Order->Shop_Order_Items->findAll();
$email = isset($this->_shopOrder->email) ? $this->_shopOrder->email : '';
$phone = isset($this->_shopOrder->phone) ? $this->_shopOrder->phone : '';
if (!empty($email)) {
$builder->setReceiptEmail($email);
}
if (!empty($phone)) {
$builder->setReceiptPhone($phone);
}
$disc = 0;
$osum = 0;
foreach ($aShopOrderItems as $kk => $item) {
if ($item->price < 0) {
$disc -= $item->getAmount();
unset($aShopOrderItems[$kk]);
} else {
if ($item->shop_item_id) {
$osum += $item->getAmount();
}
}
}
unset($item);
$disc = abs($disc) / $osum;
foreach ($aShopOrderItems as $item) {
$tax_id = null;
if ($item->shop_item_id) {
$tax_id = $item->Shop_Item->shop_tax_id;
}
$tax = Core_Array::get($this->kassaTaxRates, $tax_id, $this->kassaTaxRateDefault);
$amount = $item->getPrice() * ($item->shop_item_id ? 1 - $disc : 1);
$builder->addReceiptItem(
$item->name,
$amount,
$item->quantity,
$tax,
$this->defaultPaymentMode,
$this->defaultPaymentSubject
);
}
if ($this->kassaTaxSystem) {
$builder->setTaxSystemCode($this->kassaTaxSystem);
}
}
$createPaymentRequest = $builder->build();
$receipt = $createPaymentRequest->getReceipt();
if ($receipt instanceof Receipt) {
$receipt->normalize($createPaymentRequest->getAmount());
}
return $client->createPayment($createPaymentRequest);
}
/**
* @param $order
* @throws Core_Exception
*/
private function completePayment($order)
{
$this->log('info', 'Payment completed');
$this->shopOrder($order)->shopOrderBeforeAction(clone $order);
$order->system_information = "Заказ оплачен через сервис ЮKassa.\n";
$order->paid();
if (method_exists($this, 'setMailSubjects')) {
$this->setMailSubjects();
}
$this->setXSLs();
$this->sendEmail($this->sendChangeStatusEmail);
ob_start();
$this->changedOrder('changeStatusPaid');
ob_get_clean();
}
/**
* @param string $level
* @param string $message
*/
private function log($level, $message)
{
if ($this->enable_logging) {
YooMoneyLogger::instance()->log($level, $message);
}
}
/**
* @param mixed $value
* @param string $status
* @param string $logMessage
*/
function checkValueIsNotEmpty($value, $status, $logMessage)
{
if (!$value) {
$this->log('error', $logMessage);
header('HTTP/1.1 '.$status);
header('Status: '.$status);
exit();
}
}
private function exit200()
{
header('HTTP/1.1 200 OK');
header('Status: 200 OK');
exit();
}
private function exit400()
{
header('HTTP/1.1 400 Bad Request');
header('Status: 400 Bad Request');
exit();
}
/**
* @return string
*/
private function createDescription()
{
$descriptionTemplate = $this->yoo_description;
$replace = array();
$patterns = explode('%', $descriptionTemplate);
foreach ($patterns as $pattern) {
$value = null;
if (isset($this->getShopOrder()->$pattern)) {
$value = $this->getShopOrder()->$pattern;
} else {
$method = 'get'.ucfirst($pattern);
if (method_exists($this->getShopOrder(), $method)) {
$value = $this->getShopOrder()->{$method}();
}
}
if (!is_null($value) && is_scalar($value)) {
$replace['%'.$pattern.'%'] = $value;
}
}
$description = strtr($descriptionTemplate, $replace);
return (string)mb_substr($description, 0, Payment::MAX_LENGTH_DESCRIPTION);
}
/**
* @return Client
*/
private function getClient()
{
if (!$this->apiClient) {
$this->apiClient = new Client();
$userAgent = $this->apiClient->getApiClient()->getUserAgent();
$userAgent->setCms('HostCMS', Informationsystem_Module::factory('informationsystem')->version);
$userAgent->setModule('PaymentGateway', self::YOOMONEY_MODULE_VERSION);
$this->apiClient->setAuth($this->yoo_shopid, $this->yoo_password);
if ($this->enable_logging) {
$this->apiClient->setLogger(YooMoneyLogger::instance());
}
}
return $this->apiClient;
}
/**
* @param $order_status_id
* @return bool
*/
private function isNeedSecondReceipt($order_status_id)
{
return ($this->sendCheck && $this->sendSecondCheck && $this->orderStatusSecondCheck == $order_status_id);
}
/**
* @param int $order_id
* @return string|null
* @throws Core_Exception
*/
private function getOrderPaymentId($order_id)
{
$result = null;
$paymentRow = Core_QueryBuilder::select()
->from('shop_yoo_order_payments')
->where('order_id', '=', $order_id)
->orderBy('id', 'DESC')
->limit(1)
->execute()
->asAssoc()
->result();
if (is_array($paymentRow) && !empty($paymentRow[0]['payment_id'])) {
$result = $paymentRow[0]['payment_id'];
}
return $result;
}
/**
* @param $paymentId
* @return mixed|ReceiptResponseInterface
* @throws ApiException
* @throws BadApiRequestException
* @throws ExtensionNotFoundException
* @throws ForbiddenException
* @throws InternalServerError
* @throws NotFoundException
* @throws ResponseProcessingException
* @throws TooManyRequestsException
* @throws UnauthorizedException
*/
private function getLastReceipt($paymentId)
{
$receipts = $this->getClient()->getReceipts(array('payment_id' => $paymentId))->getItems();
return array_pop($receipts);
}
/**
* @param ReceiptResponseInterface $lastReceipt
* @param string $paymentId
* @param Shop_Order_Model $order
* @return CreatePostReceiptRequest|null
*/
private function buildSecondReceipt($lastReceipt, $paymentId, $order)
{
if ($lastReceipt instanceof ReceiptResponseInterface) {
if ($lastReceipt->getType() === "refund") {
return null;
}
$resendItems = $this->getResendItems($lastReceipt->getItems());
if (count($resendItems['items']) < 1) {
$this->log('info', 'Second receipt is not required');
return null;
}
try {
$customer = $this->getReceiptCustomer($order);
if (empty($customer)) {
$this->log('error', 'Need customer phone or email for second receipt');
return null;
}
$receiptBuilder = CreatePostReceiptRequest::builder();
$receiptBuilder->setObjectId($paymentId)
->setType(ReceiptType::PAYMENT)
->setItems($resendItems['items'])
->setSettlements(array(
new Settlement(array(
'type' => 'prepayment',
'amount' => array(
'value' => $resendItems['amount'],
'currency' => 'RUB',
),
)),
))
->setCustomer($customer)
->setSend(true);
if ($lastReceipt->getTaxSystemCode()) {
$receiptBuilder->setTaxSystemCode($lastReceipt->getTaxSystemCode());
}
return $receiptBuilder->build();
} catch (Exception $e) {
$this->log('error', $e->getMessage() . '. Property name: '. $e->getProperty());
}
}
return null;
}
/**
* @param Shop_Order_Model $order
* @return bool|ReceiptCustomer
*/
private function getReceiptCustomer($order)
{
$customerData = array();
if (!empty($order->email)) {
$customerData['email'] = $order->email;
}
if (!empty($order->phone)) {
$customerData['phone'] = $order->phone;
}
if (!empty($order->tin)) {
$customerData['inn'] = $order->tin;
}
$userName = array();
if (!empty($order->surname)) $userName[] = $order->surname;
if (!empty($order->name)) $userName[] = $order->name;
if (!empty($order->patronymic)) $userName[] = $order->patronymic;
if ($userFullName = implode(' ', $userName)) {
$customerData['full_name'] = $userFullName;
}
return new ReceiptCustomer($customerData);
}
/**
* @param ReceiptResponseItemInterface[] $items
*
* @return array
*/
private function getResendItems($items)
{
$result = array(
'items' => array(),
'amount' => 0,
);
foreach ($items as $item) {
if ($this->isNeedResendItem($item->getPaymentMode())) {
$item->setPaymentMode(PaymentMode::FULL_PAYMENT);
$result['items'][] = new ReceiptItem($item->jsonSerialize());
$result['amount'] += $item->getAmount() / 100.0;
}
}
return $result;
}
/**
* @param string $paymentMode
*
* @return bool
*/
private function isNeedResendItem($paymentMode)
{
return in_array($paymentMode, self::getValidPaymentMode());
}
}
class YooMoneyLogger extends Core_Log
{
static public function instance()
{
return new self();
}
public function getLogName($date)
{
return $this->_logDir.DIRECTORY_SEPARATOR.'yoo_payment_log_'.date('d_m_Y',
Core_Date::sql2timestamp($date)).'.log';
}
public function log($level, $message, $context = null)
{
$this->clear()
->notify(false)
->status($this->convertLevelToType($level))
->write($message);
}
/**
* Convert standard log level to HostCMS type
* @param string $level
* @return int
*/
private function convertLevelToType ($level)
{
$type = self::$MESSAGE;
switch ($level) {
case 'info':
$type = self::$MESSAGE;
break;
case 'debug':
$type = self::$SUCCESS;
break;
case 'notice':
$type = self::$NOTICE;
break;
case 'warn':
$type = self::$WARNING;
break;
case 'error':
$type = self::$ERROR;
break;
}
return $type;
}
}
lezhenkin
21 ноября 2023 г.
class Shop_Payment_System_Handler4 extends Shop_Payment_System_Handler
У вас здесь число в конце имени класса должно соответствовать значению идентификатора обработчика, который был ему присвоен системой при добавлении. Он совпадает?
Авторизация