微信支付分好几种支付场景,1、微信公众号内支付、2、微信扫码支付、3、H5支付(微信浏览器之外的其他浏览器)4、小程序支付、5、APP支付(第三方应用调起微信支付)、6、 刷卡支付(收银员用扫码设备扫描用户的二维码)。下面我们来讲解一下微信公众号支付的开发流程,包括简单的代码测试。
一、前期准备工作
1、申请一个认证服务号,并且开通支付功能
2、在公众号后台设置好网页授权域名,微信商户后台设置好支付授权目录
这两步是整个公众号支付流程的关键步骤,确保正确设置。如果不清楚怎么设置,这一步可以先自行度娘。
3、备案的域名+服务器(这个是必备的)
二、微信公众号支付开发流程

1、根据微信支付文档封装支付参数,调用微信支付统一下单接口向微信服务器发起请求,返回预支付交易会话标识。微信文档链接
2、封装JS支付参数,调用微信浏览器内置WeixinJSBridge对象,直接调起微信支付。
三、代码开发流程
前面粗略讲解了微信公众号支付的开发流程,理解起来应该是相对简单。下面是示例代码(只是测试代码,不要用于生产环境):
1、第一步先获取用户openid,openid是公众号支付的一个重要参数,这里我们用微信网页授权接口来获取用户openid。
需要设置好网页域名授权,前期准备工作的第二步
微信页面授权类 thirdAuth.class.php1
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<?php
class ThirdAuth{
private $code_url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect';
private $access_token_url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code';
private $redirect_url;
private $appid;
private $appsecret;
function __construct($appid, $appsecret, $redirect_url){
$this->appid = $appid;
$this->appsecret = $appsecret;
$this->redirect_url = $redirect_url;
}
public function authCode($state = ''){
$makeUrl = $this->makeCodeUrl($this->code_url, $this->appid, urlencode($this->redirect_url), 'snsapi_base', $state);
header('Location:'.$makeUrl);
return;
}
function getCode($code, $state = ''){
$openid = $this->getOpenid($code);
if(empty($openid)){
$this->authCode($state);
}
return $openid;
}
public function getOpenid($code){
$makeUrl = $this->makeAccessTokenUrl($this->access_token_url, $this->appid, $this->appsecret, $code);
$result = $this->http_post($makeUrl);
if(!empty($result['openid'])){
return $result['openid'];
}else{
return;
}
}
public function makeCodeUrl($url, $appid, $redirect_uri, $scope, $state){
return sprintf($url, $appid, $redirect_uri, $scope, $state);
}
public function makeAccessTokenUrl($url, $appid, $secret, $code){
return sprintf($url, $appid, $secret, $code);
}
//发送post请求
function http_post($url, $param) {
$header [] = "content-type: application/json; charset=UTF-8";
$ch = curl_init ();
curl_setopt ( $ch, CURLOPT_URL, $url );
curl_setopt ( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
curl_setopt ( $ch, CURLOPT_SSL_VERIFYPEER, FALSE );
curl_setopt ( $ch, CURLOPT_SSL_VERIFYHOST, FALSE );
curl_setopt ( $ch, CURLOPT_HTTPHEADER, $header );
curl_setopt ( $ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)' );
curl_setopt ( $ch, CURLOPT_FOLLOWLOCATION, 1 );
curl_setopt ( $ch, CURLOPT_AUTOREFERER, 1 );
curl_setopt ( $ch, CURLOPT_POSTFIELDS, $param );
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
$res = curl_exec ( $ch );
curl_close ( $ch );
$res = json_decode ( $res, true );
return $res;
}
}
?>
测试脚本 test.php1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<?php
//引入微信页面授权类
include 'thirdAuth.class.php';
//公众号appid
$appid = 'APPID';
//公众号appsecret
$appsecret = 'APPSECRET';
//页面回调域名
$redirect_url = 'http://whatcode.cn/test.php';
$thirdAuth = new ThirdAuth($appid, $appsecret, $redirect_url);
$state = 'test';
if(isset($_GET['code']) && !empty($_GET['code'])){
$code = $_GET['code'];
$state = $_GET['state'];
$openid = $thirdAuth->getCode($code, $state);
//打印openid看看输出结果
print_r($openid);
}else{
$thirdAuth->authCode($state);
}
?>
2、调用微信统一下单接口,获取微信预支付交易会话标识。
调用统一下单接口类 wxpay.class.php1
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<?php
/**
* 微信支付
*/
class WxPay
{
private $apiUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
private $key;
private $mch_id;
private $appid;
private $openid;
private $nonce_str;
private $body;
private $out_trade_no;
private $total_fee;
private $spbill_create_ip;
private $notify_url;
private $trade_type;
function __construct($attributes)
{
$this->key = $attributes['key'];
$this->mch_id = $attributes['mch_id'];
$this->appid = $attributes['appid'];
$this->openid = $attributes['openid'];
$this->body = $attributes['body'];
$this->total_fee = $attributes['total_fee'];
$this->trade_type = $attributes['trade_type'];
$this->notify_url = $attributes['notify_url'];
}
//发送post请求
function http_post($url, $param) {
$header [] = "content-type: application/json; charset=UTF-8";
$ch = curl_init ();
curl_setopt ( $ch, CURLOPT_URL, $url );
curl_setopt ( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
curl_setopt ( $ch, CURLOPT_SSL_VERIFYPEER, FALSE );
curl_setopt ( $ch, CURLOPT_SSL_VERIFYHOST, FALSE );
curl_setopt ( $ch, CURLOPT_HTTPHEADER, $header );
curl_setopt ( $ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)' );
curl_setopt ( $ch, CURLOPT_FOLLOWLOCATION, 1 );
curl_setopt ( $ch, CURLOPT_AUTOREFERER, 1 );
curl_setopt ( $ch, CURLOPT_POSTFIELDS, $param );
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
$res = curl_exec ( $ch );
curl_close ( $ch );
//$res = json_decode ( $res, true );
return $res;
}
//数组转换为xml格式
function arrayToXml($arr)
{
$xml = "<xml>";
foreach ($arr as $key=>$val)
{
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
//将XML转为数组
function xmlToArray($xml)
{
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $values;
}
//支付参数签名
function paySign($array, $key){
ksort($array);
$str = '';
foreach($array as $k => $value){
$str .= $k.'='.$value.'&';
}
$str .= "key=".$key;
$str = strtoupper(md5($str));
return $str;
}
//生成随机字符串
function get_rand_char($length = 6, $type = 1) {
$str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
$str2 = "0123456789";
$strLength1 = 61;
$strLength2 = 9;
$res = '';
if($type == 1){
for($i = 0; $i < $length; $i ++) {
$res .= $str1 [rand ( 0, $strLength1 )];
}
}else{
for($i = 0; $i < $length; $i ++) {
$res .= $str2 [rand ( 0, $strLength2 )];
}
}
return $res;
}
//获取IP
function get_client_ip() {
$ip = $_SERVER['REMOTE_ADDR'];
if (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif(isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
foreach ($matches[0] AS $xip) {
if (!preg_match('#^(10|172\.16|192\.168)\.#', $xip)) {
$ip = $xip;
break;
}
}
}
return $ip == '::1' ? '127.0.0.1' : $ip;
}
function prepare(){
$payConfig = [
'mch_id' => $this->mch_id,
'appid' => $this->appid,
'openid' => $this->openid,
'nonce_str' => $this->get_rand_char(32,2),
'body' => $this->body,
'out_trade_no' => date('YmdHis').$this->get_rand_char(6,1),
'total_fee' => $this->total_fee,
'spbill_create_ip' => $this->get_client_ip(),
'notify_url' => $this->notify_url,
'trade_type' => $this->trade_type,
];
$sign = $this->paySign($payConfig, $this->key);
$payConfig['sign'] = $sign;
$payXML = $this->arrayToXml($payConfig);
$result = $this->http_post($this->apiUrl, $payXML);
return $this->xmlToArray($result);
}
function configForPayment($prepayId){
$jsPayConfig = [
'appId' => $this->appid,
'timeStamp' => strval(time()),
'nonceStr' => $this->get_rand_char(32,2),
'package' => "prepay_id=$prepayId",
'signType' => 'MD5',
];
$jsPayConfig['paySign'] = $this->paySign($jsPayConfig, $this->key);
return json_encode($jsPayConfig);
}
}
测试脚本 test.php1
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<?php
//引入微信页面授权类
include 'thirdAuth.class.php';
include 'wxpay.class.php';
//公众号appid
$appid = 'APPID';
//公众号appsecret
$appsecret = 'APPSECRET';
//页面回调域名
$redirect_url = 'http://whatcode.cn/test.php';
$thirdAuth = new ThirdAuth($appid, $appsecret, $redirect_url);
$state = 'test';
if(isset($_GET['code']) && !empty($_GET['code'])){
$code = $_GET['code'];
$state = $_GET['state'];
$openid = $thirdAuth->getCode($code, $state);
}else{
$thirdAuth->authCode($state);
exit;
}
//商户号
$mch_id = 'MCH_ID';
//API KEY
$key = 'KEY';
//支付回调地址
$notify_url = 'http://whatcode.cn/notify.php';
//商品描述
$body = '微信测试';
//商品价格
$total_fee = '100';
//微信支付类型
$trade_type = 'JSAPI';
//支付配置参数
$attributes = [
'mch_id' => $mch_id,
'key' => $key,
'appid' => $appid,
'openid' => $openid,
'body' => $body,
'total_fee' => $total_fee,
'notify_url' => $notify_url,
'trade_type' => $trade_type,
];
$wxpay = new WxPay($attributes);
$result = $wxpay->prepare();
//打印输出$result
print_r($result);
?>
3、封装JS支付参数,调用微信浏览器内置WeixinJSBridge对象,直接调起微信支付。
测试脚本 test.php1
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<?php
//引入微信页面授权类
include 'thirdAuth.class.php';
include 'wxpay.class.php';
//公众号appid
$appid = 'APPID';
//公众号appsecret
$appsecret = 'APPSECRET';
//页面回调域名
$redirect_url = 'http://whatcode.cn/test.php';
$thirdAuth = new ThirdAuth($appid, $appsecret, $redirect_url);
$state = 'test';
if(isset($_GET['code']) && !empty($_GET['code'])){
$code = $_GET['code'];
$state = $_GET['state'];
$openid = $thirdAuth->getCode($code, $state);
}else{
$thirdAuth->authCode($state);
exit;
}
//商户号
$mch_id = 'MCH_ID';
//API KEY
$key = 'KEY';
//支付回调地址
$notify_url = 'http://whatcode.cn/notify.php';
//商品描述
$body = '微信测试';
//商品价格
$total_fee = '100';
//微信支付类型
$trade_type = 'JSAPI';
//支付配置参数
$attributes = [
'mch_id' => $mch_id,
'key' => $key,
'appid' => $appid,
'openid' => $openid,
'body' => $body,
'total_fee' => $total_fee,
'notify_url' => $notify_url,
'trade_type' => $trade_type,
];
$wxpay = new WxPay($attributes);
$result = $wxpay->prepare();
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS'){
$prepayId = $result['prepay_id'];
$jsapi = $wxpay->configForPayment($prepayId);
//调用微信WeixinJSBridge对象
echo <<<EOT
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>微信支付-支付</title>
<script type="text/javascript">
function jsApiCall()
{
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
{$jsapi},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok"){
alert('支付成功');
}else{
alert('支付失败');
}
}
);
}
function callpay()
{
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', jsApiCall);
document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
}
}else{
jsApiCall();
}
}
</script>
</head>
<body onload="callpay()"></body>
</html>
EOT;
}else{
exit('调用统一下单接口出错');
}
?>
到这里应该可以正常调起微信支付了。
四、总结
1、如果没能正常调起微信支付,最好是对照着微信的错误码来查找原因,尽量避免配置上的失误。
2、支付目录如果没设置正确,是没有错误码提示的,一定要看清楚你的支付路由的上一级目录就是支付目录。
3、根据我的示例代码,一步一步来,应该能成功调起微信支付,当然这只是示例代码,代码逻辑还不够严谨,这里只是用来讲解微信支付的开发流程。后面我会整理一个通用版的微信支付类。