<?php
declare(strict_types=1);
namespace Wbfk\MultipleTransactions\Subscriber;
use Shopware\Core\Checkout\Cart\Order\CartConvertedEvent;
use Shopware\Core\Checkout\Cart\Price\CashRounding;
use Shopware\Core\Checkout\Cart\Price\Struct\CalculatedPrice;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionCollection;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Pricing\CashRoundingConfig;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\System\StateMachine\Aggregation\StateMachineState\StateMachineStateEntity;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Wbfk\MultipleTransactions\Core\Checkout\Order\TransactionsSummary;
class CartConvertedEventSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly CashRounding $cashRounding,
private readonly EntityRepository $stateMachineStateRepository,
private readonly EntityRepository $orderTransactionRepository
) {
}
public static function getSubscribedEvents(): array
{
return [
CartConvertedEvent::class => 'adjustTransactionAmountToIncludeAlreadyPaidAmount',
];
}
public function adjustTransactionAmountToIncludeAlreadyPaidAmount(CartConvertedEvent $event): void
{
/** @var TransactionsSummary $transactionsSummary */
$transactionsSummary = $event->getCart()->getExtension('transactionsSummary');
if (!$transactionsSummary) {
return;
}
if ($transactionsSummary->alreadyPaid === 0.0) {
return;
}
$convertedCart = $event->getConvertedCart();
/** @var ?CalculatedPrice $calculatedPrice */
$calculatedPrice = $convertedCart['transactions'][0]['amount'] ?? null;
if (!$calculatedPrice) {
return;
}
$total = $calculatedPrice->getTotalPrice();
$total -= $transactionsSummary->alreadyPaid;
$convertedCart['transactions'][0]['stateId'] = $this->getPartiallyPaidStateId($event->getContext());
// If there is excess payment, this should be handled by the shop admin.
// If the total is same as already paid, then there is no need to create a new transaction.
if ($total <= 0) {
unset($convertedCart['transactions']);
// Need to delete last transaction if it is partially paid transaction as it is not needed anymore.
// This happens when order is already edited added new items or quantity changed to higher requiring extra payment.
// Then this order might be further edited and removed some items or quantity changed to lower requiring refund/ or not requiring extra payment.
$lastTransaction = $transactionsSummary->transactions->last();
if ($lastTransaction && $lastTransaction->stateMachineState->technicalName === OrderTransactionStates::STATE_PARTIALLY_PAID) {
$this->orderTransactionRepository->delete([
['id' => $lastTransaction->getId()],
], $event->getContext());
}
} else {
$cashRoundingConfig = new CashRoundingConfig(2, 0.01, true);
$total = $this->cashRounding->cashRound($total, $cashRoundingConfig);
$convertedCart['transactions'][0]['amount'] =
new CalculatedPrice($total, $total, $calculatedPrice->getCalculatedTaxes(), $calculatedPrice->getTaxRules(), 1);
// Saving previous transactions summary in custom fields to for any debugging purposes.
$customFields = $convertedCart['transactions'][0]['customFields'] ?? [];
$customFields['previousTransactionsSummary'] = $transactionsSummary;
$convertedCart['transactions'][0]['customFields'] = $customFields;
}
$event->setConvertedCart($convertedCart);
}
private function getPartiallyPaidStateId(Context $context): string
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('technicalName', OrderTransactionStates::STATE_PARTIALLY_PAID));
$criteria->setLimit(1);
/** @var StateMachineStateEntity $state */
$state = $this->stateMachineStateRepository->search($criteria, $context)->first();
if ($state === null) {
throw new \RuntimeException('Could not find state for partially paid transactions');
}
return $state->getId();
}
}