最近把支付宝、银联和微信支付全都做了一遍,目前做的都还只涉及到消费的功能。
做下来感觉就是各个平台的支付流程都是大同小异,签名方式也是一样的。
这里主要总结一下微信支付公众号支付的一些东西。
微信公众号支付的主要流程如下:
1、生成我们自己系统的订单。
2、调用微信支付的统一下单接口把订单信息推给微信。
3、在第二部会返回一个预支付会话标识,然后凭这个标识用JS去调用支付操作。
关于支付页面的url问题,微信要求是最后必须要有“/
”,我看到很多文章说不适合MVC结构的程序,我的情况是否定的,MVC结构一样可以。
比如url是这个:http://www.example.com/payment/wechatpay/
,url里面payment
是controller
,wechatpay
是action
,这有问题吗?
一样可以访问,可以支付,是不是一个真正的目录,在微信看来就是,实际上其实不是。
好,下面进入正题。
微信支付配置如下:
$config = [ 'mch_id' => '1234455666', //商户号 'signType' => 'MD5', //签名方式,目前只有MD5 'key' => 'sdsfdhgjh34343krn3453tnelt', //api密钥 ];
Weixinpay代码清单如下:
<?php namespace weixin\components; //这个是命名空间,可以根据需要修改 /** * @link http://www.360us.net/ * @author dyllen_zhong@qq.com */ class WeixinPay { //支付配置 public $config; //支付参数 public $params; //统一下单url const POST_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; //订单查询url const ORDER_QUERY_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'; /** * 创建微信js发起支付参数 * @return array */ public function createJsPayData() { $this->params['nonce_str'] = $this->getRandomStr(); $this->params['sign'] = $this->sign(); $xmlStr = $this->arrayToXml(); $res = $this->postUrl(self::POST_ORDER_URL, $xmlStr); $res = $this->xmlToArray($res); if( $res['return_code'] == 'SUCCESS' && $res['result_code'] == 'SUCCESS' && $this->verifySignResponse($res) ) { $params = [ 'appId' => $this->params['appid'], 'timeStamp' => (string)time(), 'nonceStr' => $this->getRandomStr(), 'package' => 'prepay_id='.$res['prepay_id'], 'signType' => 'MD5' ]; $this->params = $params; $this->params['paySign'] = $this->sign(); return $this->params; } if($res['return_code'] == 'FAIL') { throw new \Exception("提交预支付交易单失败:{$res['return_msg']}"); } throw new \Exception("提交预支付交易单失败,{$res['err_code']}:{$res['err_code']}"); } /** * 验证异步通知 * @return boolean */ public function verifyNotify() { $this->params = $this->xmlToArray($this->params); if( empty($this->params['sign']) ) { return false; } $sign = $this->sign(); return $this->params['sign'] == $sign; } /** * 取成功响应 * @return string */ public function getSucessXml() { $xml = '<xml>'; $xml .= '<return_code><![CDATA[SUCCESS]]></return_code>'; $xml .= '<return_msg><![CDATA[OK]]></return_msg>'; $xml .= '</xml>'; return $xml; } public function getFailXml() { $xml = '<xml>'; $xml .= '<return_code><![CDATA[FAIL]]></return_code>'; $xml .= '<return_msg><![CDATA[OK]]></return_msg>'; $xml .= '</xml>'; return $xml; } /** * 数组转成xml字符串 * * @return string */ protected function arrayToXml() { $xml = '<xml>'; foreach($this->params as $key => $value) { $xml .= "<{$key}>"; $xml .= "<![CDATA[{$value}]]>"; $xml .= "</{$key}>"; } $xml .= '</xml>'; return $xml; } /** * xml 转换成数组 * @param string $xml * @return array */ protected function xmlToArray($xml) { $xmlObj = simplexml_load_string( $xml, 'SimpleXMLIterator', //可迭代对象 LIBXML_NOCDATA ); $arr = []; $xmlObj->rewind(); //指针指向第一个元素 while (1) { if( ! is_object($xmlObj->current()) ) { break; } $arr[$xmlObj->key()] = $xmlObj->current()->__toString(); $xmlObj->next(); //指向下一个元素 } return $arr; } //验证统一下单接口响应 protected function verifySignResponse($arr) { $tmpArr = $arr; unset($tmpArr['sign']); ksort($tmpArr); $str = ''; foreach($tmpArr as $key => $value) { $str .= "$key=$value&"; } $str .= 'key='.$this->config['key']; if($arr['sign'] == $this->signMd5($str)) { return true; } return false; } /** * 签名 * 规则: * 先按照参数名字典排序 * 用&符号拼接成字符串 * 最后拼接上API秘钥,str&key=密钥 * md5运算,全部转换为大写 * * @return string */ protected function sign() { ksort($this->params); $signStr = $this->arrayToString(); $signStr .= '&key='.$this->config['key']; if($this->config['signType'] == 'MD5') { return $this->signMd5($signStr); } throw new \InvalidArgumentException('Unsupported sign method'); } /** * 数组转成字符串 * @return string */ protected function arrayToString() { $params = $this->filter($this->params); $str = ''; foreach($params as $key => $value) { $str .= "{$key}={$value}&"; } return substr($str, 0, strlen($str)-1); } /* * 过滤待签名数据,sign和空值不参加签名 * * @return array */ protected function filter($params) { $tmpParams = []; foreach ($params as $key => $value) { if( $key != 'sign' && ! empty($value) ) { $tmpParams[$key] = $value; } } return $tmpParams; } /** * MD5签名 * * @param string $str 待签名字符串 * @return string 生成的签名,最终数据转换成大写 */ protected function signMd5($str) { $sign = md5($str); return strtoupper($sign); } /** * 获取随机字符串 * @return string 不长于32位 */ protected function getRandomStr() { return substr( rand(10, 999).strrev(uniqid()), 0, 15 ); } /** * 通过POST方法请求URL * @param string $url * @param array|string $data post的数据 * * @return mixed */ protected function postUrl($url, $data) { $curl = curl_init($url); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); //忽略证书验证 curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $data); $result = curl_exec($curl); return $result; } }
拿发起支付参数:
try{ $weixinPay = new WeixinPay(); $weixinPay->config = $config; $weixinPay->params = [ 'appid' => 'sdfdgf1234345', //APP ID 'mch_id' => $config['mch_id'], //商户号 'body' => 'test', //商品描述 'out_trade_no' => 'esdfrdgegtr234365546', //订单号 'total_fee' => 100, //总金额,单位分 'spbill_create_ip' => '192.168.100.100', //终端IP 'notify_url' => 'http://www.example.com/paynofify', //异步通知地址 'trade_type' => 'JSAPI', //交易类型 'openid' => 'xxxxdfdfdgdfxcvcvgfg', //用户标识 ]; $return = $weixinPay->createJsPayData(); } catch (\Exception $e) { Yii::error('微信支付错误:'.$e->getMessage()); return [ 'code' => 0, 'errmsg' => '创建支付参数失败', ]; }
变量$return
的内容如下,就是网页调起支付api的参数:
[ 'appId' => 'dfgfg', //APP ID 'timeStamp' => (string)time(), //时间戳 'nonceStr' => 'dfdsfdgfgdsg', //随机字符串 'package' => 'prepay_id=sdsfgdhgfh4565756', //预支付会话标识 'signType' => 'MD5' ];
这里有个提示,timeStamp
参数必须是字符串类型,不能是整数类型,否则在iPhone上面会报缺少timeStamp参数的错误。
我们这里可以直接响应json格式的数据。
然后拿到这个数据之后直接放进微信jsapi的参数里面就行。
js发起支付请求如下:
WeixinJSBridge.invoke( "getBrandWCPayRequest", params, //这个就是上面$return变量的json格式 //下面是支付完成后的回调,可以直接提示成功 function(res) { if(res.err_msg == "get_brand_wcpay_request:ok") { //....... } } );
如果支付成功之后,微信会发起主动调用,通知商户支付成功,业务处理可以放在那里进行。
$weixinPay = new WeixinPay(); $weixinPay->config = $config; $weixinPay->params = 'xxxxx'; //微信通知提交过来的xml if(empty($weixinPay->params) || !$weixinPay->verifyNotify()) { return $weixinPay->getFailXml(); } if($weixinPay->params['return_code'] == 'SUCCESS' && $weixinPay->params['result_code'] == 'SUCCESS') { //处理业务.... //..... return $weixinPay->getSucessXml(); } return $weixinPay->getFailXml();
至此微信支付的整个过程就结束了。
需要注意的一点是微信5.0以下版本不支持微信支付功能。
还有就是在支付url后面加上showwxpaytitle=1
字符串,会有“微信安全支付”的文字提示,最终的url就变成了http://www.example.com/payment/wechatpay/?showwxpaytitle=1
。
本文链接:https://360us.net/article/22.html