微信支付之公众号支付

最近项目中用到了微信支付,于是找到了这篇资料,为了防止遗忘转载一下,先谢过作者。原文地址:http://www.360us.net/article/22.html

最近把支付宝、银联和微信支付全都做了一遍,目前做的都还只涉及到消费的功能。
做下来感觉就是各个平台的支付流程都是大同小异,签名方式也是一样的。

这里主要总结一下微信支付公众号支付的一些东西。

微信公众号支付的主要流程如下:
1、生成我们自己系统的订单。
2、调用微信支付的统一下单接口把订单信息推给微信。
3、在第二部会返回一个预支付会话标识,然后凭这个标识用JS去调用支付操作。


关于支付页面的url问题,微信要求是最后必须要有“/”,我看到很多文章说不适合MVC结构的程序,我的情况是否定的,MVC结构一样可以。
比如url是这个:http://www.example.com/payment/wechatpay/ ,url里面paymentcontroller,wechatpayaction,这有问题吗?
一样可以访问,可以支付,是不是一个真正的目录,在微信看来就是,实际上其实不是。


好,下面进入正题。


微信支付配置如下:

1
2
3
4
5
$config  = [
     'mch_id'  =>  '1234455666' //商户号
     'signType'  =>  'MD5' //签名方式,目前只有MD5
     'key'  =>  'sdsfdhgjh34343krn3453tnelt' //api密钥
];

Weixinpay代码清单如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
<?php
namespace  weixin\components;  //这个是命名空间,可以根据需要修改
 
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 ;
     }
}

拿发起支付参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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的参数:

1
2
3
4
5
6
7
[
     'appId'  =>  'dfgfg' ,   //APP ID
     'timeStamp'  => (string)time(),   //时间戳
     'nonceStr'  =>  'dfdsfdgfgdsg' ,   //随机字符串
     'package'  =>  'prepay_id=sdsfgdhgfh4565756' ,   //预支付会话标识
     'signType'  =>  'MD5'
];

这里有个提示,timeStamp参数必须是字符串类型,不能是整数类型,否则在iPhone上面会报缺少timeStamp参数的错误。

我们这里可以直接响应json格式的数据。
然后拿到这个数据之后直接放进微信jsapi的参数里面就行。
js发起支付请求如下:

1
2
3
4
5
6
7
8
9
10
WeixinJSBridge.invoke(
     "getBrandWCPayRequest" ,
     params,    //这个就是上面$return变量的json格式
     //下面是支付完成后的回调,可以直接提示成功
     function (res) {
         if (res.err_msg ==  "get_brand_wcpay_request:ok" ) {
             //.......
         }
     }
);


如果支付成功之后,微信会发起主动调用,通知商户支付成功,业务处理可以放在那里进行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值