前言
最近完成了微信公众号内的支付功能,在开发的过程中遇到了各种各样的问题,在这里,我把开发的过程分享出来,给大家做个参考。
首先,在准备开发的时候需要进行必要的配置。
1.登录微信商户平台,在产品中心->开发配置中对支付授权目录进行配置
(注意:支付授权目录的配置规则是你使用微信支付控件页面的上一级目录,比如:你在www.xxx.cpm/wx/pay/pay.html中调用微信支付控件,那么你需要配置的目录是www.xxx.cpm/wx/pay/)
2.在账户中心->API安全中配置key值,此参数在生成签名时需要用到。
3.其他参数与配置:appid,开发者密码以及网页授权
在做完这些配置以后,我们就可以进行微信支付的开发了。在这里推荐参考微信平台提供的demo。https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
按照文档中统一接口的实现流程,我们需要先实现发起微信支付的请求。这里的请求链接是统一下单的链接,POST请求
/**
* 发起微信支付请求
* @param requestUrl 请求链接
* @param method 请求方式
* @param param 请求参数
* @param connectTimeOut 连接超时时间
* @param readTimeOut 读取超时时间
* @return 请求结果
*/
public String requestOnce(final String requestUrl, final String method, String param, boolean useCert, int connectTimeOut, int readTimeOut) {
HttpURLConnection conn = null;
StringBuilder builder = new StringBuilder();
try {
URL url = new URL(requestUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod(method);
conn.setConnectTimeout(connectTimeOut);
conn.setReadTimeout(readTimeOut);
OutputStream out = conn.getOutputStream();
out.write(param.getBytes());
out.flush();
out.close();
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
InputStream inputStream = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
String line = null;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
reader.close();
inputStream.close();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return builder.toString();
}
在这里我们将微信支付所需要的参数进行填充。
/**
* 填充微信支付参数
* @param data 需要填充的Hashmap
* @return 参数
*/
public Map<String, String> fillRequestData(Map<String, String> data) throws Exception {
data.put("appid", config.getAPPID());
data.put("mch_id", config.getMCHID());
data.put("nonce_str", WXPayUtil.generateNonceStr());
data.put("notify_url",config.getNOTIFY_URL());
data.put("spbill_create_ip", config.getSERVER_IP());
data.put("trade_type", config.getJSAPI());
if (WXPayConstants.SignType.MD5.equals(this.signType)) {
data.put("sign_type", WXPayConstants.MD5);
}
else if (WXPayConstants.SignType.HMACSHA256.equals(this.signType)) {
data.put("sign_type", WXPayConstants.HMACSHA256);
}
data.put("sign", WXPayUtil.generateSignature(data, config.getKEY(), this.signType));
return data;
}
在请求成功之后,微信服务器会返回xml形式的参数,我们可以将xml转换为Map(此方法在微信demo中有实现)。此外在结果返回成功以后,需要再次生成签名,此签名是由于前端页面调起微信支付控件所用。
/**
* 发起微信支付认证请求
* @param requestUrl 请求链接
* @param method 请求方式
* @param param 请求参数
* @param useCert 是否使用证书
* @return 认证结果
*/
private Map<String, String> request(final String requestUrl, final String method, String param, boolean useCert) {
String strXml = wxPayRequest.request(requestUrl, method, param, useCert, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());
Map<String, String> data;
try {
data = WXPayUtil.xmlToMap(strXml);
} catch (Exception e) {
data = null;
e.printStackTrace();
}
return data;
}
private Map<String, String> returnAuthParam(Map<String, String> temp, String key) {
Map<String, String> result = new HashMap<String, String>();
long timestamp = System.currentTimeMillis() / 1000;
result.put("appId", temp.get("appid"));
result.put("timeStamp", String.valueOf(timestamp));
result.put("nonceStr", temp.get("nonce_str"));
result.put("package", "prepay_id=" + temp.get("prepay_id"));
result.put("signType", WXPayConstants.MD5);
String sign = null;
try {
sign = WXPayUtil.generateSignature(result, key, WXPayConstants.SignType.MD5);
} catch (Exception e) {
e.printStackTrace();
}
result.put("paySign", sign);
result.put("return_code", temp.get("return_code"));
result.put("result_code", temp.get("result_code"));
return result;
}
签名生成方法:
/**
* 生成签名 MD5 HMACSHA256
* @param data 参数
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String,String> data, String key, WXPayConstants.SignType signType) throws Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) {
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
}
sb.append("key=").append(key);
if (WXPayConstants.SignType.MD5 == signType) {
return MD5(sb.toString()).toUpperCase();
}
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
在做完这些操作以后,就可以在前端实现调起微信支付控件的逻辑了。在微信支付文档中给出了一种写法:
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(data);
}
function onBridgeReady(data){
orderPage.orderParam.orderId = data.orderId;
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": data.appId, //公众号名称,由商户传入
"timeStamp": data.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": data.nonceStr, //随机串
"package": data.package,
"signType": data.signType, //微信签名方式:
"paySign": data.paySign //微信签名
},
function(res){
alert(res.err_msg + " " + res.err_code + " " + res.err_desc);
if(res.err_msg == "get_brand_wcpay_request:ok"){
orderPage.orderParam.status = "WAITSEND";
orderPage.submitOrder();
}else if(res.err_msg == "get_brand_wcpay_request:fail"){
alert("调用微信支付失败");
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
alert("已取消支付");
}
}
);
}
在上面所使用到的参数都是服务端微信支付请求成功后获取的参数。完成这些之后,微信支付就可以使用了。
但是我们开发的过程中,如果没有相关的项目经验,难免会遇到很多坑。这里我就列举出我在开发中遇到的问题。
- 参数缺失:缺少openid,在微信公众号内嵌网页中调用微信支付需要添加用户的openid。
- 参数形式错误:在前端调起微信支付控件时,将package参数传错,package参数的正确格式为:“prepay_id=XXXXXXXXX”,而我忘了加入“prepay_id=”,这个错误着实让我找了好久(都是不仔细阅读文档的问题)。
- 签名失败:这个签名失败是在前端提示。造成的原因是我在微信支付请求成功之后,直接使用了服务端申请微信支付请求生成的签名,而正确的应该是在申请支付权限成功之后,需要**将前端用到的参数拿过来重新生成一次签名才对。**这个错误卡了我大半天才搞定,需要注意。
- get_wcpay_branch_request:fail,这个错误的排查顺序是:首先查看返回参数中request_code,result_code的返回值,如果都为SUCCESS,那么说明服务端请求微信支付权限已经成功,错误就来自于前端,或者参数错误(参考3)。
微信支付的调试:
由于开发微信支付需要调用第三方接口,所以在调试的时候会遇到诸多不便。这里我们就需要使用一些工具来帮助我们完成。
1.微信web开发者工具,我们可以使用这个工具进行调试,直接输入链接地址,然后在这里可以断点调试也可以查看相关错误信息。
2。打印log。在前端我们可以打印出关键信息进行错误排查。比如说,在调用微信支付控件时,输出err_msg,err_code,err_desc,进行查看。
最后说两句
在做完微信支付以后,其实你会发现并没有太大难度,就是在开发的过程中我们需要善于发现问题。还有就是要仔细阅读文档,我遇到的一些错误有很多都是文档中所提到的,但是没有仔细看所以就花费了很多时间去排查错误,而这些都是可以避免的。
最后一张,微信支付的目录结构(仅供参考):