Skip to content

Commit

Permalink
Merge branch 'bugfix/ignore-expired-orders' into release-week-19
Browse files Browse the repository at this point in the history
  • Loading branch information
michielgerritsen committed May 8, 2023
2 parents 6c0792e + 43b37ec commit 27a67a5
Show file tree
Hide file tree
Showing 14 changed files with 528 additions and 3 deletions.
12 changes: 12 additions & 0 deletions Api/Data/TransactionToOrderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface TransactionToOrderInterface extends ExtensibleDataInterface
{
public const TRANSACTION_ID = 'transaction_id';
public const ORDER_ID = 'order_id';
public const SKIPPED = 'skipped';
public const CREATED_AT = 'created_at';

/**
Expand Down Expand Up @@ -45,6 +46,17 @@ public function getCreatedAt(): ?string;
*/
public function setCreatedAt(string $created_at): \Mollie\Payment\Api\Data\TransactionToOrderInterface;

/**
* @return string|null
*/
public function getSkipped(): ?int;

/**
* @param int $skipped
* @return \Mollie\Payment\Api\Data\TransactionToOrderInterface
*/
public function setSkipped(int $skipped): \Mollie\Payment\Api\Data\TransactionToOrderInterface;

/**
* @return \Mollie\Payment\Api\Data\TransactionToOrderExtensionInterface|null
*/
Expand Down
66 changes: 66 additions & 0 deletions Model/Client/Orders/Processors/ExpiredProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Mollie\Payment\Model\Client\Orders\Processors;

use Magento\Sales\Api\Data\OrderInterface;
use Mollie\Api\Resources\Order;
use Mollie\Payment\Model\Client\OrderProcessorInterface;
use Mollie\Payment\Model\Client\ProcessTransactionResponse;
use Mollie\Payment\Model\Client\ProcessTransactionResponseFactory;
use Mollie\Payment\Service\Order\ExpiredOrderToTransaction;

class ExpiredProcessor implements OrderProcessorInterface
{
/**
* @var CancelledProcessor
*/
private $cancelledProcessor;

/**
* @var ProcessTransactionResponseFactory
*/
private $processTransactionResponseFactory;

/**
* @var ExpiredOrderToTransaction
*/
private $expiredOrderToTransaction;

public function __construct(
CancelledProcessor $cancelledProcessor,
ExpiredOrderToTransaction $expiredOrderToTransaction,
ProcessTransactionResponseFactory $processTransactionResponseFactory
) {
$this->cancelledProcessor = $cancelledProcessor;
$this->processTransactionResponseFactory = $processTransactionResponseFactory;
$this->expiredOrderToTransaction = $expiredOrderToTransaction;
}

public function process(
OrderInterface $magentoOrder,
Order $mollieOrder,
string $type,
ProcessTransactionResponse $response
): ?ProcessTransactionResponse {
if ($this->shouldCancelProcessing($magentoOrder)) {
return $this->processTransactionResponseFactory->create([
'success' => false,
'status' => $mollieOrder->status,
'order_id' => $magentoOrder->getEntityId(),
'type' => $type
]);
}

return $this->cancelledProcessor->process($magentoOrder, $mollieOrder, $type, $response);
}

private function shouldCancelProcessing(OrderInterface $order): bool
{
if (!$this->expiredOrderToTransaction->hasMultipleTransactions($order)) {
return false;
}

$this->expiredOrderToTransaction->markTransactionAsSkipped($order->getMollieTransactionId());
return true;
}
}
19 changes: 18 additions & 1 deletion Model/Client/Payments.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use Mollie\Payment\Service\Order\OrderAmount;
use Mollie\Payment\Service\Order\CancelOrder;
use Mollie\Payment\Service\Order\OrderCommentHistory;
use Mollie\Payment\Service\Order\ExpiredOrderToTransaction;
use Mollie\Payment\Service\Order\SaveAdditionalInformationDetails;
use Mollie\Payment\Service\Order\SendOrderEmails;
use Mollie\Payment\Service\Order\Transaction;
Expand Down Expand Up @@ -125,6 +126,11 @@ class Payments extends AbstractModel
*/
private $saveAdditionalInformationDetails;

/**
* @var ExpiredOrderToTransaction
*/
private $expiredOrderToTransaction;

/**
* Payments constructor.
*
Expand All @@ -146,6 +152,7 @@ class Payments extends AbstractModel
* @param ProcessTransaction $processTransaction
* @param ValidateMetadata $validateMetadata
* @param SaveAdditionalInformationDetails $saveAdditionalInformationDetails
* @param ExpiredOrderToTransaction $expiredOrderToTransaction
*/
public function __construct(
OrderRepository $orderRepository,
Expand All @@ -165,7 +172,8 @@ public function __construct(
LinkTransactionToOrder $linkTransactionToOrder,
ProcessTransaction $processTransaction,
ValidateMetadata $validateMetadata,
SaveAdditionalInformationDetails $saveAdditionalInformationDetails
SaveAdditionalInformationDetails $saveAdditionalInformationDetails,
ExpiredOrderToTransaction $expiredOrderToTransaction
) {
$this->orderRepository = $orderRepository;
$this->checkoutSession = $checkoutSession;
Expand All @@ -185,6 +193,7 @@ public function __construct(
$this->processTransaction = $processTransaction;
$this->validateMetadata = $validateMetadata;
$this->saveAdditionalInformationDetails = $saveAdditionalInformationDetails;
$this->expiredOrderToTransaction = $expiredOrderToTransaction;
}

/**
Expand Down Expand Up @@ -422,6 +431,14 @@ public function processTransaction(Order $order, $mollieApi, $type = 'webhook',
$this->mollieHelper->addTolog('success', $msg);
return $msg;
}
if ($status == 'expired') {
if ($this->expiredOrderToTransaction->hasMultipleTransactions($order)) {
$this->expiredOrderToTransaction->markTransactionAsSkipped($transactionId);
$msg = ['success' => false, 'status' => $status, 'order_id' => $orderId, 'type' => $type];
$this->mollieHelper->addTolog('success', $msg);
return $msg;
}
}
if ($status == 'canceled' || $status == 'failed' || $status == 'expired') {
if ($type == 'webhook') {
$this->cancelOrder->execute($order, $status);
Expand Down
10 changes: 10 additions & 0 deletions Model/Client/Payments/ProcessTransaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ public function execute(
);
}

if ($status == 'expired') {
return $this->paymentProcessors->process(
'expired',
$magentoOrder,
$molliePayment,
$type,
$defaultResponse
);
}

if ($status == 'canceled' || $status == 'failed' || $status == 'expired') {
return $this->paymentProcessors->process(
'failed',
Expand Down
64 changes: 64 additions & 0 deletions Model/Client/Payments/Processors/ExpiredStatusProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Mollie\Payment\Model\Client\Payments\Processors;

use Magento\Sales\Api\Data\OrderInterface;
use Mollie\Api\Resources\Payment;
use Mollie\Payment\Model\Client\PaymentProcessorInterface;
use Mollie\Payment\Model\Client\ProcessTransactionResponse;
use Mollie\Payment\Model\Client\ProcessTransactionResponseFactory;
use Mollie\Payment\Service\Order\ExpiredOrderToTransaction;

class ExpiredStatusProcessor implements PaymentProcessorInterface
{
/**
* @var ExpiredOrderToTransaction
*/
private $expiredOrderToTransaction;
/**
* @var FailedStatusProcessor
*/
private $failedStatusProcessor;
/**
* @var ProcessTransactionResponseFactory
*/
private $processTransactionResponseFactory;

public function __construct(
ExpiredOrderToTransaction $expiredOrderToTransaction,
FailedStatusProcessor $failedStatusProcessor,
ProcessTransactionResponseFactory $processTransactionResponseFactory
) {
$this->expiredOrderToTransaction = $expiredOrderToTransaction;
$this->failedStatusProcessor = $failedStatusProcessor;
$this->processTransactionResponseFactory = $processTransactionResponseFactory;
}

public function process(
OrderInterface $magentoOrder,
Payment $molliePayment,
string $type,
ProcessTransactionResponse $response
): ?ProcessTransactionResponse {
if ($this->shouldCancelProcessing($magentoOrder)) {
return $this->processTransactionResponseFactory->create([
'success' => false,
'status' => $molliePayment->status,
'order_id' => $magentoOrder->getEntityId(),
'type' => $type
]);
}

return $this->failedStatusProcessor->process($magentoOrder, $molliePayment, $type, $response);
}

private function shouldCancelProcessing(OrderInterface $order): bool
{
if (!$this->expiredOrderToTransaction->hasMultipleTransactions($order)) {
return false;
}

$this->expiredOrderToTransaction->markTransactionAsSkipped($order->getMollieTransactionId());
return true;
}
}
19 changes: 19 additions & 0 deletions Model/TransactionToOrder.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,25 @@ public function setCreatedAt(string $created_at): TransactionToOrderInterface
return $this->setData(self::CREATED_AT, $created_at);
}

/**
* Get skipped
* @return int|null
*/
public function getSkipped(): ?int
{
return (int)$this->getData(self::SKIPPED);
}

/**
* Set skipped
* @param int $skipped
* @return TransactionToOrderInterface
*/
public function setSkipped(int $skipped): TransactionToOrderInterface
{
return $this->setData(self::SKIPPED, $skipped);
}

/**
* Retrieve existing extension attributes object or create a new one.
* @return TransactionToOrderExtensionInterface|null
Expand Down
69 changes: 69 additions & 0 deletions Service/Order/ExpiredOrderToTransaction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Mollie\Payment\Service\Order;

use Magento\Framework\Api\SearchCriteriaBuilderFactory;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Sales\Api\Data\OrderInterface;
use Mollie\Payment\Api\Data\TransactionToOrderInterface;
use Mollie\Payment\Model\TransactionToOrderRepository;

class ExpiredOrderToTransaction
{
/**
* @var TransactionToOrderRepository
*/
private $transactionToOrderRepository;
/**
* @var SearchCriteriaBuilderFactory
*/
private $criteriaBuilderFactory;

public function __construct(
TransactionToOrderRepository $transactionToOrderRepository,
SearchCriteriaBuilderFactory $criteriaBuilderFactory
) {
$this->transactionToOrderRepository = $transactionToOrderRepository;
$this->criteriaBuilderFactory = $criteriaBuilderFactory;
}

public function hasMultipleTransactions(OrderInterface $order): bool
{
$criteria = $this->criteriaBuilderFactory->create();
$criteria->addFilter('skipped', '0');
$criteria->addFilter('order_id', $order->getEntityId());

$result = $this->transactionToOrderRepository->getList($criteria->create());

return $result->getTotalCount() > 1;
}

public function getByTransactionId(string $transactionId): TransactionToOrderInterface
{
$criteria = $this->criteriaBuilderFactory->create();
$criteria->addFilter('transaction_id', $transactionId);

$result = $this->transactionToOrderRepository->getList($criteria->create());

$items = $result->getItems();

if (empty($items)) {
throw new NoSuchEntityException(__("Transaction with ID %1 not found", $transactionId));
}

return array_shift($items);
}

/**
* A transaction can be skipped if there are multiple transactions for a single order, and this transaction
* is expired. In that case, we don't want to cancel the order, but we do want to mark the transaction as skipped.
* When the next transaction is also expired, and there are no other transactions left, we will cancel the order.
*/
public function markTransactionAsSkipped(string $transactionId): void
{
$transaction = $this->getByTransactionId($transactionId);
$transaction->setSkipped(true);

$this->transactionToOrderRepository->save($transaction);
}
}
Loading

0 comments on commit 27a67a5

Please sign in to comment.