Tenken blog

微信支付--APP支付开发

微信APP支付开发的流程跟前面所讲的微信公众号支付开发、微信扫码开发基本一致,但是所需要的appid和mch_id不一样,这个是比较坑的地方,而且微信的开发文档并没有明显说明这一点,导致很多开发者在这一步掉坑。下面会在开发过程中特殊所遇到的坑与解决方法。

一、前期准备工作

1、申请一个微信开发者账号
2、创建APP应用
3、申请开通该APP应用支付功能(会开通一个新的微信商户后台,跟公众号的商户后台不是同一个,注意:商户号mch_id也不是同一个)
4、备案的域名+服务器(这个是必备的)

二、微信公众号支付开发流程


1、根据微信支付文档封装支付参数,调用微信支付统一下单接口向微信服务器发起请求,返回微信预支付交易会话标识。微信文档链接
2、封装APP端需要的支付接口参数
3、APP应用接收支付参数,调起微信APP支付。

三、代码开发流程

前面粗略讲解了微信APP支付的开发流程,理解起来应该是相对简单。下面是示例代码(只是测试代码,不要用于生产环境):
1、调用微信统一下单接口,获取微信预支付交易会话标识。
调用统一下单接口类 wxpay.class.php

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
<?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 qr_prepare(){
$payConfig = [
'mch_id' => $this->mch_id,
'appid' => $this->appid,
'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);
}

function configForApp($prepayId){
$PayConfig = [
'appid' => $this->appid,
'partnerid' => $this->mch_id,
'prepayid' => $prepayId,
'package' => 'Sign=WXPay',
'nonceStr' => $this->get_rand_char(32,2),
'timestamp' => strval(time()),
];
$PayConfig['sign'] = $this->paySign($PayConfig, $this->key);
return json_encode($PayConfig);
}

}

测试代码 test.php

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
<?php
//引入微信支付类
include 'wxpay.class.php';

//APPID(微信开放平台审核通过的应用APPID)
$appid = 'APPID';
//商户号(微信开放平台对应的商户平台的商户号)
$mch_id = 'MCH_ID';
//API KEY
$key = 'KEY';
//支付回调地址
$notify_url = 'http://whatcode.cn/notify.php';
//商品描述
$body = '微信测试';
//商品价格
$total_fee = '100';
//微信支付类型(注意支付类型不要搞错,这里用APP)
$trade_type = 'APP';

//支付配置参数
$attributes = [
'mch_id' => $mch_id,
'key' => $key,
'appid' => $appid,
'body' => $body,
'total_fee' => $total_fee,
'notify_url' => $notify_url,
'trade_type' => $trade_type,
];

$wxpay = new WxPay($attributes);
$result = $wxpay->qr_prepare();
//打印输出$result
print_r($result);
//如果提示“商户未开通此接口权限”,有两个可能:1、商户号mch_id填错了,appid填错了 2、该APP应用没用开通微信支付,第一种可能性最大
?>

2、封装APP端需要的支付接口参数
测试代码:

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
//引入微信支付类
include 'wxpay.class.php';

//APPID(微信开放平台审核通过的应用APPID)
$appid = 'APPID';
//商户号(微信开放平台对应的商户平台的商户号)
$mch_id = 'MCH_ID';
//API KEY
$key = 'KEY';
//支付回调地址
$notify_url = 'http://whatcode.cn/notify.php';
//商品描述
$body = '微信测试';
//商品价格
$total_fee = '100';
//微信支付类型(注意支付类型不要搞错,这里用APP)
$trade_type = 'APP';

//支付配置参数
$attributes = [
'mch_id' => $mch_id,
'key' => $key,
'appid' => $appid,
'body' => $body,
'total_fee' => $total_fee,
'notify_url' => $notify_url,
'trade_type' => $trade_type,
];

$wxpay = new WxPay($attributes);
$result = $wxpay->qr_prepare();
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS'){
$appPayConfig = $wxpay->configForApp($result['prepay_id']);
//APP端需要的支付参数(安卓或者IOS)
print_r($appPayConfig)
}

如果到这一步都没问题,那么剩下的就是安卓端或者IOS端的事情了。

四、总结

微信APP支付开发的流程跟前面所讲的微信公众号支付开发、微信扫码开发基本一致,但是所需要的appid和mch_id不一样,这里很容易搞错,加上微信的开发文档没有特殊说明,加上商户后台众多,很容易就掉坑。

请我吃辣条吧~~