微信支付之公眾號支付


最近項目中用到了微信支付,於是找到了這篇資料,為了防止遺忘轉載一下,先謝過作者。原文地址: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



注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



微信公眾號支付 微信公眾號支付 微信公眾號支付 微信公眾號支付 微信公眾號支付 微信公眾號支付 微信公眾號支付 【支付】微信公眾號支付 微信支付(公眾號支付)總結 java開發微信公眾號支付
 
粤ICP备14056181号  © 2014-2021 ITdaan.com