支付扫码有两种模式:
模式一:模式一开发前,商户必须在公众平台后台设置支付回调URL。URL实现的功能:接收用户扫码后微信支付系统回调的productid和openid;
模式二:模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。
模式二的流程比较简单,所以我使用模式二【不用通过微信公众平台设置回调】
开发流程:
第一步,下载sdk--php版--【包含doc,example(可参考),lib(重要)】
lib是微信支付sdk的核心文件包,可以放在扩展包vendor下
二维码的生成,可以通过composer require endroid/qrcode 2.5.1或者直接用微信example里面的phpqrcode类
example下WxPay.Config.php这个是配置文件--可以移动到lib文件下或者你自己想一个位置,自己重构一个。
第二步,参考example里面的native.php---这个就是扫码的例子,例子里面包含两种模式的例子【模式一不再提供支付方式--来自native.php例子说明】,开始使用模式二支付方式【在这之前,我稍微看了微信支付文档的一些流程图描述】,通过例子,可以知道,引用example里面的WxPay.NativePay.php,打开该文件,有三个方法,两个是模式一的【GetPrePayUrl,ToUrlParams】,一个模式二的【GetPayUrl】,因此可以先建一个控制器【如WxPay.php】,直接把模式二的方法搬到我们的控制器下【记得引入你lib文件所在的命名空间】,这里说明一下这个方法【GetPayUrl】,其实就是生成一个微信支付的链接【类似:weixin://******】,再回到native.php,这时候,就可以把native.php中的操作封装成一个方法,然后返回一个code_url,再根据这个生成二维码
到这里,前面的都是比较简单,可以直接搬就可以【稍微的修改】
第三步,写同步和异步操作,微信这里没有callback回调函数,所以采用的订单查询返回值,再进行改状态【同步回调,前端使用轮询,向微信服务器惊喜查询订单的支付状态--SUCCESS】,这个查询不需要进行签名验证,只需要注意一点,当查询到状态是支付成功【必须是成功才改状态】的时候后,给自己的订单改状态,异步回调,这个通过
$testxml = file_get_contents("php://input");
//将xml转化为json格式
$jsonxml = json_encode(simplexml_load_string($testxml, 'SimpleXMLElement', LIBXML_NOCDATA));
//转成数组
$result = json_decode($jsonxml, true);获取到这个$result,就算返回的$result['result_code']==SUCCESS,也要做签名验证【这样比较安全,存在一种可能,别人知道你的异步回调,那么他就可以通过构造参数,模拟发送数据给你这个接口,为了避免这个,我做了签名验证】签名验证【官网:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3】
大体如下:这个是tp3的使用方法,具体可以修改一些类库引入,就可以用到tp5中
/**
* 微信回调通知
* 数据存在被伪造的可能[非法的xml数据来源,即黑客如果知道这个接口,那么他就能通过订单号进行伪造数据,
* 所以验证分三步进行,一步一步验证数据的安全]
* 1.先查看数据中的订单是否存在或者已经付款,那么就直接返回false
* 这里可以返回xml数据,假如来源是微信,
* 那么就告诉微信我已经知道用户付款了,不用再向我发起请求;
* 不返回xml数据,那么微信就会
* 在15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m
* 向我们发起10次请求通知我们用户已经付款
* 下面的方法直接返回false,那么正常情况下,我们会收到10次异步通知
* 2.1验证携带的参数,我们自定义的参数,如果错误--则不用再进行验证签名函数
* 这一步其实也可以不用的,只是为了减少错误的数据的执行流程,一旦出错那么就直接返回false
* 2.2开始验证签名的正确性,
* xml中所有的数据,除了sign值,其他按照ksort()排序,
* 再通过http_build_query组合成新的字符串
* 然后再加上支付密钥组成新的字符串
* 之后将字符串加密:md5和sha256 [具体看你的配置值使用哪种加密方式]
* 3.确认签名正确,正式更新订单的状态
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function wxNotify()
{
//获取返回的xml
$testxml = file_get_contents("php://input");
//将xml转化为json格式
$jsonxml = json_encode(simplexml_load_string($testxml, 'SimpleXMLElement', LIBXML_NOCDATA));
//转成数组
$result = json_decode($jsonxml, true);
if ($result) {
//如果成功返回了
vendor("wxpay.log");
$logHandler = new \CLogFileHandler(LOG_PATH . "/Wxpay/" . date('Y-m-d') . 'wxnotify.log');
\Log::Init($logHandler, 15);
\Log::INFO(json_encode(['time'=>date("Y-m-d H:i:s",time()),'data'=>$result,'ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
//进行改变订单状态等操作。。。。 ['return_msg'=>['transaction_id','out_trade_no','attach','total_fee']]
//TODO: 1.检查订单的支付状态,如果以付款则,直接返回xml数据,或者直接返回false|true
$_where = ['order_no' => $result['out_trade_no'], 'states' => 4];
$order = M("order_list")->where($_where)->find();
if (!$order) {
//写入日志-$result['out_trade_no']
\Log::WARN(json_encode(['wxNotify-TODO-1' => '订单号:' . $result['out_trade_no']
. "不存在或已经付款", 'ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
return false;
}
//TODO: 2校验签名--保障数据是来源微信的服务器;
//TODO: 2.1:校验参数----查看return_msg中是否有我们发送给微信服务器端的数据attach[商家数据包]
if (isset($result['attach']) && $result['attach'] != "wwwahai574com") {
//写入日志-attach数据包错误
\Log::ERROR(json_encode(['wxNotify-TODO-2.1' => "attach数据包错误", 'ip' => get_client_ip()]));
return false;
}
//TODO: 2.2:验证签名
//获取返回的所以参数
//这里是要把微信返给我们的所有值,先删除sign的值,其他值 按ASCII从小到大排序;
$xmlSign = "我是默认的sign,如果没有获取到微信返回的sign值,那么就写入日志";
foreach ($result as $k => $v) {
if ($k == 'sign') {
$xmlSign = $result[$k];
unset($result[$k]);
};
}
//排序
ksort($result);
$sign = http_build_query($result);
//md5处理
vendor("wxpay.WxPay#Config");
$config = new \WxPayConfig();
$string = $sign . '&key=' . $config->GetKey();
//加密的方式:下面2选1 Wxpay.Config.php中的配置是HMAC-SHA256,所以不用md5加密
//如果是“MD5”,则:$sign = md5($string);
$sign = hash_hmac("sha256",$string ,$config->GetKey());
//转大写
$sign = strtoupper($sign);
//\Log::INFO(json_encode(['string'=>$string,'sign'=>$sign],JSON_UNESCAPED_UNICODE));
//验签名。默认支持MD5
//验证加密后的32位值和 微信返回的sign 是否一致!!!
if ($sign === $xmlSign) {
//TODO: 3修改订单为付款状态
\Log::INFO(json_encode(['wxNotify-TODO-3'=>'验证签名正确,开始更新订单的状态','string'=>$string,'sign'=>$sign],JSON_UNESCAPED_UNICODE));
//更新订单状态,本来应该给微信返回值说明支付成功的,不返回值,微信就只会触发多次请求,
//反正查询条件必须是未支付的订单才会改状态,所以多次请求,只有第一次真正成功才会写入数据库
$data['states'] = 0;
$data['completetime'] = date("Y-m-d H:i:s", time());
M("order_list")->where($_where)->save($data);
//\Log::INFO(json_encode(['wxNotify-tody-3'=>'验证签名正确,结束更新订单的状态'],JSON_UNESCAPED_UNICODE));
}else{
\Log::ERROR(json_encode(['wxNotify-TODO-3' => "签名错误",'xml中的sign'=>$xmlSign,'xml组成的sign'=>$sign, '访问者ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
}
}
}
} 控制器中的代码---测试通过了---具体的可以在进行修改--这个只是粗略的写出来--主要是为了说明流程:/**
* 发起微信支付 -- 统一下单
* 流程:WxPayController->GetPayUrl()
* ->WxPayApi->unifiedOrder()
* ->WxPayUnifiedOrder->WxPayDatabase->setSign()
* 生成签名值
* @throws \WxPayException
*/
public function payOrder()
{
if(empty($this->udata)){
$this->redirect('Login/index');
}
$order = M('Order_list')
->where(array('mw_order_list.order_no' => $_GET['order_id'], 'mw_order_list.states' => 4))
->join("mw_order_info on mw_order_list.id = mw_order_info.order_id")
->join("mw_product on mw_product.id = mw_order_info.product_id")
->field('mw_order_list.id,mw_order_list.states,mw_order_list.order_no,mw_order_list.name,mw_order_list.tel,mw_order_list.address,mw_order_list.total,mw_product.thum_url,mw_product.title,mw_product.img_url,mw_order_info.pro_title,mw_order_info.qty,mw_order_info.shop_price,mw_order_info.total as shop_total_price')
->select();
//查询订单id是否已经入库,入库则调起统一下单返回code_url,模板上面将code_url生成二维码,用户扫码支付
if ($order) {
$wx = new WxPayController();
vendor("wxpay.WxPay#Api");
//$out_trade_no = "wxpayby574" . date("YmdHis");
$out_trade_no = $_GET['order_id'];
$input = new \WxPayUnifiedOrder();
$input->SetBody($order[0]['title']);
$input->SetAttach("wwwahai574com");
$input->SetOut_trade_no($out_trade_no);
$input->SetTotal_fee($order[0]['total'] * 100);//单位是分
$input->SetTime_start(date("YmdHis"));
$input->SetTime_expire(date("YmdHis", time() + 600));
$input->SetGoods_tag($order[0]['title']);
//获取微信通知的url
$input->SetNotify_url("https://www.ahai574.com/Home/Car/wxNotify");
$input->SetTrade_type("NATIVE");
$input->SetProduct_id($_GET['order_id']);
$result = $wx->GetPayUrl($input);
//dump($result);
$url = $result["code_url"];
$this->assign('out_trade_no', $out_trade_no);
$this->assign('code_url', $url);
$this->assign("orderInfo", $order);
} else {
$this->error("订单不存在或已经完成付款,请勿重复操作!");
}
$this->display();
}
/**
* 微信回调通知
* 数据存在被伪造的可能[非法的xml数据来源,即黑客如果知道这个接口,那么他就能通过订单号进行伪造数据,
* 所以验证分三步进行,一步一步验证数据的安全]
* 1.先查看数据中的订单是否存在或者已经付款,那么就直接返回false
* 这里可以返回xml数据,假如来源是微信,
* 那么就告诉微信我已经知道用户付款了,不用再向我发起请求;
* 不返回xml数据,那么微信就会
* 在15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m
* 向我们发起10次请求通知我们用户已经付款
* 下面的方法直接返回false,那么正常情况下,我们会收到10次异步通知
* 2.1验证携带的参数,我们自定义的参数,如果错误--则不用再进行验证签名函数
* 这一步其实也可以不用的,只是为了减少错误的数据的执行流程,一旦出错那么就直接返回false
* 2.2开始验证签名的正确性,
* xml中所有的数据,除了sign值,其他按照ksort()排序,
* 再通过http_build_query组合成新的字符串
* 然后再加上支付密钥组成新的字符串
* 之后将字符串加密:md5和sha256 [具体看你的配置值使用哪种加密方式]
* 3.确认签名正确,正式更新订单的状态
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function wxNotify()
{
//获取返回的xml
$testxml = file_get_contents("php://input");
//将xml转化为json格式
$jsonxml = json_encode(simplexml_load_string($testxml, 'SimpleXMLElement', LIBXML_NOCDATA));
//转成数组
$result = json_decode($jsonxml, true);
if ($result) {
//如果成功返回了
vendor("wxpay.log");
$logHandler = new \CLogFileHandler(LOG_PATH . "/Wxpay/" . date('Y-m-d') . 'wxnotify.log');
\Log::Init($logHandler, 15);
\Log::INFO(json_encode(['time'=>date("Y-m-d H:i:s",time()),'data'=>$result,'ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
//进行改变订单状态等操作。。。。 ['return_msg'=>['transaction_id','out_trade_no','attach','total_fee']]
//TODO: 1.检查订单的支付状态,如果以付款则,直接返回xml数据,或者直接返回false|true
$_where = ['order_no' => $result['out_trade_no'], 'states' => 4];
$order = M("order_list")->where($_where)->find();
if (!$order) {
//写入日志-$result['out_trade_no']
\Log::WARN(json_encode(['wxNotify-TODO-1' => '订单号:' . $result['out_trade_no']
. "不存在或已经付款", 'ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
return false;
}
//TODO: 2校验签名--保障数据是来源微信的服务器;
//TODO: 2.1:校验参数----查看return_msg中是否有我们发送给微信服务器端的数据attach[商家数据包]
if (isset($result['attach']) && $result['attach'] != "wwwahai574com") {
//写入日志-attach数据包错误
\Log::ERROR(json_encode(['wxNotify-TODO-2.1' => "attach数据包错误", 'ip' => get_client_ip()]));
return false;
}
//TODO: 2.2:验证签名
//获取返回的所以参数
//这里是要把微信返给我们的所有值,先删除sign的值,其他值 按ASCII从小到大排序;
$xmlSign = "我是默认的sign,如果没有获取到微信返回的sign值,那么就写入日志";
foreach ($result as $k => $v) {
if ($k == 'sign') {
$xmlSign = $result[$k];
unset($result[$k]);
};
}
//排序
ksort($result);
$sign = http_build_query($result);
//md5处理
vendor("wxpay.WxPay#Config");
$config = new \WxPayConfig();
$string = $sign . '&key=' . $config->GetKey();
//加密的方式:下面2选1 Wxpay.Config.php中的配置是HMAC-SHA256,所以不用md5加密
//如果是“MD5”,则:$sign = md5($string);
$sign = hash_hmac("sha256",$string ,$config->GetKey());
//转大写
$sign = strtoupper($sign);
//\Log::INFO(json_encode(['string'=>$string,'sign'=>$sign],JSON_UNESCAPED_UNICODE));
//验签名。默认支持MD5
//验证加密后的32位值和 微信返回的sign 是否一致!!!
if ($sign === $xmlSign) {
//TODO: 3修改订单为付款状态
\Log::INFO(json_encode(['wxNotify-TODO-3'=>'验证签名正确,开始更新订单的状态','string'=>$string,'sign'=>$sign],JSON_UNESCAPED_UNICODE));
//更新订单状态,本来应该给微信返回值说明支付成功的,不返回值,微信就只会触发多次请求,
//反正查询条件必须是未支付的订单才会改状态,所以多次请求,只有第一次真正成功才会写入数据库
$data['states'] = 0;
$data['completetime'] = date("Y-m-d H:i:s", time());
M("order_list")->where($_where)->save($data);
//\Log::INFO(json_encode(['wxNotify-tody-3'=>'验证签名正确,结束更新订单的状态'],JSON_UNESCAPED_UNICODE));
}else{
\Log::ERROR(json_encode(['wxNotify-TODO-3' => "签名错误",'xml中的sign'=>$xmlSign,'xml组成的sign'=>$sign, '访问者ip' => get_client_ip()],JSON_UNESCAPED_UNICODE));
}
}
}
}
//查询订单支付状态---虽然微信的交易号优先,但是微信的交易号需要通过异步通知才能获取到,
//异步查询订单状态:传值是商家自己的交易号【订单号】
public function orderQuery()
{
if(empty($this->udata)){
$this->redirect('Login/index');
}
if ((isset($_REQUEST["transaction_id"]) && $_REQUEST["transaction_id"] != ""
&& !preg_match("/^[0-9a-zA-Z]{10,64}$/i", $_REQUEST["transaction_id"], $matches))
|| (isset($_REQUEST["out_trade_no"]) && $_REQUEST["out_trade_no"] != ""
&& !preg_match("/^[0-9a-zA-Z]{10,64}$/i", $_REQUEST["out_trade_no"], $matches))) {
$this->ajaxReturn(['result' => false, 'errorMsg' => '微信订单号和商家订单号均为空!']);
exit();
}
vendor("wxpay.WxPay#Api");
vendor("wxpay.WxPay#Config");
vendor("wxpay.log");
$logHandler = new \CLogFileHandler(LOG_PATH . "/Wxpay/" . date('Y-m-d') . 'orderQuery.log');
\Log::Init($logHandler, 15);
if (isset($_REQUEST["out_trade_no"]) && $_REQUEST["out_trade_no"] != "") {
try {
$out_trade_no = $_REQUEST["out_trade_no"];
$input = new \WxPayOrderQuery();
$input->SetOut_trade_no($out_trade_no);
$config = new \WxPayConfig();
$data = \WxPayApi::orderQuery($config, $input);
//如果查询成功,验证订单号,总价格是否正确,正确则改状态,前端再进行跳转
\Log::INFO(json_encode(['orderQueryTime'=>date("Y-m-d H:i:s",time()),'msg'=>$data,'ip'=>get_client_ip()],JSON_UNESCAPED_UNICODE));
if($data['return_code'] == 'SUCCESS' && isset($data['trade_state']) && $data['trade_state'] == 'SUCCESS')
{
$result = $this->changeOrderStatus($data['out_trade_no']);
if($result['result']){
//\Log::INFO(json_encode(['paidTime'=>date("Y-m-d H:i:s",time()),'msg'=>'订单支付完成','ip'=>get_client_ip()],JSON_UNESCAPED_UNICODE));
$this->ajaxReturn(['result' => true, 'msg' => "订单已经付款"]);
}else{
$this->ajaxReturn(['result' => false, 'msg' => "找不到订单"]);
}
}else{
$this->ajaxReturn(['result' => false, 'msg' => "订单未支付!"]);
}
} catch (Exception $e) {
\Log::ERROR(json_encode($e,JSON_UNESCAPED_UNICODE));
$this->ajaxReturn(['result' => false, 'errorMsg' => '微信查询失败!']);
}
exit();
}
$this->ajaxReturn(['result' => false, 'msg' => "订单号不能为空!"]);
}
//查询订单是 支付状态的时候,修改订单的付款状态
//
private function changeOrderStatus($order_no)
{
$_where['order_no'] = $order_no;
$find = M("order_list")->where($_where)->find();
if($find){
$_where['status'] = 4;
$data['states'] = 0;
$data['completetime'] = date("Y-m-d H:i:s", time());
$order = M("order_list")->where($_where)->save($data);
if($order){
return ['result'=>true,'msg'=>'订单完成更新!'];
}else{
return ['result'=>true,'msg'=>'订单已经更新了!'];
}
}else{
return ['result'=>false,'msg'=>"查找不到订单!"];
}
}
/**
* 把url生成二维码
* @throws \WxPayException
*/
public function buildQrcode()
{
$url = urldecode($_GET['url']);
if (substr($url, 0, 6) == "weixin") {
Vendor("phpqrcode.phpqrcode");
$level = 3;
$size = 4;
$errorCorrectionLevel = intval($level);//容错级别
$matrixPointSize = intval($size);//生成图片大小
\QRcode::png($url, false, $errorCorrectionLevel, $matrixPointSize, 2);
} else {
header('HTTP/1.1 404 Not Found');
}
} 题外话:老板把以前别人写的网站【tp3】,让我接入微信支付,然后客户要使用第三方的支付,然后第三方支付只提供一个word的文档,好吧,按照流程写了之后,一直是签名错误,而签名的方式和微信的签名类似,搞了一天,任然是报签名错误,第三方,又是由客户自己提供的【完全不知道是谁的,没有官网,只能靠猜测排查问题--第三方不提供服务,只提供文档^V^】,最后想起了之前有一位同事说他之前遇到一个网站是asp做的,然后登录使用的加密方式是md5【16位】,而php的md5是32位;然后下班之后,突然想起,第三方使用的是java的md5,于是百度查了一些java的md5发现java与php的md5是不一样的,只能发问题给老板是不是md5的问题,让老板->客户->第三方->客户->老板->我,然后....,今天先记录这些。最佳答案