PHP版本微信支付开发
PHP版本微信支付开发----电脑网站扫码支付(native)(心得、总结)
此文章引用爱玲姐姐(如有意见联系课删除)
早就听说微信支付比支付宝支付的坑多,但还得得该填的填,该绕的绕, 最终我们网站的微信支付功能成功上线啦♪(^ ∇ ^*)
首先自报家门,我的PHP版本是7,微信demo用的是php_sdk_v3.0.9
一、手续备齐、按流程走
在进行微信支付开发之前,首先你得拥有微信支付的权限。哦对了,在此之前,你还得有一个公众号(服务号), 在微信公众平台申请。进入微信公众后台后,
左侧有一个微信支付
,如果你想要获得微信支付功能,还需要先进行微信公众号的认证,嘻嘻(此处省略300元
~~~费时费力)
我觉得微信认证过程还蛮麻烦的,我是和老赵一起做的,他负责给我认证材料,我负责上传。
认证完之后,还要申请商户号
,这也是老赵申请的,花费了好几天,然后申请好了之后你会得到微信发给你的商户号,如果你是超级管理员,你就可以在微信支付平台横着走了,你可以给别人(员工)设置不同的权限。在我们本次操作中,老赵是“超级管理员
”,我被授权为“管理员
”(仅次于“超级管理员”的位卑权低小角色>_<) 不得不说的是,一涉及到账号或者权限或者秘钥的页面,都只能超级管理员
查看,这真是太不方便了,以至于这几天我天天求着老赵的微信给我扫码登一下看看┭┮﹏┭┮
二、下载微信支付demo,修改配置信息
1.开发文档
我做的项目是电脑网站,所以就选择了native支付
在这里可以下载demo,我下载了PHP的,这是目录结构
做过支付宝支付的人可能知道支付宝提供了异步回调和同步回调两种方式,而微信没有提供同步回调,只有异步回调(在此处理你的业务逻辑如数据库的更新)。
2.修改配置文件example/Wxpay.config.php
AppId
这是你的微信公众平台的APPID,去微信公众平台里面查看MerchantId
这是你申请的商户号NotifyUrl
这是异步回调地址,要求外网可以访问(不能写localhost域名)、不能带任何参数Key
这是你的APIkey,在微信开发平台里,超级管理员
可以修改
3.配置文件代码:
<?php
/**
*
* example目录下为简单的支付样例,仅能用于搭建快速体验微信支付使用
* 样例的作用仅限于指导如何使用sdk,在安全上面仅做了简单处理, 复制使用样例代码时请慎重
* 请勿直接直接使用样例对外提供服务
*
**/
require_once "../lib/WxPay.Config.Interface.php";
/**
*
* 该类需要业务自己继承, 该类只是作为deamon使用
* 实际部署时,请务必保管自己的商户密钥,证书等
*
*/
class WxPayConfig extends WxPayConfigInterface
{
//=======【基本信息设置】=====================================
/**
* TODO: 修改这里配置为您自己申请的商户信息
* 微信公众号信息配置
*
* APPID:绑定支付的APPID(必须配置,开户邮件中可查看)
*
* MCHID:商户号(必须配置,开户邮件中可查看)
*
*/
public function GetAppId()
{
return 'xxxxxxxxxxxx';//jal
}
public function GetMerchantId()
{
return 'xxxxxxxxxxx';//jal
}
//=======【支付相关配置:支付成功回调地址/签名方式】===================================
/**
* TODO:支付回调url
* 签名和验证签名方式, 支持md5和sha256方式
**/
public function GetNotifyUrl()
{
return "http://xxx.xxxxxx.com/JudgeOnline/wxpay/example/notify.php";
}
public function GetSignType()
{
return "HMAC-SHA256";
}
//=======【curl代理设置】===================================
/**
* TODO:这里设置代理机器,只有需要代理的时候才设置,不需要代理,请设置为0.0.0.0和0
* 本例程通过curl使用HTTP POST方法,此处可修改代理服务器,
* 默认CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此时不开启代理(如有需要才设置)
* @var unknown_type
*/
public function GetProxy(&$proxyHost, &$proxyPort)
{
$proxyHost = "0.0.0.0";
$proxyPort = 0;
}
//=======【上报信息配置】===================================
/**
* TODO:接口调用上报等级,默认紧错误上报(注意:上报超时间为【1s】,上报无论成败【永不抛出异常】,
* 不会影响接口调用流程),开启上报之后,方便微信监控请求调用的质量,建议至少
* 开启错误上报。
* 上报等级,0.关闭上报; 1.仅错误出错上报; 2.全量上报
* @var int
*/
public function GetReportLevenl()
{
return 1;
}
//=======【商户密钥信息-需要业务方继承】===================================
/*
* KEY:商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置), 请妥善保管, 避免密钥泄露
* 设置地址:https://pay.weixin.qq.com/index.php/account/api_cert
*
* APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置), 请妥善保管, 避免密钥泄露
* 获取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
* @var string
*/
public function GetKey()
{
return 'xxxxxxxxxxxxxxxxxxxxxxxxxx';//jal
//return '6f637dd7fe44a1e8ea19e4599fb2c7ff';//sandbox_sign_key这里有坑
}
public function GetAppSecret()
{
return '7813490da6f1265e4901ffb80afaa36f';//native不需要填
}
//=======【证书路径设置-需要业务方继承】=====================================
/**
* TODO:设置商户证书路径
* 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要,可登录商户平台下载,
* API证书下载地址:https://pay.weixin.qq.com/index.php/account/api_cert,下载之前需要安装商户操作证书)
* 注意:
* 1.证书文件不能放在web服务器虚拟目录,应放在有访问权限控制的目录中,防止被他人下载;
* 2.建议将证书文件名改为复杂且不容易猜测的文件名;
* 3.商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件。
* @var path
*/
public function GetSSLCertPath(&$sslCertPath, &$sslKeyPath)
{
$sslCertPath = '../cert/apiclient_cert.pem';
$sslKeyPath = '../cert/apiclient_key.pem';
}
}
三、微信支付的巨坑(速速绕过
) ---- 沙箱环境–>getsignkey
相信大家都看到了这里吧,知道要获取获取验签秘钥APIsignkey
这个东西。但我估计大家也一定在此处吃尽了苦头,耗费了很长时间吧。
微信的开发文档中只说了去https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey
获取验签秘钥API,但并没有给出具体的实现代码,┭┮﹏┭┮这可真是难为我了,我在这里花了整整两天时间查资料研究怎么获取验签秘钥API,最终我是下载了一个谷歌插件PostMan工具来模拟http请求获得了signkey,
然而,每次我获取到的signkey总是不变的,每次都是一样的值(我每次给出的随机串不同),这令我很郁闷,我感觉我的逻辑没错啊,不应该每次都获得一样的返回值。
我拿着我得到的这个极有可能有毛病的signkey去放到了配置文件中,
替换了GetKey的返回值。然后还将整个demo中所有的wei.qq.com
后面加上了/sandboxnew
,然而,这时候打开native.php页面居然又报错了,说我的沙箱秘钥不正确,让我去https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey
获取验签秘钥API,真让人感到很无语,我我我…唉,气死我了。我在这里耗费了两天,居然就给我这个结果!我又继续上网查资料,发现有很多很多人都遇到了微信沙箱的各种坑,我心里获得了不少安慰。之后,我决定不用沙箱环境了,非得用沙箱环境仿真吗,非得提交验收吗,答案是NO
,我发现不用沙箱环境在服务器上代码也能跑的起来,也可以支付,那为什么要用那个麻烦的沙箱环境呢,浪费了我那么长时间,实在是忍不下去了。我觉得绕过沙箱环境是提高微信支付开发效率最关键的一步
四、 现在要正式进行编程了,首先是前台的支付页面完成后的跳转
由于微信没有给出同步回调地址,那什么时候完成支付进行页面跳转都需要看我们用js发送请求查询了。
上面是我的订单提交页面(index.php)这个页面比较简单不再多说。点击付款按钮后,就会跳转到下面的扫码支付页面example/native.php,这是我自己模仿微信给出的样例画的一个漂亮的收银台,嘻嘻* ^_^ *
微信的图片logo是在这个页面下载的,也可以点击点击直接下载
1.native.php代码如下:
<?php /** * * example目录下为简单的支付样例,仅能用于搭建快速体验微信支付使用 * 样例的作用仅限于指导如何使用sdk,在安全上面仅做了简单处理, 复制使用样例代码时请慎重 * 请勿直接直接使用样例对外提供服务 * **/ require_once "../lib/WxPay.Api.php"; require_once "WxPay.NativePay.php"; require_once 'log.php'; //初始化日志 $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log'); $log = Log::Init($logHandler, 15); $notify = new NativePay(); //模式二 /** * 流程: * 1、调用统一下单,取得code_url,生成二维码 * 2、用户扫描二维码,进行支付 * 3、支付完成之后,微信服务器会通知支付成功 * 4、在支付成功通知中需要查单确认是否真正支付成功(见:notify.php) */ //jal $amount = isset($_POST['WIDtotal_amount'])?$_POST['WIDtotal_amount']:""; $body = isset($_POST['WIDsubject'])?$_POST['WIDsubject']:""; $total_fee = intval(doubleval($amount)*100); $product_id = isset($_POST['goods_id'])?$_POST['goods_id']:""; $attach = isset($_POST['word'])?$_POST['word']:"test"; $out_trade_no = isset($_POST['WIDout_trade_no'])?$_POST['WIDout_trade_no']:"phpsdk".date("YmdHis"); $input = new WxPayUnifiedOrder(); $input->SetBody($body); $input->SetAttach($attach); $input->SetOut_trade_no($out_trade_no); $input->SetTotal_fee($total_fee); $input->SetTime_start(date("YmdHis")); $input->SetTime_expire(date("YmdHis", time() + 600)); $input->SetGoods_tag("test"); //$input->SetNotify_url("http://paysdk.weixin.qq.com/notify.php"); $input->SetNotify_url("http://xxx.xxxxxxx.com/JudgeOnline/wxpay/example/notify.php");//这里写你的回调地址 $input->SetTrade_type("NATIVE"); $input->SetProduct_id($product_id); $result = $notify->GetPayUrl($input); $url2 = $result["code_url"]; ?> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1" /> <script src="../../template/bs3/jquery.min.js"></script> <link rel="icon" href="img/logo.png"> <title>微信支付</title> <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <div class="row"> <div style="padding: 10px 30px"> <img src="img/logo.png" alt="" style="height: 35px;vertical-align:middle"><span style="font-size: large; font-weight: 600">微信收银台</span> </div> </div> <div class="row" style="background-color: lightgrey; padding: 10px 30px;"> <div style="display: inline-block;"> <div>订单编号:<span><?php echo $out_trade_no?></span></div> <div>订单名称:<span><?php echo $body?></span></div> </div> <div style="float: right; display: inline-block;padding: 10px">应付金额:¥<span><?php echo $amount?></span></div> </div> <div class="row" style="margin: 20px 30px; padding: 10px 20px; border-color: orange; border-style: solid;border-width: medium"> <div style="display: inline-block"> <img src="img/WePayLogo.png" alt="" style="height: 50px;vertical-align: middle"> <img src="img/right.png" alt="" style="height: 30px; vertical-align: middle"> <span style="color: lightgrey">亿万用户的选择,更快更安全</span> </div> <div style="float: right; display: inline-block; padding: 10px"> 支付<span style="color: orange"><?php echo $amount?></span>元 </div> </div> <div class="row" style="text-align: center"> <img alt="扫码支付" src="qrcode.php?data=<?php echo urlencode($url2);?>" style="width:250px;height:250px;"/><div> 支付提示:<span id="query_result" style="color: red">WAITING...</span> </div> <input id="out_trade_no" type="hidden" value="<?php echo $out_trade_no;?>"> <img src="img/scan.png" alt="" style="width: 250px; height: 90px"> </div> </div> <script> var t1; var sum=0; $(document).ready(function () { t1=setInterval("ajaxstatus()", 3000); }); function ajaxstatus() { sum++; if(sum>600){ window.clearInterval(t1);return false;} if(sum>180){ m=sum % 10; if(m!=0){return false;} } if ($("#out_trade_no").val() != 0) { $.post("orderqueryajax.php", { out_trade_no:$("#out_trade_no").val() }, function (data) { data = $.trim(data); $("#query_result").html(data); if (data=="SUCCESS") { $("#query_result").html("支付成功,即将跳转..."); <?php if (isset($_POST['history_go']) && $_POST['history_go'] == 3){ echo 'window.setTimeout("history.go(-3);",2000);'; }else echo 'window.setTimeout("history.go(-2);",2000);'; ?> } }); } } </script> </body> </html>
2. 其中这个页面包涵了js定时查询部分,看注释
var t1; var sum=0; $(document).ready(function () { t1=setInterval("ajaxstatus()", 3000);//三秒查询一次 }); function ajaxstatus() { sum++; if(sum>600){ window.clearInterval(t1);return false;}//如果查询次数过多就放弃 if(sum>180){ m=sum % 10; if(m!=0){return false;} } if ($("#out_trade_no").val() != 0) { //此处很关键,用ajax异步查询orderqueryajax.php页面,看看这笔订单是否已完成支付 $.post("orderqueryajax.php", { out_trade_no:$("#out_trade_no").val() }, function (data) { data = $.trim(data); $("#query_result").html(data);//一直更新查询结果 if (data=="SUCCESS") { $("#query_result").html("支付成功,即将跳转...");//如果查询到的结果是成功支付,就进行页面跳转 <?php if (isset($_POST['history_go']) && $_POST['history_go'] == 3){ echo 'window.setTimeout("history.go(-3);",2000);';//此处只是为了判断是页面返回两页还是三页 }else echo 'window.setTimeout("history.go(-2);",2000);'; ?> } }); } }
3. 后台orderqueryajax.php页面,其实这个页面是我仿照已有的example/orderquery.php抄的啦,嘿嘿
<?php /** * * ajax异步查询订单是否完成 * **/ require_once "../lib/WxPay.Api.php"; require_once 'log.php'; require_once "WxPay.Config.php"; //初始化日志 $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log'); $log = Log::Init($logHandler, 15); $v = $_POST["out_trade_no"]; if(isset($v) && $v != ""){ $out_trade_no = $v; $input = new WxPayOrderQuery(); $input->SetOut_trade_no($out_trade_no); $config = new WxPayConfig(); $result = WxPayApi::orderQuery($config, $input); if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS'){//返回查询结果 echo $result['trade_state']; }else{ echo "FAIL"; } } ?>
这些写好后,就可以提交到服务器上跑一下啦
哦对了,如果你的代码在服务器上跑不起来的话,连最初demo里面的二维码都无法显示的话,那应该是你的php.ini里面的curl扩展功能没有开,我当时就是因为curl没开,导致在服务器上打开就是500错误,怎么打开curl扩展还是老赵帮我的,你们可以自行百度~
五、支付完成后异步回调,处理业务逻辑如:更新数据库
这一部分功能我是在example/notify.php中处理的
<?php /** * * example目录下为简单的支付样例,仅能用于搭建快速体验微信支付使用 * 样例的作用仅限于指导如何使用sdk,在安全上面仅做了简单处理, 复制使用样例代码时请慎重 * 请勿直接直接使用样例对外提供服务 * **/ require_once "../lib/WxPay.Api.php"; require_once '../lib/WxPay.Notify.php'; require_once "WxPay.Config.php"; require_once 'log.php'; require_once("../../include/memcache.php"); //初始化日志 $logHandler= new CLogFileHandler("../logs/".date('Y-m-d').'.log'); $log = Log::Init($logHandler, 15); class PayNotifyCallBack extends WxPayNotify { //查询订单 public function Queryorder($transaction_id) { $input = new WxPayOrderQuery(); $input->SetTransaction_id($transaction_id); $config = new WxPayConfig(); $result = WxPayApi::orderQuery($config, $input); Log::DEBUG("query:" . json_encode($result)); if(array_key_exists("return_code", $result) && array_key_exists("result_code", $result) && $result["return_code"] == "SUCCESS" && $result["result_code"] == "SUCCESS") { return true; } return false; } /** * * 回包前的回调方法 * 业务可以继承该方法,打印日志方便定位 * @param string $xmlData 返回的xml参数 * **/ public function LogAfterProcess($xmlData) { Log::DEBUG("call back, return xml:" . $xmlData); return; } //重写回调处理函数 /** * @param WxPayNotifyResults $data 回调解释出的参数 * @param WxPayConfigInterface $config * @param string $msg 如果回调处理失败,可以将错误信息输出到该方法 * @return true回调出来完成不需要继续回调,false回调处理未完成需要继续回调 */ public function NotifyProcess($objData, $config, &$msg) { $data = $objData->GetValues(); //TODO 1、进行参数校验 if(!array_key_exists("return_code", $data) ||(array_key_exists("return_code", $data) && $data['return_code'] != "SUCCESS")) { //TODO失败,不是支付成功的通知 //如果有需要可以做失败时候的一些清理处理,并且做一些监控 $msg = "异常异常"; return false; } if(!array_key_exists("transaction_id", $data)){ $msg = "输入参数不正确"; return false; } //TODO 2、进行签名验证 try { $checkResult = $objData->CheckSign($config); if($checkResult == false){ //签名错误 Log::ERROR("签名错误..."); return false; } } catch(Exception $e) { Log::ERROR(json_encode($e)); } //TODO 3、处理业务逻辑 Log::DEBUG("call back:" . json_encode($data)); $notfiyOutput = array(); //查询订单,判断订单真实性 if(!$this->Queryorder($data["transaction_id"])){ $msg = "订单查询失败"; return false; } //可以在此处写入数据库 $trade_no = $data['transaction_id']; $trade_status = $data['result_code']; $receipt_amount = $data['total_fee']; $gmt_payment = date("Y-m-d H:i:s"); $buyer_logon_id = ""; $buyer_user_id = ""; $total_amount = $data['total_fee']; $out_trade_no = $data['out_trade_no']; $body = ""; $subject= pdo_query("select subject from trade WHERE out_trade_no = ?", $out_trade_no)[0][0]; $sql = "update trade set trade_no = ?, trade_status = ?,valid=1, receipt_amount = ?, gmt_payment = ?, buyer_logon_id = ?, buyer_user_id = ? where out_trade_no = ? limit 1; "; $res = pdo_query($sql,$trade_no, $trade_status, $receipt_amount, $gmt_payment, $buyer_logon_id, $buyer_user_id,$out_trade_no); $sql = "update goods set goods_number = goods_number-1, goods_trade_number = goods_trade_number+1 where goods_name=?"; pdo_query($sql, $subject); return true; } } $config = new WxPayConfig(); Log::DEBUG("begin notify"); $notify = new PayNotifyCallBack(); $notify->Handle($config, false);
这里回调部分又有坑啦,不踩几个坑都不好意思说自己弄过微信开发~
如果你的回调中的代码没有生效,但你的回调地址又没有写错的话,估计你是和我遇着同样的坑了,且不得不填。极有可能是因为微信给你发的支付通知信息你没收到,解决方法我写在这篇文里面了,微信支付开发中的坑---- php废弃$GLOBALS[‘HTTP_RAW_POST_DATA’]
时间仓促,如有错误欢迎指出,欢迎在评论区讨论,如对您有帮助还请点个推荐、关注支持一下
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。
若内容有侵犯您权益的地方,请公告栏处联系本人,本人定积极配合处理或删除。