Magento – Order Status : Pending Payment vs. Payment Review

magento2order-statuspaymentpayment-gatewaytransaction

I'm creating an integration for a Payment Service Provider / Payment Gateway, but I'm a bit stuck in the way how Magento handles stuff.

The payment method in question returns a "pending state" with a redirect URL to send the customer to. When the customer returns from that URL, a status update or asynchronous webhook can be sent that can either result in a successful payment or a failure. If the latter is the case, the order should be canceled.

I'm sticking as much as possible to the default way on how to do this in Magento. This means that if I get the "pending state" from the gateway, I mark my payment transaction as "pending" meaning that some approval needs to take place at the payment gateway:

$redirect = $response->getBody()->getRedirect()->getUrl();

$paymentModel = $order->getPayment();
$paymentModel->setAdditionalInformation('redirect', $redirect);
$paymentModel->setAdditionalInformation('transaction_id', $response->getBody()->getTransaction()->getId());
$paymentModel->setIsTransactionPending(true);
$paymentModel->setTransactionId($response->getBody()->getTransaction()->getId());

By setting the "pending"-flag, the \Magento\Sales\Model\Order\Payment\Operations\CaptureOperation::capture()-operation will not mark the invoice as paid:

$message = $this->stateCommand->execute($payment, $amountToCapture, $order);
if ($payment->getIsTransactionPending()) {
    $invoice->setIsPaid(false);
} else {
    $invoice->setIsPaid(true);
    $this->updateTotals($payment, ['base_amount_paid_online' => $amountToCapture]);
}

The $message that is set also shows me I'm in the right direction:

\Magento\Sales\Model\Order\Payment\State\CaptureCommand::execute():

if ($payment->getIsTransactionPending()) {
    $state = Order::STATE_PAYMENT_REVIEW;
    $message = 'An amount of %1 will be captured after being approved at the payment gateway.';
}

So if I understand correctly, if some action is required by the gateway, the "payment review"-the state is used.

Now later on, if the asynchronous event is received, I can simply get my invoice and mark it as being paid:

$invoice = $order->getInvoiceCollection()->getLastItem();
$invoice->pay();

So far, everything works perfectly. But: if my payment fails I want to cancel my order. I thought this would be as simple as just doing $order->cancel(), but the \Magento\Sales\Model\Order::canCancel()-a method that is triggered, checks if the order is in the payment review-state, and if so, it does not allow cancellation. (please correct me if I'm wrong).

So now the question is: what to do? Should the order state be "pending payment" instead of "payment review"? If so: what is then the most friendly way to set this property using default Magento mechanics? Because if I look for usages of the "pending payment" state in the code, it doesn't look like this state is set by Magento itself (at least not using its constant). The weird part is that if I tell Magento that a transaction is pending, the status becomes payment review. :-/

Is it even accepted in general to cancel an order that has the "payment review" state?

Best Answer

In my opinion, you have to cancel the invoice you have created earlier if you are in the "payment review" state and the payment fails. After that, you should be able to cancel the order too because at the end of the invoices cancel() method the order state is set to "processing".

This shoud do it:

$invoice = $order->getInvoiceCollection()->getLastItem();
$invoice->cancel();
$order->cancel();
Related Topic