Magento PayPal Duplicate Invoice Error - PullRequest
       0

Magento PayPal Duplicate Invoice Error

11 голосов
/ 13 января 2012

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

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

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

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

EDIT:

Я стал немного дальше с этим. Я добавил код к контролю IndexController, чтобы поймать ошибку, и, если это ошибка дублирующегося счета, он отменяет reserved_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() цитаты.

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

Ответы [ 5 ]

4 голосов
/ 17 апреля 2015
  1. Войдите в свою учетную запись Paypal
  2. Перейдите на Профиль> Настройки получения платежей
  3. Под Блокировка случайных платежей выберите Нет, разрешить несколько платежей на один номер счета

enter image description here

0 голосов
/ 08 декабря 2015

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

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

0 голосов
/ 01 сентября 2015

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

Представленное решение - только обходные пути для основной проблемы, которая заключается в том, что 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 |
+-----------------+----------------+----------+------------------+-------------------+

Интересные значения здесь:

  1. ORDER increment_last_id: entity_type_id = 5 (100001708)
  2. INVOICE increment_last_id: entity_type_id = 6 (100000926)

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

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

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

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

0 голосов
/ 17 апреля 2015

По сути, происходит то, что magento отправляет PayPal OrderId (номер счета), который уже оплачен в системе.Это заставляет 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);
        ...
    }
}

to:

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();

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

0 голосов
/ 15 февраля 2012

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...