-
Notifications
You must be signed in to change notification settings - Fork 89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Checkout bug #351
Checkout bug #351
Changes from 8 commits
aa5c48d
5d84f8a
105a578
2751cd3
641bc40
67e4d37
bb1dcff
3d08ba7
f0babec
dbfb101
f2ced4e
8a57a0a
b08fa03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,28 +9,66 @@ | |
use SM\StateMachine\StateMachineInterface; | ||
use Sylius\Component\Core\Model\CustomerInterface; | ||
use Sylius\Component\Core\Model\OrderInterface; | ||
use Sylius\Component\Core\Model\ShopUserInterface; | ||
use Sylius\Component\Core\OrderCheckoutTransitions; | ||
use Sylius\Component\Core\Repository\CustomerRepositoryInterface; | ||
use Sylius\Component\Core\Repository\OrderRepositoryInterface; | ||
use Sylius\Component\Resource\Factory\FactoryInterface; | ||
use Sylius\ShopApiPlugin\Command\CompleteOrder; | ||
use Sylius\ShopApiPlugin\Provider\CustomerProviderInterface; | ||
use Sylius\ShopApiPlugin\Exception\WrongUserException; | ||
use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; | ||
|
||
final class CompleteOrderHandlerSpec extends ObjectBehavior | ||
{ | ||
function let(OrderRepositoryInterface $orderRepository, CustomerProviderInterface $customerProvider, StateMachineFactoryInterface $stateMachineFactory): void | ||
{ | ||
$this->beConstructedWith($orderRepository, $customerProvider, $stateMachineFactory); | ||
function let( | ||
OrderRepositoryInterface $orderRepository, | ||
CustomerRepositoryInterface $customerRepository, | ||
FactoryInterface $customerFactory, | ||
LoggedInUserProviderInterface $loggedInUserProvider, | ||
StateMachineFactoryInterface $stateMachineFactory | ||
): void { | ||
$this->beConstructedWith($orderRepository, $customerRepository, $customerFactory, $loggedInUserProvider, $stateMachineFactory); | ||
} | ||
|
||
function it_handles_order_completion_for_guest_checkout( | ||
CustomerInterface $customer, | ||
CustomerRepositoryInterface $customerRepository, | ||
FactoryInterface $customerFactory, | ||
OrderInterface $order, | ||
OrderRepositoryInterface $orderRepository, | ||
StateMachineFactoryInterface $stateMachineFactory, | ||
StateMachineInterface $stateMachine | ||
): void { | ||
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); | ||
|
||
$customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn(null); | ||
$customerFactory->createNew()->willReturn($customer); | ||
|
||
$stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); | ||
$stateMachine->can('complete')->willReturn(true); | ||
|
||
$order->setNotes(null)->shouldBeCalled(); | ||
$order->setCustomer($customer)->shouldBeCalled(); | ||
$stateMachine->apply('complete')->shouldBeCalled(); | ||
|
||
$this->handle(new CompleteOrder('ORDERTOKEN', 'example@customer.com')); | ||
} | ||
|
||
function it_handles_order_completion_for_existing_customer( | ||
CustomerInterface $customer, | ||
CustomerProviderInterface $customerProvider, | ||
CustomerRepositoryInterface $customerRepository, | ||
LoggedInUserProviderInterface $loggedInUserProvider, | ||
ShopUserInterface $shopUser, | ||
OrderInterface $order, | ||
OrderRepositoryInterface $orderRepository, | ||
StateMachineFactoryInterface $stateMachineFactory, | ||
StateMachineInterface $stateMachine | ||
): void { | ||
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); | ||
$customerProvider->provide('example@customer.com')->willReturn($customer); | ||
|
||
$customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); | ||
$shopUser->getCustomer()->willReturn($customer); | ||
$loggedInUserProvider->provide()->willReturn($shopUser); | ||
|
||
$stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); | ||
$stateMachine->can('complete')->willReturn(true); | ||
|
@@ -44,14 +82,19 @@ function it_handles_order_completion_for_existing_customer( | |
|
||
function it_handles_order_completion_with_notes( | ||
CustomerInterface $customer, | ||
CustomerProviderInterface $customerProvider, | ||
CustomerRepositoryInterface $customerRepository, | ||
LoggedInUserProviderInterface $loggedInUserProvider, | ||
ShopUserInterface $shopUser, | ||
OrderInterface $order, | ||
OrderRepositoryInterface $orderRepository, | ||
StateMachineFactoryInterface $stateMachineFactory, | ||
StateMachineInterface $stateMachine | ||
): void { | ||
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); | ||
$customerProvider->provide('example@customer.com')->willReturn($customer); | ||
|
||
$customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); | ||
$shopUser->getCustomer()->willReturn($customer); | ||
$loggedInUserProvider->provide()->willReturn($shopUser); | ||
|
||
$stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); | ||
$stateMachine->can('complete')->willReturn(true); | ||
|
@@ -63,11 +106,42 @@ function it_handles_order_completion_with_notes( | |
$this->handle(new CompleteOrder('ORDERTOKEN', 'example@customer.com', 'Some notes')); | ||
} | ||
|
||
function it_throws_an_exception_if_order_does_not_exist(OrderRepositoryInterface $orderRepository): void | ||
{ | ||
function it_throws_an_exception_if_order_does_not_exist( | ||
OrderRepositoryInterface $orderRepository | ||
): void { | ||
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn(null); | ||
|
||
$this->shouldThrow(\InvalidArgumentException::class)->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com')]); | ||
$this->shouldThrow(\InvalidArgumentException::class) | ||
->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com')]) | ||
; | ||
} | ||
|
||
function it_throws_an_exception_if_the_user_is_not_logged_in( | ||
CustomerInterface $customer, | ||
CustomerRepositoryInterface $customerRepository, | ||
LoggedInUserProviderInterface $loggedInUserProvider, | ||
CustomerInterface $loggedInCustomer, | ||
ShopUserInterface $shopUser, | ||
OrderInterface $order, | ||
OrderRepositoryInterface $orderRepository, | ||
StateMachineFactoryInterface $stateMachineFactory, | ||
StateMachineInterface $stateMachine | ||
): void { | ||
$orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); | ||
|
||
$customerRepository->findOneBy(['email' => 'example@customer.com'])->willReturn($customer); | ||
$shopUser->getCustomer()->willReturn($loggedInCustomer); | ||
$loggedInUserProvider->provide()->willReturn($shopUser); | ||
|
||
$stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); | ||
$stateMachine->can('complete')->willReturn(true); | ||
|
||
$order->setCustomer($customer)->shouldNotBeCalled(); | ||
$stateMachine->apply('complete')->shouldNotBeCalled(); | ||
|
||
$this->shouldThrow(WrongUserException::class) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 but I'm not sure about naming. WDYT about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A little too descriptive but sure, I can change that. I would have a catchier name in mind something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes it possible to use it also in other places. |
||
->during('handle', [new CompleteOrder('ORDERTOKEN', 'example@customer.com', 'Some notes')]) | ||
; | ||
} | ||
|
||
function it_throws_an_exception_if_order_cannot_be_addressed( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,11 +7,12 @@ | |
use FOS\RestBundle\View\View; | ||
use FOS\RestBundle\View\ViewHandlerInterface; | ||
use League\Tactician\CommandBus; | ||
use Sylius\Component\Core\Model\ShopUserInterface; | ||
use Sylius\ShopApiPlugin\Command\CompleteOrder; | ||
use Sylius\ShopApiPlugin\Exception\WrongUserException; | ||
use Sylius\ShopApiPlugin\Provider\LoggedInUserProviderInterface; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | ||
use Symfony\Component\Security\Core\Exception\TokenNotFoundException; | ||
|
||
final class CompleteOrderAction | ||
{ | ||
|
@@ -21,37 +22,53 @@ final class CompleteOrderAction | |
/** @var CommandBus */ | ||
private $bus; | ||
|
||
/** @var TokenStorageInterface */ | ||
private $tokenStorage; | ||
/** @var LoggedInUserProviderInterface */ | ||
private $loggedInUserProvider; | ||
|
||
public function __construct(ViewHandlerInterface $viewHandler, CommandBus $bus, TokenStorageInterface $tokenStorage) | ||
{ | ||
public function __construct( | ||
ViewHandlerInterface $viewHandler, | ||
CommandBus $bus, | ||
LoggedInUserProviderInterface $loggedInUserProvider | ||
) { | ||
$this->viewHandler = $viewHandler; | ||
$this->bus = $bus; | ||
$this->tokenStorage = $tokenStorage; | ||
$this->loggedInUserProvider = $loggedInUserProvider; | ||
} | ||
|
||
public function __invoke(Request $request): Response | ||
{ | ||
$email = $this->provideUserEmail($request); | ||
|
||
$this->bus->handle(new CompleteOrder( | ||
$request->attributes->get('token'), | ||
$email, | ||
$request->request->get('notes') | ||
)); | ||
try { | ||
$this->bus->handle( | ||
new CompleteOrder( | ||
$request->attributes->get('token'), | ||
$email, | ||
$request->request->get('notes') | ||
) | ||
); | ||
} catch (WrongUserException $notLoggedInException) { | ||
return $this->viewHandler->handle( | ||
View::create( | ||
'You need to be logged in with the same user that wants to complete the order', | ||
Response::HTTP_UNAUTHORIZED | ||
) | ||
); | ||
} catch (TokenNotFoundException $notLoggedInException) { | ||
return $this->viewHandler->handle( | ||
View::create('You need to be logged in', Response::HTTP_UNAUTHORIZED) | ||
); | ||
} | ||
|
||
return $this->viewHandler->handle(View::create(null, Response::HTTP_NO_CONTENT)); | ||
} | ||
|
||
private function provideUserEmail(Request $request): string | ||
{ | ||
$user = $this->tokenStorage->getToken()->getUser(); | ||
|
||
if ($user instanceof ShopUserInterface) { | ||
return $user->getCustomer()->getEmail(); | ||
try { | ||
return $this->loggedInUserProvider->provide()->getEmail(); | ||
} catch (TokenNotFoundException $tokenNotFoundException) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to base our login on exception? I would prefer to use if statement and add a new method in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well that is how the user provider is implemented. I am sure we could also add another method to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be awesome. I don't like base business logic on exceptions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will add that to the interface and then to the |
||
return $request->request->get('email'); | ||
} | ||
|
||
return $request->request->get('email'); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sylius\ShopApiPlugin\Exception; | ||
|
||
use Exception; | ||
|
||
class WrongUserException extends Exception | ||
{ | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to fetch customer from the database? Won't checking just the customer's mail be enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need the customer object later on anyways to add it to the order.