分析Ecshop自带PayPal标准支付模块支付失败的原因 ecshop模板网 / 2014-07-06

本文分析了Ecshop自带的PayPal标准支付模块在特定设置下导致支付成功却显示失败的问题,并深入探讨了其背后的代码逻辑及解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分析Ecshop自带PayPal标准支付模块支付失败的原因

ecshop模板网 / 2014-07-06





结论 如果卖家的PayPal帐号设置了不能设置自动返回,那么支付完成后将在10秒内自动跳转到购物网站






Ecshop自带有一个PayPal标准支付模块,只需要在后台安装并设置PayPal帐号即可使用。但是这个Ecshop v2.7.3这个版本的PayPal标准支付是有bug的,当在PayPal设置自动返回后,就会出现订单在返回自己网站后明明已支付成功,却显示支付失败的问题。但只要是手动从PayPal返回的,却一切正常。
刚开始一直不知道是哪里出了问题。仔细分析了一下PayPal的标准支付流程后就可以很容易的找到问题的症结所在了。首先在自己的网站需要生成一个包含购物车信息的表单,用来提交到PayPal。在includes/modules/payment/paypal.php文件中的如下代码:

/**
* 生成支付代码
* @param   array   $order  订单信息
* @param   array   $payment    支付方式信息
*/
function get_code($order, $payment)
{
$data_order_id      = $order['log_id'];
$data_amount        = $order['order_amount'];
$data_return_url    = return_url(basename(__FILE__, '.php'));
$data_pay_account   = $payment['paypal_account'];
$currency_code      = $payment['paypal_currency'];
$data_notify_url    = return_url(basename(__FILE__, '.php'));
$cancel_return      = $GLOBALS['ecs']->url();

$def_url  = '
' .   // 不能省略
"" .                             // 不能省略
"" .                 // 贝宝帐号
"" .                 // payment for
"" .                        // 订单金额
"" .            // 货币
"" .                    // 付款后页面
"" .                      // 订单号
"" .                              // 字符集
"" .                              // 不要求客户提供收货地址
"" .                                  // 付款说明
"" .
"" .
"" .
"" .                      // 按钮
"
";

return $def_url;
}
当该表单提交到PayPal后,客户可在PayPal平台完成支付。当客户完成支付,PayPal会立即post一个表单到购物的站点,具体的返回地址就是刚才那个表单中的notify_url的值。而客户在返回购物网站的时候有两种可能,如果卖家的PayPal帐号设置了自动返回,那么支付完成后将在10秒内自动跳转到购物网站,而这个跳转是没有post那些必要的返回信息的。另一种情况,就是卖家没有设置自动返回,这是在客户点击跳转会购物网站的页面如果用firebug查看一下,是可以看到一个post的表单的,里面包含了所以必须的信息。而Ecshop的PayPal标准支付模块的bug恰恰就是没有考虑到返回的这个差异。
再回过头来看看paypal.php的相关代码,问题就一目了然了,首先上述表单中的return_url是用户完成支付后返回网站时显示给用户看的页面,notify_url的值是用户完成支付时,PayPal用来post相关信息的地址。那么在get_code($order, $payment)这个方法中,不难发现这两个地址是相同的。
那么在继续看paypal.php中用来处理返回信息的代码。代码如下:

/**
* 响应操作
*/
function respond()
{
$payment        = get_payment('paypal');
$merchant_id    = $payment['paypal_account'];               ///获取商户编号

// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value)
{
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}

// post back to PayPal system to validate
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) ."\r\n\r\n";
$fp = fsockopen ('www.paypal.com', 80, $errno, $errstr, 30);

// assign posted variables to local variables
$item_name = $_POST['item_name'];
$item_number = $_POST['item_number'];
$payment_status = $_POST['payment_status'];
$payment_amount = $_POST['mc_gross'];
$payment_currency = $_POST['mc_currency'];
$txn_id = $_POST['txn_id'];
$receiver_email = $_POST['receiver_email'];
$payer_email = $_POST['payer_email'];
$order_sn = $_POST['invoice'];
$memo = !empty($_POST['memo']) ? $_POST['memo'] : '';
$action_note = $txn_id . '(' . $GLOBALS['_LANG']['paypal_txn_id'] . ')' . $memo;

if (!$fp)
{
fclose($fp);

return false;
}
else
{
fputs($fp, $header . $req);
while (!feof($fp))
{
$res = fgets($fp, 1024);
if (strcmp($res, 'VERIFIED') == 0)
{
// check the payment_status is Completed
if ($payment_status != 'Completed' && $payment_status != 'Pending')
{
fclose($fp);

return false;
}

// check that txn_id has not been previously processed
/*$sql = "SELECT COUNT(*) FROM " . $GLOBALS['ecs']->table('order_action') . " WHERE action_note LIKE '" . mysql_like_quote($txn_id) . "%'";
if ($GLOBALS['db']->getOne($sql) > 0)
{
fclose($fp);

return false;
}*/

// check that receiver_email is your Primary PayPal email
if ($receiver_email != $merchant_id)
{
fclose($fp);

return false;
}

// check that payment_amount/payment_currency are correct
$sql = "SELECT order_amount FROM " . $GLOBALS['ecs']->table('pay_log') . " WHERE log_id = '$order_sn'";
if ($GLOBALS['db']->getOne($sql) != $payment_amount)
{
fclose($fp);

return false;
}
if ($payment['paypal_currency'] != $payment_currency)
{
fclose($fp);

return false;
}

// process payment
order_paid($order_sn, PS_PAYED, $action_note);
fclose($fp);

return true;
}
elseif (strcmp($res, 'INVALID') == 0)
{
// log for manual investigation
fclose($fp);

return false;
}
}
}
}
接下来的流程是在接到PayPal post过来的表单后,直接在拼接上cmd=_notify-validate,然后在发回PayPal的服务器进行验证,再对PayPal返回的信息进行逐行匹配,如果发现有’VERIFIED’就表示整个支付完成。所以整个流程并不复杂,当客户完成支付,PayPal会立即post一个表单到notify_url的这个地址,也就会先执行一次respond()这个方法,在这个时候其实网站后台对于订单数据的操作已经完成了。当客户在手动返回的时候又要同样有一个post表单过来,所以会重复执行一遍respond(),当然结果会显示successfully,而当客户是自动返回购物网站的,由于自动返回并没有post过来任何表单,那么拼接上cmd=_notify-validate再发回PayPal服务器验证一定是失败的,所以会显示支付失败,而实际情况是支付成功的。
至此,问题已经很清楚了
前言 3 一、商圈和地区进行关联 4 二、其他页面显示购买记录的函数 5 三、多货币解决方案 6 四、仿淘宝商品详细页实现尺码颜色关联显示库存 12 五、仿淘宝商品详细页加入购物车效果 14 六、ecshop加入购物车效果(各个页面) 22 七、商品列表页面“喜欢“ 功能实现 29 八、商品列表也显示获赠消费积分 32 九、分析 ecshop 里的$GLOBALS 37 十、解决 选择属性 直接就是属性价格不需要在原价基础上加价 38 十一、商品详细页下载商品介绍相关图片 39 十二、实现后台二次开发后功能模块仍然可以分配权限 41 十三、ecshop调用bbs数据 42 十四、整理了一个 获取用户等级的函数 43 十五、商品页显示具体属性排序 44 十六、解决ecshop新建页面分页问题 49 十七、刚修改的伪静态分类URL 52 十八、ecshop批发销售 66 十九、网银支付直通插件 实现方法 69 二十、商品列表页多图切换的实现 75 二十一、完善论坛发布的qq登录插件 77 二十二、倒计时代码 78 二十三、关于ecshop回调函数应用 83 二十四、商品内容页加订单留言功能分析 84 二十五、有关商品详细页是常规显示还是显示宣传页的实现方法 85 二十六、后台添加搜索功能讲解 87 二十七、分享ec搜索出现相关商品的效果滑动效果(模仿百度) 88 二十八、修改注册发送邮件(更新) 94 二十九、新增加一调用编辑器函数 97 三十、简单实现 各个页面都可显示友情链接 97 三十一、Ecshop系统框架分析 99 三十二、二次开发技术共享 后台邮件群发共享 100 三十三、ecshop内页调用友情链接 100 三十四、项目要求在后台商品分类添加图片的功能。 102 三十五、分类按照拼音第一字母排序显示实现 102 三十六、获取分类的一级分类和二级分类 106 三十七、如何调用解决列表的购买数量 107 三十八、详情页调评论次数 108 三十九、关于购买数量 订单数量 109 四十、Ecshop ajax应用讨论 109 四十一、调用当前分类或当前分类的下级分类函数 115 四十二、在商品列表显示购买记录 118 四十三、ajax更新购物车数量 118 四十四、PHP导出excle数据 123 四十五、页面多倒计时显示最新修改 125 四十六、关于lbi文件控制原理分析 129 四十七、页面浏览历史图片调用分析 130
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值