<?php declare(strict_types=1);
namespace DonCarneTheme\Subscriber;
use PayonePayment\PaymentHandler\PayoneCreditCardPaymentHandler;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use DonCarneTheme\Service\GraphQLClient;
use Shopware\Storefront\Page\Checkout\Confirm\CheckoutConfirmPageLoadedEvent;
use DateTime;
use DonCarneTheme\Service\ExpectedDeliveryTimes;
use PayonePayment\PaymentHandler\PayonePaypalPaymentHandler;
use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryEntity;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionDefinition;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity;
use Shopware\Core\Checkout\Order\OrderStates;
use Shopware\Core\Checkout\Payment\PaymentMethodEntity;
use Shopware\Core\Checkout\Shipping\ShippingMethodEntity;
use Shopware\Core\Framework\Struct\ArrayStruct;
use Shopware\Core\System\StateMachine\StateMachineRegistry;
use Shopware\Core\System\StateMachine\Transition;
use Shopware\Storefront\Page\Checkout\Finish\CheckoutFinishPageLoadedEvent;
use Shopware\Core\Checkout\Cart\Event\CheckoutOrderPlacedEvent;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class CheckoutSubscriber implements EventSubscriberInterface
{
private ExpectedDeliveryTimes $deliveryService;
private StateMachineRegistry $stateMachineRegistry;
private EntityRepositoryInterface $orderRepository;
private RequestStack $requestStack;
public function __construct(
ExpectedDeliveryTimes $deliveryService,
StateMachineRegistry $stateMachineRegistry,
EntityRepositoryInterface $orderRepository,
RequestStack $requestStack
) {
$this->deliveryService = $deliveryService;
$this->stateMachineRegistry = $stateMachineRegistry;
$this->orderRepository = $orderRepository;
$this->requestStack = $requestStack;
}
public static function getSubscribedEvents(): array
{
return [
CheckoutConfirmPageLoadedEvent::class => 'onConfirmPage',
CheckoutFinishPageLoadedEvent::class => 'onFinishPage',
CheckoutOrderPlacedEvent::class => 'onOrderPlaced'
];
}
public function onFinishPage(CheckoutFinishPageLoadedEvent $event)
{
/** @var OrderDeliveryEntity|null $delivery */
$delivery = $event->getPage()->getOrder()->getDeliveries()->first();
$items = $event->getPage()->getOrder()->getLineItems();
if (empty($delivery)) {
return;
}
$customFields = $delivery->getShippingMethod()->getCustomFields() ?? [];
$graphQLId = $customFields['custom_shipping_method_graphql_id'] ?? '';
$countryIso = $delivery->getShippingOrderAddress()->getCountry()->getIso();
$zipcode = $delivery->getShippingOrderAddress()->getZipcode();
$deliveryTimes = $this->deliveryService->getDeliveryTimes($countryIso, $zipcode, $items);
if ($deliveryTimes && array_key_exists($graphQLId, $deliveryTimes)) {
$event->getPage()->addExtension('delivery_time', new ArrayStruct($deliveryTimes[$graphQLId]));
}
/*
* Change the Payment State for PayOne Paypal from "open" to "in_progress"
* @see https://developer.shopware.com/docs/guides/plugins/plugins/checkout/order/using-the-state-machine.html#order-transaction-state
*/
/** @var OrderTransactionEntity $transaction */
$transaction = $event->getPage()->getOrder()->getTransactions()->last();
if (
$transaction &&
(
$transaction->getPaymentMethod()->getHandlerIdentifier() === PayonePaypalPaymentHandler::class ||
$transaction->getPaymentMethod()->getHandlerIdentifier() === PayoneCreditCardPaymentHandler::class ||
$transaction->getPaymentMethod()->getId() === 'fcee8e87a0834a32895d15c7849eadeb'
) &&
$transaction->getStateMachineState()->getTechnicalName() === OrderStates::STATE_OPEN
) {
$this->stateMachineRegistry->transition(new Transition(
OrderTransactionDefinition::ENTITY_NAME,
$transaction->getId(),
'do_pay', // will result in 'in_progress' state
'stateId'
), $event->getContext());
}
}
public function onConfirmPage(CheckoutConfirmPageLoadedEvent $event)
{
$customer = $event->getSalesChannelContext()->getCustomer();
$items = $event->getPage()->getCart()->getLineItems();
$countryIso = $customer->getDefaultShippingAddress()->getCountry()->getIso();
$zipcode = $customer->getDefaultShippingAddress()->getZipCode();
$deliveryTimes = $this->deliveryService->getDeliveryTimes($countryIso, $zipcode, $items);
// Validate and clean up saved delivery dates in session
$this->validateAndCleanupSavedDates($deliveryTimes);
// Sort methods to avoid selected method being first
$shippingMethods = $event->getPage()->getShippingMethods();
$paymentMethods = $event->getPage()->getPaymentMethods();
$shippingMethods->sort(
static fn(ShippingMethodEntity $a, ShippingMethodEntity $b) => $a->getPosition() >= $b->getPosition()
);
$paymentMethods->sort(
static fn(PaymentMethodEntity $a, PaymentMethodEntity $b) => $a->getPosition() >= $b->getPosition()
);
$event->getPage()->getShippingMethods()->setExtensions([
'delivery_times' => $deliveryTimes
]);
}
public function onOrderPlaced(CheckoutOrderPlacedEvent $event) {
// Selected shipping method has a GraphQL id?
$delivery = $event->getOrder()->getDeliveries()->first();
$items = $event->getOrder()->getLineItems();
$selectedShippingMethodCustomFields = $delivery->getShippingMethod()->getCustomFields();
// If so, insert the correct delivery time
if ($selectedShippingMethodCustomFields && $selectedShippingMethodCustomFields['custom_shipping_method_graphql_id']) {
// Get the infos about the customer's shipping address
$shippingAddress = $delivery->getShippingOrderAddress();
$deliveryTimes = $this->deliveryService->getDeliveryTimes($shippingAddress->getCountry()->getIso(), $shippingAddress->getZipcode(), $items);
if ($deliveryTimes) {
foreach ($deliveryTimes as $key => $deliveryTime) {
if ($selectedShippingMethodCustomFields['custom_shipping_method_graphql_id'] == $key) {
$this->orderRepository->upsert([
[
'id' => $event->getOrderId(),
'customFields' => [
'expected_delivery_date' => isset($deliveryTime['date']['fullDate']) ? ($deliveryTime['date']['fullDate'])->format('Y-m-d') : null
]
]
], $event->getContext());
}
}
}
}
}
/**
* Validates saved delivery dates against current GraphQL data and removes invalid ones
*/
private function validateAndCleanupSavedDates(?array $currentDeliveryTimes): void
{
$request = $this->requestStack->getCurrentRequest();
if (!$request) {
return;
}
$session = $request->getSession();
$savedDeliveryDates = $session->get('selected_delivery_dates', []);
if (empty($savedDeliveryDates) || !$currentDeliveryTimes) {
return;
}
$validatedDates = [];
foreach ($savedDeliveryDates as $shippingMethodId => $savedDate) {
if ($this->isDeliveryDateValid($savedDate, $currentDeliveryTimes, $shippingMethodId)) {
$validatedDates[$shippingMethodId] = $savedDate;
}
}
// Update session with only valid dates
if (count($validatedDates) !== count($savedDeliveryDates)) {
$session->set('selected_delivery_dates', $validatedDates);
}
}
/**
* Checks if a saved delivery date is still available in current delivery times
*/
private function isDeliveryDateValid(array $savedDate, array $currentDeliveryTimes, string|int $shippingMethodId): bool
{
if (!isset($currentDeliveryTimes[$shippingMethodId])) {
return false;
}
$allDates = $currentDeliveryTimes[$shippingMethodId]['allDates'] ?? [];
if (empty($allDates)) {
return false;
}
$savedDateStr = $savedDate['date'] ?? '';
$savedTimeFrom = $savedDate['timeFrom'] ?? '';
$savedTimeUntil = $savedDate['timeUntil'] ?? '';
foreach ($allDates as $availableDate) {
$availableDateStr = $availableDate['date']['fullDate']->format('Y-m-d');
$availableTimeFrom = $availableDate['time']['from'] ?? '';
$availableTimeUntil = $availableDate['time']['until'] ?? '';
if ($availableDateStr === $savedDateStr &&
$availableTimeFrom === $savedTimeFrom &&
$availableTimeUntil === $savedTimeUntil) {
return true;
}
}
return false;
}
}