Skip to content

Commit

Permalink
RATESWSX-271: update payment status on order-item operation
Browse files Browse the repository at this point in the history
fix issue: paid_partialy->paid is not possible with paid-transition
  • Loading branch information
rommelfreddy committed Mar 22, 2024
1 parent e303ad0 commit c97ba76
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 37 deletions.
3 changes: 2 additions & 1 deletion rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

return static function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__ . '/src/'
__DIR__ . '/src/',
__DIR__ . '/tests',
]);

$rectorConfig->bootstrapFiles([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@

use Ratepay\RpayPayments\Core\Event\OrderItemOperationDoneEvent;
use Ratepay\RpayPayments\Core\PluginConfigService;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionDefinition;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates;
use Shopware\Core\System\StateMachine\StateMachineRegistry;
use Shopware\Core\System\StateMachine\Transition;

class PaymentStatusSubscriber extends AbstractOrderStatusSubscriber
{
public function __construct(
private readonly StateMachineRegistry $stateMachine,
private readonly OrderTransactionStateHandler $transactionStateHandler,
private readonly PluginConfigService $configService
) {
Expand Down Expand Up @@ -50,10 +55,32 @@ protected function onFullRefund(OrderItemOperationDoneEvent $event): void

protected function onFullDelivery(OrderItemOperationDoneEvent $event): void
{
$this->transactionStateHandler->paid(
// note: if the transaction has the state "partly_paid" it is not possible to switch to "paid" with the helper-function of the transactionStateHandler.
// the action-name for partly_paid->paid is `pay` - not `paid`
// because the transactionStateHandler does not have this transition, we need to do it manually.
// to keep it "simple" we search for the possible transition. So we can make sure, that if shopware solve this "issue", the code keeps compatible.

$transitions = $this->stateMachine->getAvailableTransitions(
OrderTransactionDefinition::ENTITY_NAME,
$event->getOrderOperationData()->getTransaction()->getId(),
'stateId',
$event->getContext()
);

foreach ($transitions as $transition) {
if ($transition->getToStateMachineState()->getTechnicalName() === OrderTransactionStates::STATE_PAID) {
$this->stateMachine->transition(
new Transition(
OrderTransactionDefinition::ENTITY_NAME,
$event->getOrderOperationData()->getTransaction()->getId(),
$transition->getActionName(),
'stateId'
),
$event->getContext()
);
break;
}
}
}

protected function onPartlyDelivery(OrderItemOperationDoneEvent $event): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,36 @@
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionCollection;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates;
use Shopware\Core\Checkout\Order\OrderEntity;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\System\StateMachine\Aggregation\StateMachineState\StateMachineStateCollection;
use Shopware\Core\System\StateMachine\Aggregation\StateMachineState\StateMachineStateEntity;
use Shopware\Core\System\StateMachine\Aggregation\StateMachineTransition\StateMachineTransitionEntity;
use Shopware\Core\System\StateMachine\StateMachineRegistry;
use Shopware\Core\System\StateMachine\Transition;

class PaymentStatusSubscriberTest extends TestCase
{
private readonly PluginConfigService $configService;
/**
* @var StateMachineRegistry&MockObject
*/
private StateMachineRegistry $stateMachine; // @phpstan-ignore-line

/**
* @var PluginConfigService&MockObject
*/
private PluginConfigService $configService; // @phpstan-ignore-line

protected function setUp(): void
{
$this->stateMachine = $this->createMock(StateMachineRegistry::class);
$this->configService = $this->createMock(PluginConfigService::class);
$this->configService->method('isUpdatePaymentStatusOnOrderItemOperation')->willReturn(true);
}

public function testIfConfigCanDisableFunktion()
public function testIfConfigCanDisableFunktion(): void
{
$configService = $this->createMock(PluginConfigService::class);
$configService->method('isUpdatePaymentStatusOnOrderItemOperation')->willReturn(false);
Expand All @@ -52,10 +67,10 @@ public function testIfConfigCanDisableFunktion()
]);

$transactionStateHandler = $this->createTransactionHandlerMock(null);
(new PaymentStatusSubscriber($transactionStateHandler, $configService))->onItemsOperationDone($event);
(new PaymentStatusSubscriber($this->stateMachine, $transactionStateHandler, $configService))->onItemsOperationDone($event);
}

public function testSimpleFullCancel()
public function testSimpleFullCancel(): void
{
$event = $this->createOrderOperationDoneEvent([
$this->getLineItem(5, 0, 5, 0),
Expand All @@ -64,19 +79,19 @@ public function testSimpleFullCancel()
]);

$transactionStateHandler = $this->createTransactionHandlerMock('cancel');
(new PaymentStatusSubscriber($transactionStateHandler, $this->configService))->onItemsOperationDone($event);
(new PaymentStatusSubscriber($this->stateMachine, $transactionStateHandler, $this->configService))->onItemsOperationDone($event);
}

/**
* @dataProvider dataProviderPartlyPaid
*/
public function testPartlyPayed(array ...$items)
public function testPartlyPayed(array ...$items): void
{
$that = $this;
$event = $this->createOrderOperationDoneEvent(array_map(static fn (array $item) => call_user_func_array([$that, 'getLineItem'], $item), $items));
$event = $this->createOrderOperationDoneEvent(array_map(static fn (array $item): mixed => $that->getLineItem(...$item), $items));

$transactionStateHandler = $this->createTransactionHandlerMock('payPartially');
(new PaymentStatusSubscriber($transactionStateHandler, $this->configService))->onItemsOperationDone($event);
(new PaymentStatusSubscriber($this->stateMachine, $transactionStateHandler, $this->configService))->onItemsOperationDone($event);
}

public static function dataProviderPartlyPaid(): array
Expand Down Expand Up @@ -118,13 +133,12 @@ public static function dataProviderPartlyPaid(): array
/**
* @dataProvider dataProviderFullPaid
*/
public function testFullPaid(array ...$items)
public function testFullPaid(array ...$items): void
{
$that = $this;
$event = $this->createOrderOperationDoneEvent(array_map(static fn (array $item) => call_user_func_array([$that, 'getLineItem'], $item), $items));
$event = $this->createOrderOperationDoneEvent(array_map(static fn (array $item): mixed => $that->getLineItem(...$item), $items));

$transactionStateHandler = $this->createTransactionHandlerMock('paid');
(new PaymentStatusSubscriber($transactionStateHandler, $this->configService))->onItemsOperationDone($event);
$this->getPaymentStatusSubscriberForDelivery()->onItemsOperationDone($event);
}

public static function dataProviderFullPaid(): array
Expand All @@ -151,13 +165,13 @@ public static function dataProviderFullPaid(): array
/**
* @dataProvider dataFullRefunded
*/
public function testFullRefunded(array ...$items)
public function testFullRefunded(array ...$items): void
{
$that = $this;
$event = $this->createOrderOperationDoneEvent(array_map(static fn (array $item) => call_user_func_array([$that, 'getLineItem'], $item), $items));
$event = $this->createOrderOperationDoneEvent(array_map(static fn (array $item): mixed => $that->getLineItem(...$item), $items));

$transactionStateHandler = $this->createTransactionHandlerMock('refund');
(new PaymentStatusSubscriber($transactionStateHandler, $this->configService))->onItemsOperationDone($event);
(new PaymentStatusSubscriber($this->stateMachine, $transactionStateHandler, $this->configService))->onItemsOperationDone($event);
}

public static function dataFullRefunded(): array
Expand All @@ -179,13 +193,13 @@ public static function dataFullRefunded(): array
/**
* @dataProvider dataProviderPartlyRefunded
*/
public function testPartlyRefunded(array ...$items)
public function testPartlyRefunded(array ...$items): void
{
$that = $this;
$event = $this->createOrderOperationDoneEvent(array_map(static fn (array $item) => call_user_func_array([$that, 'getLineItem'], $item), $items));
$event = $this->createOrderOperationDoneEvent(array_map(static fn (array $item): mixed => $that->getLineItem(...$item), $items));

$transactionStateHandler = $this->createTransactionHandlerMock('refundPartially');
(new PaymentStatusSubscriber($transactionStateHandler, $this->configService))->onItemsOperationDone($event);
(new PaymentStatusSubscriber($this->stateMachine, $transactionStateHandler, $this->configService))->onItemsOperationDone($event);
}

public static function dataProviderPartlyRefunded(): array
Expand All @@ -212,7 +226,7 @@ public static function dataProviderPartlyRefunded(): array
/**
* @dataProvider dataProviderShipping
*/
public function testIfShippingGotHandledCorrectly(int $delivered, int $canceled, int $refunded, string $expectedMethod = null)
public function testIfShippingGotHandledCorrectly(int $delivered, int $canceled, int $refunded, string $expectedMethod = null): void
{
$event = $this->createOrderOperationDoneEvent([
$this->getLineItem(5, 0, 5, 0),
Expand All @@ -225,8 +239,12 @@ public function testIfShippingGotHandledCorrectly(int $delivered, int $canceled,
RatepayOrderDataEntity::FIELD_SHIPPING_POSITION => $this->createPosition($delivered, $canceled, $refunded),
]);

$transactionStateHandler = $this->createTransactionHandlerMock($expectedMethod);
(new PaymentStatusSubscriber($transactionStateHandler, $this->configService))->onItemsOperationDone($event);
if ($expectedMethod === 'paid') {
$this->getPaymentStatusSubscriberForDelivery()->onItemsOperationDone($event);
} else {
$transactionStateHandler = $this->createTransactionHandlerMock($expectedMethod);
(new PaymentStatusSubscriber($this->stateMachine, $transactionStateHandler, $this->configService))->onItemsOperationDone($event);
}
}

public static function dataProviderShipping(): array
Expand All @@ -239,10 +257,7 @@ public static function dataProviderShipping(): array
];
}

/**
* @depends testFullRefunded
*/
public function testIfNoRatepayItemsGotProcessedCorrectly()
public function testIfNoRatepayItemsGotProcessedCorrectly(): void
{
$event = $this->createOrderOperationDoneEvent([
$this->getLineItem(5, 5, 0, 5),
Expand All @@ -253,13 +268,13 @@ public function testIfNoRatepayItemsGotProcessedCorrectly()
// test if refunded got still called
$event->getOrderEntity()->getLineItems()->last()->setExtensions([]); // remove position from last item.
$transactionStateHandler = $this->createTransactionHandlerMock('refund');
(new PaymentStatusSubscriber($transactionStateHandler, $this->configService))->onItemsOperationDone($event);
(new PaymentStatusSubscriber($this->stateMachine, $transactionStateHandler, $this->configService))->onItemsOperationDone($event);

// test if subscriber does not change the state of the payment.
// remove all ratepay data from order-line-items
$event->getOrderEntity()->getLineItems()->map(static fn (OrderLineItemEntity $item) => $item->setExtensions([]));
$transactionStateHandler = $this->createTransactionHandlerMock(null);
(new PaymentStatusSubscriber($transactionStateHandler, $this->configService))->onItemsOperationDone($event);
(new PaymentStatusSubscriber($this->stateMachine, $transactionStateHandler, $this->configService))->onItemsOperationDone($event);
}

private function getLineItem(int $qty, int $delivered, int $canceled, int $refunded): OrderLineItemEntity
Expand Down Expand Up @@ -301,27 +316,67 @@ private function createOrderOperationDoneEvent(array $lineItems): OrderItemOpera
]);

$orderOperationData = new OrderOperationData(Context::createDefaultContext(), $orderEntity, '--', [], false);

return new OrderItemOperationDoneEvent($orderEntity, $orderOperationData, $orderOperationData->getContext());
}

/**
* @return OrderTransactionStateHandler&MockObject
*/
private function createTransactionHandlerMock(string $expectedMethod = null)
private function createTransactionHandlerMock(string $expectedMethod = null): OrderTransactionStateHandler
{
$transactionStateHandler = $this->createMock(OrderTransactionStateHandler::class);

$expectedMethod !== 'cancel' && $transactionStateHandler->expects($this->never())->method('cancel');
if ($expectedMethod !== 'cancel') {
$transactionStateHandler->expects($this->never())->method('cancel');
}

if ($expectedMethod !== 'paid') {
$transactionStateHandler->expects($this->never())->method('paid');
}

if ($expectedMethod !== 'payPartially') {
$transactionStateHandler->expects($this->never())->method('payPartially');
}

if ($expectedMethod !== 'refundPartially') {
$transactionStateHandler->expects($this->never())->method('refundPartially');
}

$expectedMethod !== 'paid' && $transactionStateHandler->expects($this->never())->method('paid');
$expectedMethod !== 'payPartially' && $transactionStateHandler->expects($this->never())->method('payPartially');
if ($expectedMethod !== 'refund') {
$transactionStateHandler->expects($this->never())->method('refund');
}

$expectedMethod !== 'refundPartially' && $transactionStateHandler->expects($this->never())->method('refundPartially');
$expectedMethod !== 'refund' && $transactionStateHandler->expects($this->never())->method('refund');
if ($expectedMethod !== null) {
$transactionStateHandler->expects($this->once())->method($expectedMethod);
}

$expectedMethod !== null && $transactionStateHandler->expects($this->once())->method($expectedMethod);
$expectedMethod === null && $transactionStateHandler->expects($this->never())->method($this->anything());
if ($expectedMethod === null) {
$transactionStateHandler->expects($this->never())->method($this->anything());
}

return $transactionStateHandler;
}

private function getPaymentStatusSubscriberForDelivery(): PaymentStatusSubscriber
{
$this->stateMachine->method('getAvailableTransitions')->willReturn([
(new StateMachineTransitionEntity())->assign([
'actionName' => 'pay',
'toStateMachineState' => (new StateMachineStateEntity())->assign([
'technicalName' => OrderTransactionStates::STATE_PAID,
]),
]),
]);

$this->stateMachine->method('transition')->willReturnCallback(static function (Transition $transition, Context $context): StateMachineStateCollection {
self::assertEquals('pay', $transition->getTransitionName());

return new StateMachineStateCollection();
});

$transactionStateHandler = $this->createMock(OrderTransactionStateHandler::class);

return new PaymentStatusSubscriber($this->stateMachine, $transactionStateHandler, $this->configService);
}
}

0 comments on commit c97ba76

Please sign in to comment.