漫谈微信支付-公共号支付


本文对微信公共号支付的实现过程进行详细介绍。

概述

公共号支付主要应用于微信内H5页面上发起的微信支付流程,它相对于扫码支付和APP要复杂一些,而且在实际开发过程中遇到的问题相对较多。

实现过程

公共号支付与APP支付类似,都需要调起统一下单接口获得prepay_id,根据prepay_id等参数调起微信发起支付流程,最后微信后台将支付结果异步回调给商户后台。
准备工作
公共号支付需要提前在微信公共平台进行业务配置,包括设置支付授权目录、设置JS接口安全域名以及设置授权回调页面域名。
支付授权目录:
位置:微信支付——>开发配置——>公共号支付
[1] 所有使用公众号支付方式发起支付请求的链接地址,都必须在支付授权目录之下;
[2] 正式支付授权目录最多设置3个,测试授权目录最多设置1个,且域名必须通过ICP备案;
[3] 头部要包含http或https,须细化到二级或三级目录,以左斜杠“/”结尾。
业务中发起支付的页面地址必须在授权目录下,否则调用下单接口时会提示“当前页面的URL未注册”。
JS接口安全域名:
位置:微信支付——>公共号设置——>功能设置——>JS接口安全域名
设置JS接口安全域名后,公众号开发者可在该域名下调用微信开放的JS接口。
注意事项:
[1] 可填写三个域名,要求是一级或一级以上域名(例:qq.com,或者 www.qq.com ),需使用字母、数字及“-”的组合,不支持IP地址及端口号;
[2] 填写的域名须通过ICP备案的验证;
[3] 一个自然月内最多可修改并保存三次。
授权回调页面域名:
位置:微信支付——>接口权限——>网页授权获取用户基本信息
用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠。
注意事项:
[1] 回调页面域名需使用字母、数字及“-”的组合,不支持IP地址及端口号。填写的域名需与实际回调URL中的域名相同;
[2] 填写的域名须通过ICP备案的验证。
获取用户授权时redirect_uri对应的URL必须在此域名下,否则回调的地址会无法打开。
统一下单
公共号支付统一下单接口需要传递openid参数,微信公共平台对于openid的解释如下:
在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。
公众号可根据以下接口来获取用户的openid,如需获取用户的昵称、头像、性别、所在城市、语言和关注时间,则需要用户授权。
参考信息:http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
开发者如果需要将同一个用户在不同公众号下的openid统一为一个id来记录,可以参考以下接口:
参考信息:http://mp.weixin.qq.com/wiki/14/bb5031008f1494a59c6f71fa0f319c66.html

即openid其实是每个用户在不同公共号内的唯一标示,获取openid需要先经过用户同意授权,获取code,再通过code获取openid。
获取用户授权
微信提供了两种授权方式:snsapi_base和snsapi_userinfo。
snsapi_base:不弹出授权页面,直接跳转,只能获取用户openid;
snsapi_userinfo:弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息。
想要获取code,需要构造如下地址:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
其中SCOPE参数为snsapi_base或snsapi_userinfo,REDIRECT_URI为获取用户授权后跳转的地址。
此处需要在微信公共平台配置授权回调页面域名:微信公共平台——>接口权限——>网页授权获取用户基本信息,点击“修改”,在弹出的页面中填写授权回调页面域名,REDIRECT_URI所在的域名要与填写的回调页面域名相同,否则在进行用户授权时会提示redirect_uri错误。
获取code后,再请求以下链接获取openid:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
其中APPID为公共号唯一标示,SECRET为公共号APPSECRET。
有关如何获取用户openid的详细内容可以参考微信官网文档网页授权获取用户基本信息(PS:以上这是最新版的公共号开发文档,这是旧版的文档,建议查看新版文档)
微信公共号支付SDK文件JsApiPay类提供了获取用户openid的方法GetOpenid:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
*
* 通过跳转获取用户的openid,跳转流程如下:
* 1、设置自己需要调回的url及其其他参数,跳转到微信服务器https://open.weixin.qq.com/connect/oauth2/authorize
* 2、微信服务处理完成之后会跳转回用户redirect_uri地址,此时会带上一些参数,如:code
*
* @return 用户的openid
*/

public function GetOpenid()
{

//通过code获得openid
if (!isset($_GET['code'])){
//触发微信返回code码
$baseUrl = urlencode('http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].$_SERVER['QUERY_STRING']);
$url = $this->__CreateOauthUrlForCode($baseUrl);
Header("Location: $url");
exit();
} else {
//使用code码,以获取openid
$code = $_GET['code'];
$openid = $this->getOpenidFromMp($code);
return $openid;
}
}

其中GetOpenidFromMp方法在官方SDK中存在错误,CURLOPT_TIMEOUT写错了:

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
/**
*
* 通过code从工作平台获取openid机器access_token
* @param string $code 微信跳转回来带上的code
* @return openid
*/

public function GetOpenidFromMp($code)
{

$url = $this->__CreateOauthUrlForOpenid($code);
//初始化curl
$ch = curl_init();
//设置超时
//curl_setopt($ch, CURLOP_TIMEOUT, 30); //CURLOP_TIMEOUT写错了!!!!应该是CURLOPT_TIMEOUT
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,FALSE);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if(WxPayConfig::CURL_PROXY_HOST != "0.0.0.0"
&& WxPayConfig::CURL_PROXY_PORT != 0){
curl_setopt($ch,CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
}
//运行curl,结果以json形式返回
$res = curl_exec($ch);
curl_close($ch);
//取出openid
$data = json_decode($res,true);
$this->data = $data;
$openid = $data['openid'];
return $openid;
}

获取openid后就可以调用统一下单接口获得prepay_id。
H5调起微信支付
网页端调起支付API提供了前端调起微信发起支付的方法,此处需要特别注意:公共号支付调起微信支付的接口参数与APP支付调起微信支付的参数大小写不同!
APP拉起微信支付接口参数列表:
APP拉起微信支付
公共号拉起微信支付接口参数列表:
公共号拉起微信支付
可以发现变量名一列,公共号接口有的字母是大写的,生成签名是需要区分参数大小写的,所以这块一定要注意,否则在调用接口时你就会一直收到签名错误的提示。
签名生成成功了,但是在调用调起微信支付接口时你可能会收到提示”调用支付JSAPI缺少参数:timeStamp,但是明明已经传递该参数了,怎么还会提示缺少参数呢?是由timeStamp的类型引起的,上图的参数列表中timeStamp类型为String字符串类型,很多时候我们程序里直接使用time()生成时间戳是整数,因此在将timeStamp传递给该接口时需要转换为字符串类型。
如果是服务器端调用拉起微信支付接口,需要将WxPay.Data.php文件中WxPayJsApiPay的SetTimeStamp方法作相应修改:

1
2
3
4
5
6
7
8
9
/**
* 设置支付时间戳
* @param string $value
**/

public function SetTimeStamp($value)
{

//modify by yuri 2016/7/5 解决JSAPI微信支付提示“缺少timeStamp参数”问题
$this->values['timeStamp'] = (string)$value;
}

测试开发时配置好测试授权目录,将微信号添加到测试白名单,H5调用拉起微信支付接口即可完成支付。

常见问题

支付场景非法






错误场景:前端调用拉起微信支付接口getBrandWCPayRequest报错。
原因分析:与开发用的APPID有关。JSAPI支付用到的APPID是从微信公告平台申请,APP支付用到的APPID是从微信开放平台申请,如果将APP支付的APPID用于JSAPI支付,就会收到该错误提示。
解决方法:不同的支付场景要使用相对应的APPID,不允许跨场景使用APPID支付。

您没有JSAPI支付权限
错误场景:后台调用unifiedorder统一下单接口报错。
原因分析:同“支付场景非法”
解决方法:同“支付场景非法”

不允许跨号支付








错误场景:在公共号内调用unifiedorder统一下单接口报错。
原因分析:公共号A申请开通了JSAPI支付,得到该公共号微信支付开发对应的APPID、KEY、APPSECRET,如果在公共号B内调用公共号A开发的微信支付接口,就会提示“不允许跨号支付”,微信侧限制了微信支付开发的相关接口只能在对应的公共号内调用。
解决方法:在微信JSAPI开发中配置的公共号内调用微信支付相关接口。

缺少openid







错误场景:后台调用unifiedorder统一下单接口报错。
原因分析:公共号支付的下单接口需要传递openid参数,为用户在商户appid下的唯一标识。调用统一下单接口时需要先获取用户openid。
解决方法:调用unifiedorder统一下单接口前,确保已经获取到用户openid。

缺少timeStamp








错误场景:前端调用拉起微信支付接口getBrandWCPayRequest报错。
原因分析getBrandWCPayRequest参数列表中写明了timeStamp参数类型为String,如果传递的timeStamp值为整型,会产生该错误。
解决方法:将timeStamp的值显示转换为字符串类型。

签名失败






错误场景:前端调用拉起微信支付接口getBrandWCPayRequest报错。
原因分析:公共号支付拉起微信支付接口与APP支付拉起微信支付接口中参数的大小写不同,生成签名会区分大小写。
解决方法:生成签名要严格按照参数列表中变量名称进行生成,尤其注意参数大小写。

body参数长度有误






错误场景:后台调用unifiedorder统一下单接口报错。
原因分析:body参数长度太长,unifiedorder统一下单接口中body参数长度为String(128),即占用128个字节,一个汉字占用3个字节,一个字母或数字占用1个字节,所以如果全部为汉字的话,最多42个。
解决方法:必要时使用字符串截取函数限制body的长度。

URL未注册








错误场景:前端调用拉起微信支付接口getBrandWCPayRequest报错。
原因分析:授权目录配置问题,当前支付页与授权目录设置不匹配。
解决方法:详细解决方案可参考另一篇博文<漫谈微信支付-授权目录设置>

下单账号与支付账号不一致






错误场景:前端调用拉起微信支付接口getBrandWCPayRequest报错。前端调用拉起微信支付接口getBrandWCPayRequest,拉起微信账号A,但是在微信A中没有完成支付(例如:执行了取消操作),此时如果换另一个微信账号B,再对微信端同一个订单进行支付,拉起微信,此时会报该错。
原因分析:微信端同一个订单号,限制了下单与支付账号必须使用同一个微信。在调用下单接口时传递了openid,在调用拉起微信支付接口时,如果当前用户的微信与下单微信不同,就会报该错。
解决方法:下单账号与支付账号必须使用同一个微信。

商户订单号重复






错误场景:后台调用unifiedorder统一下单接口报错。
原因分析:同一个订单,如果修改了商品描述或者商品价格,再用同一个订单号去调用统一下单接口就会报该错。
解决方法:重新生成订单。

订单已过期






错误场景:后台调用unifiedorder统一下单接口报错。
原因分析:下单有时效性。
解决方法:重新下单。

调用微信支付相关接口异常缓慢
微信支付SDK中所有最终请求微信服务器的出口均是经过WxPay.Api.php中的postXmlCurl接口:

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
/**
* 以post方式提交xml到对应的接口url
* @param string $xml 需要post的xml数据
* @param string $url url
* @param bool $useCert 是否需要证书,默认不需要
* @param int $second url执行超时时间,默认30s
* @throws WxPayException
*/

private static function postXmlCurl($xml, $url, $useCert = false, $second = 30)
{

$ch = curl_init();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);

//如果有配置代理这里就设置代理
if(WxPayConfig::CURL_PROXY_HOST != "0.0.0.0"
&& WxPayConfig::CURL_PROXY_PORT != 0){
curl_setopt($ch,CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
}
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
//modify by yuri 2016/7/5 设置curl默认访问IPv4网络
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
if($useCert == true){
//设置证书
//使用证书:cert 与 key 分别属于两个.pem文件
curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
curl_setopt($ch,CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);
}
//post提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//运行curl
$data = curl_exec($ch);
//返回结果
if($data){
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
throw new WxPayException("curl出错,错误码:$error");
}
}

因为CURL默认先解析IPV6,再解析IPV4,所以如果你的支付场景只有IPV4网络,则最好设置CURL默认访问IPV4,这样可以省去解析IPV6的时间,接口调用速度明显加快。
转载请注明出处:http://yurixu.com/blog/2016/08/16/漫谈微信支付-公共号支付/

分享 本文总阅读量
< !-- add by yurixu 替换Google的jquery并且添加判断逻辑 -->