Ошибка дублирования счета Magento PayPal

Время от времени клиент получает сообщение об ошибке при попытке отправить заказ, в котором говорится, что шлюз PayPal отклонил запрос. Сделка была отклонена в результате получения дублированного идентификатора счета. После того, как вы немного вникаете в это, я считаю, что я сузил проблему. В последних случаях клиент попытался разместить заказ 4 месяца назад и получил Внутреннюю ошибку от PayPal. Я узнал от разговоров с PayPal, что эта кредитная карта клиента была отмечена. Когда они попытались разместить первый заказ, PayPal отклонил его, но все еще считал идентификатор счета, который наш Magento Store предоставил "использован".

Ускорьте вперед на сегодняшний день... тот же клиент, новый заказ. У Magento STILL была старая цитата из сентября в таблице sales_flat_quote. Когда они вошли в систему, она загрузила котировку клиента (которая по-прежнему активна) и попыталась использовать ее для этого заказа. Это привело к ошибке Duplicate Invoice ID.

В классе Mage_Sales_Model_Observer я вижу метод cleanExpiredQuotes, который вызывается из задания cron. Тем не менее, это только эффекты котировки с "is_active" = 0. Поскольку эта цитата считается активной, она никогда не очищалась.

Так ясно, что есть разрыв между кодом Magento и PayPal. Но об этом, насколько я понял. Кто-нибудь еще испытал это? Если да, то какие-либо предложения?

EDIT:

Я получил немного больше. Я добавил код в checkout IndexController, чтобы поймать ошибку, и, если это Duplicate Invoice Error, он отключает зарезервировать_order_id в цитате, снова вызывает saveOrderAction. Это заставляет цитату зарезервировать новый идентификатор заказа, который затем отправляет в PayPal. Проблема, с которой я столкнулся сейчас, заключается в том, что когда она пытается второй раз с новым номером счета, все итоговые значения равны 0. Я попытался установить значение totals_collected_flag на false, чтобы он собирал итоговые суммы повторно, но они всегда 0 во второй раз. Более конкретно, итоговые значения в Mage_Sales_Model_Quote_Address равны 0, и это означает, что используется Mage_Sales_Model_Order. Итоговые значения в Mage_Sales_Model_Quote верны, но они переписываются в методе collectTotals() цитаты.

Очевидно, что после первой попытки что-то отменяет все значения, но я не знаю, что и где. Если у кого-то есть идеи, я бы хотел их услышать!

Ответ 1

Что в основном происходит, так это то, что magento отправляет paypal номер заказа (номер счета-фактуры), который уже был оплачен в системе. Это заставляет paypal возвращать ответ, который указывает, что этот номер счета-фактуры является дубликатом. Итак, то, что я делаю здесь, пытается обнаружить этот ответ сообщения, сгенерировать новый orderId и повторно отправить на paypal для переработки.

Вот действие, которое запускает целую цепочку отправки информации в magento. Он расположен в 'Mage_Paypal_Controller_Express_Abstract'. Я изменил "токен", сгенерированный paypal-ответом. Этот токен будет содержать информацию об ошибке, которая произошла.

startAction(){
    ...
    $token = $this->_checkout->start(Mage::getUrl('*/*/return'), Mage::getUrl('*/*/cancel'));
    if ($token && $url = $this->_checkout->getRedirectUrl()) {
        $this->_initToken($token);
        ...
    }
}

в

startAction(){
    ...
    $token = $this->_checkout->start(Mage::getUrl('*/*/return'), Mage::getUrl('*/*/cancel'));

    //while this token is invalid
    while (isset($token['error'])) {
        //generate a new token
        $token = $this->_checkout->start(Mage::getUrl('*/*/return'),Mage::getUrl('*/*/cancel'), TRUE);
    }
    if ($token['token'] && $url = $this->_checkout->getRedirectUrl()) {
        $this->_initToken($token['token']);
         ...
    }

}

Этот токен генерируется методом start() в 'Mage_Paypal_Model_Express_Checkout'. start() также обрабатывает весь процесс манипулирования объектами. Здесь мы условно изменим productId.

измененная функция будет выглядеть так:

public function start($returnUrl, $cancelUrl, $errorAgain = FALSE)
{
    $this->_quote->collectTotals();

    if (!$this->_quote->getGrandTotal() && !$this->_quote->hasNominalItems()) {
        Mage::throwException(Mage::helper('paypal')->__('PayPal does not support processing orders with zero amount. To complete your purchase, proceed to the standard checkout process.'));
    }
    if ($errorAgain) {
        Mage::log('why is this running?');
        $this->_quote->setReservedOrderId($this->_quote->getReservedOrderId($this->_quote));
        $this->_quote->reserveOrderId()->save();
    }
    //$this->_quote->setReservedOrderId($this->_quote->_getResource()->getReservedOrderId($this->_quote));
    //$this->_quote->setReservedOrderId($this->_quote->getReservedOrderId($this->_quote));
    $this->_quote->reserveOrderId()->save();
    // prepare API
    $this->_getApi();
    $this->_api->setAmount($this->_quote->getBaseGrandTotal())
        ->setCurrencyCode($this->_quote->getBaseCurrencyCode())
        ->setInvNum($this->_quote->getReservedOrderId())
        ->setReturnUrl($returnUrl)
        ->setCancelUrl($cancelUrl)
        ->setSolutionType($this->_config->solutionType)
        ->setPaymentAction($this->_config->paymentAction)
    ;
    if ($this->_giropayUrls) {
        list($successUrl, $cancelUrl, $pendingUrl) = $this->_giropayUrls;
        $this->_api->addData(array(
            'giropay_cancel_url' => $cancelUrl,
            'giropay_success_url' => $successUrl,
            'giropay_bank_txn_pending_url' => $pendingUrl,
        ));
    }

    $this->_setBillingAgreementRequest();

    if ($this->_config->requireBillingAddress == Mage_Paypal_Model_Config::REQUIRE_BILLING_ADDRESS_ALL) {
        $this->_api->setRequireBillingAddress(1);
    }

    // supress or export shipping address
    if ($this->_quote->getIsVirtual()) {
        if ($this->_config->requireBillingAddress == Mage_Paypal_Model_Config::REQUIRE_BILLING_ADDRESS_VIRTUAL) {
            $this->_api->setRequireBillingAddress(1);
        }
        $this->_api->setSuppressShipping(true);
    } else {
        $address = $this->_quote->getShippingAddress();
        $isOverriden = 0;
        if (true === $address->validate()) {
            $isOverriden = 1;
            $this->_api->setAddress($address);
        }
        $this->_quote->getPayment()->setAdditionalInformation(
            self::PAYMENT_INFO_TRANSPORT_SHIPPING_OVERRIDEN, $isOverriden
        );
        $this->_quote->getPayment()->save();
    }

    // add line items
    $paypalCart = Mage::getModel('paypal/cart', array($this->_quote));
    $this->_api->setPaypalCart($paypalCart)
        ->setIsLineItemsEnabled($this->_config->lineItemsEnabled)
    ;

    // add shipping options if needed and line items are available
    if ($this->_config->lineItemsEnabled && $this->_config->transferShippingOptions && $paypalCart->getItems()) {
        if (!$this->_quote->getIsVirtual() && !$this->_quote->hasNominalItems()) {
            if ($options = $this->_prepareShippingOptions($address, true)) {
                $this->_api->setShippingOptionsCallbackUrl(
                    Mage::getUrl('*/*/shippingOptionsCallback', array('quote_id' => $this->_quote->getId()))
                )->setShippingOptions($options);
            }
        }
    }

    // add recurring payment profiles information
    if ($profiles = $this->_quote->prepareRecurringPaymentProfiles()) {
        foreach ($profiles as $profile) {
            $profile->setMethodCode(Mage_Paypal_Model_Config::METHOD_WPP_EXPRESS);
            if (!$profile->isValid()) {
                Mage::throwException($profile->getValidationErrors(true, true));
            }
        }
        $this->_api->addRecurringPaymentProfiles($profiles);
    }

    $this->_config->exportExpressCheckoutStyleSettings($this->_api);

    // call API and redirect with token
    $response = $this->_api->callSetExpressCheckout();
    $token['token'] = $this->_api->getToken();
    $this->_redirectUrl = $this->_config->getExpressCheckoutStartUrl($token['token']);
    if ($response == 'duplicate') {
        $token['error'] = 'duplicate';
        return $token;
    } elseif (isset($token['error'])) {
        unset($token['error']);
    }
    $this->_quote->getPayment()->unsAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT);
    $this->_quote->getPayment()->save();
    return $token;
}

Теперь, заключительная часть обрабатывает фактический вызов и ответ в PayPal. Это делается с помощью функции call(), расположенной в 'Mage_Paypal_Model_Api_Nvp'.

После генерации ответа мы проверим ответ на ошибку и вместо перенаправления просто вернем его в цепочку.

расположенный вокруг строки 997:

if ($response['L_SHORTMESSAGE0'] == 'Duplicate invoice') {
    return $response;
}

Итак, это выглядит так:

startaction()->start()->call()->start()->startaction()->redirect();

если есть повторяющаяся ошибка ввода, это сделает это.

startaction()->start()->call(error)->start()->call()->start()->staraction()->redirect();

Сообщите мне, если у вас есть какие-либо вопросы.

Ответ 2

  • Войдите в свою учетную запись Paypal
  • Перейдите к Профиль > Настройки получения платежей
  • В разделе Блокировать случайные платежи выберите Нет, разрешите несколько платежей за идентификатор счета

enter image description here

Ответ 3

Я полагаю, что это может быть необходимым изменением, когда у вас есть несколько установок Magento, все указали на одну и ту же учетную запись PayPal. Я только что развернул новую установку Magento и получил ошибку из нового магазина. Я предполагаю, что один из ранних идентификаторов счетов на новой установке перекрывается с ранним идентификатором счета из одной из моих существующих установок...

Один из вариантов как упоминалось здесь @george здесь, - это изменить настройки PayPal, которые помогут вам запустить и запустить ASAP. Тем не менее, это открывает дверь для некоторого риска. Скорее всего, лучше установить плагин, например Custom Order Prefix на всех ваших установках Magento. Таким образом, каждая установка будет передавать идентификаторы счетов с другим префиксом, несмотря на то, что сами идентификаторы счетов перекрываются.

Ответ 4

Еще не сделали этого, но похоже, что установка Расширение Fooman может исправить кавычки, используя неправильный/старый Order #. Это работает для вас?

Любовь слышать в любом случае. Я собираюсь решить эту проблему и сделать наши исключения в PayPal более четкими (используя некоторые объяснения PayPal из здесь. Так что если вы найдете, что это помогает, хотел бы знать.

Изменить: Найдена дополнительная информация здесь. По-видимому, расширение Fooman помогает, но не полностью устраняет проблемы. Помогает ли это решить вашу проблему?

Изменить: В журнале изменений для Magento 1.7.0.0 (который вышел в апреле), они считают, что они исправили проблему:

Fixed: "Wrong order ID" exception in PayPal Express module under heavy load

Может ли кто-нибудь подтвердить, что обновление до Magento 1.7 действительно устраняет проблему? Каждый раз, когда я смотрел на него, это кажется проблемой PayPal Express (наши платежи обычно проходят через PayPal Pro и, похоже, не имеют ошибок).

Ответ 5

ПРИЧИНА ПРОБЛЕМЫ:

Представленное решение только обходные методы для основной проблемы, которая заключается в том, что increment_last_id для счетов-фактур отстает от increment_last_id для заказов.

В коде Magento нет ничего плохого, это База данных, которая находится в проблемном состоянии. Обычно это происходит после обновления Magento.

Чтобы устранить проблему, вам просто нужно установить свой счет-фактуру increment_last_id на то же значение, что и заказ.

UPDATE

Как отмечают другие, это нормально, что эти идентификаторы не синхронизированы, но если идентификатор счета слишком много, то может возникнуть проблема с порядком (а не наоборот) проблемы с PayPal (дополнительная информация здесь). Прежде чем отказаться от этого решения, попробуйте его, он отлично работает для меня и других.

КАК ИСКАТЬ:

Используйте свой предпочтительный инструмент управления базами данных (PHPmyAdmin, Adminer,...) перейдите в таблицу eav_entity_store и проверьте значения. Он должен выглядеть так. например:

+-----------------+----------------+----------+------------------+-------------------+
| entity_store_id | entity_type_id | store_id | increment_prefix | increment_last_id |
+-----------------+----------------+----------+------------------+-------------------+
|               1 |              5 |        1 |                1 |         100001708 |
|               2 |              6 |        1 |                1 |         100000926 |
|               3 |              8 |        1 |                1 |         100000888 |
|               4 |              7 |        1 |                1 |         100000054 |
+-----------------+----------------+----------+------------------+-------------------+

Интересными здесь значениями являются:

  • ORDER increment_last_id: entity_type_id = 5 (100001708)
  • INVOICE increment_last_id: entity_type_id = 6 (100000926)

Итак, единственное, что нам нужно сделать, это установить значение INVOICE в значение ORDER. Мы можем сделать это с помощью любого инструмента управления db или непосредственно с помощью команды sql

UPDATE eav_entity_store
  SET increment_last_id="100001708"
  WHERE entity_type_id="6" AND store_id="1"

Если у вас несколько магазинов, вам придется изменить store_id.

Этот ответ основан на информации от этой статьи.