时间: 2016年8月11日

说明: 文档主要描述微信公众号支付开发过程中处理流程和一些遇到的问题。

1 准备工作

步骤

  • 微信公众号申请,类型选择服务号或企业号,订阅号不支持微信支付功能
  • 微信商户帐号申请(需要审核)
  • 选择对应版本的 SDK 文件准备开发

2 支付原理

业务流程

3 通知(同步、异步)

  • 3.1 同步通知

    商户根据微信支付后返回的结果来处理自己的业务。

  • 3.2 异步通知

    微信支付完成后,会通过在下单时候设置的 notify_url 通知商户支付结果,商户通过返回的结果来处理内部订单然后给微信返回处理结果,如果返回成功则说明商户校验信息正常通知微信不需要再次通知,否则微信会在一定时间内重复发通知给商户。通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒。

4 开发流程

官方文档: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

4.1 统一下单 unifiedorder

  • 4.1.1 方法中需要参数都填写,注意参数名大小写和长度

  • 4.1.2 统一下单时签名问题

  • 4.1.3 微信用户 openid 获取

  • 4.1.4 加强下单时参数验证

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
public static function unifiedOrder($wxpay_config = array(), $ssl_config = array(), $proxy_config = array())
{
$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//检测必填参数
if (!array_key_exists('out_trade_no', $wxpay_config)) {
throw new WxPayException("缺少统一支付接口必填参数 out_trade_no!");
}
if (!array_key_exists('body', $wxpay_config)) {
throw new WxPayException("缺少统一支付接口必填参数 body!");
}
if (!array_key_exists('total_fee', $wxpay_config)) {
throw new WxPayException("缺少统一支付接口必填参数 total_fee!");
}
if (!array_key_exists('trade_type', $wxpay_config)) {
throw new WxPayException("缺少统一支付接口必填参数 trade_type!");
}
//关联参数
if ('JSAPI' == $wxpay_config['trade_type']) {
if (!array_key_exists('openid', $wxpay_config)) {
throw new WxPayException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");
}
}
if ('NATIVE' == $wxpay_config['trade_type']) {
if (!array_key_exists('product_id', $wxpay_config)) {
throw new WxPayException("统一支付接口中,缺少必填参数product_id!trade_type为JSAPI时,product_id为必填参数!");
}
}
if (!array_key_exists('notify_url', $wxpay_config)) {
throw new WxPayException("缺少统一支付接口必填参数!notify_url");
}
$xml = WxPayCore::toXml($wxpay_config);
$startTimeStamp = WxPayCore::getMillisecond();//请求开始时间
$response = WxPayCore::postXmlCurl($xml, $url, $proxy_config['curl_proxy_host'], $proxy_config['curl_proxy_port'], $ssl_config['sslcert_path'], $ssl_config['sslkey_path'], true);
// 响应结果
$responseArr = WxPayCore::fromXml($response);
return $responseArr;
}

4.2 微信支付JSAPI

  • 4.2.1 统一下单成功后返回数据

  • 4.2.2 签名验证

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
public function getJSApiParameters($UnifiedOrderResult, $key)
{
if (!array_key_exists("appid", $UnifiedOrderResult)
|| !array_key_exists("prepay_id", $UnifiedOrderResult)
|| $UnifiedOrderResult['prepay_id'] == "") {
throw new WxPayException("参数错误");
}
$timemap = time();
// 生成随机数
$o_ranchar = new Core\Utility\RandChar();
$s_ranchar = $o_ranchar->randChar('mix', 32);
// 生成签名
$sign_config = array(
"appId" => $UnifiedOrderResult['appid'],
"timeStamp" => "$timemap",
"nonceStr" => $s_ranchar,
"package" => "prepay_id=" . $UnifiedOrderResult['prepay_id'],
"signType" => "MD5",
);
$o_sign = new Core\Utility\WxPay\WxPaySign($sign_config);
$s_sign = $o_sign->makeSign($key);
$params = array(
"appId" => $UnifiedOrderResult['appid'],
"timeStamp" => "$timemap",
"nonceStr" => $s_ranchar,
"package" => "prepay_id=" . $UnifiedOrderResult['prepay_id'],
"signType" => "MD5",
"paySign" => $s_sign
);
return $params;
}

4.3 微信公众号内置支付功能

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
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp":" 1395712654", //时间戳,自1970年以来的秒数
"nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串
"package" : "prepay_id=u802345jgfjsdfgsdg888",
"signType" : "MD5", //微信签名方式:
"paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {}
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}

将 微信支付 JSAPI 生成的数据作为微信内置函数 WeixinJSBridge.invoke() 发起 getBrandWCPayRequest 请求时的参数,如果参数正确当点击支付时候可以在微信公众号中调用微信客户端支付功能。

4.4 同步通知流程处理
商户根据微信内置函数调用支付结果来处理业务,例如:当支付返回 get_brand_wcpay_request:ok 说明支付成功,get_brand_wcpay_request:failget_brand_wcpay_request:cancle 说明支付失败,但是 返回数据不一定可靠

4.5 异步通知处理
微信会通过统一下单时设置的 notify_url 向商户发送支付结果通知,商户应该根据返回的数据对订单信息和商户信息等重要信息验证
避免订单错误导致资金错误,然后更新内部订单状态,最后将处理后的结果返回给微信,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)

1
2
3
4
5
6
7
8
9
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
------------------------------------------------------------
<xml>
<return_code><![CDATA[FAIL]]></return_code>
<return_msg><![CDATA[商户返回失败的原因]]></return_msg>
</xml>

5 常见问题

  • 5.1 统一下单时签名问题

  • 5.2 支付时签名验证失败

  • 5.3 异步通知未响应

Q&A: 签名错误

1
2
3
签名问题大部分是因为生成签名时参数不一致,例如:在统一下单时候需要传递签名参数和其他参数,个人感觉微信在调用生成预支付订单时候内部也产生签名并且
和传递过来的签名进行对比验证,当不一致时就会提示签名错误,原因就是在下单时商户主动生成的签名参数和微信内部生成参数不一致,当签名不一致时建议将方
法所需的参数作为生成签名的参数,然后再将生成签名传递。

Q&A: 异步通知未响应

1
2
3
4
1. 统一下单时 notify_url 是否设置
2. notify_url 是否符合规范:1.不能带有参数,例如:https://pay.weixin.qq.com/notify.php?wx=20160811 2.能够访问
3. 检查接口是否有访问权限设置
4. 通过 error_log 检查是否有日志文件产生

6 总结

1
2
在微信公众号支付开发过程中,遇到很多问题有些官方文档没怎么描述清楚,导致后面花较长时间去测试原因。然后是理解到同步和异步通知的区别并且学习到
error_log 日志功能的好处,通过生成的日志文件方便去解决问题和优化,远远超过自己预测情况时没有数据来对比分析。