最近把支付宝、银联和微信支付全都做了一遍,目前做的都还只涉及到消费的功能。
做下来感觉就是各个平台的支付流程都是大同小异,签名方式也是一样的。
这里主要总结一下微信支付公众号支付的一些东西。
微信公众号支付的主要流程如下:
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://www.360us.net/article/22.html