Skip to content

Commit 71ef1b5

Browse files
authored
[PAYSHIP-3662] Added authorization capture logic (#1420)
* Added authorization capture logic --------- Co-authored-by: L3RAZ <[email protected]>
1 parent d8e00cc commit 71ef1b5

27 files changed

+796
-441
lines changed

api/src/Http/Exception/PayPalError.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public function __construct($message)
4242
*
4343
* @throws PayPalException
4444
*/
45-
public function throwException(HttpException $previous = null)
45+
public function throwException(?HttpException $previous)
4646
{
4747
switch ($this->message) {
4848
case 'ACTION_DOES_NOT_MATCH_INTENT':

api/src/Http/OrderHttpClient.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public function sendRequest(RequestInterface $request): ResponseInterface
5757
/**
5858
* {@inheritdoc}
5959
*/
60-
public function createOrder(array $payload, string $requestId = null, string $clientMetadataId = null): ResponseInterface
60+
public function createOrder(array $payload, ?string $requestId, ?string $clientMetadataId): ResponseInterface
6161
{
6262
$headers = [];
6363

@@ -83,7 +83,7 @@ public function fetchOrder(string $orderId): ResponseInterface
8383
/**
8484
* {@inheritdoc}
8585
*/
86-
public function captureOrder(string $orderId, array $payload, string $requestId = null, string $clientMetadataId = null): ResponseInterface
86+
public function captureOrder(string $orderId, array $payload, ?string $requestId = null, ?string $clientMetadataId = null): ResponseInterface
8787
{
8888
$headers = [];
8989

api/src/Http/OrderHttpClientInterface.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ interface OrderHttpClientInterface
3838
*
3939
* @throws NetworkException|HttpException|RequestException|TransferException|PayPalException
4040
*/
41-
public function createOrder(array $payload, string $requestId = null, string $clientMetadataId = null): ResponseInterface;
41+
public function createOrder(array $payload, ?string $requestId, ?string $clientMetadataId): ResponseInterface;
4242

4343
/**
4444
* @param string $orderId
@@ -60,7 +60,7 @@ public function fetchOrder(string $orderId): ResponseInterface;
6060
*
6161
* @throws NetworkException|HttpException|RequestException|TransferException|PayPalException
6262
*/
63-
public function captureOrder(string $orderId, array $payload, string $requestId = null, string $clientMetadataId = null): ResponseInterface;
63+
public function captureOrder(string $orderId, array $payload, ?string $requestId = null, ?string $clientMetadataId = null): ResponseInterface;
6464

6565
/**
6666
* @param array $payload

api/src/Http/PaymentHttpClient.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ public function refundOrder(string $captureId, array $payload): ResponseInterfac
7777
return $this->sendRequest(new Request('POST', "captures/$captureId/refund", [], json_encode($payload)));
7878
}
7979

80+
/**
81+
* {@inheritdoc}
82+
*/
83+
public function captureAuthorization(string $authorizationId, array $payload = []): ResponseInterface
84+
{
85+
$payloadString = !empty($payload) && json_encode($payload) ? json_encode($payload) : '{}';
86+
87+
return $this->sendRequest(new Request('POST', "authorizations/$authorizationId/capture", [], $payloadString));
88+
}
89+
8090
/**
8191
* @inheritDoc
8292
*/

api/src/Http/PaymentHttpClientInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ interface PaymentHttpClientInterface
4141
*/
4242
public function refundOrder(string $captureId, array $payload): ResponseInterface;
4343

44+
/**
45+
* @param string $authorizationId
46+
* @param array<mixed> $payload
47+
*
48+
* @return ResponseInterface
49+
*
50+
* @throws NetworkException|HttpException|RequestException|TransferException|PayPalException
51+
*/
52+
public function captureAuthorization(string $authorizationId, array $payload = []): ResponseInterface;
53+
4454
/**
4555
* @param string $authorizationId
4656
*

api/src/ValueObject/PayPalOrderResponse.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,15 +309,39 @@ public function getOrderAmountValue()
309309
*/
310310
public function getVault()
311311
{
312-
return $this->getPaymentSource()[key($this->getPaymentSource())]['attributes']['vault'] ?? null;
312+
$paymentSourceArray = $this->getPaymentSource();
313+
314+
if (!is_array($paymentSourceArray)) {
315+
return null;
316+
}
317+
318+
$paymentSource = key($paymentSourceArray);
319+
320+
if ($paymentSource) {
321+
return $paymentSourceArray[$paymentSource]['attributes']['vault'] ?? null;
322+
}
323+
324+
return null;
313325
}
314326

315327
/**
316328
* @return string|null
317329
*/
318330
public function getCustomerId()
319331
{
320-
return $this->getPaymentSource()[key($this->getPaymentSource())]['attributes']['vault']['customer']['id'] ?? null;
332+
$paymentSourceArray = $this->getPaymentSource();
333+
334+
if (!is_array($paymentSourceArray)) {
335+
return null;
336+
}
337+
338+
$paymentSource = key($paymentSourceArray);
339+
340+
if ($paymentSource) {
341+
return $paymentSourceArray[$paymentSource]['attributes']['vault']['customer']['id'] ?? null;
342+
}
343+
344+
return null;
321345
}
322346

323347
/**

core/src/Exception/PsCheckoutException.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,21 @@ class PsCheckoutException extends \Exception
146146

147147
const PAYPAL_ORDER_INTENT_INVALID = 66;
148148

149-
const PAYPAL_ORDER_AUTHORIZATIONS_INVALID = 67;
149+
const PAYPAL_AUTHORIZATION_NOT_FOUND = 67;
150150

151-
const PAYPAL_ORDER_AUTHORIZATIONS_EMPTY = 68;
151+
const PAYPAL_AUTHORIZATION_STATUS_INVALID = 68;
152152

153-
const PAYPAL_ORDER_AUTHORIZATIONS_NOT_UNIQUE = 69;
153+
const PAYPAL_AUTHORIZATION_VOIDED = 69;
154154

155-
const PAYPAL_AUTHORIZATION_STATUS_INVALID = 70;
155+
const PAYPAL_AUTHORIZATION_EXPIRED = 70;
156+
157+
const PAYPAL_ORDER_AUTHORIZATIONS_INVALID = 71;
156158

157-
const PAYPAL_AUTHORIZATION_REAUTHORIZATION_FAILURE = 71;
159+
const PAYPAL_ORDER_AUTHORIZATIONS_EMPTY = 72;
158160

159-
const PAYPAL_AUTHORIZATION_DATABASE_FAILURE = 72;
161+
const PAYPAL_ORDER_AUTHORIZATIONS_NOT_UNIQUE = 73;
162+
163+
const PAYPAL_AUTHORIZATION_REAUTHORIZATION_FAILURE = 74;
164+
165+
const PAYPAL_AUTHORIZATION_DATABASE_FAILURE = 75;
160166
}

core/src/PayPal/Order/Action/AuthorizePayPalOrderAction.php

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use PsCheckout\Core\PayPal\Order\Handler\EventHandlerInterface;
3131
use PsCheckout\Core\PayPal\Order\Provider\PayPalOrderProviderInterface;
3232
use PsCheckout\Core\PayPal\Order\Repository\PayPalOrderAuthorizationRepositoryInterface;
33+
use Psr\Log\LoggerInterface;
3334

3435
class AuthorizePayPalOrderAction implements AuthorizePayPalOrderActionInterface
3536
{
@@ -63,20 +64,27 @@ class AuthorizePayPalOrderAction implements AuthorizePayPalOrderActionInterface
6364
*/
6465
private $payPalOrderAuthorizationRepository;
6566

67+
/**
68+
* @var LoggerInterface
69+
*/
70+
private $logger;
71+
6672
public function __construct(
6773
OrderHttpClientInterface $orderHttpClient,
6874
PayPalOrderCacheInterface $payPalOrderCache,
6975
EventHandlerInterface $paymentPendingEventHandler,
7076
EventHandlerInterface $paymentDeniedEventHandler,
7177
PayPalOrderProviderInterface $payPalOrderProvider,
72-
PayPalOrderAuthorizationRepositoryInterface $payPalOrderAuthorizationRepository
78+
PayPalOrderAuthorizationRepositoryInterface $payPalOrderAuthorizationRepository,
79+
LoggerInterface $logger
7380
) {
7481
$this->orderHttpClient = $orderHttpClient;
7582
$this->payPalOrderCache = $payPalOrderCache;
7683
$this->paymentPendingEventHandler = $paymentPendingEventHandler;
7784
$this->paymentDeniedEventHandler = $paymentDeniedEventHandler;
7885
$this->payPalOrderProvider = $payPalOrderProvider;
7986
$this->payPalOrderAuthorizationRepository = $payPalOrderAuthorizationRepository;
87+
$this->logger = $logger;
8088
}
8189

8290
/**
@@ -88,24 +96,32 @@ public function execute(PayPalOrderResponse $payPalOrder): PayPalOrderResponse
8896
throw new PsCheckoutException(sprintf('PayPal Order %s status must be APPROVED, current status: %s', $payPalOrder->getId(), $payPalOrder->getStatus()), PsCheckoutException::PAYPAL_ORDER_STATUS_INVALID);
8997
}
9098

91-
$response = $this->orderHttpClient->authorizeOrder($payPalOrder->getId(), []);
99+
$orderId = $payPalOrder->getId();
100+
101+
$response = $this->orderHttpClient->authorizeOrder($orderId, []);
92102

93103
$orderPayPal = json_decode($response->getBody(), true);
94-
$cachedOrder = $this->payPalOrderCache->getValue($orderPayPal['id']);
104+
$cachedOrder = $this->payPalOrderCache->getValue($orderId);
95105

96-
$this->payPalOrderCache->set($orderPayPal['id'], array_replace_recursive($cachedOrder, $orderPayPal));
106+
$this->payPalOrderCache->set($orderId, array_replace_recursive($cachedOrder, $orderPayPal));
97107

98-
$payPalOrderResponse = $this->payPalOrderProvider->getById($orderPayPal['id']);
108+
$payPalOrderResponse = $this->payPalOrderProvider->getById($orderId);
99109

100110
$authorization = $payPalOrderResponse->getAuthorization();
101111

112+
if (!$authorization) {
113+
$this->logger->warning("Authorize response doesn't contain authorization info for order: " . $orderId);
114+
115+
return $payPalOrderResponse;
116+
}
117+
102118
$payPalAuthorization = new PayPalOrderAuthorization(
103119
$authorization['id'],
104-
$orderPayPal['id'],
120+
$orderId,
105121
$authorization['status'],
106-
$authorization['expiration_time'],
107-
$authorization['create_time'],
108-
$authorization['update_time']
122+
$authorization['expiration_time'] ?? '',
123+
$authorization['create_time'] ?? '',
124+
$authorization['update_time'] ?? ''
109125
);
110126

111127
$this->payPalOrderAuthorizationRepository->save($payPalAuthorization);
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
/**
3+
* Copyright since 2007 PrestaShop SA and Contributors
4+
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
5+
*
6+
* NOTICE OF LICENSE
7+
*
8+
* This source file is subject to the Academic Free License version 3.0
9+
* that is bundled with this package in the file LICENSE.md.
10+
* It is also available through the world-wide-web at this URL:
11+
* https://opensource.org/licenses/AFL-3.0
12+
* If you did not receive a copy of the license and are unable to
13+
* obtain it through the world-wide-web, please send an email
14+
* to [email protected] so we can send you a copy immediately.
15+
*
16+
* @author PrestaShop SA and Contributors <[email protected]>
17+
* @copyright Since 2007 PrestaShop SA and Contributors
18+
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
19+
*/
20+
21+
namespace PsCheckout\Core\PayPal\Order\Action;
22+
23+
use PsCheckout\Api\Http\PaymentHttpClientInterface;
24+
use PsCheckout\Api\ValueObject\PayPalOrderResponse;
25+
use PsCheckout\Core\Exception\PsCheckoutException;
26+
use PsCheckout\Core\PayPal\Order\Configuration\PayPalAuthorizationStatus;
27+
use PsCheckout\Core\PayPal\Order\Configuration\PayPalOrderStatus;
28+
use PsCheckout\Core\PayPal\Order\Entity\PayPalOrderAuthorization;
29+
use PsCheckout\Core\PayPal\Order\Repository\PayPalOrderAuthorizationRepositoryInterface;
30+
31+
class CaptureAuthorizationAction implements CaptureAuthorizationActionInterface
32+
{
33+
/**
34+
* @var PaymentHttpClientInterface
35+
*/
36+
private $paymentHttpClient;
37+
38+
/**
39+
* @var PayPalOrderAuthorizationRepositoryInterface
40+
*/
41+
private $authorizationRepository;
42+
43+
public function __construct(
44+
PaymentHttpClientInterface $paymentHttpClient,
45+
PayPalOrderAuthorizationRepositoryInterface $authorizationRepository
46+
) {
47+
$this->paymentHttpClient = $paymentHttpClient;
48+
$this->authorizationRepository = $authorizationRepository;
49+
}
50+
51+
/**
52+
* {@inheritDoc}
53+
*/
54+
public function execute(PayPalOrderResponse $payPalOrder): PayPalOrderAuthorization
55+
{
56+
// Check PayPal order status must be APPROVED
57+
if ($payPalOrder->getStatus() !== PayPalOrderStatus::APPROVED) {
58+
throw new PsCheckoutException(
59+
sprintf('PayPal Order %s status must be APPROVED, current status: %s', $payPalOrder->getId(), $payPalOrder->getStatus()),
60+
PsCheckoutException::PAYPAL_ORDER_STATUS_INVALID
61+
);
62+
}
63+
64+
// Check intent must be AUTHORIZE
65+
if ($payPalOrder->getIntent() !== 'AUTHORIZE') {
66+
throw new PsCheckoutException(
67+
sprintf('PayPal Order %s intent must be AUTHORIZE, current intent: %s', $payPalOrder->getId(), $payPalOrder->getIntent()),
68+
PsCheckoutException::PAYPAL_ORDER_INTENT_INVALID
69+
);
70+
}
71+
72+
// Fetch payment authorization from order
73+
$authorization = $payPalOrder->getAuthorization();
74+
75+
if (!$authorization) {
76+
throw new PsCheckoutException(
77+
sprintf('PayPal Order %s does not have a valid authorization', $payPalOrder->getId()),
78+
PsCheckoutException::PAYPAL_AUTHORIZATION_NOT_FOUND
79+
);
80+
}
81+
82+
$authorizationId = $authorization['id'];
83+
$authorizationStatus = $authorization['status'];
84+
85+
// Check if status is VOIDED
86+
if ($authorizationStatus === PayPalAuthorizationStatus::VOIDED) {
87+
throw new PsCheckoutException(
88+
"Authorization $authorizationId is voided and cannot be captured",
89+
PsCheckoutException::PAYPAL_AUTHORIZATION_VOIDED
90+
);
91+
}
92+
93+
// Validate authorization status must be CREATED or PARTIALLY_CAPTURED
94+
if (!in_array($authorizationStatus, [PayPalAuthorizationStatus::CREATED, PayPalAuthorizationStatus::PARTIALLY_CAPTURED], true)) {
95+
throw new PsCheckoutException(
96+
"Authorization $authorizationId status must be CREATED or PARTIALLY_CAPTURED, current status: $authorizationStatus",
97+
PsCheckoutException::PAYPAL_AUTHORIZATION_STATUS_INVALID
98+
);
99+
}
100+
101+
if (isset($authorization['expiration_time'])) {
102+
$expirationTime = new \DateTime((string) $authorization['expiration_time']);
103+
} else {
104+
$expirationTime = new \DateTime((string) $authorization['create_time']);
105+
$expirationTime->modify('+30 days');
106+
}
107+
108+
$currentTime = new \DateTime('now', new \DateTimeZone('UTC'));
109+
110+
if ($expirationTime < $currentTime) {
111+
throw new PsCheckoutException(
112+
"Authorization $authorizationId has expired",
113+
PsCheckoutException::PAYPAL_AUTHORIZATION_EXPIRED
114+
);
115+
}
116+
117+
$captureResponse = $this->paymentHttpClient->captureAuthorization($authorizationId);
118+
119+
/**
120+
* @var array{
121+
* id: string,
122+
* status: string,
123+
* create_time: string,
124+
* update_time: string
125+
* } $capturedAuthorization
126+
*/
127+
$capturedAuthorization = json_decode($captureResponse->getBody(), true);
128+
129+
$authorizationEntity = $this->authorizationRepository->getById($authorizationId);
130+
131+
if ($authorizationEntity) {
132+
$authorizationEntity->setStatus($capturedAuthorization['status']);
133+
} else {
134+
$authorizationEntity = new PayPalOrderAuthorization(
135+
$authorizationId,
136+
$payPalOrder->getId(),
137+
$capturedAuthorization['status'],
138+
'',
139+
$capturedAuthorization['create_time'],
140+
$capturedAuthorization['update_time'],
141+
);
142+
}
143+
144+
$this->authorizationRepository->save($authorizationEntity);
145+
146+
return $authorizationEntity;
147+
}
148+
}

0 commit comments

Comments
 (0)