Skip to content

Commit 15acefd

Browse files
[ExpressCheckout] Get paypal methods directly instead of defaulting
Allow disabling paypal methods prioritization
1 parent fee6748 commit 15acefd

File tree

8 files changed

+261
-15
lines changed

8 files changed

+261
-15
lines changed

spec/Resolver/PayPalDefaultPaymentMethodResolverSpec.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,15 @@ function it_returns_prioritised_payment_method_for_channel(
5959
$this->getDefaultPaymentMethod($subject, 'prioritised.payment')->shouldReturn($secondPayment);
6060
}
6161

62-
function it_returns_first_available_payment_method_if_priotitised_payment_method_is_invalid(
62+
function it_delegates_to_decorated_default_payment_method_when_prioritised_payment_is_missing(
63+
DefaultPaymentMethodResolverInterface $decoratedDefaultPaymentMethodResolver,
6364
PaymentMethodRepositoryInterface $paymentMethodRepository,
6465
ChannelInterface $channel,
6566
PaymentMethodInterface $firstPayment,
6667
PaymentMethodInterface $secondPayment,
6768
GatewayConfigInterface $firstGatewayConfig,
6869
GatewayConfigInterface $secondGatewayConfig,
70+
PaymentMethodInterface $defaultPaymentMethod,
6971
PaymentInterface $subject,
7072
OrderInterface $order,
7173
): void {
@@ -80,7 +82,9 @@ function it_returns_first_available_payment_method_if_priotitised_payment_method
8082
$subject->getOrder()->willReturn($order);
8183
$order->getChannel()->willReturn($channel);
8284

83-
$this->getDefaultPaymentMethod($subject, 'prioritised')->shouldReturn($firstPayment);
85+
$decoratedDefaultPaymentMethodResolver->getDefaultPaymentMethod($subject)->willReturn($defaultPaymentMethod);
86+
87+
$this->getDefaultPaymentMethod($subject, 'prioritised')->shouldReturn($defaultPaymentMethod);
8488
}
8589

8690
function it_throws_error_if_there_is_no_available_payment(
@@ -96,4 +100,17 @@ function it_throws_error_if_there_is_no_available_payment(
96100

97101
$this->shouldThrow(UnresolvedDefaultPaymentMethodException::class)->during('getDefaultPaymentMethod', [$subject, 'prioritised']);
98102
}
103+
104+
function it_delegates_to_decorated_default_payment_method_when_prioritisation_is_disabled(
105+
DefaultPaymentMethodResolverInterface $decoratedDefaultPaymentMethodResolver,
106+
PaymentMethodRepositoryInterface $paymentMethodRepository,
107+
PaymentInterface $subject,
108+
PaymentMethodInterface $decoratedDefaultPaymentMethod,
109+
): void {
110+
$this->beConstructedWith($decoratedDefaultPaymentMethodResolver, $paymentMethodRepository, false);
111+
112+
$decoratedDefaultPaymentMethodResolver->getDefaultPaymentMethod($subject)->willReturn($decoratedDefaultPaymentMethod);
113+
114+
$this->getDefaultPaymentMethod($subject)->shouldReturn($decoratedDefaultPaymentMethod);
115+
}
99116
}

src/Controller/CreatePayPalOrderFromCartAction.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Sylius\PayPalPlugin\DependencyInjection\SyliusPayPalExtension;
2727
use Sylius\PayPalPlugin\Provider\OrderProviderInterface;
2828
use Sylius\PayPalPlugin\Resolver\CapturePaymentResolverInterface;
29+
use Sylius\PayPalPlugin\Resolver\PayPalPaymentMethodsResolverInterface;
2930
use Symfony\Component\HttpFoundation\JsonResponse;
3031
use Symfony\Component\HttpFoundation\Request;
3132
use Symfony\Component\HttpFoundation\Response;
@@ -42,6 +43,7 @@ public function __construct(
4243
private readonly CapturePaymentResolverInterface $capturePaymentResolver,
4344
private readonly ?OrderPaymentsRemoverInterface $orderPaymentsRemover = null,
4445
private readonly ?OrderProcessorInterface $orderProcessor = null,
46+
private readonly ?PayPalPaymentMethodsResolverInterface $payPalMethodsResolver = null,
4547
) {
4648
if (null !== $this->payum) {
4749
trigger_deprecation(
@@ -89,6 +91,14 @@ public function __construct(
8991
self::class,
9092
);
9193
}
94+
if (null === $this->payPalMethodsResolver) {
95+
trigger_deprecation(
96+
'sylius/paypal-plugin',
97+
'1.7',
98+
'Not passing a $payPalMethodsResolver to %s constructor is deprecated and will be prohibited in 3.0',
99+
self::class,
100+
);
101+
}
92102
}
93103

94104
public function __invoke(Request $request): Response
@@ -135,6 +145,14 @@ private function getPayment(OrderInterface $order): PaymentInterface
135145
$this->orderPaymentsRemover->removePayments($order);
136146
$this->orderProcessor->process($order);
137147

138-
return $order->getLastPayment(PaymentInterface::STATE_CART);
148+
$payment = $order->getLastPayment(PaymentInterface::STATE_CART);
149+
if ($order->getChannel() !== null && $this->payPalMethodsResolver !== null) {
150+
$paypalMethods = $this->payPalMethodsResolver->getInChannel($order->getChannel());
151+
if ([] !== $paypalMethods) {
152+
$payment->setMethod($paypalMethods[0]);
153+
}
154+
}
155+
156+
return $payment;
139157
}
140158
}

src/Resolver/PayPalDefaultPaymentMethodResolver.php

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,29 @@
2626
use Sylius\PayPalPlugin\DependencyInjection\SyliusPayPalExtension;
2727
use Webmozart\Assert\Assert;
2828

29+
trigger_deprecation(
30+
'sylius/paypal-plugin',
31+
'1.7',
32+
'The "%s" class is deprecated and will be removed in Sylius/PayPalPlugin 3.0.',
33+
PayPalDefaultPaymentMethodResolver::class,
34+
);
35+
36+
/** @deprecated since Sylius/PayPalPlugin 1.7 and will be removed in Sylius/PayPalPlugin 3.0. */
2937
final class PayPalDefaultPaymentMethodResolver implements DefaultPaymentMethodResolverInterface
3038
{
31-
private PaymentMethodRepositoryInterface $paymentMethodRepository;
32-
33-
private DefaultPaymentMethodResolverInterface $decoratedDefaultPaymentMethodResolver;
34-
3539
public function __construct(
36-
DefaultPaymentMethodResolverInterface $decoratedDefaultPaymentMethodResolver,
37-
PaymentMethodRepositoryInterface $paymentMethodRepository,
40+
private DefaultPaymentMethodResolverInterface $decoratedDefaultPaymentMethodResolver,
41+
private PaymentMethodRepositoryInterface $paymentMethodRepository,
42+
private bool $prioritizePayPal = true,
3843
) {
39-
$this->decoratedDefaultPaymentMethodResolver = $decoratedDefaultPaymentMethodResolver;
40-
$this->paymentMethodRepository = $paymentMethodRepository;
4144
}
4245

4346
public function getDefaultPaymentMethod(BasePaymentInterface $payment, string $prioritisedPayment = SyliusPayPalExtension::PAYPAL_FACTORY_NAME): PaymentMethodInterface
4447
{
48+
if (!$this->prioritizePayPal) {
49+
return $this->decoratedDefaultPaymentMethodResolver->getDefaultPaymentMethod($payment);
50+
}
51+
4552
/** @var PaymentInterface $payment */
4653
Assert::isInstanceOf($payment, PaymentInterface::class);
4754

@@ -51,14 +58,13 @@ public function getDefaultPaymentMethod(BasePaymentInterface $payment, string $p
5158
/** @var ChannelInterface $channel */
5259
$channel = $order->getChannel();
5360

54-
return $this->getFirstPrioritisedPaymentForChannel($channel, $prioritisedPayment);
61+
return $this->getFirstPrioritisedPaymentForChannel($payment, $channel, $prioritisedPayment);
5562
}
5663

57-
private function getFirstPrioritisedPaymentForChannel(ChannelInterface $channel, string $prioritisedPayment): PaymentMethodInterface
64+
private function getFirstPrioritisedPaymentForChannel(PaymentInterface $payment, ChannelInterface $channel, string $prioritisedPayment): PaymentMethodInterface
5865
{
5966
/** @var array<CorePaymentMethodInterface> $paymentMethods */
6067
$paymentMethods = $this->paymentMethodRepository->findEnabledForChannel($channel);
61-
6268
if (empty($paymentMethods)) {
6369
throw new UnresolvedDefaultPaymentMethodException();
6470
}
@@ -72,6 +78,6 @@ private function getFirstPrioritisedPaymentForChannel(ChannelInterface $channel,
7278
}
7379
}
7480

75-
return $paymentMethods[0];
81+
return $this->decoratedDefaultPaymentMethodResolver->getDefaultPaymentMethod($payment);
7682
}
7783
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius 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 Sylius\PayPalPlugin\Resolver;
15+
16+
use Sylius\Component\Core\Model\ChannelInterface;
17+
use Sylius\Component\Core\Model\PaymentMethodInterface;
18+
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
19+
use Sylius\PayPalPlugin\DependencyInjection\SyliusPayPalExtension;
20+
21+
final class PayPalPaymentMethodsResolver implements PayPalPaymentMethodsResolverInterface
22+
{
23+
public function __construct(
24+
private readonly PaymentMethodRepositoryInterface $paymentMethodRepository,
25+
) {
26+
}
27+
28+
public function getInChannel(ChannelInterface $channel): array
29+
{
30+
$enabledMethods = $this->paymentMethodRepository->findEnabledForChannel($channel);
31+
32+
$paypalMethods = [];
33+
/** @var PaymentMethodInterface $method */
34+
foreach ($enabledMethods as $method) {
35+
if ($method->getGatewayConfig()?->getFactoryName() === SyliusPayPalExtension::PAYPAL_FACTORY_NAME) {
36+
$paypalMethods[] = $method;
37+
}
38+
}
39+
40+
usort(
41+
$paypalMethods,
42+
fn (PaymentMethodInterface $a, PaymentMethodInterface $b) => $a->getPosition() <=> $b->getPosition(),
43+
);
44+
45+
return $paypalMethods;
46+
}
47+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius 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 Sylius\PayPalPlugin\Resolver;
15+
16+
use Sylius\Component\Core\Model\ChannelInterface;
17+
use Sylius\Component\Core\Model\PaymentMethodInterface;
18+
19+
interface PayPalPaymentMethodsResolverInterface
20+
{
21+
/** @return array<PaymentMethodInterface> */
22+
public function getInChannel(ChannelInterface $channel): array;
23+
}

src/Resources/config/services.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<parameters>
2424
<parameter key="sylius.paypal.prioritized_factory_name">sylius.pay_pal</parameter>
2525
<parameter key="sylius_paypal.prioritized_factory_name">%sylius.paypal.prioritized_factory_name%</parameter>
26+
<parameter key="sylius_paypal.prioritize_paypal_as_default_method">true</parameter>
2627

2728
<parameter type="collection" key="sylius_paypal.repository.query.pay_pal_payment.updatable_states">
2829
<parameter type="constant">Sylius\Component\Payment\Model\PaymentInterface::STATE_CART</parameter>
@@ -217,6 +218,7 @@
217218
>
218219
<argument type="service" id=".inner" />
219220
<argument type="service" id="sylius.repository.payment_method" />
221+
<argument>%sylius_paypal.prioritize_paypal_as_default_method%</argument>
220222
</service>
221223
<service id="sylius_paypal.resolver.payment_method.paypal" alias="Sylius\PayPalPlugin\Resolver\PayPalDefaultPaymentMethodResolver" />
222224

@@ -348,5 +350,10 @@
348350
<argument>%sylius_paypal.repository.query.pay_pal_payment.refundable_states%</argument>
349351
</service>
350352
<service id="Sylius\PayPalPlugin\Repository\Query\PaypalPaymentQueryInterface" alias="sylius_paypal.repository.query.paypal_payment" />
353+
354+
<service id="sylius_paypal.resolver.paypal_payment_methods" class="Sylius\PayPalPlugin\Resolver\PayPalPaymentMethodsResolver">
355+
<argument type="service" id="sylius.repository.payment_method" />
356+
</service>
357+
<service id="Sylius\PayPalPlugin\Resolver\PayPalPaymentMethodsResolverInterface" alias="sylius_paypal.resolver.paypal_payment_methods" />
351358
</services>
352359
</container>

src/Resources/config/services/controller.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
<argument type="service" id="sylius_paypal.resolver.capture_payment" />
111111
<argument type="service" id="Sylius\Component\Core\Payment\Remover\OrderPaymentsRemoverInterface" />
112112
<argument type="service" id="sylius.order_processing.order_processor" />
113+
<argument type="service" id="sylius_paypal.resolver.paypal_payment_methods" />
113114
</service>
114115
<service id="sylius_paypal.controller.create_paypal_order_from_cart" alias="Sylius\PayPalPlugin\Controller\CreatePayPalOrderFromCartAction" />
115116

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius 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\PayPalPlugin\Unit\Resolver;
15+
16+
use PHPUnit\Framework\TestCase;
17+
use Sylius\Bundle\PayumBundle\Model\GatewayConfigInterface;
18+
use Sylius\Component\Core\Model\ChannelInterface;
19+
use Sylius\Component\Core\Model\PaymentMethodInterface;
20+
use Sylius\Component\Core\Repository\PaymentMethodRepositoryInterface;
21+
use Sylius\PayPalPlugin\DependencyInjection\SyliusPayPalExtension;
22+
use Sylius\PayPalPlugin\Resolver\PayPalPaymentMethodsResolver;
23+
24+
final class PayPalPaymentMethodsResolverTest extends TestCase
25+
{
26+
public function test_returns_sorted_paypal_methods_from_channel(): void
27+
{
28+
$repository = $this->createMock(PaymentMethodRepositoryInterface::class);
29+
$channel = $this->createMock(ChannelInterface::class);
30+
31+
$paypalGatewayLow = $this->createMock(GatewayConfigInterface::class);
32+
$paypalGatewayLow->method('getFactoryName')->willReturn(SyliusPayPalExtension::PAYPAL_FACTORY_NAME);
33+
34+
$paypalMethodLow = $this->createMock(PaymentMethodInterface::class);
35+
$paypalMethodLow->method('getGatewayConfig')->willReturn($paypalGatewayLow);
36+
$paypalMethodLow->method('getPosition')->willReturn(10);
37+
38+
$paypalGatewayHigh = $this->createMock(GatewayConfigInterface::class);
39+
$paypalGatewayHigh->method('getFactoryName')->willReturn(SyliusPayPalExtension::PAYPAL_FACTORY_NAME);
40+
41+
$paypalMethodHigh = $this->createMock(PaymentMethodInterface::class);
42+
$paypalMethodHigh->method('getGatewayConfig')->willReturn($paypalGatewayHigh);
43+
$paypalMethodHigh->method('getPosition')->willReturn(5);
44+
45+
$otherGateway = $this->createMock(GatewayConfigInterface::class);
46+
$otherGateway->method('getFactoryName')->willReturn('other_factory');
47+
48+
$otherMethod = $this->createMock(PaymentMethodInterface::class);
49+
$otherMethod->method('getGatewayConfig')->willReturn($otherGateway);
50+
51+
$repository
52+
->expects($this->once())
53+
->method('findEnabledForChannel')
54+
->with($channel)
55+
->willReturn([$paypalMethodLow, $otherMethod, $paypalMethodHigh]);
56+
57+
$resolver = new PayPalPaymentMethodsResolver($repository);
58+
59+
$result = $resolver->getInChannel($channel);
60+
61+
$this->assertSame([$paypalMethodHigh, $paypalMethodLow], $result);
62+
}
63+
64+
public function test_returns_empty_array_when_channel_has_no_paypal_methods(): void
65+
{
66+
$repository = $this->createMock(PaymentMethodRepositoryInterface::class);
67+
$channel = $this->createMock(ChannelInterface::class);
68+
69+
$nonPaypalGateway = $this->createMock(GatewayConfigInterface::class);
70+
$nonPaypalGateway->method('getFactoryName')->willReturn('non_paypal');
71+
72+
$nonPaypalMethod = $this->createMock(PaymentMethodInterface::class);
73+
$nonPaypalMethod->method('getGatewayConfig')->willReturn($nonPaypalGateway);
74+
75+
$repository
76+
->expects($this->once())
77+
->method('findEnabledForChannel')
78+
->with($channel)
79+
->willReturn([$nonPaypalMethod]);
80+
81+
$resolver = new PayPalPaymentMethodsResolver($repository);
82+
83+
$this->assertSame([], $resolver->getInChannel($channel));
84+
}
85+
86+
public function test_returns_empty_array_when_channel_has_no_payment_methods(): void
87+
{
88+
$repository = $this->createMock(PaymentMethodRepositoryInterface::class);
89+
$channel = $this->createMock(ChannelInterface::class);
90+
91+
$repository
92+
->expects($this->once())
93+
->method('findEnabledForChannel')
94+
->with($channel)
95+
->willReturn([]);
96+
97+
$resolver = new PayPalPaymentMethodsResolver($repository);
98+
99+
$this->assertSame([], $resolver->getInChannel($channel));
100+
}
101+
102+
public function test_skips_methods_without_gateway_configuration(): void
103+
{
104+
$repository = $this->createMock(PaymentMethodRepositoryInterface::class);
105+
$channel = $this->createMock(ChannelInterface::class);
106+
107+
$paypalGateway = $this->createMock(GatewayConfigInterface::class);
108+
$paypalGateway->method('getFactoryName')->willReturn(SyliusPayPalExtension::PAYPAL_FACTORY_NAME);
109+
110+
$paypalMethod = $this->createMock(PaymentMethodInterface::class);
111+
$paypalMethod->method('getGatewayConfig')->willReturn($paypalGateway);
112+
$paypalMethod->method('getPosition')->willReturn(1);
113+
114+
$methodWithoutGateway = $this->createMock(PaymentMethodInterface::class);
115+
$methodWithoutGateway->method('getGatewayConfig')->willReturn(null);
116+
117+
$repository
118+
->expects($this->once())
119+
->method('findEnabledForChannel')
120+
->with($channel)
121+
->willReturn([$methodWithoutGateway, $paypalMethod]);
122+
123+
$resolver = new PayPalPaymentMethodsResolver($repository);
124+
125+
$this->assertSame([$paypalMethod], $resolver->getInChannel($channel));
126+
}
127+
}

0 commit comments

Comments
 (0)