Skip to content

Commit c61e40d

Browse files
[ExpressCheckout] Fix handling checkout with skipped shipping or payment step (#166)
2 parents 25b50b4 + 515332a commit c61e40d

File tree

2 files changed

+283
-2
lines changed

2 files changed

+283
-2
lines changed

src/Resolver/ExpressCheckout/CheckoutResolver.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,20 @@ public function resolve(OrderInterface $order): void
3737
{
3838
$this->stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_ADDRESS);
3939

40-
if ($order->isShippingRequired()) {
40+
if (
41+
$order->isShippingRequired() &&
42+
$this->stateMachine->can($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING)
43+
) {
4144
$this->stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING);
4245
}
4346

4447
$paymentMethod = $this->adyenPaymentMethodQuery->findOneAdyenByChannel($order->getChannel());
4548
Assert::isInstanceOf($paymentMethod, PaymentMethodInterface::class);
4649
$order->getLastPayment(PaymentInterface::STATE_CART)->setMethod($paymentMethod);
47-
$this->stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
50+
51+
if ($this->stateMachine->can($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT)) {
52+
$this->stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT);
53+
}
4854

4955
$this->orderCheckoutCompleteIntegrityChecker->check($order);
5056

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius Adyen Plugin package.
5+
*
6+
* (c) Sylius Sp. z o.o.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Tests\Sylius\AdyenPlugin\Unit\Resolver\ExpressCheckout;
15+
16+
use Doctrine\Persistence\ObjectManager;
17+
use PHPUnit\Framework\MockObject\MockObject;
18+
use PHPUnit\Framework\TestCase;
19+
use Sylius\Abstraction\StateMachine\StateMachineInterface;
20+
use Sylius\AdyenPlugin\Checker\OrderCheckoutCompleteIntegrityCheckerInterface;
21+
use Sylius\AdyenPlugin\Repository\Query\AdyenPaymentMethodQueryInterface;
22+
use Sylius\AdyenPlugin\Resolver\ExpressCheckout\CheckoutResolver;
23+
use Sylius\Component\Core\Model\ChannelInterface;
24+
use Sylius\Component\Core\Model\OrderInterface;
25+
use Sylius\Component\Core\Model\PaymentInterface;
26+
use Sylius\Component\Core\Model\PaymentMethodInterface;
27+
use Sylius\Component\Core\OrderCheckoutTransitions;
28+
29+
final class CheckoutResolverTest extends TestCase
30+
{
31+
private MockObject&ObjectManager $orderManager;
32+
33+
private MockObject&StateMachineInterface $stateMachine;
34+
35+
private AdyenPaymentMethodQueryInterface&MockObject $adyenPaymentMethodQuery;
36+
37+
private MockObject&OrderCheckoutCompleteIntegrityCheckerInterface $integrityChecker;
38+
39+
protected function setUp(): void
40+
{
41+
$this->orderManager = $this->createMock(ObjectManager::class);
42+
$this->stateMachine = $this->createMock(StateMachineInterface::class);
43+
$this->adyenPaymentMethodQuery = $this->createMock(AdyenPaymentMethodQueryInterface::class);
44+
$this->integrityChecker = $this->createMock(OrderCheckoutCompleteIntegrityCheckerInterface::class);
45+
}
46+
47+
public function testItResolvesCheckoutWithShippingRequired(): void
48+
{
49+
$resolver = new CheckoutResolver(
50+
$this->orderManager,
51+
$this->stateMachine,
52+
$this->adyenPaymentMethodQuery,
53+
$this->integrityChecker,
54+
);
55+
56+
$order = $this->createMock(OrderInterface::class);
57+
$channel = $this->createMock(ChannelInterface::class);
58+
$paymentMethod = $this->createMock(PaymentMethodInterface::class);
59+
$payment = $this->createMock(PaymentInterface::class);
60+
61+
$order->method('getChannel')->willReturn($channel);
62+
$order->method('isShippingRequired')->willReturn(true);
63+
$order->method('getLastPayment')->with(PaymentInterface::STATE_CART)->willReturn($payment);
64+
65+
$this->adyenPaymentMethodQuery
66+
->expects(self::once())
67+
->method('findOneAdyenByChannel')
68+
->with($channel)
69+
->willReturn($paymentMethod)
70+
;
71+
72+
$payment->expects(self::once())->method('setMethod')->with($paymentMethod);
73+
74+
$this->stateMachine
75+
->expects(self::exactly(2))
76+
->method('can')
77+
->willReturnCallback(function (OrderInterface $order, string $graph, string $transition) {
78+
return
79+
$transition === OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING ||
80+
$transition === OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT
81+
;
82+
});
83+
84+
$this->stateMachine
85+
->expects(self::exactly(3))
86+
->method('apply')
87+
->willReturnCallback(function (OrderInterface $order, string $graph, string $transition) {
88+
static $callCount = 0;
89+
++$callCount;
90+
91+
if ($callCount === 1) {
92+
self::assertSame(OrderCheckoutTransitions::TRANSITION_ADDRESS, $transition);
93+
} elseif ($callCount === 2) {
94+
self::assertSame(OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING, $transition);
95+
} elseif ($callCount === 3) {
96+
self::assertSame(OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT, $transition);
97+
}
98+
});
99+
100+
$this->integrityChecker->expects(self::once())->method('check')->with($order);
101+
$this->orderManager->expects(self::once())->method('flush');
102+
103+
$resolver->resolve($order);
104+
}
105+
106+
public function testItSkipsSelectShippingWhenNotRequired(): void
107+
{
108+
$resolver = new CheckoutResolver(
109+
$this->orderManager,
110+
$this->stateMachine,
111+
$this->adyenPaymentMethodQuery,
112+
$this->integrityChecker,
113+
);
114+
115+
$order = $this->createMock(OrderInterface::class);
116+
$channel = $this->createMock(ChannelInterface::class);
117+
$paymentMethod = $this->createMock(PaymentMethodInterface::class);
118+
$payment = $this->createMock(PaymentInterface::class);
119+
120+
$order->method('getChannel')->willReturn($channel);
121+
$order->method('isShippingRequired')->willReturn(false);
122+
$order->method('getLastPayment')->with(PaymentInterface::STATE_CART)->willReturn($payment);
123+
124+
$this->adyenPaymentMethodQuery
125+
->expects(self::once())
126+
->method('findOneAdyenByChannel')
127+
->with($channel)
128+
->willReturn($paymentMethod)
129+
;
130+
131+
$payment->expects(self::once())->method('setMethod')->with($paymentMethod);
132+
133+
$this->stateMachine
134+
->expects(self::once())
135+
->method('can')
136+
->with($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT)
137+
->willReturn(true)
138+
;
139+
140+
$this->stateMachine
141+
->expects(self::exactly(2))
142+
->method('apply')
143+
->willReturnCallback(function (OrderInterface $order, string $graph, string $transition) {
144+
static $callCount = 0;
145+
++$callCount;
146+
147+
if ($callCount === 1) {
148+
self::assertSame(OrderCheckoutTransitions::TRANSITION_ADDRESS, $transition);
149+
} elseif ($callCount === 2) {
150+
self::assertSame(OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT, $transition);
151+
}
152+
});
153+
154+
$this->integrityChecker->expects(self::once())->method('check')->with($order);
155+
$this->orderManager->expects(self::once())->method('flush');
156+
157+
$resolver->resolve($order);
158+
}
159+
160+
public function testItSkipsSelectShippingWhenTransitionNotAvailable(): void
161+
{
162+
$resolver = new CheckoutResolver(
163+
$this->orderManager,
164+
$this->stateMachine,
165+
$this->adyenPaymentMethodQuery,
166+
$this->integrityChecker,
167+
);
168+
169+
$order = $this->createMock(OrderInterface::class);
170+
$channel = $this->createMock(ChannelInterface::class);
171+
$paymentMethod = $this->createMock(PaymentMethodInterface::class);
172+
$payment = $this->createMock(PaymentInterface::class);
173+
174+
$order->method('getChannel')->willReturn($channel);
175+
$order->method('isShippingRequired')->willReturn(true);
176+
$order->method('getLastPayment')->with(PaymentInterface::STATE_CART)->willReturn($payment);
177+
178+
$this->adyenPaymentMethodQuery
179+
->expects(self::once())
180+
->method('findOneAdyenByChannel')
181+
->with($channel)
182+
->willReturn($paymentMethod)
183+
;
184+
185+
$payment->expects(self::once())->method('setMethod')->with($paymentMethod);
186+
187+
$this->stateMachine
188+
->expects(self::exactly(2))
189+
->method('can')
190+
->willReturnCallback(function (OrderInterface $order, string $graph, string $transition) {
191+
if ($transition === OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING) {
192+
return false;
193+
}
194+
195+
return $transition === OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT;
196+
});
197+
198+
$this->stateMachine
199+
->expects(self::exactly(2))
200+
->method('apply')
201+
->willReturnCallback(function (OrderInterface $order, string $graph, string $transition) {
202+
static $callCount = 0;
203+
++$callCount;
204+
205+
if ($callCount === 1) {
206+
self::assertSame(OrderCheckoutTransitions::TRANSITION_ADDRESS, $transition);
207+
} elseif ($callCount === 2) {
208+
self::assertSame(OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT, $transition);
209+
}
210+
});
211+
212+
$this->integrityChecker->expects(self::once())->method('check')->with($order);
213+
$this->orderManager->expects(self::once())->method('flush');
214+
215+
$resolver->resolve($order);
216+
}
217+
218+
public function testItSkipsSelectPaymentWhenTransitionNotAvailable(): void
219+
{
220+
$resolver = new CheckoutResolver(
221+
$this->orderManager,
222+
$this->stateMachine,
223+
$this->adyenPaymentMethodQuery,
224+
$this->integrityChecker,
225+
);
226+
227+
$order = $this->createMock(OrderInterface::class);
228+
$channel = $this->createMock(ChannelInterface::class);
229+
$paymentMethod = $this->createMock(PaymentMethodInterface::class);
230+
$payment = $this->createMock(PaymentInterface::class);
231+
232+
$order->method('getChannel')->willReturn($channel);
233+
$order->method('isShippingRequired')->willReturn(true);
234+
$order->method('getLastPayment')->with(PaymentInterface::STATE_CART)->willReturn($payment);
235+
236+
$this->adyenPaymentMethodQuery
237+
->expects(self::once())
238+
->method('findOneAdyenByChannel')
239+
->with($channel)
240+
->willReturn($paymentMethod)
241+
;
242+
243+
$payment->expects(self::once())->method('setMethod')->with($paymentMethod);
244+
245+
$this->stateMachine
246+
->expects(self::exactly(2))
247+
->method('can')
248+
->willReturnCallback(function (OrderInterface $order, string $graph, string $transition) {
249+
if ($transition === OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT) {
250+
return false;
251+
}
252+
253+
return $transition === OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING;
254+
});
255+
256+
$this->stateMachine
257+
->expects(self::exactly(2))
258+
->method('apply')
259+
->willReturnCallback(function (OrderInterface $order, string $graph, string $transition) {
260+
static $callCount = 0;
261+
++$callCount;
262+
263+
if ($callCount === 1) {
264+
self::assertSame(OrderCheckoutTransitions::TRANSITION_ADDRESS, $transition);
265+
} elseif ($callCount === 2) {
266+
self::assertSame(OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING, $transition);
267+
}
268+
});
269+
270+
$this->integrityChecker->expects(self::once())->method('check')->with($order);
271+
$this->orderManager->expects(self::once())->method('flush');
272+
273+
$resolver->resolve($order);
274+
}
275+
}

0 commit comments

Comments
 (0)