Skip to content

Commit

Permalink
PISHPS-310: added OrderEditSubscriber
Browse files Browse the repository at this point in the history
  • Loading branch information
m-muxfeld-diw authored Aug 20, 2024
1 parent ca01cee commit 1d6865c
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/Resources/config/services/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
<argument type="service" id="Kiener\MolliePayments\Service\UrlParsingService"/>
</service>

<service id="Kiener\MolliePayments\Service\Order\OrderTimeService"/>

<service id="Kiener\MolliePayments\Service\Order\OrderStatusUpdater">
<argument type="service" id="Kiener\MolliePayments\Service\Order\OrderStateService"/>
Expand Down
7 changes: 7 additions & 0 deletions src/Resources/config/services/subscriber.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,12 @@
<tag name="kernel.event_subscriber"/>
</service>

<service id="Kiener\MolliePayments\Subscriber\OrderEditSubscriber">
<argument type="service" id="Kiener\MolliePayments\Service\Order\OrderStatusUpdater"/>
<argument type="service" id="Kiener\MolliePayments\Service\Order\OrderTimeService"/>
<argument type="service" id="Kiener\MolliePayments\Service\SettingsService"/>
<tag name="kernel.event_subscriber" />
</service>

</services>
</container>
3 changes: 2 additions & 1 deletion src/Service/Order/OrderStatusUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

class OrderStatusUpdater
{
public const ORDER_STATE_FORCE_OPEN = 'order-state-force-open';
/**
* @var OrderStateService
*/
Expand Down Expand Up @@ -101,7 +102,7 @@ public function updatePaymentStatus(OrderTransactionEntity $transaction, string
{
# if we are already in_progress...then don't switch to OPEN again
# otherwise SEPA bank transfer would switch back to OPEN
if ($currentShopwareStatusKey !== OrderTransactionStates::STATE_IN_PROGRESS) {
if ($currentShopwareStatusKey !== OrderTransactionStates::STATE_IN_PROGRESS || $context->hasState(self::ORDER_STATE_FORCE_OPEN)) {
$addLog = true;
$this->transactionTransitionService->reOpenTransaction($transaction, $context);
}
Expand Down
56 changes: 56 additions & 0 deletions src/Service/Order/OrderTimeService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);

namespace Kiener\MolliePayments\Service\Order;

use DateTime;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity;
use Shopware\Core\Checkout\Order\OrderEntity;

class OrderTimeService
{
/**
* @var DateTime
*/
private $now;

public function __construct(?DateTime $now = null)
{
$this->now = $now ?? new DateTime();
}

/**
* Checks if the age of the last transaction of the order is greater than the specified number of hours.
*
* @param OrderEntity $order The order entity to check.
* @param int $hours The number of hours to compare against.
*
* @return bool Returns true if the order is older than the specified number of hours, false otherwise.
*/
public function isOrderAgeGreaterThan(OrderEntity $order, int $hours): bool
{
$transactions = $order->getTransactions();

if ($transactions === null || count($transactions) === 0) {
return false;
}

/** @var ?OrderTransactionEntity $lastTransaction */
$lastTransaction = $transactions->last();

if ($lastTransaction === null) {
return false;
}

$transitionDate = $lastTransaction->getCreatedAt();

if ($transitionDate === null) {
return false;
}

$interval = $this->now->diff($transitionDate);
$diffInHours = $interval->h + ($interval->days * 24);

return $diffInHours > $hours;
}
}
6 changes: 6 additions & 0 deletions src/Service/SettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ class SettingsService implements PluginSettingsServiceInterface
{
public const SYSTEM_CONFIG_DOMAIN = 'MolliePayments.config';
private const SYSTEM_CORE_LOGIN_REGISTRATION_CONFIG_DOMAIN = 'core.loginRegistration';
private const SYSTEM_CORE_CART_CONFIG_DOMAIN = 'core.cart';

private const PHONE_NUMBER_FIELD_REQUIRED = 'phoneNumberFieldRequired';
private const PAYMENT_FINALIZE_TRANSACTION_TIME = 'paymentFinalizeTransactionTime';
const LIVE_API_KEY = 'liveApiKey';
const TEST_API_KEY = 'testApiKey';
const LIVE_PROFILE_ID = 'liveProfileId';
Expand Down Expand Up @@ -88,6 +90,10 @@ public function getSettings(?string $salesChannelId = null): MollieSettingStruct

$structData[self::PHONE_NUMBER_FIELD_REQUIRED] = $coreSettings[self::PHONE_NUMBER_FIELD_REQUIRED] ?? false;

/** @var array<mixed> $cartSettings */
$cartSettings = $this->systemConfigService->get(self::SYSTEM_CORE_CART_CONFIG_DOMAIN, $salesChannelId);
$structData[self::PAYMENT_FINALIZE_TRANSACTION_TIME] = $cartSettings[self::PAYMENT_FINALIZE_TRANSACTION_TIME] ?? 1800;

return (new MollieSettingStruct())->assign($structData);
}

Expand Down
15 changes: 15 additions & 0 deletions src/Setting/MollieSettingStruct.php
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ class MollieSettingStruct extends Struct
*/
protected $applePayDirectDomainAllowList = '';

/**
* @var int
*/
protected $paymentFinalizeTransactionTime;

/**
* @return string
*/
Expand Down Expand Up @@ -982,4 +987,14 @@ public function setApplePayDirectDomainAllowList(string $applePayDirectDomainAll
{
$this->applePayDirectDomainAllowList = $applePayDirectDomainAllowList;
}

public function getPaymentFinalizeTransactionTime(): int
{
return $this->paymentFinalizeTransactionTime;
}

public function setPaymentFinalizeTransactionTime(int $paymentFinalizeTransactionTime): void
{
$this->paymentFinalizeTransactionTime = $paymentFinalizeTransactionTime;
}
}
144 changes: 144 additions & 0 deletions src/Subscriber/OrderEditSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);

namespace Kiener\MolliePayments\Subscriber;

use Closure;
use Kiener\MolliePayments\Handler\Method\BankTransferPayment;
use Kiener\MolliePayments\Service\Mollie\MolliePaymentStatus;
use Kiener\MolliePayments\Service\Order\OrderStatusUpdater;
use Kiener\MolliePayments\Service\Order\OrderTimeService;
use Kiener\MolliePayments\Service\SettingsService;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Checkout\Order\OrderStates;
use Shopware\Storefront\Page\Account\Order\AccountOrderPageLoadedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class OrderEditSubscriber implements EventSubscriberInterface
{
/**
* @var OrderStatusUpdater
*/
private $orderStatusUpdater;

/**
* @var OrderTimeService
*/
private $orderTimeService;

/**
* @var SettingsService
*/
private $settingsService;

public function __construct(
OrderStatusUpdater $orderStatusUpdater,
OrderTimeService $orderTimeService,
SettingsService $settingsService
) {
$this->orderStatusUpdater = $orderStatusUpdater;
$this->orderTimeService = $orderTimeService;
$this->settingsService = $settingsService;
}

public static function getSubscribedEvents(): array
{
return [
AccountOrderPageLoadedEvent::class => 'accountOrderDetailPageLoaded'
];
}

public function accountOrderDetailPageLoaded(AccountOrderPageLoadedEvent $event): void
{
$orders = $event->getPage()->getOrders();

foreach ($orders as $order) {
if (!$order instanceof OrderEntity || $this->isMolliePayment($order) === false) {
continue;
}

$transactions = $order->getTransactions();

if ($transactions === null || $transactions->count() === 0) {
continue;
}

$lastTransaction = $transactions->filter(Closure::fromCallable([$this, 'sortTransactionsByDate']))->last();

$lastStatus = $lastTransaction->getStateMachineState()->getTechnicalName();

// disregard any orders that are not in progress
if ($lastStatus !== OrderStates::STATE_IN_PROGRESS) {
continue;
}

$settings = $this->settingsService->getSettings();
$finalizeTransactionTimeInMinutes = $settings->getPaymentFinalizeTransactionTime();
$finalizeTransactionTimeInHours = (int) ceil($finalizeTransactionTimeInMinutes / 60);

if ($this->orderUsesSepaPayment($order)) {
$finalizeTransactionTimeInHours = (int) ceil($settings->getPaymentMethodBankTransferDueDateDays() / 24);
}

if ($this->orderTimeService->isOrderAgeGreaterThan($order, $finalizeTransactionTimeInHours) === false) {
continue;
}

// orderStatusUpdater needs the order to be set on the transaction
$lastTransaction->setOrder($order);
$context = $event->getContext();
// this forces the order to be open again
$context->addState(OrderStatusUpdater::ORDER_STATE_FORCE_OPEN);
try {
$this->orderStatusUpdater->updatePaymentStatus($lastTransaction, MolliePaymentStatus::MOLLIE_PAYMENT_CANCELED, $context);
} catch (\Exception $exception) {
}
}
}

/**
* @param OrderEntity $order
* @return bool
* @todo refactor once php8.0 is minimum version. Use Null-safe operator
*/
private function orderUsesSepaPayment(OrderEntity $order): bool
{
$transactions = $order->getTransactions();

if ($transactions === null || count($transactions) === 0) {
return false;
}

$lastTransaction = $transactions->last();

if ($lastTransaction instanceof OrderTransactionEntity === false) {
return false;
}

$paymentMethod = $lastTransaction->getPaymentMethod();

if ($paymentMethod === null) {
return false;
}

return $paymentMethod->getHandlerIdentifier() === BankTransferPayment::class;
}

private function isMolliePayment(OrderEntity $order): bool
{
$customFields = $order->getCustomFields();

return is_array($customFields) && count($customFields) && isset($customFields['mollie_payments']);
}

/**
* @param OrderTransactionEntity $a
* @param OrderTransactionEntity $b
* @return int
*/
private function sortTransactionsByDate(OrderTransactionEntity $a, OrderTransactionEntity $b): int
{
return $a->getCreatedAt() <=> $b->getCreatedAt();
}
}
75 changes: 75 additions & 0 deletions tests/PHPUnit/Service/Order/OrderTimeServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);

namespace MolliePayments\Tests\Service\Order;


use Kiener\MolliePayments\Service\Order\OrderTimeService;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionCollection;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\System\StateMachine\Aggregation\StateMachineState\StateMachineStateEntity;

class OrderTimeServiceTest extends TestCase
{
/**
* @param \DateTime $now
* @param \DateTime $orderDate
* @param bool $expected
*
* @dataProvider dateComparisonLogicProvider
*/
public function testDateComparisonLogic(\DateTime $now, \DateTime $orderDate, bool $expected): void
{
$order = $this->orderMockWithLastTransactionTimestamp($orderDate);

$result = (new OrderTimeService($now))->isOrderAgeGreaterThan($order, 1);

$this->assertSame($expected, $result);
}

private function orderMockWithLastTransactionTimestamp(\DateTime $time): OrderEntity
{
$entity = $this->createMock(OrderEntity::class);
$transaction = $this->createMock(OrderTransactionEntity::class);
$transactions = new OrderTransactionCollection([$transaction]);

$entity->method('getTransactions')->willReturn($transactions);

$transaction->method('getCreatedAt')->willReturn($time);

return $entity;
}

public function dateComparisonLogicProvider()
{
return [
'order is older than 1 hour' => [
new \DateTime('2021-01-01 12:00:00'),
new \DateTime('2021-01-01 10:00:00'),
true
],
'order is not older than 1 hour' => [
new \DateTime('2021-01-01 12:00:00'),
new \DateTime('2021-01-01 11:00:00'),
false
],
'order is not older than 1 hour, but 1 second' => [
new \DateTime('2021-01-01 12:00:00'),
new \DateTime('2021-01-01 11:59:59'),
false
],
'order is older than a year' => [
new \DateTime('2021-01-01 12:00:00'),
new \DateTime('2020-01-01 12:00:00'),
true
],
'order is 2 months old' => [
new \DateTime('2021-01-01 12:00:00'),
new \DateTime('2020-11-01 12:00:00'),
true
],
];
}
}

0 comments on commit 1d6865c

Please sign in to comment.