7

I have noticed Paypal orders on my site at random get cancelled even though the customer paid and completed the transaction. Payment information gets populated and I can see the ipn registered message too.

  • 2 Apr 2017 09:24:21|Canceled
  • Customer Notification Not Applicable
  • 2 Apr 2017 08:44:17|Pending
  • Customer Notification Not Applicable
  • IPN "Completed". Registered notification about captured amount of £30.95. Transaction ID: ############ 2 Apr 2017 08:43:23|Pending Payment
  • Customer Not Notified

This excerpt of the comments history shows 08:43 the order was in pending payment status. Then 8.44 ipn confirmed capture of payment. Making the order pending. I have written code to stop auto-invoice.

But, 40 Minutes only after was cancelled. Customer hasn't cancelled anything payment is there in paypal. Neither have we. Why would the system be doing this ?

Update:

Here is another example, whereby it got cancelled 2 minutes and 52 seconds later.

8 Dec 2017 00:00:51|Canceled Customer Notification Not Applicable 7 Dec 2017 23:57:59|Pending Customer Notification Not Applicable IPN "Completed". Registered notification about captured amount of £xx.xx. Transaction ID: "xxxxxxxxxxx". 7 Dec 2017 23:57:59|Pending Customer Notified Notified Customer 7 Dec 2017 23:56:56|Pending Payment Customer Not Notified

My code to stop the auto invoicing is as follows:

config.xml:

    <?xml version="1.0"?>
<config>
<modules>
    <Wfh_DisableAutoInvoice>
        <version>0.1.0</version>
    </Wfh_DisableAutoInvoice>
</modules>
<global>
    <models>
        <disableautoinvoice>
            <class>Wfh_DisableAutoInvoice_Model</class>
            <resourceModel>disableautoinvoice_mysql4</resourceModel>
        </disableautoinvoice>
        <sales>
            <rewrite>
        <order_payment>Wfh_DisableAutoInvoice_Model_Sales_Order_Payment</order_payment>
            </rewrite>
        </sales>
    </models>
</global>
</config> 

My payment.php

<?php
    class Wfh_DisableAutoInvoice_Model_Sales_Order_Payment extends Mage_Sales_Model_Order_Payment
{   

/**
 * Authorize or authorize and capture payment on gateway, if applicable
 * This method is supposed to be called only when order is placed
 *
 * @return Mage_Sales_Model_Order_Payment
 */
public function place()
{
    Mage::dispatchEvent('sales_order_payment_place_start', array('payment' => $this));
    $order = $this->getOrder();

    $this->setAmountOrdered($order->getTotalDue());
    $this->setBaseAmountOrdered($order->getBaseTotalDue());
    $this->setShippingAmount($order->getShippingAmount());
    $this->setBaseShippingAmount($order->getBaseShippingAmount());

    $methodInstance = $this->getMethodInstance();
    $methodInstance->setStore($order->getStoreId());
    $orderState = Mage_Sales_Model_Order::STATE_NEW;
    $stateObject = new Varien_Object();

    /**
     * Do order payment validation on payment method level
     */
    $methodInstance->validate();
    $action = $methodInstance->getConfigPaymentAction();
    if ($action) {
        if ($methodInstance->isInitializeNeeded()) {
            /**
             * For method initialization we have to use original config value for payment action
             */
            $methodInstance->initialize($methodInstance->getConfigData('payment_action'), $stateObject);
        } else {
            $orderState = Mage_Sales_Model_Order::STATE_NEW;
            switch ($action) {
                case Mage_Payment_Model_Method_Abstract::ACTION_ORDER:
                    $this->_order($order->getBaseTotalDue());
                    break;
                case Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE:
                    $this->_authorize(true, $order->getBaseTotalDue()); // base amount will be set inside
                    $this->setAmountAuthorized($order->getTotalDue());
                    break;
                case Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE:
                    $this->setAmountAuthorized($order->getTotalDue());
                    $this->setBaseAmountAuthorized($order->getBaseTotalDue());
                    $this->capture(null);
                    break;
                default:
                    break;
            }
        }
    }

    $this->_createBillingAgreement();

    $orderIsNotified = null;
    if ($stateObject->getState() && $stateObject->getStatus()) {
        $orderState      = $stateObject->getState();
        $orderStatus     = $stateObject->getStatus();
        $orderIsNotified = $stateObject->getIsNotified();
    } else {
        $orderStatus = $methodInstance->getConfigData('order_status');
        if (!$orderStatus) {
            $orderStatus = $order->getConfig()->getStateDefaultStatus($orderState);
        } else {
            // check if $orderStatus has assigned a state
            $states = $order->getConfig()->getStatusStates($orderStatus);
            if (count($states) == 0) {
                $orderStatus = $order->getConfig()->getStateDefaultStatus($orderState);
            }
        }
    }
    $isCustomerNotified = (null !== $orderIsNotified) ? $orderIsNotified : $order->getCustomerNoteNotify();
    $message = $order->getCustomerNote();

    // add message if order was put into review during authorization or capture
    if ($order->getState() == Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW) {
        if ($message) {
            $order->addStatusToHistory($order->getStatus(), $message, $isCustomerNotified);
        }
    } elseif ($order->getState() && ($orderStatus !== $order->getStatus() || $message)) {
        // add message to history if order state already declared
        $order->setState($orderState, $orderStatus, $message, $isCustomerNotified);
    } elseif (($order->getState() != $orderState) || ($order->getStatus() != $orderStatus) || $message) {
        // set order state
        $order->setState($orderState, $orderStatus, $message, $isCustomerNotified);
    }

    Mage::dispatchEvent('sales_order_payment_place_end', array('payment' => $this));

    return $this;
}


/**
 * Capture the payment online
 * Does not create invoice automatically for order
 * Updates transactions hierarchy, if required
 * Updates payment totals, updates order status and adds proper comments
 *
 * TODO: eliminate logic duplication with registerCaptureNotification()
 *
 * @return Mage_Sales_Model_Order_Payment
 * @throws Mage_Core_Exception
 */
public function capture($invoice)
{

    $invoice = null;
    $order = $this->getOrder();

    $amountToCapture = $this->_formatAmount($order->getBaseGrandTotal());

    $paidWorkaround = (float)$amountToCapture;
    $this->_isCaptureFinal($paidWorkaround);

    $this->_generateTransactionId(
        Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE,
        $this->getAuthorizationTransaction()
    );

    Mage::dispatchEvent('sales_order_payment_capture', array('payment' => $this, 'invoice' => $invoice));

    $status = true;
    if (!$this->getIsTransactionPending()) {
        // attempt to capture: this can trigger "is_transaction_pending"
        $this->getMethodInstance()->setStore($order->getStoreId())->capture($this, $amountToCapture);

        $transaction = $this->_addTransaction(
            Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE,
            $invoice,
            true
        );

        if ($this->getIsTransactionPending()) {
            $message = Mage::helper('sales')->__('Capturing amount of %s is pending approval on gateway.', $this->_formatPrice($amountToCapture));
            $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW;
            if ($this->getIsFraudDetected()) {
                $status = Mage_Sales_Model_Order::STATUS_FRAUD;
            }
        } else { // normal online capture: invoice is marked as "paid"
            $message = Mage::helper('sales')->__('Captured amount of %s online.', $this->_formatPrice($amountToCapture));
            $state = Mage_Sales_Model_Order::STATE_NEW;
            $this->_updateTotals(array('base_amount_paid_online' => $amountToCapture));
        }
        if ($order->isNominal()) {
            $message = $this->_prependMessage(Mage::helper('sales')->__('Nominal order registered.'));
        } else {
            $message = $this->_prependMessage($message);
            $message = $this->_appendTransactionToMessage($transaction, $message);
        }
        $order->setState($state, $status, $message);

        return $this;
    }
    Mage::throwException(
        Mage::helper('sales')->__('The transaction "%s" cannot be captured yet.', $amountToCapture)
    );
}

/**
 * Process a capture notification from a payment gateway for specified amount
 * Does not create an invoice automatically if the amount if the amount covers the order base grand total completely
 * Updates transactions hierarchy, if required
 * Prevents transaction double processing
 * Updates payment totals, updates order status and adds proper comments
 *
 * TODO: eliminate logic duplication with capture()
 *
 * @param float $amount
 * @param bool $skipFraudDetection
 * @return Mage_Sales_Model_Order_Payment
 */
public function registerCaptureNotification($amount, $skipFraudDetection = false)
{

    $this->_generateTransactionId(Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE,
        $this->getAuthorizationTransaction()
    );

    $order   = $this->getOrder();
    $amount  = (float)$amount;
    $invoice = null;

    // register new capture
    if (!$invoice) {
        $isSameCurrency = $this->_isSameCurrency();
        if (!$isSameCurrency && !$this->_isCaptureFinal($amount)) {
            if (!$skipFraudDetection || !$isSameCurrency) {
                $this->setIsFraudDetected(true);
            }
            $this->_updateTotals(array('base_amount_paid_online' => $amount));
        }
    }

    $status = true;
    $letsEmail = false;
    if ($this->getIsTransactionPending()) {
        $message = Mage::helper('sales')->__('Capturing amount of %s is pending approval on gateway.', $this->_formatPrice($amount));
        $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW;
        $letsEmail = true;
        if ($this->getIsFraudDetected()) {
            $message = Mage::helper('sales')->__('Order is suspended as its capture amount %s is suspected to be fraudulent.', $this->_formatPrice($amount, $this->getCurrencyCode()));
            $status = Mage_Sales_Model_Order::STATUS_FRAUD;
            $letsEmail = false;
        }
    } else {
        $message = Mage::helper('sales')->__('Registered notification about captured amount of %s.', $this->_formatPrice($amount));
        $state = Mage_Sales_Model_Order::STATE_NEW;
        $letsEmail = true;
        if ($this->getIsFraudDetected()) {
            $state = Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW;
            $message = Mage::helper('sales')->__('Order is suspended as its capture amount %s is suspected to be fraudulent.', $this->_formatPrice($amount, $this->getCurrencyCode()));
            $status = Mage_Sales_Model_Order::STATUS_FRAUD;
            $letsEmail = false;
        }
    }

    $transaction = $this->_addTransaction(Mage_Sales_Model_Order_Payment_Transaction::TYPE_CAPTURE, $invoice, true);
    $message = $this->_prependMessage($message);
    $message = $this->_appendTransactionToMessage($transaction, $message);
    $order->setState($state, $status, $message);
    if ($letsEmail === true && !$order->getEmailSent()){
         $order->queueNewOrderEmail()->addStatusHistoryComment(
                Mage::helper('paypal')->__('Notified Customer')
            )
            ->setIsCustomerNotified(true)
            ->save();
    }

    return $this;
}
}
?>
  • Did you ever get a solution to this problem? – ProxiBlue Nov 23 '17 at 2:17
  • ProxiBlue to this date still no soliton. Just made a script to uncancel – Vaishal Patel Nov 23 '17 at 6:50
1

We have this issue appear occasionally

Having placed debug trace on the paypal cancel action, I got the following information:

enter image description here

which clearly shows the cancel action was called via the Mage_Paypal_StandardController

so, this is an external call, to cancel the order.

Locating the actual webserver logs, I can then see the call did indeed come from external (paypal)

enter image description here

The interesting bit there is that it was a 302 (redirect)

If I load that referrer URL, I get a paypal display that the session has timed out.

Also, I could determine this was from an iPad. I don't have enough of these to determine other devices, so that could mean squat.

So, given from what I see, I am making a calculated assumption that this cancel action is initiated from paypal, when/if teh user session on teh paypal side has times out, or had issues keeping session during the transaction period.

Paypal then actions a cancel request ?

enter image description here

UPDATE: Setting the trace:

As per comments request, here is how I placed the trace. When the order is cancelled, a comment is placed in the order history/comments concerning that cancellation. This is where I injected my trace code, thus tracing back the code path to having reached that comment insertion.

It can just as well be pushed to a log file.

I created a small module for this:

I simply did a rewrite of the core sales order module (be aware of creating a rewrite conflict if some other 3rd party module also rewrites this class. In my case this was not an issue:

In module config.xml

<global>
        <models>
            <enjo_sales>
                <class>Enjo_Sales_Model</class>
            </enjo_sales>
            <sales>
                <rewrite>
                    <order>Enjo_Sales_Model_Order</order>
                </rewrite>
            </sales>
        </models>
    </global>

and then in the model class:

<?php

class Enjo_Sales_Model_Order extends Mage_Sales_Model_Order
{
    protected function _setState($state, $status = false, $comment = '',
                                 $isCustomerNotified = null, $shouldProtectState = false)
    {
        if ($this->getState() != $state && $state == self::STATE_CANCELED) {
            $comment .= Varien_Debug::backtrace(true,true, true);
        }
        return parent::_setState($state, $status, $comment, $isCustomerNotified, $shouldProtectState);

    }
}
  • Interesting insight, 302 redirects are non-permenet redirects. Could be worth contacting paypal. – Vaishal Patel Nov 23 '17 at 7:25
  • We have only had 2 instances happen over a 6month period. I placed the debug trace after the first as I could not figure out why it happens. Our current view is to monitor, and action something if it becomes more frequent. We have 100s of PayPal transactions daily. It would be of interest, from your more frequently occurring instances to check if this is specific to certain devices, ie: ipads – ProxiBlue Nov 23 '17 at 23:22
  • Would you like me to debug anything specific? – Vaishal Patel Nov 24 '17 at 7:45
  • Just curious if the issue is specific to a set of devices, for example only IPads, or is it spread over desktops, android etc. – ProxiBlue Nov 24 '17 at 12:27
  • Mainly I have desktop users, but not happening frequently it is intermittent. – Vaishal Patel Nov 27 '17 at 13:20
0

It could be related to Paypal Express, did you check that? More info here: https://stackoverflow.com/questions/4563732/paypal-orders-cancelling-automatically-in-magento

  • 1
    I use standard paypal rather than express – Vaishal Patel Jun 21 '17 at 13:53
0

Integrating Magento with PayPal Express (not PayPal standard) the users can buy our store's products.

However, there are two different situations: 1) If the customer has got a Paypal account, he will be able to finish the buy process without issues. 2) However, if the customer hasn't got a Paypal account, or if he doesn't want to use it, and prefers to buy the products using his credit card without use the PayPal credentials, then the process will finish with the following error:"This payment cannot be processed using your paypal account at this time".

The problem is the seller will receive the money, but the customer will think just the opposite.

This issue has been discused and confirmed with PayPal support team.

The real issue is during the buy process, the DoExpressCheckoutPayment PayPal's function is called. However, when the buy process finish with this error, a parameter called successpageredirectrequested is set to true. This flag indicates whether you need to redirect the customer to back to PayPal after completing the transaction. However, this is an obsolete PayPal's behavior, and should't be used.

As Magento is using this parameter's function, when he tries to redirect again to PayPal (that's what successpageredirectrequested set to true is doing) PayPal will return this error.

We can fix it making a little change in Magento's source code, at

app/code/core/Mage/Paypal/Controller/Express/Abstract.php

Just changing the function public function placeOrderAction(). Look for the following code inside this function:

// redirect if PayPal specified some URL (for example, to Giropay bank)

$url = $this->_checkout->getRedirectUrl();
  if ($url) {
  $this->getResponse()->setRedirect($url);
  return;
  }
  $this->_initToken(false); // no need in token anymore
  $this->_redirect(?checkout/onepage/success?);
  return;
  }

We just need to comment the "if", just like this:

//if ($url) {
//$this->getResponse()->setRedirect($url);
// return;
//}

That's enough to fix the issue. However, this is not a current fix, and this should be reviewed by Magento's programmers just to fix it in the Magento's new versions.

  • This is for Paypal express, need a solution for the standard. – Vaishal Patel Jun 27 '17 at 15:35
  • still no solution ? – Vaishal Patel Jul 20 '17 at 10:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.