Merge branch '1.12' into 1.13

* 1.12:
  [Maintenance][CI] Prevent setup errors when using symfony commands with no db connection
  [Order] Prevent paying with a disabled method
This commit is contained in:
Grzegorz Sadowski 2023-04-04 07:20:06 +02:00
commit 07d8cd9cc8
No known key found for this signature in database
GPG key ID: EF87FF4E6E3BD364
14 changed files with 319 additions and 11 deletions

View file

@ -38,7 +38,7 @@ jobs:
with: with:
path: '.github/workflows/matrix.json' path: '.github/workflows/matrix.json'
prop_path: '${{ inputs.type }}.static-checks' prop_path: '${{ inputs.type }}.static-checks'
static-checks: static-checks:
needs: get-matrix needs: get-matrix
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -46,6 +46,9 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }} matrix: ${{ fromJson(needs.get-matrix.outputs.matrix) }}
env:
APP_ENV: test_cached
DATABASE_URL: "mysql://root:root@127.0.0.1/sylius?charset=utf8mb4&serverVersion=8.0"
steps: steps:
- name: "Checkout (With Branch)" - name: "Checkout (With Branch)"
if: "${{ inputs.branch != '' }}" if: "${{ inputs.branch != '' }}"
@ -72,11 +75,11 @@ jobs:
composer global require --no-progress --no-scripts --no-plugins "symfony/flex:1.18.5" composer global require --no-progress --no-scripts --no-plugins "symfony/flex:1.18.5"
composer config extra.symfony.require "${{ matrix.symfony }}" composer config extra.symfony.require "${{ matrix.symfony }}"
composer require "api-platform/core:${{ matrix.api-platform }}" --no-update --no-scripts --no-interaction composer require "api-platform/core:${{ matrix.api-platform }}" --no-update --no-scripts --no-interaction
- name: Restrict API Platform version for ApiBundle - name: Restrict API Platform version for ApiBundle
run: composer require "api-platform/core:${{ matrix.api-platform }}" --no-update --no-scripts --no-interaction run: composer require "api-platform/core:${{ matrix.api-platform }}" --no-update --no-scripts --no-interaction
working-directory: "src/Sylius/Bundle/ApiBundle" working-directory: "src/Sylius/Bundle/ApiBundle"
- name: Get Composer cache directory - name: Get Composer cache directory
id: composer-cache id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
@ -121,7 +124,7 @@ jobs:
- name: Run PHPStan - name: Run PHPStan
run: vendor/bin/phpstan analyse run: vendor/bin/phpstan analyse
- name: Run PHPSpec - name: Run PHPSpec
run: vendor/bin/phpspec run --ansi --no-interaction -f dot run: vendor/bin/phpspec run --ansi --no-interaction -f dot

View file

@ -21,3 +21,10 @@ Feature: Changing a payment method of a placed order
When I browse my orders When I browse my orders
And I change my payment method to "Bank Transfer" And I change my payment method to "Bank Transfer"
Then I should have "Bank Transfer" payment method on my order Then I should have "Bank Transfer" payment method on my order
@ui @api
Scenario: Changing a payment method of an order with a disabled payment method
Given the payment method "Cash on Delivery" is disabled
When I browse my orders
And I change my payment method to "Bank Transfer"
Then I should have "Bank Transfer" payment method on my order

View file

@ -21,7 +21,7 @@ Feature: Changing the offline payment method after order confirmation
And I retry the payment with "Offline" payment method And I retry the payment with "Offline" payment method
Then I should have chosen "Offline" payment method Then I should have chosen "Offline" payment method
@ui @ui @no-api
Scenario: Retrying the payment with different Offline payment works correctly together with inventory Scenario: Retrying the payment with different Offline payment works correctly together with inventory
Given there is 1 unit of product "PHP T-Shirt" available in the inventory Given there is 1 unit of product "PHP T-Shirt" available in the inventory
And this product is tracked by the inventory And this product is tracked by the inventory
@ -33,12 +33,69 @@ Feature: Changing the offline payment method after order confirmation
And I retry the payment with "Offline" payment method And I retry the payment with "Offline" payment method
Then I should have chosen "Offline" payment method Then I should have chosen "Offline" payment method
@api @ui @no-api
Scenario: Being unable to pay for my order if my chosen payment method gets disabled
Given I added product "PHP T-Shirt" to the cart
When I complete addressing step with email "john@example.com" and "United States" based billing address
And I have proceeded selecting "Free" shipping method
And I have proceeded selecting "Cash on delivery" payment method
And I have confirmed order
And the payment method "Cash on delivery" gets disabled
And I try to pay for my order
Then I should be notified to choose a payment method
@ui @no-api
Scenario: Retrying the payment with different payment method after order confirmation when the original is disabled
Given I added product "PHP T-Shirt" to the cart
When I complete addressing step with email "john@example.com" and "United States" based billing address
And I have proceeded selecting "Free" shipping method
And I have proceeded selecting "Cash on delivery" payment method
And I have confirmed order
And the payment method "Cash on delivery" gets disabled
And I retry the payment with "Offline" payment method
Then I should have chosen "Offline" payment method
@ui @no-api
Scenario: Being unable to pay for my order if no methods are available
Given I added product "PHP T-Shirt" to the cart
When I complete addressing step with email "john@example.com" and "United States" based billing address
And I have proceeded selecting "Free" shipping method
And I have proceeded selecting "Cash on delivery" payment method
And I have confirmed order
And the payment method "Cash on delivery" gets disabled
And the payment method "Offline" gets disabled
And I want to pay for my order
Then I should not be able to pay
@api @no-ui
Scenario: Changing chosen Offline payment method to another Offline payment method after checkout Scenario: Changing chosen Offline payment method to another Offline payment method after checkout
Given I added product "PHP T-Shirt" to the cart Given I added product "PHP T-Shirt" to the cart
When I complete addressing step with email "john@example.com" and "United States" based billing address When I complete addressing step with email "john@example.com" and "United States" based billing address
And I proceed selecting "Free" shipping method And I proceed with "Free" shipping method
And I proceed selecting "Cash on delivery" payment method And I proceed selecting "Cash on delivery" payment method
And I confirm my order And I confirm my order
And I change payment method to "Offline" after checkout And I change payment method to "Offline" after checkout
Then I should have chosen "Offline" payment method Then I should have chosen "Offline" payment method
@api @no-ui
Scenario: Changing the payment method to a different Offline payment method works correctly together with inventory
Given there is 1 unit of product "PHP T-Shirt" available in the inventory
And this product is tracked by the inventory
When I added product "PHP T-Shirt" to the cart
And I complete addressing step with email "john@example.com" and "United States" based billing address
And I proceed with "Free" shipping method
And I proceed selecting "Cash on delivery" payment method
And I confirm my order
And I change payment method to "Offline" after checkout
Then I should have chosen "Offline" payment method
@api @no-ui
Scenario: Changing chosen Offline payment method to another Offline payment method after checkout when the original is disabled
Given I added product "PHP T-Shirt" to the cart
When I complete addressing step with email "john@example.com" and "United States" based billing address
And I proceed with "Free" shipping method
And I proceed selecting "Cash on delivery" payment method
And I confirm my order
And the payment method "Cash on delivery" gets disabled
And I change payment method to "Offline" after checkout
Then I should have chosen "Offline" payment method

View file

@ -36,7 +36,7 @@ Feature: Preventing not available payment method selection
And I should be able to select "Paypal Express Checkout" payment method And I should be able to select "Paypal Express Checkout" payment method
@api @api
Scenario: Preventing customer from selecting inexistent payment method Scenario: Preventing customer from selecting nonexistent payment method
Given I have product "PHP T-Shirt" in the cart Given I have product "PHP T-Shirt" in the cart
And I am at the checkout addressing step And I am at the checkout addressing step
When I specify the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Snow" When I specify the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Snow"

View file

@ -104,6 +104,7 @@ final class PaymentContext implements Context
/** /**
* @Given the payment method :paymentMethod is disabled * @Given the payment method :paymentMethod is disabled
* @Given /^(this payment method) has been disabled$/ * @Given /^(this payment method) has been disabled$/
* @When the payment method :paymentMethod gets disabled
*/ */
public function theStoreHasAPaymentMethodDisabled(PaymentMethodInterface $paymentMethod) public function theStoreHasAPaymentMethodDisabled(PaymentMethodInterface $paymentMethod)
{ {

View file

@ -54,6 +54,23 @@ final class CheckoutOrderDetailsContext implements Context
$this->orderDetails->pay(); $this->orderDetails->pay();
} }
/**
* @When I want to pay for my order
*/
public function iWantToPayForMyOrder(): void
{
$this->thankYouPage->goToTheChangePaymentMethodPage();
}
/**
* @When I try to pay for my order
*/
public function iTryToPayForMyOrder(): void
{
$this->thankYouPage->goToTheChangePaymentMethodPage();
$this->orderDetails->pay();
}
/** /**
* @Then I should be able to pay (again) * @Then I should be able to pay (again)
*/ */
@ -67,7 +84,7 @@ final class CheckoutOrderDetailsContext implements Context
*/ */
public function iShouldNotBeAbleToPay(): void public function iShouldNotBeAbleToPay(): void
{ {
Assert::false($this->orderDetails->hasPayAction()); Assert::false($this->orderDetails->canBePaid());
} }
/** /**
@ -86,4 +103,12 @@ final class CheckoutOrderDetailsContext implements Context
$this->thankYouPage->goToTheChangePaymentMethodPage(); $this->thankYouPage->goToTheChangePaymentMethodPage();
Assert::same($this->orderDetails->getChosenPaymentMethod(), $paymentMethodName); Assert::same($this->orderDetails->getChosenPaymentMethod(), $paymentMethodName);
} }
/**
* @Then I should be notified to choose a payment method
*/
public function iShouldBeNotifiedToChooseAPaymentMethod(): void
{
Assert::contains($this->orderDetails->getPaymentValidationMessage(), 'Please select a payment method.');
}
} }

View file

@ -23,6 +23,11 @@ class ShowPage extends SymfonyPage implements ShowPageInterface
return $this->hasElement('pay_link'); return $this->hasElement('pay_link');
} }
public function canBePaid(): bool
{
return $this->hasPayAction() && !$this->getElement('pay_link')->hasAttribute('disabled');
}
public function pay(): void public function pay(): void
{ {
$this->getElement('pay_link')->click(); $this->getElement('pay_link')->click();
@ -72,6 +77,17 @@ class ShowPage extends SymfonyPage implements ShowPageInterface
return ''; return '';
} }
public function getPaymentValidationMessage(): string
{
$message = '';
$validationElements = $this->getDocument()->findAll('css', 'form .items .sylius-validation-error');
foreach ($validationElements as $validationElement) {
$message .= $validationElement->getText();
}
return $message;
}
protected function getDefinedElements(): array protected function getDefinedElements(): array
{ {
return array_merge(parent::getDefinedElements(), [ return array_merge(parent::getDefinedElements(), [

View file

@ -19,6 +19,8 @@ interface ShowPageInterface extends SymfonyPageInterface
{ {
public function hasPayAction(): bool; public function hasPayAction(): bool;
public function canBePaid(): bool;
public function pay(): void; public function pay(): void;
public function choosePaymentMethod(string $paymentMethodName): void; public function choosePaymentMethod(string $paymentMethodName): void;
@ -28,4 +30,6 @@ interface ShowPageInterface extends SymfonyPageInterface
public function getAmountOfItems(): int; public function getAmountOfItems(): int;
public function getChosenPaymentMethod(): string; public function getChosenPaymentMethod(): string;
public function getPaymentValidationMessage(): string;
} }

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<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\Component\Core\Model\Payment">
<property name="method">
<constraint name="NotBlank">
<option name="message">sylius.payment.method.not_blank</option>
<option name="groups">sylius_order_pay</option>
</constraint>
</property>
</class>
</constraint-mapping>

View file

@ -37,7 +37,7 @@ sylius_shop_order_show:
form: form:
type: Sylius\Bundle\CoreBundle\Form\Type\Checkout\SelectPaymentType type: Sylius\Bundle\CoreBundle\Form\Type\Checkout\SelectPaymentType
options: options:
validation_groups: [] validation_groups: [sylius_order_pay]
redirect: redirect:
route: sylius_shop_order_pay route: sylius_shop_order_pay
parameters: parameters:

View file

@ -28,5 +28,10 @@
<service id="sylius.twig.extension.original_price_to_display" class="Sylius\Bundle\ShopBundle\Twig\OrderItemOriginalPriceToDisplayExtension" public="false"> <service id="sylius.twig.extension.original_price_to_display" class="Sylius\Bundle\ShopBundle\Twig\OrderItemOriginalPriceToDisplayExtension" public="false">
<tag name="twig.extension" /> <tag name="twig.extension" />
</service> </service>
<service id="Sylius\Bundle\ShopBundle\Twig\OrderPaymentsExtension" public="false">
<argument type="service" id="sylius.payment_methods_resolver" />
<tag name="twig.extension" />
</service>
</services> </services>
</container> </container>

View file

@ -16,7 +16,8 @@
{% include '@SyliusShop/Checkout/SelectPayment/_form.html.twig' %} {% include '@SyliusShop/Checkout/SelectPayment/_form.html.twig' %}
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<button type="submit" class="ui large blue icon labeled button" id="sylius-pay-link" {{ sylius_test_html_attribute('pay-link') }}> {% set order_cannot_be_paid = not sylius_order_can_be_paid(order) %}
<button type="submit" class="ui large blue icon labeled button {% if order_cannot_be_paid %}disabled{% endif %}" id="sylius-pay-link" {{ sylius_test_html_attribute('pay-link') }} {% if order_cannot_be_paid %}disabled{% endif %}>
<i class="check icon"></i> {{ 'sylius.ui.pay'|trans }} <i class="check icon"></i> {{ 'sylius.ui.pay'|trans }}
</button> </button>

View file

@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\ShopBundle\Twig;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Payment\Resolver\PaymentMethodsResolverInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
final class OrderPaymentsExtension extends AbstractExtension
{
public function __construct(private PaymentMethodsResolverInterface $paymentMethodsResolver)
{
}
public function getFunctions(): array
{
return [
new TwigFunction('sylius_order_can_be_paid', [$this, 'allNewPaymentsCanBePaid']),
];
}
public function allNewPaymentsCanBePaid(OrderInterface $order): bool
{
$newPayments = $order->getPayments()->filter(function (PaymentInterface $payment) {
return $payment->getState() === PaymentInterface::STATE_NEW;
});
if ($newPayments->isEmpty()) {
return false;
}
foreach ($newPayments as $payment) {
if (0 === count($this->paymentMethodsResolver->getSupportedMethods($payment))) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,113 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\ShopBundle\Twig;
use Doctrine\Common\Collections\ArrayCollection;
use PhpSpec\ObjectBehavior;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Payment\Resolver\PaymentMethodsResolverInterface;
use Twig\Extension\AbstractExtension;
final class OrderPaymentsExtensionSpec extends ObjectBehavior
{
function let(PaymentMethodsResolverInterface $paymentMethodsResolver): void
{
$this->beConstructedWith($paymentMethodsResolver);
}
function it_is_a_twig_extension(): void
{
$this->shouldImplement(AbstractExtension::class);
}
function it_returns_false_if_order_has_no_new_payments(OrderInterface $order): void
{
$order->getPayments()->willReturn(new ArrayCollection());
$this->allNewPaymentsCanBePaid($order)->shouldReturn(false);
}
function it_returns_false_when_all_new_payments_have_no_supported_methods(
PaymentMethodsResolverInterface $paymentMethodsResolver,
PaymentInterface $firstPayment,
PaymentInterface $secondPayment,
PaymentInterface $thirdPayment,
OrderInterface $order,
): void {
$firstPayment->getState()->willReturn(PaymentInterface::STATE_NEW);
$secondPayment->getState()->willReturn(PaymentInterface::STATE_CANCELLED);
$thirdPayment->getState()->willReturn(PaymentInterface::STATE_NEW);
$order->getPayments()->willReturn(new ArrayCollection([
$firstPayment->getWrappedObject(),
$secondPayment->getWrappedObject(),
$thirdPayment->getWrappedObject(),
]));
$paymentMethodsResolver->getSupportedMethods($firstPayment)->willReturn([]);
$paymentMethodsResolver->getSupportedMethods($secondPayment)->shouldNotBeCalled();
$paymentMethodsResolver->getSupportedMethods($thirdPayment)->willReturn([]);
$this->allNewPaymentsCanBePaid($order)->shouldReturn(false);
}
function it_returns_false_when_at_least_one_new_payment_has_no_supported_methods(
PaymentMethodsResolverInterface $paymentMethodsResolver,
PaymentInterface $firstPayment,
PaymentInterface $secondPayment,
PaymentInterface $thirdPayment,
OrderInterface $order,
): void {
$firstPayment->getState()->willReturn(PaymentInterface::STATE_NEW);
$secondPayment->getState()->willReturn(PaymentInterface::STATE_CANCELLED);
$thirdPayment->getState()->willReturn(PaymentInterface::STATE_NEW);
$order->getPayments()->willReturn(new ArrayCollection([
$firstPayment->getWrappedObject(),
$secondPayment->getWrappedObject(),
$thirdPayment->getWrappedObject(),
]));
$paymentMethodsResolver->getSupportedMethods($firstPayment)->willReturn(['method']);
$paymentMethodsResolver->getSupportedMethods($secondPayment)->shouldNotBeCalled();
$paymentMethodsResolver->getSupportedMethods($thirdPayment)->willReturn([]);
$this->allNewPaymentsCanBePaid($order)->shouldReturn(false);
}
function it_returns_true_when_all_new_payments_have_at_least_one_supported_method(
PaymentMethodsResolverInterface $paymentMethodsResolver,
PaymentInterface $firstPayment,
PaymentInterface $secondPayment,
PaymentInterface $thirdPayment,
OrderInterface $order,
): void {
$firstPayment->getState()->willReturn(PaymentInterface::STATE_NEW);
$secondPayment->getState()->willReturn(PaymentInterface::STATE_CANCELLED);
$thirdPayment->getState()->willReturn(PaymentInterface::STATE_NEW);
$order->getPayments()->willReturn(new ArrayCollection([
$firstPayment->getWrappedObject(),
$secondPayment->getWrappedObject(),
$thirdPayment->getWrappedObject(),
]));
$paymentMethodsResolver->getSupportedMethods($firstPayment)->willReturn(['method', 'another_method']);
$paymentMethodsResolver->getSupportedMethods($secondPayment)->shouldNotBeCalled();
$paymentMethodsResolver->getSupportedMethods($thirdPayment)->willReturn(['method']);
$this->allNewPaymentsCanBePaid($order)->shouldReturn(true);
}
}