Skip to content

Commit fee6748

Browse files
authored
Fix performance issue of getting all payments within the payment flow (Sylius#392)
| Q | A | --------------- | ----- | Branch? | 1.7 | Bug fix? | yes | New feature? | no | Related tickets | fixes Sylius#247, replaces Sylius#248
2 parents b8bd8a0 + 53afaea commit fee6748

File tree

9 files changed

+620
-19
lines changed

9 files changed

+620
-19
lines changed

src/Controller/CancelPayPalCheckoutPaymentAction.php

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,31 @@
1515

1616
use Sylius\PayPalPlugin\Manager\PaymentStateManagerInterface;
1717
use Sylius\PayPalPlugin\Provider\PaymentProviderInterface;
18+
use Sylius\PayPalPlugin\Repository\Query\PaypalPaymentQueryInterface;
1819
use Symfony\Component\HttpFoundation\Request;
1920
use Symfony\Component\HttpFoundation\Response;
2021
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
2122

2223
final class CancelPayPalCheckoutPaymentAction
2324
{
24-
private PaymentProviderInterface $paymentProvider;
25-
26-
private PaymentStateManagerInterface $paymentStateManager;
27-
2825
public function __construct(
29-
PaymentProviderInterface $paymentProvider,
30-
PaymentStateManagerInterface $paymentStateManager,
26+
private PaymentProviderInterface $paymentProvider,
27+
private PaymentStateManagerInterface $paymentStateManager,
28+
private ?PaypalPaymentQueryInterface $paypalPaymentQuery = null,
3129
) {
32-
$this->paymentProvider = $paymentProvider;
33-
$this->paymentStateManager = $paymentStateManager;
3430
}
3531

3632
public function __invoke(Request $request): Response
3733
{
38-
/**
39-
* @var string $content
40-
*/
34+
/** @var string $content */
4135
$content = $request->getContent();
42-
4336
$content = (array) json_decode($content, true);
4437

45-
$payment = $this->paymentProvider->getByPayPalOrderId((string) $content['payPalOrderId']);
38+
if (null !== $this->paypalPaymentQuery) {
39+
$payment = $this->paypalPaymentQuery->getForCancellationByOrderId((string) $content['payPalOrderId']);
40+
} else {
41+
$payment = $this->paymentProvider->getByPayPalOrderId((string) $content['payPalOrderId']);
42+
}
4643

4744
/** @var FlashBagInterface $flashBag */
4845
$flashBag = $request->getSession()->getBag('flashes');

src/Controller/CancelPayPalPaymentAction.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Sylius\Component\Payment\PaymentTransitions;
2323
use Sylius\PayPalPlugin\Provider\FlashBagProvider;
2424
use Sylius\PayPalPlugin\Provider\PaymentProviderInterface;
25+
use Sylius\PayPalPlugin\Repository\Query\PaypalPaymentQueryInterface;
2526
use Symfony\Component\HttpFoundation\Request;
2627
use Symfony\Component\HttpFoundation\RequestStack;
2728
use Symfony\Component\HttpFoundation\Response;
@@ -35,6 +36,7 @@ public function __construct(
3536
private readonly FlashBag|RequestStack $flashBagOrRequestStack,
3637
private readonly FactoryInterface|StateMachineInterface $stateMachineFactory,
3738
private readonly OrderProcessorInterface $orderPaymentProcessor,
39+
private readonly ?PaypalPaymentQueryInterface $paypalPaymentQuery = null,
3840
) {
3941
if ($flashBagOrRequestStack instanceof FlashBag) {
4042
trigger_deprecation('sylius/paypal-plugin', '1.5', sprintf('Passing an instance of %s as constructor argument for %s is deprecated as of PayPalPlugin 1.5 and will be removed in 2.0. Pass an instance of %s instead.', FlashBag::class, self::class, RequestStack::class));
@@ -55,13 +57,15 @@ public function __construct(
5557

5658
public function __invoke(Request $request): Response
5759
{
58-
/**
59-
* @var string $content
60-
*/
60+
/** @var string $content */
6161
$content = $request->getContent();
6262
$content = (array) json_decode($content, true);
6363

64-
$payment = $this->paymentProvider->getByPayPalOrderId((string) $content['payPalOrderId']);
64+
if (null !== $this->paypalPaymentQuery) {
65+
$payment = $this->paypalPaymentQuery->getForCancellationByOrderId((string) $content['payPalOrderId']);
66+
} else {
67+
$payment = $this->paymentProvider->getByPayPalOrderId((string) $content['payPalOrderId']);
68+
}
6569

6670
/** @var OrderInterface $order */
6771
$order = $payment->getOrder();

src/Controller/UpdatePayPalOrderAction.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Sylius\PayPalPlugin\Api\OrderDetailsApiInterface;
2424
use Sylius\PayPalPlugin\Api\UpdateOrderApiInterface;
2525
use Sylius\PayPalPlugin\Provider\PaymentProviderInterface;
26+
use Sylius\PayPalPlugin\Repository\Query\PaypalPaymentQueryInterface;
2627
use Symfony\Component\HttpFoundation\JsonResponse;
2728
use Symfony\Component\HttpFoundation\Request;
2829
use Symfony\Component\HttpFoundation\Response;
@@ -36,6 +37,7 @@ public function __construct(
3637
private readonly UpdateOrderApiInterface $updateOrderApi,
3738
private readonly AddressFactoryInterface $addressFactory,
3839
private readonly OrderProcessorInterface $orderProcessor,
40+
private readonly ?PaypalPaymentQueryInterface $paypalPaymentQuery = null,
3941
) {
4042
if (null !== $this->orderDetailsApi) {
4143
trigger_deprecation(
@@ -51,7 +53,11 @@ public function __construct(
5153

5254
public function __invoke(Request $request): Response
5355
{
54-
$payment = $this->paymentProvider->getByPayPalOrderId((string) $request->request->get('orderID'));
56+
if (null !== $this->paypalPaymentQuery) {
57+
$payment = $this->paypalPaymentQuery->getForUpdateByOrderId((string) $request->request->get('orderID'));
58+
} else {
59+
$payment = $this->paymentProvider->getByPayPalOrderId((string) $request->request->get('orderID'));
60+
}
5561
/** @var OrderInterface $order */
5662
$order = $payment->getOrder();
5763

src/Controller/Webhook/RefundOrderAction.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Sylius\PayPalPlugin\Exception\PayPalWrongDataException;
2323
use Sylius\PayPalPlugin\Provider\PaymentProviderInterface;
2424
use Sylius\PayPalPlugin\Provider\PayPalRefundDataProviderInterface;
25+
use Sylius\PayPalPlugin\Repository\Query\PaypalPaymentQueryInterface;
2526
use Symfony\Component\HttpFoundation\JsonResponse;
2627
use Symfony\Component\HttpFoundation\Request;
2728
use Symfony\Component\HttpFoundation\Response;
@@ -34,6 +35,7 @@ public function __construct(
3435
private readonly PaymentProviderInterface $paymentProvider,
3536
private readonly ObjectManager $paymentManager,
3637
private readonly PayPalRefundDataProviderInterface $payPalRefundDataProvider,
38+
private readonly ?PaypalPaymentQueryInterface $paypalPaymentQuery = null,
3739
) {
3840
if ($this->stateMachineFactory instanceof FactoryInterface) {
3941
trigger_deprecation(
@@ -53,7 +55,11 @@ public function __invoke(Request $request): Response
5355
$refundData = $this->payPalRefundDataProvider->provide($this->getPayPalPaymentUrl($request));
5456

5557
try {
56-
$payment = $this->paymentProvider->getByPayPalOrderId((string) $refundData['id']);
58+
if (null !== $this->paypalPaymentQuery) {
59+
$payment = $this->paypalPaymentQuery->getForRefundingByOrderId((string) $refundData['id']);
60+
} else {
61+
$payment = $this->paymentProvider->getByPayPalOrderId((string) $refundData['id']);
62+
}
5763
} catch (PaymentNotFoundException $exception) {
5864
return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_NOT_FOUND);
5965
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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\Repository\Query;
15+
16+
use Doctrine\ORM\EntityManagerInterface;
17+
use Doctrine\ORM\QueryBuilder;
18+
use Sylius\Component\Core\Model\PaymentInterface;
19+
use Sylius\Component\Core\Repository\PaymentRepositoryInterface;
20+
use Sylius\PayPalPlugin\DependencyInjection\SyliusPayPalExtension;
21+
use Sylius\PayPalPlugin\Exception\PaymentNotFoundException;
22+
23+
final class PaypalPaymentQuery implements PaypalPaymentQueryInterface
24+
{
25+
public function __construct(
26+
private readonly EntityManagerInterface $entityManager,
27+
private readonly PaymentRepositoryInterface $paymentRepository,
28+
private readonly array $updatableStates = ['cart', 'new', 'processing'],
29+
private readonly array $cancellableStates = ['cart', 'new', 'processing', 'completed'],
30+
private readonly array $refundableStates = ['completed'],
31+
) {
32+
}
33+
34+
public function getForUpdateByOrderId(string $paypalOrderId): ?PaymentInterface
35+
{
36+
$queryBuilder = $this->getPaypalPaymentQueryBuilder()
37+
->andWhere('o.state IN (:states)')
38+
->setParameter('states', $this->updatableStates)
39+
->addOrderBy('o.createdAt', 'DESC')
40+
;
41+
42+
return $this->doGetPayment($queryBuilder, $paypalOrderId);
43+
}
44+
45+
public function getForCancellationByOrderId(string $paypalOrderId): ?PaymentInterface
46+
{
47+
$queryBuilder = $this->getPaypalPaymentQueryBuilder()
48+
->andWhere('o.state IN (:states)')
49+
->setParameter('states', $this->cancellableStates)
50+
->addOrderBy('o.createdAt', 'DESC')
51+
;
52+
53+
return $this->doGetPayment($queryBuilder, $paypalOrderId);
54+
}
55+
56+
public function getForRefundingByOrderId(string $paypalOrderId): ?PaymentInterface
57+
{
58+
$queryBuilder = $this->getPaypalPaymentQueryBuilder()
59+
->andWhere('o.state IN (:states)')
60+
->setParameter('states', $this->refundableStates)
61+
->addOrderBy('o.updatedAt', 'DESC')
62+
;
63+
64+
return $this->doGetPayment($queryBuilder, $paypalOrderId);
65+
}
66+
67+
private function doGetPayment(QueryBuilder $queryBuilder, string $paypalOrderId): ?PaymentInterface
68+
{
69+
if ($this->isCastAvailable()) {
70+
$payment = $queryBuilder
71+
->andWhere('CAST(o.details AS text) LIKE :orderId')
72+
->setParameter('orderId', '%"' . $paypalOrderId . '"%')
73+
->setMaxResults(1)
74+
->getQuery()
75+
->getOneOrNullResult()
76+
;
77+
if (null !== $payment) {
78+
return $payment;
79+
}
80+
81+
throw new PaymentNotFoundException();
82+
}
83+
84+
$payments = $queryBuilder
85+
->getQuery()
86+
->toIterable()
87+
;
88+
89+
foreach ($payments as $payment) {
90+
$details = $payment->getDetails();
91+
if (isset($details['paypal_order_id']) && $details['paypal_order_id'] === $paypalOrderId) {
92+
return $payment;
93+
}
94+
}
95+
96+
throw new PaymentNotFoundException();
97+
}
98+
99+
private function getPaypalPaymentQueryBuilder(): QueryBuilder
100+
{
101+
return $this->paymentRepository
102+
->createQueryBuilder('o')
103+
->innerJoin('o.method', 'method')
104+
->innerJoin('method.gatewayConfig', 'gatewayConfig')
105+
->andWhere('gatewayConfig.factoryName = :factoryName')
106+
->setParameter('factoryName', SyliusPayPalExtension::PAYPAL_FACTORY_NAME)
107+
;
108+
}
109+
110+
private function isCastAvailable(): bool
111+
{
112+
return null !== $this->entityManager->getConfiguration()->getCustomStringFunction('CAST');
113+
}
114+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\Repository\Query;
15+
16+
use Sylius\Component\Core\Model\PaymentInterface;
17+
use Sylius\PayPalPlugin\Exception\PaymentNotFoundException;
18+
19+
interface PaypalPaymentQueryInterface
20+
{
21+
/** @throws PaymentNotFoundException */
22+
public function getForUpdateByOrderId(string $paypalOrderId): ?PaymentInterface;
23+
24+
/** @throws PaymentNotFoundException */
25+
public function getForCancellationByOrderId(string $paypalOrderId): ?PaymentInterface;
26+
27+
/** @throws PaymentNotFoundException */
28+
public function getForRefundingByOrderId(string $paypalOrderId): ?PaymentInterface;
29+
}

src/Resources/config/services.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@
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+
27+
<parameter type="collection" key="sylius_paypal.repository.query.pay_pal_payment.updatable_states">
28+
<parameter type="constant">Sylius\Component\Payment\Model\PaymentInterface::STATE_CART</parameter>
29+
<parameter type="constant">Sylius\Component\Payment\Model\PaymentInterface::STATE_NEW</parameter>
30+
</parameter>
31+
<parameter type="collection" key="sylius_paypal.repository.query.pay_pal_payment.cancellable_states">
32+
<parameter type="constant">Sylius\Component\Payment\Model\PaymentInterface::STATE_CART</parameter>
33+
<parameter type="constant">Sylius\Component\Payment\Model\PaymentInterface::STATE_NEW</parameter>
34+
<parameter type="constant">Sylius\Component\Payment\Model\PaymentInterface::STATE_PROCESSING</parameter>
35+
<parameter type="constant">Sylius\Component\Payment\Model\PaymentInterface::STATE_AUTHORIZED</parameter>
36+
</parameter>
37+
<parameter type="collection" key="sylius_paypal.repository.query.pay_pal_payment.refundable_states">
38+
<parameter type="constant">Sylius\Component\Payment\Model\PaymentInterface::STATE_COMPLETED</parameter>
39+
</parameter>
2640
</parameters>
2741

2842
<services>
@@ -322,5 +336,17 @@
322336

323337
<service id="sylius_paypal.verifier.payment_amount" class="Sylius\PayPalPlugin\Verifier\PaymentAmountVerifier" />
324338
<service id="Sylius\PayPalPlugin\Verifier\PaymentAmountVerifierInterface" alias="sylius_paypal.verifier.payment_amount" />
339+
340+
<service
341+
id="sylius_paypal.repository.query.paypal_payment"
342+
class="Sylius\PayPalPlugin\Repository\Query\PaypalPaymentQuery"
343+
>
344+
<argument type="service" id="sylius.manager.payment" />
345+
<argument type="service" id="sylius.repository.payment"/>
346+
<argument>%sylius_paypal.repository.query.pay_pal_payment.updatable_states%</argument>
347+
<argument>%sylius_paypal.repository.query.pay_pal_payment.cancellable_states%</argument>
348+
<argument>%sylius_paypal.repository.query.pay_pal_payment.refundable_states%</argument>
349+
</service>
350+
<service id="Sylius\PayPalPlugin\Repository\Query\PaypalPaymentQueryInterface" alias="sylius_paypal.repository.query.paypal_payment" />
325351
</services>
326352
</container>

src/Resources/config/services/controller.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<argument type="service" id="sylius_paypal.provider.payment" />
2525
<argument type="service" id="sylius.manager.payment" />
2626
<argument type="service" id="sylius_paypal.provider.paypal_refund_data" />
27+
<argument type="service" id="sylius_paypal.repository.query.paypal_payment" />
2728
</service>
2829
<service id="sylius_paypal.controller.webhook.refund_order" alias="Sylius\PayPalPlugin\Controller\Webhook\RefundOrderAction" />
2930

@@ -40,6 +41,7 @@
4041
<argument type="service" id="request_stack" />
4142
<argument type="service" id="sylius_abstraction.state_machine" />
4243
<argument type="service" id="sylius.order_processing.order_payment_processor.checkout" />
44+
<argument type="service" id="sylius_paypal.repository.query.paypal_payment" />
4345
</service>
4446
<service id="sylius_paypal.controller.cancel_paypal_payment" alias="Sylius\PayPalPlugin\Controller\CancelPayPalPaymentAction" />
4547

@@ -54,6 +56,7 @@
5456
<service id="Sylius\PayPalPlugin\Controller\CancelPayPalCheckoutPaymentAction">
5557
<argument type="service" id="sylius_paypal.provider.payment" />
5658
<argument type="service" id="sylius_paypal.manager.payment_state" />
59+
<argument type="service" id="sylius_paypal.repository.query.paypal_payment" />
5760
</service>
5861
<service id="sylius_paypal.controller.cancel_paypal_checkout_payment" alias="Sylius\PayPalPlugin\Controller\CancelPayPalCheckoutPaymentAction" />
5962

@@ -153,6 +156,7 @@
153156
<argument type="service" id="sylius_paypal.api.update_order" />
154157
<argument type="service" id="sylius.factory.address" />
155158
<argument type="service" id="sylius.order_processing.order_processor" />
159+
<argument type="service" id="sylius_paypal.repository.query.paypal_payment" />
156160
</service>
157161
<service id="sylius_paypal.controller.update_paypal_order" alias="Sylius\PayPalPlugin\Controller\UpdatePayPalOrderAction" />
158162

0 commit comments

Comments
 (0)