【问题标题】:Magento PayPal Duplicate Invoice ErrorMagento PayPal 重复发票错误
【发布时间】:2012-02-09 03:31:21
【问题描述】:

每隔一段时间,客户在尝试提交显示 PayPal 网关已拒绝请求的订单时都会收到错误消息。由于提供了重复的发票 ID,交易被拒绝。 在深入研究后,我相信我已经缩小了问题的范围。在最近的案例中,客户在 4 个月前尝试下订单,但收到了来自 PayPal 的内部错误。我从与 PayPal 的交谈中得知,该客户的信用卡已被标记。当他们尝试下第一个订单时,PayPal 拒绝了它,但仍然认为我们的 Magento 商店提供的发票 ID“已使用”。

快进到今天……同样的客户,新的订单。 Magento 仍然在 sales_flat_quote 表中保留了 9 月的旧报价。当他们登录时,它会加载客户报价(仍处于活动状态)并尝试将其用于此订单。这导致了Duplicate Invoice ID错误。

我在 Mage_Sales_Model_Observer 类中看到有一个从 cron 作业调用的 cleanExpiredQuotes 方法。但是,这只会影响 "is_active" = 0 的引号。由于此引号被认为是有效的,因此它从未被清除。

很明显,Magento 代码和 PayPal 之间存在脱节。但这就是我所掌握的。有没有其他人经历过这个?如果有,有什么建议吗?

编辑:

我对此有所了解。我已将代码添加到结帐 IndexController 以捕获错误,如果它是重复发票错误,它会取消设置报价中的 reserved_order_id 再次调用 saveOrderAction。这会导致报价保留一个新的订单 ID,然后将其提交给 PayPal。我现在遇到的问题是,当它第二次尝试使用新发票号时,所有总数均为 0。我尝试将 totals_collected_flag 设置为 false,以便它重新收集总数,但是他们第二次总是为 0。更具体地说,Mage_Sales_Model_Quote_Address 中的总数为 0,这是 Mage_Sales_Model_Order 最终使用的。 Mage_Sales_Model_Quote 中的总数是正确的,但它们会在报价的 collectTotals() 方法中被覆盖。

显然,在第一次尝试之后,有些东西正在取消所有值,但我不知道是什么或在哪里。如果有人有任何想法,我很想听听他们的意见!

【问题讨论】:

  • 好问题。我自己也遇到过这个。您使用的是什么版本的 Magento?我已经在 Magento 1.4 和 1.5(我们当前使用的版本)中看到了这一点。
  • 我们使用的是 1.5.1.0,但我可能会在一两个月内升级我们。如果我不再看到这种情况,我会回复。
  • @BrianVPS 你解决了吗?
  • 正如我在您的回答中指出的那样,我确实想出了一个解决方法,但我喜欢您的回答。最终,它现在可以工作了,我很惊讶我不得不做出这样的改变。
  • rakeshjesadiya.com/error-10412-paypal-duplicate-invoice-php按链接中给出的步骤解决错误

标签: php magento paypal


【解决方案1】:
  1. 登录您的 Paypal 帐户
  2. 转到Profile > Payment Receiving Preferences
  3. 阻止意外付款下选择否,允许每个发票 ID 多次付款

【讨论】:

  • 这有点冒险,但我想我的解决方案基本上是一样的。感谢您发布此信息!我不知道那个设置,我会记住的。
  • 我不确定它为什么会有风险。你能解释一下吗?恕我直言,让客户被错误消​​息阻止而没有机会使用该网站并购买产品是有风险的。
  • 我觉得没有那张支票是有风险的,所以客户不能两次支付发票。但是,正如我所说,我的“修复”基本上做同样的事情,尽管它确实记录了它得到重复错误的事实,所以我可以回去追踪任何重复的付款。我同意允许客户进行购买至关重要,但重复收费激怒他们也无济于事。
  • 嗯,我理解你的担心。但是你真的有过重复付款的情况吗?我认为这主要是一场虚惊。也许最好的办法是将这个错误报告给 Magento(也可能报告给 PayPal)。
  • 我相信当您有多个 Magento 安装都指向同一个 PayPal 帐户时,这可能是必要的更改。我刚刚部署了一个新的 Magento 安装,并从新商店收到了错误。我的猜测是,新安装的早期发票 ID 之一与我现有安装之一的早期发票 ID 重叠......除非你有一些东西可以在多个 Magento 安装中保持发票 ID 唯一,否则我不知道你是怎么做到的d 避免这个问题。
【解决方案2】:

实际上发生的事情是,magento 正在向贝宝发送一个已经在系统中支付的 orderId(发票号码)。这会导致贝宝返回指示此发票编号重复的响应。因此,我在这里所做的是尝试检测该消息响应,生成新的 orderId,然后重新提交到 paypal 进行重新处理。

这是启动向 magento 发送信息的整个链的操作。它位于“Mage_Paypal_Controller_Express_Abstract”。我修改了贝宝响应生成的“令牌”。此令牌将包含有关所发生错误的信息。

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']);
         ...
    }

}

此令牌由“Mage_Paypal_Model_Express_Checkout”中的 start() 方法生成。 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 调用和响应。这是通过位于“Mage_Paypal_Model_Api_Nvp”的 call() 函数完成的。

在生成响应后,我们将检查错误响应,而不是重定向,我们将简单地将其返回链上。

位于第 997 行附近:

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

原来是这样的:

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

如果有重复输入错误,它将执行此操作..

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

如果您有任何问题,请告诉我。

【讨论】:

  • 感谢您的深入回答。从表面上看,这看起来不错,尽管我没有办法轻松测试它。我最近升级到 1.9 仍然有这个问题,所以我最终做的是更改结帐控制器中的 saveOrder 函数,以便在触发此错误时重新调用自身。就在之前,我取消了保留的订单 ID 并重新保留了一个,这会增加数字。这很像您的解决方案,只是在控制器中而不是 PP 代码中。你的可能更优雅;)。感谢您的发布!我会接受这个答案,因为这基本上就是我所做的。
【解决方案3】:

我相信当您有多个 Magento 安装都指向同一个 PayPal 帐户时,这可能是必要的更改。我刚刚部署了一个新的 Magento 安装,并从新商店收到了错误。我的猜测是,新安装的早期发票 ID 之一与我现有安装的早期发票 ID 重叠...

as mentioned by @george here 的一个选项是编辑您的 PayPal 设置,这将使您尽快启动并运行。不过,这确实为一些风险打开了大门。可能更好的是在所有 Magento 安装上安装一个插件,例如 Custom Order Prefix。这样,尽管发票 ID 本身重叠,但每次安装都会传递具有不同前缀的发票 ID。

【讨论】:

    【解决方案4】:

    尚未完成此操作,但似乎安装此Fooman extension 可能会使用错误/旧订单号修复引号。这对你有用吗?

    无论哪种方式都喜欢听。我正准备解决这个问题并让我们的 PayPal 例外情况更加清晰(使用来自 here 的 PayPal 的一些解释。所以如果你发现它有帮助,很想知道。

    编辑: 找到更多信息here。显然 Fooman 扩展有帮助,但并不能完全解决问题。这有助于解决您的问题吗?

    编辑: 根据change log for Magento 1.7.0.0(4 月发布),他们认为他们已经解决了问题:

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

    谁能确认升级到 Magento 1.7 确实可以解决问题?每次我查看它时,似乎确实是 PayPal Express 问题(我们的付款通常通过 PayPal Pro 进行,而且似乎没有错误)。

    【讨论】:

    • 不,我们已经有了 Fooman 扩展,但它没有帮助。我 99% 确定问题是由以下原因引起的……当客户尝试下单但失败时,PP 认为 Invoice # 已被使用,而 Magento 没有。下次客户尝试订购时,Magento 会在报价表中找到旧记录并尝试使用它。 PP 返回 dup 错误,Magento 忽略该错误并继续使用相同的数字。如果收到 dup 错误,我已经通过增加数字来“修补”它,但它没有解决根本原因,因此客户仍然会看到至少一个错误。
    • 如果您找到任何好的信息或真正的解决方法,请在此处发布,因为这对我们来说仍然是一个问题。
    • Brian,刚刚发现 Magento 1.7 可能有修复(见我的编辑)。您是否尝试过升级到它?
    • 谢谢,acorncom,这是很棒的信息!我还没有尝试升级,而且这可能不会很快出现。升级 Magento 可能是一场噩梦。曾经从 1.4.2.0 升级到 1.5.0.1。我不期待很快再次升级。您是否确认这确实解决了问题?如果是这样,我很想知道它们发生了什么变化,并可能将其应用于 1.5.0.1 代码。
    • 这个问题在 1.7 中没有修复。
    【解决方案5】:

    问题原因:

    所提出的解决方案仅是解决方法的根本问题,即发票的increment_last_id 落后于订单的increment_last_id

    Magento 代码没有问题,是 数据库 处于有问题的状态。这通常发生在 Magento 更新之后。

    要解决此问题,您只需将发票 increment_last_id 设置为与订单相同的值。

    更新

    正如其他人所指出的,这些 id 不同步是正常的,但如果发票 id 太落后于订单 id(而不是相反),PayPal 可能会出现问题(更多信息 @987654321 @)。 在拒绝此解决方案之前,请先尝试一下,它对我和其他人都非常有效。

    如何解决:

    使用您首选的数据库管理工具(PHPmyAdminAdminer、...)转到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。

    此答案基于来自this article 的信息。

    【讨论】:

    • 我觉得order_idinvoice_id不同步很正常。
    • 是的,但是当 INVOICE id 落后于订单 id 时,paypal 可能会出现问题。更多信息在这里:magetricks.com/tricks/…
    猜你喜欢
    • 2014-09-07
    • 1970-01-01
    • 2013-10-31
    • 2015-12-07
    • 2015-10-09
    • 2011-01-28
    • 1970-01-01
    • 1970-01-01
    • 2016-02-22
    相关资源
    最近更新 更多