Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Model/Observer/HoldPlacedOrderObserver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Astound\Affirm\Model\Observer;

use Astound\Affirm\Service\PlacedOrderHolder;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Sales\Api\Data\OrderInterface;

class HoldPlacedOrderObserver implements ObserverInterface
{
public function __construct(
private PlacedOrderHolder $placedOrderHolder
) {
}

public function execute(Observer $observer): void
{
/** @var OrderInterface $order */
$order = $observer->getEvent()->getOrder();

$this->placedOrderHolder->hold($order);
}
}
127 changes: 127 additions & 0 deletions Plugin/OrderCancellation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

declare(strict_types=1);

namespace Astound\Affirm\Plugin;

use Astound\Affirm\Service\PlacedOrderHolder;

use Closure;
use Magento\Quote\Api\CartManagementInterface;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Api\Data\PaymentInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Sales\Model\Order\CreditmemoFactory;
use RuntimeException;

class OrderCancellation
{
/**
* @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
* @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository
* @param \Astound\Affirm\Service\PlacedOrderHolder\PlacedOrderHolder $placedOrderHolder
* @param \Magento\Sales\Model\Order\CreditmemoFactory $creditmemoFactory
*/
public function __construct(
private CartRepositoryInterface $quoteRepository,
private OrderRepositoryInterface $orderRepository,
private PlacedOrderHolder $placedOrderHolder,
private CreditmemoFactory $creditmemoFactory
) {
}

public function aroundPlaceOrder(
CartManagementInterface $subject,
Closure $proceed,
int $cartId,
PaymentInterface $payment = null
): int {
try {
return (int)$proceed($cartId, $payment);
} catch (\Throwable $e) {
$quote = $this->quoteRepository->get((int) $cartId);

$payment = $quote->getPayment();

// Abort if the payment method is not relevant.
if ($payment->getMethod() !== 'affirm_gateway') {
throw $e;
}

$errorMessagePrefix = 'Unable to cancel payment: ';

/** @var \Magento\Sales\Model\Order|null */
$order = $this->placedOrderHolder->retrieve();

// Abort if the order object is not available
if (!$order) {
throw new RuntimeException(
$errorMessagePrefix . "Order data unavailable. Reserved order ID: {$quote->getReservedOrderId()}",
$e->getCode(),
$e
);
}

// Abort if the order object is not relevant for transaction.
if ($order->getIncrementId() !== $quote->getReservedOrderId()) {
throw new RuntimeException(
$errorMessagePrefix . "Available order data ({$order->getIncrementId()}, {$order->getId()}) doesn't match the quote value: {$quote->getReservedOrderId()}",
$e->getCode(),
$e
);
}

// Cancel the order in case when it was saved.
if ($order->getId()) {
$order->cancel();

$this->orderRepository->save($order);

throw $e;
}

/** @var \Magento\Sales\Model\Order\Payment|null */
$orderPayment = $order->getPayment();

// Abort if the order lacks payment information.
if (!$orderPayment) {
throw $e;
}

$methodInstance = $orderPayment->getMethodInstance();
$methodInstance->setStore($order->getStoreId());

$errorMessage = "";

if (!$methodInstance->canRefund()) {
$errorMessage = "Transaction can not be refunded.";
}

if (!$orderPayment->getCreatedTransaction()) {
$errorMessage = "Transaction information is missing.";
}

if (!$orderPayment->getCreatedInvoice()) {
$errorMessage = "Invoice is missing.";
}

if ($errorMessage) {
throw new RuntimeException(
$errorMessagePrefix . $errorMessage,
$e->getCode(),
$e
);
}

$creditmemo = $this->creditmemoFactory->createByOrder($order);
$creditmemo->setInvoice($orderPayment->getCreatedInvoice());

$orderPayment->setCreditmemo($creditmemo);
$orderPayment->setParentTransactionId($orderPayment->getCreatedTransaction()->getTxnId());

$methodInstance->refund($orderPayment, $orderPayment->getAmountPaid());

throw $e;
}
}
}
26 changes: 26 additions & 0 deletions Service/PlacedOrderHolder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Astound\Affirm\Service;

use Magento\Sales\Api\Data\OrderInterface;

class PlacedOrderHolder
{
private ?OrderInterface $order = null;

public function hold(OrderInterface $order): void
{
if ($this->order) {
throw new \DomainException('Order is already held');
}

$this->order = $order;
}

public function retrieve(): ?OrderInterface
{
return $this->order;
}
}
4 changes: 4 additions & 0 deletions etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@
<plugin name="SaveInvoiceAmountToCapture" type="Astound\Affirm\Plugin\SaveInvoiceAmountToCapture" sortOrder="1" />
</type>

<type name="Magento\Quote\Api\CartManagementInterface">
<plugin name="OrderCancellation" type="Astound\Affirm\Plugin\OrderCancellation"/>
</type>

<!-- Logger -->
<type name="Astound\Affirm\Logger\Handler">
<arguments>
Expand Down
4 changes: 4 additions & 0 deletions etc/events.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@
<event name="customer_address_format">
<observer name="affirm_missing_region" instance="Astound\Affirm\Model\Observer\AddressFormat" />
</event>
<event name="sales_order_place_before">
<observer name="affirm_sales_order_place_before"
instance="Astound\Affirm\Model\Observer\HoldPlacedOrderObserver" />
</event>
</config>