Magento PayPal重复发票错误

时间:2012-01-12 20:37:28

标签: php magento paypal

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

快进到今天......同样的客户,新的订单。 Magento STILL在sales_flat_quote表格中引用了9月份的旧引用。当他们登录时,它加载了客户报价(仍然有效)并尝试将其用于此订单。这导致重复发票ID 错误。

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

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

编辑:

我已经进一步了解这一点。我已将结果添加到checkout 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()方法中被覆盖。

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

5 个答案:

答案 0 :(得分:4)

  1. 登录您的Paypal帐户
  2. 转到 Profile > Payment Receiving Preferences
  3. 阻止意外付款下选择否,允许每个发票ID多次付款
  4. enter image description here

答案 1 :(得分:0)

基本上发生的事情是,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);
        ...
    }
}

为:

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

如果您有任何问题,请与我们联系。

答案 2 :(得分:0)

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

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

答案 3 :(得分:-1)

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

喜欢听到任何一种方式。我正准备解决这个问题并使我们的PayPal例外更加清晰(使用来自here的PayPal的一些解释。所以如果你觉得它有帮助,那就很想知道。

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

编辑: 根据{{​​3}}(4月份出版),他们认为他们已经解决了问题:

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

任何人都可以确认升级到Magento 1.7确实可以解决问题吗?每次我查看它,它似乎确实是PayPal Express问题(我们的付款通常通过PayPal Pro,并且似乎没有错误)。

答案 4 :(得分:-1)

问题的原因:

针对潜在问题,所提出的解决方案仅解决方法,即发票的increment_last_id落后于订单的increment_last_id

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

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

<强>更新

正如其他人所指出的,这些ID不同步是正常的,但如果发票ID太多,那么在订单ID之后(而不是相反)可能会出现PayPal问题(更多信息{{ 3}})。 在拒绝这个解决方案之前,试试吧,它对我和其他人都很有效。

如何修复:

使用您首选的数据库管理工具(herePHPmyAdmin,...)转到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)
  3. 所以我们要做的唯一事情是将INVOICE值设置为ORDER值之一。我们可以使用任何数据库管理工具,或直接使用sql命令

    执行此操作
    UPDATE eav_entity_store
      SET increment_last_id="100001708"
      WHERE entity_type_id="6" AND store_id="1"
    

    如果您有多个商店,则必须更改store_id。

    此答案基于Adminer的信息。