checkout / checkout-magento2-plugin

Checkout.com Magento 2 official extension
MIT License
32 stars 32 forks source link

Partial refunds causing issues #514

Open JamesFX2 opened 2 years ago

JamesFX2 commented 2 years ago

We are occasionally have issues issusing partial refunds on orders.

What happens is that (1) the refund goes through on checkout.com, (2) the credit memo is generated but when we view the sales_order later, the qty_refunded on the sales_order_item hasn't been updated, with other fields related to total refunded also being 0.

Except they were updated.

Have debugged this and the issue is being caused by the webhook overlapping with the refund process.

So,

I'm going to fix this with a plugin that prevents the order from being saved when it doesn't need to be saved, presumably on handleTransaction in TransactionHandlerService, but you know, it would be kind of cool if you could fix this yourselves too.

edroberts commented 1 year ago

Further feedback from James on this:

_I'd recommend that they don't save the order if no changes are necessary.

If you modify processInvoice, processEmail, processCreditMemo to return a true/false if they actually did any changes to the order, you could use these as a flag.

I've not tested this code but it would be something like this to completely ignore handleTransaction if a credit memo already exists with a matching txnId to the transaction Id._

/**

 * @param TransactionHandlerService$subject
 * @param \Closure$proceed
 * @param OrderInterface$order
 * @param array$webhook
 * @returnvoid
 */
public functionaroundHandleTransaction(\CheckoutCom\Magento2\Model\Service\ TransactionHandlerService$subject ,\ Closure$proceed , OrderInterface$order ,array $webhook) {
    $transaction= $subject->hasTransaction(
        $order,
        $webhook['action_id']
    );

    if(!$transaction ) {
        if(!$this->isWebhookNeeded($order ,$webhook)) {
            // might still need to update sales_order_payment
            // and sales_payment_transaction
            // not sure what the non-webhook sets
           return;
        }
    }
    $proceed($order ,$webhook);
}

/**
 * @param OrderInterface$order

 * @returnbool

 */
protected functionisWebhookNeeded (OrderInterface$order ,array $webhook) {
    $isRefund= \CheckoutCom\Magento2\Model\Service\TransactionHandlerService::TRANSACTION_MAPPER[$webhook['event_type' ]] === TransactionInterface::TYPE_REFUND;
    if( $isRefund&& isset($webhook['action_id' ]) && $txnId= $webhook['action_id' ]) {

       if($this->hasCreditMemoWithTxnId($order ,$txnId)) {

           return false;
        }

    }

   return true;
}

/**
 * @param OrderInterface$order
 * @param$txnId
 * @returnbool
 */
protected functionhasCreditMemoWithTxnId($order ,$txnId) {
    $creditMemos= $order->getCreditmemosCollection();
    if(!empty($creditMemos )) {
        foreach( $creditMemosas $creditMemo) {
            if($creditMemo->getTransactionId () == $txnId) {
                return true;
            }
        }

    }

   return false;
}

_But I haven't had time to attempt to fix this and therefore test the above. I still have question marks over whether I'd need to create a transaction/update the payment or if the refund that's generated without the webhook does that.

You don't need to set order status as much as the original developers thought. A long-standing annoyance with the extension is that it tries to change the order status after a refund or partial refund since Magento handles that itself. Magento will automatically close or cancel an order when certain criteria are met after a refund - see https://github.com/magento/magento2/blob/2.4-develop/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php - you don't need to set state/status - the only gap is the following criteria._


if(

   $order

   && !$order->isCanceled()
    && !$order->canUnhold()
    && !$order->canInvoice()
) {
    $currentState= $order->getState();
    if(in_array($currentState, [\Magento\Sales\Model\Order::STATE_PROCESSING ,\Magento\Sales\Model\Order::STATE_COMPLETE])

        && !$order->canCreditmemo()

        &&$order->canShip()

    ) {

       $order->setState(\Magento\Sales\Model\Order::STATE_CLOSED);
        $order->setStatus(\Magento\Sales\Model\Order::STATE_CLOSED);

    }

}