Приветствую. У меня есть контроллер, который назначает заказу пользователя способ доставки и устанавливает его цену, предполагается что стоимость доставки я буду передавать в запросе через роут. И в принципе тестово добавление стоимости работает, на оплату идет уже значение с учетом стоимости доставки, но проблема в том что при этом не обновляется цена на фронте, не разобрался пока что как именно она обновляется, явно по какому то эвенту или хуку, она например обновляется при переключении способов доставок в виджете. Пробовал вызывать пересчет цены ордера, но не обновляет отрисовку. Может быть кто то знает подробности? По идее это может быть закопано где то в реализации виджета переключения способов доставки, но понять бы где его искать. Или может быть надо это реализовывать в переопределении пейна/флоу. В общем вот код контроллера:
<?php
namespace Drupal\cdek_checkout_flow\Controller;
use
Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\commerce_cart\CartProviderInterface;
use Drupal\commerce_shipping\Entity\Shipment;
use Drupal\commerce_price\Price;
class
CdekDeliveryPriceController extends ControllerBase {
/**
* The logger factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $loggerFactory;
/**
* The cart provider.
*
* @var \Drupal\commerce_cart\CartProviderInterface
*/
protected $cartProvider;
/**
* Constructs a new CdekDeliveryPriceController object.
*
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger factory.
* @param \Drupal\commerce_cart\CartProviderInterface $cart_provider
* The cart provider.
*/
public function __construct(LoggerChannelFactoryInterface $logger_factory, CartProviderInterface $cart_provider) {
$this->loggerFactory = $logger_factory;
$this->cartProvider = $cart_provider;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('logger.factory'),
$container->get('commerce_cart.cart_provider')
);
}
/**
* Gets the current order.
*
* @return \Drupal\commerce_order\Entity\OrderInterface|null
* The current order, or NULL if none found.
*/
protected function getCurrentOrder() {
$logger = $this->loggerFactory->get('cdek_checkout_flow');
$logger->notice('trying to get current order');
// Получаем текущего пользователя
$current_user = \Drupal::currentUser();
$logger->notice('Current user: @uid', ['@uid' => $current_user->id()]);
// Получаем текущий магазин
$store = \Drupal::service('commerce_store.current_store')->getStore();
if (!$store) {
$logger->error('No store found');
return NULL;
}
$logger->notice('Current store: @store_id', ['@store_id' => $store->id()]);
// Получаем текущую корзину
$cart = $this->cartProvider->getCart('default', $store);
$logger->notice('Cart found: @cart', ['@cart' => $cart ? $cart->id() : 'NULL']);
return $cart;
}
/**
* Updates the order price.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The JSON response.
*/
public function updatePrice() {
$logger = $this->loggerFactory->get('cdek_checkout_flow');
$logger->notice('Starting updatePrice function');
$order = $this->getCurrentOrder();
if (!$order) {
$logger->error('No order found');
return new JsonResponse(['success' => false, 'message' => 'No order found']);
}
$logger->notice('Found order: @order_id', ['@order_id' => $order->id()]);
// Получаем или создаем отправку
$shipment = NULL;
if (!$order->get('shipments')->isEmpty()) {
$shipment = $order->get('shipments')->first()->entity;
$logger->notice('Found existing shipment: @shipment_id', ['@shipment_id' => $shipment->id()]);
}
if (!$shipment) {
$logger->notice('Creating new shipment');
$shipment = Shipment::create([
'type' => 'default',
'order_id' => $order->id(),
'title' => t('CDEK доставка'),
'state' => 'ready',
]);
}
// Находим наш метод доставки
$logger->notice('Looking for CDEK shipping method');
$shipping_methods = \Drupal::entityTypeManager()
->getStorage('commerce_shipping_method')
->loadByProperties(['plugin' => 'cdek_checkout_flow']);
$shipping_method = reset($shipping_methods);
if (
$shipping_method) {
$logger->notice('Found CDEK shipping method: @method_id', ['@method_id' => $shipping_method->id()]);
$shipment->setShippingMethod($shipping_method);
$shipment->setAmount(new Price('500', 'RUB'));
$logger->notice('Set shipping method and amount for shipment');
} else {
$logger->error('CDEK shipping method not found');
return new JsonResponse(['success' => false, 'message' => 'CDEK shipping method not found']);
}
try {
$shipment->save();
$logger->notice('Shipment saved successfully');
// Добавляем отправку к заказу, если она новая
if ($order->get('shipments')->isEmpty()) {
$order->get('shipments')->appendItem($shipment);
$logger->notice('Added shipment to order');
}
// Добавляем корректировку для стоимости доставки
$shipping_amount = $shipment->getAmount();
if ($shipping_amount) {
$adjustment = new \Drupal\commerce_order\Adjustment([
'type' => 'shipping',
'label' => t('Доставка CDEK'),
'amount' => $shipping_amount,
'source_id' => $shipment->id(),
]);
$order->addAdjustment($adjustment);
$order->save();
$logger->notice('Added shipping adjustment to order');
// Пересчитываем общую сумму заказа и соъраняем
$order->recalculateTotalPrice();
$order->save();
$logger->notice('Recalculated order total price');
}
return new
JsonResponse(['success' => true]);
} catch (\Exception $e) {
$logger->error('Error saving shipment: @Error', ['@Error' => $e->getMessage()]);
return new JsonResponse(['success' => false, 'message' => $e->getMessage()]);
}
}
}?>
Комментарии
Насколько я помню, в коммерце при активированном shipment есть давнее проклятие: кнопка "Обновить стоимость" или как-то так. Я не смотрел последние версии, не в курсе осталась ли она.
Так вот, почти всегда приходилось выдумывать какие-то танцы с бубном при подключении кастомных методов доставки, чтобы инициировать пересчёт стоимости - в частности на ajax-формах выбора городов или ПВЗ. Я не уверен, что есть на текущий момент какие-либо штатные (как вы пишите) события.
Однако, помню, что вся магия заключается в каком-то предельно неудобном компоненте shipment, в который спрятан виджет кнопки "Обновить" с его ajax-коллбеком. Думаю, рациональнее начать с этого направления.
Да, спасибо, тоже нашел сейчас commerce_shipping и там есть и пейн их и много в принципе всего, в частности что то наподобие такого, помимо кнопки:
<?php
public function defaultConfiguration() {
return [
'auto_recalculate' => TRUE,
'require_shipping_profile' => TRUE,
] + parent::defaultConfiguration();
}?>
В общем есть механизм какой то для автопересчета насколько я понимаю, надо разбираться. Кажется нужно где то тут искать в
EarlyOrderProcessor
:<?php
if ($should_refresh) {
$order->unsetData(ShippingOrderManagerInterface::FORCE_REFRESH);
}?>
По поводу автопересчёта - тоже были какие-то проблемы. То ли он не работает как надо, то ли работает только на JS-событиях change для каких-то специфических полей. В общем, помню, что с ним тоже была какая-то история. И установка 'auto_recalculate' => TRUE хоть программно, хоть из интерфейса чекаута ничего не давала.
Да, тут конечно хватает особенностей, вообще иногда слабо понимаю что происходит ) Часто примеры в их документации не соответсвуют реальному коду. Ну и альтернатив в то же время как будто не сказать чтобы много...
Ну нашел у них в js вот это
Drupal.shippingRecalculate.recalculateRates();
В принципе работает, но как то супер криво, зачем то начинает валидировать поля, имя, фамилию и прочие обязательные, зачем это все в один механизм запихали ума не приложу. Я уже думаю - может есть смысл просто получать в ответе от сервера новую цену после пересчета и просто самому перезаписывать данные на фронте? Или это через чур костыльно?
ИМХО, в случае с shipping на каком-то отдельном проекте не имеет особого значения - костыли или нет. По фэншую может быть слишком/необоснованно долго и замороченно.