在尝试提交 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()
方法中被覆盖。
显然,在第一次尝试之后,某些东西会取消所有的价值,但我不知道是什么或在哪里。如果有人有任何想法,我很乐意听到他们!
答案 0 :(得分:4)
答案 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}})。 在拒绝这个解决方案之前,试试吧,它对我和其他人都很有效。
如何修复:
使用您首选的数据库管理工具(here,PHPmyAdmin,...)转到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 |
+-----------------+----------------+----------+------------------+-------------------+
这里有趣的值是:
所以我们要做的唯一事情是将INVOICE值设置为ORDER值之一。我们可以使用任何数据库管理工具,或直接使用sql命令
执行此操作UPDATE eav_entity_store
SET increment_last_id="100001708"
WHERE entity_type_id="6" AND store_id="1"
如果您有多个商店,则必须更改store_id。
此答案基于Adminer的信息。