-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1239 from BearGroup/release/5.17.1
Release/5.17.1
- Loading branch information
Showing
13 changed files
with
456 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
<?php | ||
/** | ||
* Copyright © Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
namespace Amazon\Pay\Cron; | ||
|
||
use Amazon\Pay\Helper\Transaction as TransactionHelper; | ||
use Amazon\Pay\Model\Adapter\AmazonPayAdapter; | ||
use Amazon\Pay\Model\AsyncManagement\Charge; | ||
use Amazon\Pay\Model\CheckoutSessionManagement; | ||
use Magento\Sales\Api\Data\OrderInterface; | ||
use Magento\Sales\Api\OrderRepositoryInterface; | ||
use Psr\Log\LoggerInterface; | ||
use Amazon\Pay\Model\AsyncManagement\Charge as AsyncCharge; | ||
|
||
class CleanUpIncompleteSessions | ||
{ | ||
public const SESSION_STATUS_STATE_CANCELED = 'Canceled'; | ||
public const SESSION_STATUS_STATE_OPEN = 'Open'; | ||
public const SESSION_STATUS_STATE_COMPLETED = 'Completed'; | ||
|
||
protected const LOG_PREFIX = 'AmazonCleanUpIncompleteSesssions: '; | ||
|
||
/** | ||
* @var TransactionHelper | ||
*/ | ||
protected $transactionHelper; | ||
|
||
/** | ||
* @var LoggerInterface | ||
*/ | ||
protected $logger; | ||
|
||
/** | ||
* @var AmazonPayAdapter | ||
*/ | ||
protected $amazonPayAdapter; | ||
|
||
/** | ||
* @var CheckoutSessionManagement | ||
*/ | ||
protected $checkoutSessionManagement; | ||
|
||
/** | ||
* @var OrderRepositoryInterface | ||
*/ | ||
protected $orderRepository; | ||
|
||
/** | ||
* @var AsyncCharge | ||
*/ | ||
protected $asyncCharge; | ||
|
||
/** | ||
* @param TransactionHelper $transactionHelper | ||
* @param LoggerInterface $logger | ||
* @param AmazonPayAdapter $amazonPayAdapter | ||
* @param CheckoutSessionManagement $checkoutSessionManagement | ||
* @param OrderRepositoryInterface $orderRepository | ||
* @param AsyncCharge $asyncCharge | ||
*/ | ||
public function __construct( | ||
TransactionHelper $transactionHelper, | ||
LoggerInterface $logger, | ||
AmazonPayAdapter $amazonPayAdapter, | ||
CheckoutSessionManagement $checkoutSessionManagement, | ||
OrderRepositoryInterface $orderRepository, | ||
AsyncCharge $asyncCharge | ||
) { | ||
$this->transactionHelper = $transactionHelper; | ||
$this->logger = $logger; | ||
$this->amazonPayAdapter = $amazonPayAdapter; | ||
$this->checkoutSessionManagement = $checkoutSessionManagement; | ||
$this->orderRepository = $orderRepository; | ||
$this->asyncCharge = $asyncCharge; | ||
} | ||
|
||
/** | ||
* Execute cleanup | ||
* | ||
* @return void | ||
*/ | ||
public function execute() | ||
{ | ||
// Get transactions | ||
$incompleteTransactionList = $this->transactionHelper->getIncomplete(); | ||
|
||
// Process each transaction | ||
foreach ($incompleteTransactionList as $transactionData) { | ||
$this->processTransaction($transactionData); | ||
} | ||
} | ||
|
||
/** | ||
* Process a single transaction | ||
* | ||
* @param array $transactionData | ||
* @return void | ||
*/ | ||
protected function processTransaction(array $transactionData) | ||
{ | ||
$checkoutSessionId = $transactionData['checkout_session_id']; | ||
$orderId = $transactionData['order_id']; | ||
|
||
$this->logger->info(self::LOG_PREFIX . 'Cleaning up checkout session id: ' . $checkoutSessionId); | ||
|
||
try { | ||
|
||
// Check current state of Amazon checkout session | ||
$amazonSession = $this->amazonPayAdapter->getCheckoutSession(null, $checkoutSessionId); | ||
$state = $amazonSession['statusDetails']['state'] ?? false; | ||
switch ($state) { | ||
case self::SESSION_STATUS_STATE_CANCELED: | ||
$logMessage = 'Checkout session Canceled, cancelling order and closing transaction: '; | ||
$logMessage .= $checkoutSessionId; | ||
$this->logger->info(self::LOG_PREFIX . $logMessage); | ||
$this->cancelOrder($orderId); | ||
$this->transactionHelper->closeTransaction($transactionData['transaction_id']); | ||
break; | ||
case self::SESSION_STATUS_STATE_OPEN: | ||
$logMessage = 'Checkout session Open, completing: '; | ||
$logMessage .= $checkoutSessionId; | ||
$this->logger->info(self::LOG_PREFIX . $logMessage); | ||
$this->checkoutSessionManagement->completeCheckoutSession($checkoutSessionId, null, $orderId); | ||
break; | ||
case self::SESSION_STATUS_STATE_COMPLETED: | ||
$logMessage = 'Checkout session Completed, nothing more needed: '; | ||
$logMessage .= $checkoutSessionId; | ||
$this->logger->info(self::LOG_PREFIX . $logMessage); | ||
break; | ||
} | ||
} catch (\Exception $e) { | ||
$errorMessage = 'Unable to process checkoutSessionId: ' . $checkoutSessionId; | ||
$this->logger->error(self::LOG_PREFIX . $errorMessage . '. ' . $e->getMessage()); | ||
} | ||
} | ||
|
||
/** | ||
* Cancel the order | ||
* | ||
* @param int $orderId | ||
* @return void | ||
*/ | ||
protected function cancelOrder($orderId) | ||
{ | ||
$order = $this->loadOrder($orderId); | ||
|
||
if ($order) { | ||
$this->checkoutSessionManagement->cancelOrder($order); | ||
} else { | ||
$this->logger->error(self::LOG_PREFIX . 'Order not found for ID: ' . $orderId); | ||
} | ||
} | ||
|
||
/** | ||
* Load order by ID | ||
* | ||
* @param int $orderId | ||
* @return OrderInterface | ||
*/ | ||
protected function loadOrder($orderId) | ||
{ | ||
try { | ||
return $this->orderRepository->get($orderId); | ||
} catch (\Exception $e) { | ||
$this->logger->error(self::LOG_PREFIX . 'Error loading order: ' . $e->getMessage()); | ||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
<?php | ||
/** | ||
* Copyright © Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
namespace Amazon\Pay\Helper; | ||
|
||
use Amazon\Pay\Gateway\Config\Config; | ||
use Magento\Framework\App\ResourceConnection; | ||
use Magento\Sales\Api\Data\TransactionInterface; | ||
use Magento\Sales\Api\TransactionRepositoryInterface; | ||
use Magento\Sales\Model\Order; | ||
|
||
class Transaction | ||
{ | ||
|
||
// Length of time in minutes we wait before cleaning up the transaction | ||
protected const MIN_ORDER_AGE_MINUTES = 30; | ||
|
||
/** | ||
* @var int | ||
*/ | ||
private $limit; | ||
|
||
/** | ||
* @var ResourceConnection | ||
*/ | ||
private ResourceConnection $resourceConnection; | ||
|
||
/** | ||
* @var TransactionRepositoryInterface | ||
*/ | ||
private TransactionRepositoryInterface $transactionRepository; | ||
|
||
/** | ||
* @param ResourceConnection $resourceConnection | ||
* @param TransactionRepositoryInterface $transactionRepository | ||
* @param int $limit | ||
*/ | ||
public function __construct( | ||
ResourceConnection $resourceConnection, | ||
TransactionRepositoryInterface $transactionRepository, | ||
int $limit = 100 | ||
) { | ||
$this->limit = $limit; | ||
$this->resourceConnection = $resourceConnection; | ||
$this->transactionRepository = $transactionRepository; | ||
} | ||
|
||
/** | ||
* Query for possible incomplete transactions | ||
* | ||
* @return array | ||
*/ | ||
public function getIncomplete() | ||
{ | ||
// Do not process recent orders, synchronous ones need time to be | ||
// resolved in payment gateway on auth decline | ||
// todo confirm timeout length in gateway | ||
$maxOrderPlacedTime = $this->getMaxOrderPlacedTime(); | ||
|
||
$connection = $this->resourceConnection->getConnection(); | ||
|
||
// tables used to determine stalled order status | ||
$salesOrderTable = $connection->getTableName('sales_order'); | ||
$salesOrderPaymentTable = $connection->getTableName('sales_order_payment'); | ||
$salesPaymentTransaction = $connection->getTableName('sales_payment_transaction'); | ||
$amazonPayAsyncTable = $connection->getTableName('amazon_payv2_async'); | ||
|
||
// specifying(limiting) columns is unnecessary, but helpful for debugging | ||
// pending actions: | ||
// captures for "charge when order is placed" payment action | ||
// authorizations for "charge when shipped" payment action | ||
$tableFields = [ | ||
'sales_order' => ['order_id' => 'entity_id', 'store_id', 'increment_id', 'created_at', 'state'], | ||
'sales_order_payment' => ['method'], | ||
'sales_payment_transaction' => ['transaction_id', 'checkout_session_id' => 'txn_id', 'is_closed'], | ||
'amazon_payv2_async' => ['pending_action', 'is_pending'] | ||
]; | ||
|
||
$select = $connection->select() | ||
->from(['so' => $salesOrderTable], $tableFields['sales_order']) | ||
->joinLeft( | ||
['sop' => $salesOrderPaymentTable], | ||
'so.entity_id = sop.parent_id', | ||
$tableFields['sales_order_payment'] | ||
) | ||
->joinLeft( | ||
['spt' => $salesPaymentTransaction], | ||
'sop.entity_id = spt.payment_id', | ||
$tableFields['sales_payment_transaction'] | ||
) | ||
->joinLeft( | ||
['apa' => $amazonPayAsyncTable], | ||
'spt.txn_id = apa.pending_id', | ||
$tableFields['amazon_payv2_async'] | ||
) | ||
// No async record pending | ||
->where('apa.pending_action IS NULL') | ||
// Order awaiting payment | ||
->where("so.status = ?", Order::STATE_PAYMENT_REVIEW) | ||
// A transaction is not complete | ||
->where('spt.is_closed <> ?', 1) | ||
// Delay processing new orders | ||
->where('so.created_at <= ?', $maxOrderPlacedTime) | ||
// Amazon Pay orders only | ||
->where('sop.method = ?', Config::CODE) | ||
// Meter to reduce load | ||
->limit($this->limit); | ||
|
||
// Return stalled orders | ||
return $connection->fetchAll($select); | ||
} | ||
|
||
/** | ||
* Close transaction and save | ||
* | ||
* @param mixed $transactionId | ||
* @return void | ||
*/ | ||
public function closeTransaction(mixed $transactionId) | ||
{ | ||
$transaction = $this->transactionRepository->get($transactionId); | ||
$transaction->setIsClosed(true); | ||
$this->transactionRepository->save($transaction); | ||
} | ||
|
||
/** | ||
* Use db time to reduce likelihood of server/db time mismatch | ||
* | ||
* @return string | ||
*/ | ||
private function getMaxOrderPlacedTime() | ||
{ | ||
// phpcs:ignore Magento2.SQL.RawQuery | ||
$query = 'SELECT NOW() - INTERVAL ' . self::MIN_ORDER_AGE_MINUTES . ' MINUTE'; | ||
return $this->resourceConnection->getConnection()->fetchOne($query); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.