mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +00:00
Merge branch '1.13' into 2.0
* 1.13: [Inventory] Fix deprecation note of sylius_inventory.checker configuration [Inventory] Add deprecation for sylius_inventory.checker config Add note to the UPGRADE file about changing the service argument Using the service name for the availibilty checker [API][Test] Minor updating of functions name [Payum] Move validation groups configuration to PayumBundle Move config to Core, SyliusPaymentBundle do not requires SyliusPayumBundle [Api][Admin] Add ChannelNotFoundException to OrderAddressRequirementValidator [Api][Admin] Change validation message of address requirement [Api][Admin] Fix get channels test [Api][Checkout] Add validation for required address [Maintenance][API] Secure and cleanup openapi documentation modifiers
This commit is contained in:
commit
5d93c43eca
26 changed files with 515 additions and 117 deletions
|
|
@ -817,7 +817,7 @@ final class CheckoutContext implements Context
|
|||
$this->iConfirmMyOrder();
|
||||
|
||||
$response = $this->client->getLastResponse();
|
||||
Assert::same($this->responseChecker->getError($response), 'An empty order cannot be completed.');
|
||||
Assert::same($this->responseChecker->getError($response), 'An empty order cannot be processed.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -22,71 +22,75 @@ final class AddressLogEntryDocumentationModifier implements DocumentationModifie
|
|||
$components = $docs->getComponents();
|
||||
$schemas = $components->getSchemas();
|
||||
|
||||
$schemas['Address-admin.address.log_entry.read'] = [
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'action' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
],
|
||||
'version' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'integer',
|
||||
],
|
||||
'data' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'object',
|
||||
],
|
||||
'logged_at' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$schemas['Address.jsonld-admin.address.log_entry.read'] = [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'hydra:member' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'@type' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
],
|
||||
'action' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
],
|
||||
'version' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'integer',
|
||||
],
|
||||
'data' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'object',
|
||||
],
|
||||
'logged_at' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
],
|
||||
if (isset($schemas['Address-admin.address.log_entry.read'])) {
|
||||
$schemas['Address-admin.address.log_entry.read'] = [
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'action' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
],
|
||||
'version' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'integer',
|
||||
],
|
||||
'data' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'object',
|
||||
],
|
||||
'logged_at' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
],
|
||||
],
|
||||
],
|
||||
'hydra:totalItems' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
if (isset($schemas['Address.jsonld-admin.address.log_entry.read'])) {
|
||||
$schemas['Address.jsonld-admin.address.log_entry.read'] = [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'hydra:member' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'array',
|
||||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'@type' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
],
|
||||
'action' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
],
|
||||
'version' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'integer',
|
||||
],
|
||||
'data' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'object',
|
||||
],
|
||||
'logged_at' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'hydra:totalItems' => [
|
||||
'readOnly' => true,
|
||||
'type' => 'integer',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
];
|
||||
}
|
||||
|
||||
$components = $components->withSchemas($schemas);
|
||||
|
||||
|
|
|
|||
|
|
@ -34,9 +34,7 @@ final class AttributeTypeDocumentationModifier implements DocumentationModifierI
|
|||
|
||||
$schemas = $this->updateAttributeTypesSchema($schemas);
|
||||
|
||||
return $docs->withComponents(
|
||||
$components->withSchemas($schemas),
|
||||
);
|
||||
return $docs->withComponents($components->withSchemas($schemas));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -56,6 +54,10 @@ final class AttributeTypeDocumentationModifier implements DocumentationModifierI
|
|||
];
|
||||
|
||||
foreach ($schemasToBeUpdated as $schemaToBeUpdated) {
|
||||
if (!isset($schemas[$schemaToBeUpdated])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$schemas[$schemaToBeUpdated]['properties']['type'] = [
|
||||
'type' => 'string',
|
||||
'enum' => $attributeTypes,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ use ApiPlatform\OpenApi\OpenApi;
|
|||
|
||||
final class OrderAdjustmentsTypeDocumentationModifier implements DocumentationModifierInterface
|
||||
{
|
||||
public const PATH = '%s/admin/orders/{tokenValue}/adjustments';
|
||||
|
||||
public function __construct(private string $apiRoute, private string $adjustmentResourceClass)
|
||||
{
|
||||
|
|
@ -26,11 +25,14 @@ final class OrderAdjustmentsTypeDocumentationModifier implements DocumentationMo
|
|||
|
||||
public function modify(OpenApi $docs): OpenApi
|
||||
{
|
||||
$paths = $docs->getPaths();
|
||||
$path = sprintf('%s/admin/orders/{tokenValue}/adjustments', $this->apiRoute);
|
||||
|
||||
$path = sprintf(self::PATH, $this->apiRoute);
|
||||
$paths = $docs->getPaths();
|
||||
$pathItem = $paths->getPath($path);
|
||||
$operation = $pathItem->getGet();
|
||||
$operation = $pathItem?->getGet();
|
||||
if (null === $operation) {
|
||||
return $docs;
|
||||
}
|
||||
|
||||
$parameters = $operation->getParameters();
|
||||
$parameters[] = new Parameter(
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ use Sylius\Component\Review\Model\ReviewInterface;
|
|||
|
||||
final class ProductReviewDocumentationModifier implements DocumentationModifierInterface
|
||||
{
|
||||
public const PATH = '%s/admin/product-reviews';
|
||||
|
||||
public function __construct(
|
||||
private string $apiRoute,
|
||||
) {
|
||||
|
|
@ -28,11 +26,14 @@ final class ProductReviewDocumentationModifier implements DocumentationModifierI
|
|||
|
||||
public function modify(OpenApi $docs): OpenApi
|
||||
{
|
||||
$paths = $docs->getPaths();
|
||||
$path = sprintf('%s/admin/product-reviews', $this->apiRoute);
|
||||
|
||||
$path = sprintf(self::PATH, $this->apiRoute);
|
||||
$paths = $docs->getPaths();
|
||||
$pathItem = $paths->getPath($path);
|
||||
$operation = $pathItem->getGet();
|
||||
$operation = $pathItem?->getGet();
|
||||
if (null === $operation) {
|
||||
return $docs;
|
||||
}
|
||||
|
||||
$parameters = $operation->getParameters();
|
||||
$parameters = array_filter(
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ final class ProductSlugDocumentationModifier implements DocumentationModifierInt
|
|||
$paths = $docs->getPaths();
|
||||
$pathItem = $paths->getPath($path);
|
||||
$operation = $pathItem?->getGet();
|
||||
|
||||
if (null === $operation) {
|
||||
return $docs;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,10 @@ final class PromotionDocumentationModifier implements DocumentationModifierInter
|
|||
{
|
||||
$pathItem = $paths->getPath($path);
|
||||
$methodGet = sprintf('get%s', $method);
|
||||
$operation = $pathItem->$methodGet();
|
||||
$operation = $pathItem?->$methodGet();
|
||||
if (null === $operation) {
|
||||
return;
|
||||
}
|
||||
|
||||
$description = sprintf(
|
||||
"%s\n\n Allowed rule types: `%s` \n\n Allowed action types: `%s`",
|
||||
|
|
|
|||
|
|
@ -38,36 +38,38 @@ final class ShippingMethodDocumentationModifier implements DocumentationModifier
|
|||
$components = $docs->getComponents();
|
||||
$schemas = $components->getSchemas();
|
||||
|
||||
if (!isset($schemas['ShippingMethod.jsonld-shop.shipping_method.read'])) {
|
||||
return $docs;
|
||||
if (isset($schemas['ShippingMethod.jsonld-shop.shipping_method.read'])) {
|
||||
$schemas['ShippingMethod.jsonld-shop.shipping_method.read']['properties']['price'] = [
|
||||
'type' => 'integer',
|
||||
'readOnly' => true,
|
||||
'default' => 0,
|
||||
];
|
||||
|
||||
$components = $components->withSchemas($schemas);
|
||||
$docs = $docs->withComponents($components);
|
||||
}
|
||||
|
||||
$schemas['ShippingMethod.jsonld-shop.shipping_method.read']['properties']['price'] = [
|
||||
'type' => 'integer',
|
||||
'readOnly' => true,
|
||||
'default' => 0,
|
||||
];
|
||||
|
||||
$this->modifyDescription($docs);
|
||||
|
||||
return $docs->withComponents(
|
||||
$components->withSchemas($schemas),
|
||||
)->withPaths($docs->getPaths());
|
||||
return $this->modifyDescription($docs);
|
||||
}
|
||||
|
||||
private function modifyDescription(OpenApi $docs): void
|
||||
private function modifyDescription(OpenApi $docs): OpenApi
|
||||
{
|
||||
$paths = $docs->getPaths();
|
||||
|
||||
$this->addDescription($paths, sprintf('%s%s', $this->apiRoute, self::ROUTE_ADMIN_SHIPPING_METHODS), 'Post');
|
||||
$this->addDescription($paths, sprintf('%s%s', $this->apiRoute, self::ROUTE_ADMIN_SHIPPING_METHOD), 'Put');
|
||||
|
||||
return $docs->withPaths($paths);
|
||||
}
|
||||
|
||||
private function addDescription(Paths $paths, string $path, string $method): void
|
||||
{
|
||||
$pathItem = $paths->getPath($path);
|
||||
$methodGet = sprintf('get%s', $method);
|
||||
$operation = $pathItem->$methodGet();
|
||||
$operation = $pathItem?->$methodGet();
|
||||
if (null === $operation) {
|
||||
return;
|
||||
}
|
||||
|
||||
$description = sprintf(
|
||||
"%s\n\n Allowed rule types: `%s` \n\n Allowed calculators: `%s`",
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ use Symfony\Component\HttpFoundation\Response;
|
|||
|
||||
final class StatisticsDocumentationModifier implements DocumentationModifierInterface
|
||||
{
|
||||
private const PATH = '/admin/statistics';
|
||||
|
||||
/** @param array<string, array<string, string>> $intervalsMap */
|
||||
public function __construct(
|
||||
private string $apiRoute,
|
||||
|
|
@ -82,7 +80,7 @@ final class StatisticsDocumentationModifier implements DocumentationModifierInte
|
|||
],
|
||||
];
|
||||
|
||||
$path = $this->apiRoute . self::PATH;
|
||||
$path = sprintf('%s/admin/statistics', $this->apiRoute);
|
||||
$paths = $docs->getPaths();
|
||||
$paths->addPath($path, $this->getPathItem());
|
||||
|
||||
|
|
|
|||
|
|
@ -188,5 +188,10 @@
|
|||
<argument type="service" id="Sylius\Bundle\ApiBundle\Validator\CatalogPromotion\ForVariantsScopeValidator.inner" />
|
||||
<argument type="service" id="Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.validator.order_address_requirement" class="Sylius\Bundle\ApiBundle\Validator\Constraints\OrderAddressRequirementValidator">
|
||||
<argument type="service" id="sylius.repository.order" />
|
||||
<tag name="validator.constraint_validator" alias="sylius_order_address_requirement_validator" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -13,20 +13,17 @@
|
|||
|
||||
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
|
||||
<class name="Sylius\Bundle\ApiBundle\Command\Checkout\UpdateCart">
|
||||
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\OrderAddressRequirement">
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\CorrectOrderAddress">
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\PromotionCouponEligibility">
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\OrderNotEmpty">
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
<option name="groups">sylius</option>
|
||||
</constraint>
|
||||
<property name="billingAddress">
|
||||
<constraint name="Valid" />
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ sylius:
|
|||
date_time:
|
||||
invalid: 'The date time is not valid ISO 8601 date time in Y-m-d\TH:i:s format.'
|
||||
order:
|
||||
not_empty: 'An empty order cannot be completed.'
|
||||
not_empty: 'An empty order cannot be processed.'
|
||||
payment_method:
|
||||
cannot_change_payment_method_for_cancelled_order: 'You cannot change the payment method for a cancelled order.'
|
||||
not_available: 'The payment method %name% is not available for this order. Please choose another one.'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
final class OrderAddressRequirement extends Constraint
|
||||
{
|
||||
public string $message = 'sylius.order.address_requirement';
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return 'sylius_order_address_requirement_validator';
|
||||
}
|
||||
|
||||
public function getTargets(): string
|
||||
{
|
||||
return self::CLASS_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use Sylius\Bundle\ApiBundle\Command\Checkout\UpdateCart;
|
||||
use Sylius\Bundle\ApiBundle\Exception\ChannelNotFoundException;
|
||||
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedValueException;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class OrderAddressRequirementValidator extends ConstraintValidator
|
||||
{
|
||||
/** @param OrderRepositoryInterface<OrderInterface> $orderRepository */
|
||||
public function __construct(private readonly OrderRepositoryInterface $orderRepository)
|
||||
{
|
||||
}
|
||||
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
if (!$constraint instanceof OrderAddressRequirement) {
|
||||
throw new UnexpectedTypeException($constraint, OrderAddressRequirement::class);
|
||||
}
|
||||
|
||||
if (!$value instanceof UpdateCart) {
|
||||
throw new UnexpectedValueException($value, UpdateCart::class);
|
||||
}
|
||||
|
||||
if (null === $value->billingAddress && null === $value->shippingAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var OrderInterface|null $order */
|
||||
$order = $this->orderRepository->findCartByTokenValue($value->orderTokenValue);
|
||||
$channel = $order?->getChannel();
|
||||
if (null === $channel) {
|
||||
throw new ChannelNotFoundException();
|
||||
}
|
||||
|
||||
[$method, $addressName] = $channel->isShippingAddressInCheckoutRequired() ? ['getShippingAddress', 'shipping address'] : ['getBillingAddress', 'billing address'];
|
||||
|
||||
if (null === $value->$method()) {
|
||||
$this->context->addViolation($constraint->message, ['%addressName%' => $addressName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\Validator\Constraints;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Command\Checkout\UpdateCart;
|
||||
use Sylius\Bundle\ApiBundle\Exception\ChannelNotFoundException;
|
||||
use Sylius\Bundle\ApiBundle\Validator\Constraints\OrderAddressRequirement;
|
||||
use Sylius\Component\Core\Model\AddressInterface;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedValueException;
|
||||
|
||||
final class OrderAddressRequirementValidatorSpec extends ObjectBehavior
|
||||
{
|
||||
const MESSAGE = 'sylius.order.address_requirement';
|
||||
|
||||
function let(OrderRepositoryInterface $orderRepository, ExecutionContextInterface $context): void
|
||||
{
|
||||
$this->beConstructedWith($orderRepository);
|
||||
|
||||
$this->initialize($context);
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_constraint_is_not_an_instance_of_order_address_requirement(
|
||||
Constraint $constraint,
|
||||
): void {
|
||||
$this
|
||||
->shouldThrow(UnexpectedTypeException::class)
|
||||
->during('validate', ['product_code', $constraint])
|
||||
;
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_value_is_not_an_instance_of_update_cart(
|
||||
OrderInterface $order,
|
||||
): void {
|
||||
$this
|
||||
->shouldThrow(UnexpectedValueException::class)
|
||||
->during('validate', [$order, new OrderAddressRequirement()])
|
||||
;
|
||||
}
|
||||
|
||||
function it_does_nothing_if_billing_and_shipping_addresses_are_not_provided(
|
||||
ExecutionContextInterface $context,
|
||||
OrderInterface $order,
|
||||
ChannelInterface $channel,
|
||||
): void {
|
||||
$updateCart = new UpdateCart();
|
||||
|
||||
$this->validate($updateCart, new OrderAddressRequirement());
|
||||
|
||||
$context->addViolation(self::MESSAGE, ['%addressName%' => 'billingAddress'])->shouldNotHaveBeenCalled();
|
||||
$context->addViolation(self::MESSAGE, ['%addressName%' => 'shippingAddress'])->shouldNotHaveBeenCalled();
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_order_is_not_found(
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
AddressInterface $billingAddress,
|
||||
): void {
|
||||
$orderRepository->findCartByTokenValue('TOKEN')->willReturn(null);
|
||||
|
||||
$updateCart = new UpdateCart(billingAddress: $billingAddress->getWrappedObject());
|
||||
$updateCart->setOrderTokenValue('TOKEN');
|
||||
|
||||
$this
|
||||
->shouldThrow(ChannelNotFoundException::class)
|
||||
->during('validate', [$updateCart, new OrderAddressRequirement()])
|
||||
;
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_order_does_not_have_channel(
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
OrderInterface $order,
|
||||
AddressInterface $billingAddress,
|
||||
): void {
|
||||
$orderRepository->findCartByTokenValue('TOKEN')->willReturn($order);
|
||||
$order->getChannel()->willReturn(null);
|
||||
|
||||
$updateCart = new UpdateCart(billingAddress: $billingAddress->getWrappedObject());
|
||||
$updateCart->setOrderTokenValue('TOKEN');
|
||||
|
||||
$this
|
||||
->shouldThrow(ChannelNotFoundException::class)
|
||||
->during('validate', [$updateCart, new OrderAddressRequirement()])
|
||||
;
|
||||
}
|
||||
|
||||
function it_does_nothing_if_shipping_address_is_required_and_provided(
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
ExecutionContextInterface $context,
|
||||
OrderInterface $order,
|
||||
ChannelInterface $channel,
|
||||
AddressInterface $shippingAddress,
|
||||
): void {
|
||||
$orderRepository->findCartByTokenValue('TOKEN')->willReturn($order);
|
||||
$order->getChannel()->willReturn($channel);
|
||||
$channel->isShippingAddressInCheckoutRequired()->willReturn(true);
|
||||
|
||||
$updateCart = new UpdateCart(shippingAddress: $shippingAddress->getWrappedObject());
|
||||
$updateCart->setOrderTokenValue('TOKEN');
|
||||
|
||||
$this->validate($updateCart, new OrderAddressRequirement());
|
||||
|
||||
$context->addViolation(self::MESSAGE, ['%addressName%' => 'shippingAddress'])->shouldNotHaveBeenCalled();
|
||||
}
|
||||
|
||||
function it_does_nothing_if_billing_address_is_required_and_provided(
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
ExecutionContextInterface $context,
|
||||
OrderInterface $order,
|
||||
ChannelInterface $channel,
|
||||
AddressInterface $billingAddress,
|
||||
): void {
|
||||
$orderRepository->findCartByTokenValue('TOKEN')->willReturn($order);
|
||||
$order->getChannel()->willReturn($channel);
|
||||
$channel->isShippingAddressInCheckoutRequired()->willReturn(false);
|
||||
|
||||
$updateCart = new UpdateCart(billingAddress: $billingAddress->getWrappedObject());
|
||||
$updateCart->setOrderTokenValue('TOKEN');
|
||||
|
||||
$this->validate($updateCart, new OrderAddressRequirement());
|
||||
|
||||
$context->addViolation(self::MESSAGE, ['%addressName%' => 'billingAddress'])->shouldNotHaveBeenCalled();
|
||||
}
|
||||
|
||||
function it_adds_violation_if_shipping_address_is_required_but_not_provided(
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
ExecutionContextInterface $context,
|
||||
OrderInterface $order,
|
||||
AddressInterface $billingAddress,
|
||||
ChannelInterface $channel,
|
||||
): void {
|
||||
$orderRepository->findCartByTokenValue('TOKEN')->willReturn($order);
|
||||
$order->getChannel()->willReturn($channel);
|
||||
$channel->isShippingAddressInCheckoutRequired()->willReturn(true);
|
||||
|
||||
$updateCart = new UpdateCart(billingAddress: $billingAddress->getWrappedObject());
|
||||
$updateCart->setOrderTokenValue('TOKEN');
|
||||
|
||||
$this->validate($updateCart, new OrderAddressRequirement());
|
||||
|
||||
$context->addViolation(self::MESSAGE, ['%addressName%' => 'shipping address'])->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
function it_adds_violation_if_billing_address_is_required_but_not_provided(
|
||||
OrderRepositoryInterface $orderRepository,
|
||||
ExecutionContextInterface $context,
|
||||
OrderInterface $order,
|
||||
AddressInterface $shippingAddress,
|
||||
ChannelInterface $channel,
|
||||
): void {
|
||||
$orderRepository->findCartByTokenValue('TOKEN')->willReturn($order);
|
||||
$order->getChannel()->willReturn($channel);
|
||||
$channel->isShippingAddressInCheckoutRequired()->willReturn(false);
|
||||
|
||||
$updateCart = new UpdateCart(shippingAddress: $shippingAddress->getWrappedObject());
|
||||
$updateCart->setOrderTokenValue('TOKEN');
|
||||
|
||||
$this->validate($updateCart, new OrderAddressRequirement());
|
||||
|
||||
$context->addViolation(self::MESSAGE, ['%addressName%' => 'billing address'])->shouldHaveBeenCalled();
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ imports:
|
|||
- { resource: "@SyliusLocaleBundle/Resources/config/app/config.yml" }
|
||||
- { resource: "@SyliusOrderBundle/Resources/config/app/config.yml" }
|
||||
- { resource: "@SyliusPaymentBundle/Resources/config/app/config.yml" }
|
||||
- { resource: "@SyliusPayumBundle/Resources/config/app/config.yaml" }
|
||||
- { resource: "@SyliusProductBundle/Resources/config/app/config.yml" }
|
||||
- { resource: "@SyliusPromotionBundle/Resources/config/app/config.yml" }
|
||||
- { resource: "@SyliusShippingBundle/Resources/config/app/config.yml" }
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@
|
|||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\CoreBundle\EventListener\PaymentPreCompleteListener">
|
||||
<argument type="service" id="Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface" />
|
||||
<argument type="service" id="sylius.availability_checker" />
|
||||
<tag name="kernel.event_listener" event="sylius.payment.pre_complete" method="checkStockAvailability" />
|
||||
</service>
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ sylius:
|
|||
insufficient_stock: 'Insufficient stock'
|
||||
max_integer: 'Value must be less than {{ compared_value }}.'
|
||||
order:
|
||||
address_requirement: 'Please provide a %addressName%.'
|
||||
currency_code:
|
||||
not_valid: The currency code you entered is invalid.
|
||||
invalid_state_transition: 'Cannot complete as order is in a wrong state. Current: %currentState%. Possible transitions: %possibleTransitions%.'
|
||||
|
|
|
|||
|
|
@ -34,7 +34,11 @@ final class Configuration implements ConfigurationInterface
|
|||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('driver')->defaultValue(SyliusResourceBundle::DRIVER_DOCTRINE_ORM)->end()
|
||||
->scalarNode('checker')->defaultValue('sylius.availability_checker.default')->cannotBeEmpty()->end()
|
||||
->scalarNode('checker')
|
||||
->setDeprecated('sylius/inventory-bundle', '1.13', 'The "%path%.%node%" is deprecated and will be removed in 2.0.')
|
||||
->defaultValue('sylius.availability_checker.default')
|
||||
->cannotBeEmpty()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,3 @@ jms_serializer:
|
|||
sylius-payment:
|
||||
namespace_prefix: "Sylius\\Component\\Payment"
|
||||
path: "@SyliusPaymentBundle/Resources/config/serializer"
|
||||
|
||||
sylius_payum:
|
||||
gateway_config:
|
||||
validation_groups:
|
||||
paypal_express_checkout:
|
||||
- 'sylius'
|
||||
- 'sylius_paypal_express_checkout'
|
||||
stripe_checkout:
|
||||
- 'sylius'
|
||||
- 'sylius_stripe_checkout'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
# This file is part of the Sylius package.
|
||||
# (c) Sylius Sp. z o.o.
|
||||
|
||||
sylius_payum:
|
||||
gateway_config:
|
||||
validation_groups:
|
||||
paypal_express_checkout:
|
||||
- 'sylius'
|
||||
- 'sylius_paypal_express_checkout'
|
||||
stripe_checkout:
|
||||
- 'sylius'
|
||||
- 'sylius_stripe_checkout'
|
||||
|
|
@ -32,6 +32,7 @@ Sylius\Component\Core\Model\ProductVariant:
|
|||
optionValues: ['@product_option_value_color_blue']
|
||||
channelPricings:
|
||||
WEB: '@channel_pricing_product_variant_mug_blue_web'
|
||||
MOBILE: '@channel_pricing_product_variant_mug_blue_mobile'
|
||||
product_variant_mug_nft:
|
||||
channelPricings:
|
||||
WEB: '@channel_pricing_product_variant_mug_nft_web'
|
||||
|
|
@ -77,6 +78,9 @@ Sylius\Component\Core\Model\ChannelPricing:
|
|||
channel_pricing_product_variant_free_mug_nft_web:
|
||||
channelCode: 'WEB'
|
||||
price: 0
|
||||
channel_pricing_product_variant_mug_blue_mobile:
|
||||
channelCode: 'MOBILE'
|
||||
price: 2000
|
||||
|
||||
Sylius\Component\Product\Model\ProductOption:
|
||||
product_option_color:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ Sylius\Component\Core\Model\Channel:
|
|||
taxCalculationStrategy: 'order_items_based'
|
||||
channelPriceHistoryConfig: '@mobile_price_history_config'
|
||||
shopBillingData: '@billing_data'
|
||||
shippingAddressInCheckoutRequired: true
|
||||
|
||||
Sylius\Component\Core\Model\ChannelPriceHistoryConfig:
|
||||
web_price_history_config:
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
"skippingShippingStepAllowed": false,
|
||||
"skippingPaymentStepAllowed": false,
|
||||
"accountVerificationRequired": true,
|
||||
"shippingAddressInCheckoutRequired": false,
|
||||
"shippingAddressInCheckoutRequired": true,
|
||||
"shopBillingData": "\/api\/v2\/admin\/shop-billing-datas\/@integer@",
|
||||
"menuTaxon": null,
|
||||
"channelPriceHistoryConfig": "\/api\/v2\/admin\/channel-price-history-configs\/@integer@"
|
||||
|
|
|
|||
|
|
@ -29,6 +29,108 @@ final class CheckoutCompletionTest extends JsonApiTestCase
|
|||
parent::setUp();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_allow_updating_without_items(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['channel.yaml', 'cart.yaml']);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: sprintf('/api/v2/shop/orders/%s', $tokenValue),
|
||||
server: self::CONTENT_TYPE_HEADER,
|
||||
content: json_encode([
|
||||
'email' => 'oliver@doe.com',
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseViolations(
|
||||
$this->client->getResponse(),
|
||||
[
|
||||
['propertyPath' => '', 'message' => 'An empty order cannot be processed.'],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_allow_updating_without_require_billing_address(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles([
|
||||
'channel.yaml',
|
||||
'cart.yaml',
|
||||
'country.yaml',
|
||||
]);
|
||||
|
||||
$tokenValue = $this->pickUpCart();
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: sprintf('/api/v2/shop/orders/%s', $tokenValue),
|
||||
server: self::CONTENT_TYPE_HEADER,
|
||||
content: json_encode([
|
||||
'email' => 'oliver@doe.com',
|
||||
'shippingAddress' => [
|
||||
'firstName' => 'Oliver',
|
||||
'lastName' => 'Doe',
|
||||
'phoneNumber' => '123456789',
|
||||
'countryCode' => 'US',
|
||||
'provinceCode' => 'US-MI',
|
||||
'city' => 'New York',
|
||||
'street' => 'Broadway',
|
||||
'postcode' => '10001',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseViolations(
|
||||
$this->client->getResponse(),
|
||||
[
|
||||
['propertyPath' => '', 'message' => 'Please provide a billing address.'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_does_not_allow_updating_without_require_shipping_address(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles([
|
||||
'channel.yaml',
|
||||
'cart.yaml',
|
||||
'country.yaml',
|
||||
]);
|
||||
|
||||
$tokenValue = $this->pickUpCart(channelCode: 'MOBILE');
|
||||
$this->addItemToCart('MUG_BLUE', 3, $tokenValue);
|
||||
|
||||
$this->client->request(
|
||||
method: 'PUT',
|
||||
uri: sprintf('/api/v2/shop/orders/%s', $tokenValue),
|
||||
server: self::CONTENT_TYPE_HEADER,
|
||||
content: json_encode([
|
||||
'email' => 'oliver@doe.com',
|
||||
'billingAddress' => [
|
||||
'firstName' => 'Oliver',
|
||||
'lastName' => 'Doe',
|
||||
'phoneNumber' => '123456789',
|
||||
'countryCode' => 'US',
|
||||
'provinceCode' => 'US-MI',
|
||||
'city' => 'New York',
|
||||
'street' => 'Broadway',
|
||||
'postcode' => '10001',
|
||||
],
|
||||
], \JSON_THROW_ON_ERROR),
|
||||
);
|
||||
|
||||
$this->assertResponseViolations(
|
||||
$this->client->getResponse(),
|
||||
[
|
||||
['propertyPath' => '', 'message' => 'Please provide a shipping address.'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_prevents_from_order_completion_if_order_is_in_the_cart_state(): void
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue