Skip to content

Commit 64d48ba

Browse files
authored
Merge pull request #44 from shopware5/pt-13151/capture-timeout
PT-13151 - Handle session timeout while capture payment
2 parents e0aa11c + 38697a6 commit 64d48ba

27 files changed

+833
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: PHP
2+
3+
on:
4+
workflow_call:
5+
6+
jobs:
7+
call-legacy-analyse-workflow:
8+
name: PHP legacy code analysis
9+
runs-on: ubuntu-latest
10+
container:
11+
image: ghcr.io/shopware5/docker-images-testing/install:shopware_5.2_5.7_5.6_5.2.11-5.2.27
12+
credentials:
13+
username: ${{ github.actor }}
14+
password: ${{ secrets.github_token }}
15+
env:
16+
GH_TOKEN: ${{ github.token }}
17+
18+
steps:
19+
- run: /usr/bin/supervisord -c /etc/supervisord.conf &
20+
21+
- name: Checkout SwagPaymentPayPalUnified
22+
uses: actions/checkout@v3
23+
with:
24+
path: plugin
25+
26+
- name: Move plugin
27+
run: mv "$(pwd)/plugin" /shopware/custom/plugins/SwagPaymentPayPalUnified
28+
29+
- name: Setup SwagPaymentPayPalUnified
30+
run: |
31+
cd /shopware/custom/plugins/SwagPaymentPayPalUnified
32+
make init-legacy
33+
34+
- name: Setup legacy tests
35+
run: |
36+
cd /shopware/custom/plugins/SwagPaymentPayPalUnified
37+
composer require --dev phpcompatibility/php-compatibility
38+
39+
- name: Run legacy tests
40+
run: |
41+
cd /shopware/custom/plugins/SwagPaymentPayPalUnified
42+
./vendor/bin/phpcs --config-set installed_paths vendor/phpcompatibility/php-compatibility
43+
./vendor/bin/phpcs -p --ignore="./vendor/,./PhpStan/" --standard="PHPCompatibility" --runtime-set testVersion 5.6 ./

.github/workflows/tests-launcher.yml

+7-3
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,22 @@ jobs:
2424
uses: ./.github/workflows/php-code-analysis.yml
2525
secrets: inherit
2626

27+
php-code-analysis-legacy:
28+
name: PHP
29+
uses: ./.github/workflows/php-code-analysis-legacy.yml
30+
secrets: inherit
31+
2732
php-unit-tests-shopware-5-7:
2833
name: Unit tests
2934
uses: ./.github/workflows/php-unit-tests-shopware-5-7.yml
3035
secrets: inherit
31-
needs: [ javascript-code-analysis, php-code-analysis ]
36+
needs: [ javascript-code-analysis, php-code-analysis, php-code-analysis-legacy ]
3237

3338
php-unit-tests-shopware-legacy-versions:
3439
name: Unit tests legacy
3540
uses: ./.github/workflows/php-unit-tests-shopware-legacy.yml
3641
secrets: inherit
37-
needs: [ javascript-code-analysis, php-code-analysis ]
38-
42+
needs: [ javascript-code-analysis, php-code-analysis, php-code-analysis-legacy ]
3943

4044
php-e2e-tests-shopware-5-7:
4145
name: E2E

.php-cs-fixer.php

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
'single_line_throw' => false,
6060
'visibility_required' => ['elements' => ['property', 'method']],
6161
'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false],
62+
'nullable_type_declaration_for_default_null_value' => false,
6263

6364
NoSuperfluousConcatenationFixer::name() => true,
6465
NoUselessCommentFixer::name() => true,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
/**
3+
* (c) shopware AG <info@shopware.com>
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*/
8+
9+
namespace SwagPaymentPayPalUnified\Components\Exception;
10+
11+
use Exception;
12+
13+
class InvalidOrderException extends Exception
14+
{
15+
/**
16+
* @param string $paypalOrderId
17+
*/
18+
public function __construct($paypalOrderId)
19+
{
20+
$message = \sprintf('No capture found in order with ID: %s', $paypalOrderId);
21+
22+
parent::__construct($message);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
/**
3+
* (c) shopware AG <info@shopware.com>
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*/
8+
9+
namespace SwagPaymentPayPalUnified\Components\Exception;
10+
11+
use Exception;
12+
13+
class TimeoutInfoException extends Exception
14+
{
15+
/**
16+
* @param string $paypalOrderId
17+
*/
18+
public function __construct($paypalOrderId)
19+
{
20+
$message = \sprintf('Timeout information not found for PayPal order with ID: %s', $paypalOrderId);
21+
22+
parent::__construct($message);
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
/**
3+
* (c) shopware AG <info@shopware.com>
4+
*
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*/
8+
9+
namespace SwagPaymentPayPalUnified\Components\Services;
10+
11+
use DateTime;
12+
use Doctrine\DBAL\Connection;
13+
use PDO;
14+
use Shopware\Bundle\StoreFrontBundle\Service\Core\ContextService;
15+
use SwagPaymentPayPalUnified\Components\Exception\TimeoutInfoException;
16+
use SwagPaymentPayPalUnified\PayPalBundle\V2\Api\Order\PurchaseUnit\Payments\Refund;
17+
use SwagPaymentPayPalUnified\PayPalBundle\V2\Api\Order\PurchaseUnit\Payments\Refund\Amount;
18+
use SwagPaymentPayPalUnified\PayPalBundle\V2\Resource\CaptureResource;
19+
20+
class TimeoutRefundService
21+
{
22+
/**
23+
* @var Connection
24+
*/
25+
private $connection;
26+
27+
/**
28+
* @var ContextService
29+
*/
30+
private $contextService;
31+
32+
/**
33+
* @var CaptureResource
34+
*/
35+
private $captureResource;
36+
37+
public function __construct(
38+
Connection $connection,
39+
ContextService $contextService,
40+
CaptureResource $captureResource
41+
) {
42+
$this->connection = $connection;
43+
$this->contextService = $contextService;
44+
$this->captureResource = $captureResource;
45+
}
46+
47+
/**
48+
* @param string $payPalOrderId
49+
* @param float $orderAmount
50+
*
51+
* @return void
52+
*/
53+
public function saveInfo($payPalOrderId, $orderAmount)
54+
{
55+
$this->connection->insert('swag_payment_paypal_unified_order_refund_info', [
56+
'paypal_order_id' => (string) $payPalOrderId,
57+
'order_amount' => (string) $orderAmount,
58+
'currency' => $this->contextService->getShopContext()->getCurrency()->getCurrency(),
59+
'created_at' => (string) (new DateTime())->getTimestamp(),
60+
]);
61+
}
62+
63+
/**
64+
* @param string $payPalOrderId
65+
*
66+
* @return void
67+
*/
68+
public function deleteInfo($payPalOrderId)
69+
{
70+
$this->connection->delete('swag_payment_paypal_unified_order_refund_info', [
71+
'paypal_order_id' => $payPalOrderId,
72+
]);
73+
}
74+
75+
/**
76+
* @param string $payPalOrderId
77+
* @param string $captureId
78+
*
79+
* @return void
80+
*/
81+
public function refund($payPalOrderId, $captureId)
82+
{
83+
$info = $this->getInfo($payPalOrderId);
84+
if (!\is_array($info)) {
85+
throw new TimeoutInfoException($payPalOrderId);
86+
}
87+
88+
$amount = new Amount();
89+
$amount->setCurrencyCode($info['currency']);
90+
$amount->setValue($info['order_amount']);
91+
92+
$refund = new Refund();
93+
$refund->setAmount($amount);
94+
$refund->setNoteToPayer('User timeout');
95+
96+
$this->captureResource->refund($captureId, $refund);
97+
}
98+
99+
/**
100+
* @param string $payPalOrderId
101+
*
102+
* @return array<string, string>|null
103+
*/
104+
private function getInfo($payPalOrderId)
105+
{
106+
$result = $this->connection->createQueryBuilder()
107+
->select(['*'])
108+
->from('swag_payment_paypal_unified_order_refund_info')
109+
->where('paypal_order_id = :paypalOrderId')
110+
->setParameter('paypalOrderId', $payPalOrderId)
111+
->execute()
112+
->fetch(PDO::FETCH_ASSOC);
113+
114+
if (!\is_array($result)) {
115+
return null;
116+
}
117+
118+
return $result;
119+
}
120+
}

Controllers/Backend/PaypalUnifiedSettings.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public function updateCredentialsAction()
236236
$credentialsService->updateCredentials($credentials, $shopId, $sandbox);
237237

238238
$this->updateOnboardingStatus($shopId, $sandbox);
239-
} catch (\Exception $e) {
239+
} catch (Exception $e) {
240240
$this->response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
241241
$this->view->assign([
242242
'exception' => $e->getMessage(),

Controllers/Frontend/PaypalUnified.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class Shopware_Controllers_Frontend_PaypalUnified extends Shopware_Controllers_F
6060
private $settingsService;
6161

6262
/**
63-
* @var \Shopware_Components_Config
63+
* @var Shopware_Components_Config
6464
*/
6565
private $shopwareConfig;
6666

@@ -141,7 +141,7 @@ public function gatewayAction()
141141
$this->handleError(ErrorCodes::COMMUNICATION_FAILURE, $requestEx);
142142

143143
return;
144-
} catch (\Exception $exception) {
144+
} catch (Exception $exception) {
145145
$this->handleError(ErrorCodes::UNKNOWN, $exception);
146146

147147
return;
@@ -163,7 +163,7 @@ public function gatewayAction()
163163
$addressPatch,
164164
$payerInfoPatch,
165165
]);
166-
} catch (\Exception $exception) {
166+
} catch (Exception $exception) {
167167
$this->handleError(ErrorCodes::ADDRESS_VALIDATION_ERROR, $exception);
168168

169169
return;

Controllers/Frontend/PaypalUnifiedApm.php

+54
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,32 @@
77
*/
88

99
use SwagPaymentPayPalUnified\Components\ErrorCodes;
10+
use SwagPaymentPayPalUnified\Components\Exception\InvalidOrderException;
11+
use SwagPaymentPayPalUnified\Components\Exception\TimeoutInfoException;
1012
use SwagPaymentPayPalUnified\Components\PayPalOrderParameter\ShopwareOrderData;
13+
use SwagPaymentPayPalUnified\Components\Services\TimeoutRefundService;
1114
use SwagPaymentPayPalUnified\Controllers\Frontend\AbstractPaypalPaymentController;
1215
use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\EmptyCartException;
1316
use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidBillingAddressException;
1417
use SwagPaymentPayPalUnified\Controllers\Frontend\Exceptions\InvalidShippingAddressException;
1518
use SwagPaymentPayPalUnified\PayPalBundle\V2\Api\Common\Link;
1619
use SwagPaymentPayPalUnified\PayPalBundle\V2\Api\Order;
20+
use SwagPaymentPayPalUnified\PayPalBundle\V2\Api\Order\PurchaseUnit\Payments\Capture;
1721

1822
class Shopware_Controllers_Frontend_PaypalUnifiedApm extends AbstractPaypalPaymentController
1923
{
24+
/**
25+
* @var TimeoutRefundService
26+
*/
27+
private $timeoutRefundService;
28+
29+
public function preDispatch()
30+
{
31+
parent::preDispatch();
32+
33+
$this->timeoutRefundService = $this->get('swag_payment_paypal_unified.timeout_refund_service');
34+
}
35+
2036
/**
2137
* @return void
2238
*/
@@ -85,6 +101,8 @@ public function indexAction()
85101
return;
86102
}
87103

104+
$this->timeoutRefundService->saveInfo($payPalOrder->getId(), $this->getAmount());
105+
88106
$url = $this->getUrl($payPalOrder, Link::RELATION_PAYER_ACTION_REQUIRED);
89107

90108
$this->logger->debug(sprintf('%s REDIRECT TO: %s', __METHOD__, $url));
@@ -113,6 +131,27 @@ public function returnAction()
113131
}
114132

115133
if (!$this->isCartValid($payPalOrder)) {
134+
if ($this->getUser() === null) {
135+
try {
136+
$this->timeoutRefundService->refund($payPalOrderId, $this->getCaptureId($payPalOrder));
137+
} catch (TimeoutInfoException $exception) {
138+
$this->logger->error($exception->getMessage());
139+
} catch (InvalidOrderException $exception) {
140+
$this->logger->error($exception->getMessage());
141+
}
142+
143+
$this->timeoutRefundService->deleteInfo($payPalOrderId);
144+
145+
$this->redirect([
146+
'module' => 'frontend',
147+
'controller' => 'register',
148+
'action' => 'index',
149+
'paymentApproveTimeout' => true,
150+
]);
151+
152+
return;
153+
}
154+
116155
$redirectDataBuilder = $this->redirectDataBuilderFactory->createRedirectDataBuilder()
117156
->setCode(ErrorCodes::BASKET_VALIDATION_ERROR);
118157

@@ -121,6 +160,8 @@ public function returnAction()
121160
return;
122161
}
123162

163+
$this->timeoutRefundService->deleteInfo($payPalOrderId);
164+
124165
if ($this->Request()->isXmlHttpRequest()) {
125166
$this->view->assign('token', $payPalOrderId);
126167

@@ -180,4 +221,17 @@ public function returnAction()
180221
'requireContactToMerchant' => true,
181222
]);
182223
}
224+
225+
/**
226+
* @return string
227+
*/
228+
private function getCaptureId(Order $payPalOrder)
229+
{
230+
$capture = $this->orderPropertyHelper->getFirstCapture($payPalOrder);
231+
if (!$capture instanceof Capture) {
232+
throw new InvalidOrderException($payPalOrder->getId());
233+
}
234+
235+
return $capture->getId();
236+
}
183237
}

Controllers/Widgets/PayPalUnifiedOrderNumber.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public function restoreOrderNumberAction()
4848

4949
try {
5050
$this->orderNumberService->restoreOrderNumberToPool();
51-
} catch (\Exception $exception) {
51+
} catch (Exception $exception) {
5252
$this->logger->error(sprintf('%s Cannot restore order number to pool.', __METHOD__), [
5353
'exceptionMessage' => $exception->getMessage(),
5454
'exceptionTrace' => $exception->getTrace(),

Controllers/Widgets/PaypalUnifiedV2PayUponInvoice.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function pollOrderAction()
3535
$payPalOrderId = $this->request->get('sUniqueID');
3636

3737
if (!\is_string($payPalOrderId)) {
38-
throw new \DomainException('The Paypal id must exist in the session');
38+
throw new DomainException('The Paypal id must exist in the session');
3939
}
4040

4141
$order = $this->orderResource->get($payPalOrderId);

0 commit comments

Comments
 (0)