都说微信支付有些坑,都抱怨微信支付的文档太烂,一会APPId,一会商户id,还有appsecret,支付API秘钥让你傻傻分不清楚,还有这里大写那里小写,几种标准,让你眼花缭乱。没错,这就是很多技术团队都存在的问题,没有统一的标准!且团队越大越严重,即使是在微信这样的顶尖团队。然而这些在一番痛苦折腾之后,最后都会不值一提。这里只详细讲JSAPI方式的公众号支付

简要思路图

配置支付授权目录

有点类似授权回调安全域名的韵味,需要将支付的页面路径添加到授权目录里面,否则再页面调起支付时会报没有添加支付目录的错误

配置地址: 微信公众平台-微信支付-开发配置

获取openid

参考之前的微信授权文章

统一下单

通常情况下是自身系统生成订单后进入支付页面,用户点击支付触发一个请求,将订单id、openid传给后台微信统一下单接口,后台根据订单id在自身系统查询一遍,获得价格、描述详情等信息

一次签名,发送xml报文给微信服务器

String nonceStr = "5K8264ILTKCH16CQ2502SI8ZNMTM67VS";//暂时不变

// 加密,这里只列举必填字段
Map<String, String> map = new HashMap<String, String>();
map.put("body", body);//商品描述
map.put("mch_id", MCHID);//商户平台id
map.put("appid", WX_APPID);//公众号id
map.put("nonce_str", nonceStr);//随机字符串
map.put("notify_url", WX_PAY_CALLBACK);//异步回调api
map.put("spbill_create_ip", ip);//支付ip
map.put("out_trade_no", orderSn);//商品订单号
map.put("total_fee", (int) relAmount + "");//真实金额
map.put("trade_type", "JSAPI");//JSAPI、h5调用
map.put("openid", openid);//支付用户openid String sign = WxPaySignatureUtils.signature(map, WX_PAY_KEY); String xml = "<xml>" +
"<appid>"+ WX_APPID +"</appid>"+
"<body>"+ body +"</body>"+
"<mch_id>"+ MCHID +"</mch_id>"+
"<nonce_str>"+ nonceStr +"</nonce_str>"+
"<notify_url>"+ WX_PAY_CALLBACK +"</notify_url>"+
"<openid>"+ openid +"</openid>"+
"<out_trade_no>"+ orderSn +"</out_trade_no>"+
"<spbill_create_ip>"+ ip +"</spbill_create_ip>"+
"<total_fee>"+ (int) relAmount + "" +"</total_fee>"+
"<trade_type>JSAPI</trade_type>"+
"<sign>"+ sign +"</sign>"+
"</xml>"; LOGGER.info("发送给微信的报文:" + xml);
LOGGER.info("加密后的的签名字符串:" + sign); // 请求
String response = "";
try {
response = apiService.doPostString(WX_UNIFIEDORDER, xml);
} catch (Exception e) {
//TODO
return null;
}
LOGGER.info("请求/pay/unifiedorder下单接口后返回数据:" + response);
//处理请求结果
XStream s = new XStream(new DomDriver());
s.alias("xml", WechatOrder.class);
WechatOrder order = (WechatOrder) s.fromXML(response); if ("SUCCESS".equals(order.getReturn_code()) && "SUCCESS".equals(order.getResult_code())) {
LOGGER.info("微信支付统一下单请求成功,获得的Prepay_id是:" + order.getPrepay_id());
} else {
LOGGER.error("微信支付统一下单请求错误:" + order.getReturn_msg() + order.getErr_code());
//TODO
return null;
}

注:

  1. WX_UNIFIEDORDER变量的地址是: https://api.mch.weixin.qq.com/pay/unifiedorder
  2. relAmount 是实际支付的金额,实际项目中应该是根据订单号在自身系统查询后获得
  3. body 是最终显示在支付凭证里面的【商品详情】
  4. orderSn 是最终显示在支付凭证-交易记录 里面的【商户单号】,也就是自身系统的订单号

这个时候如果你看到下面的日志,恭喜你伟大的第一步已经完成了,可以小憩喝杯咖啡再来哈哈

11:46:25.170 INFO  com.cramix.portal.service.WechatService 451 createOrder - 请求/pay/unifiedorder下单接口后返回数据:<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg><appid><![CDATA[wx8daca08d2f87216f]]></appid><mch_id><![CDATA[1482800922]]></mch_id><nonce_str><![CDATA[mjIbwmjLNFcD5tAF]]></nonce_str><sign><![CDATA[65EFB7B0BACBA01163765EB28B4E3F31]]></sign><result_code><![CDATA[SUCCESS]]></result_code><prepay_id><![CDATA[wx201706291146256b14b6eb620242392023]]></prepay_id><trade_type><![CDATA[JSAPI]]></trade_type></xml>
11:46:25.179 INFO com.cramix.portal.service.WechatService 459 createOrder - 微信支付统一下单请求成功,获得的Prepay_id是:wx201706291146256b14b6eb620242392023

当然,更多的时候不会有这么顺利,大多数人都会遇到签名错误的状态返回,微信确实是很喜欢用签名,签名错误这个四个字估计把所有的微信开发者都折磨了一遍,没有经历过签名错误的程序员不是真正的微信开发者。

然而,签名错误该怎么解决呢?通常情况下注意有三:

  1. 发送给微信的xml报文格式、字段有误。body字段如有中文一定要在请求里面加上这句:
HttpPost post = new HttpPost(uri);
post.setEntity(new StringEntity(str,"utf-8"));
  1. 加密算法是否有误,参考WxPaySignatureUtils
  2. 确认商户平台API秘钥(仔细阅读微信支付申请成功后发过来的邮件,点击【下载API证书、设置API密钥】)

二次签名

当你喝完那杯“甜”的咖啡之后,就要撸接下来的代码了。将前端需要用的字段进行加密校验,并返回

核心代码如下:

HashMap<String, String> back = new HashMap<String, String>();
String time = Long.toString(System.currentTimeMillis());
back.put("appId", WX_APPID);
back.put("timeStamp", time);
back.put("nonceStr", nonceStr);
back.put("package", "prepay_id=" + order.getPrepay_id());
back.put("signType", "MD5");
String sign2 = WxPaySignatureUtils.signature(back, WX_PAY_KEY);
LOGGER.info("二次签名后返回给前端的签名证书字符串是:" + sign2); JSONObject jsonObject = new JSONObject();
jsonObject.put("appId", WX_APPID);
jsonObject.put("timeStamp", time);
jsonObject.put("nonceStr", nonceStr);
jsonObject.put("package", "prepay_id=" + order.getPrepay_id());
jsonObject.put("signType", "MD5");
jsonObject.put("paySign", sign2); LOGGER.info("二次签名后返回给前端的数据是:" + jsonObject.toJSONString());

到此为止,微信支付统一下单环节简单的后台代码已经码好了,只欠前端唤起支付了!

前端调起支付

这里有个巨坑,千万不要踩,微信公众平台技术文档-微信JSSDK里面的微信支付跟这里没有任何关系!你只要看商户平台的文档!,也就是通过WeixinJSBridge对象去调用,当然只在微信app里有用!

//统一下单
unifiedorder: function(){
var self = this;
var userInfo = localStorage.getItem('ssqf_h5_wxUserInfo'); CRAMIX.POST({
baseUrl: URL.BASE + '/api/wechat/pay/h5',
data: {
'openid': JSON.parse(userInfo).openid,
'body':'书身祈福支付',
'orderSn': $('#orderSn').val() || 20 ,
'amount': $('#money').val() || '0.01'
},
success: function(data){
self.options.unifiedorderData = data;
}
})
}, //支付
pay: function(){
var self = this; var appId = self.options.unifiedorderData.appId;
var timeStamp = self.options.unifiedorderData.timeStamp;
var nonceStr = self.options.unifiedorderData.nonceStr;
var package1 = self.options.unifiedorderData.package;
var paySign = self.options.unifiedorderData.paySign; WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"timeStamp":timeStamp, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr, //随机串
"package":package1,
"signType":"MD5", //微信签名方式:
"paySign":paySign //微信签名
},
function(res){
WeixinJSBridge.log(res.err_msg);
if(res.err_msg == "get_brand_wcpay_request:ok"){
window.location.href = '/weixin/pay/pay-success.html';//去支付成功页面
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
$.toast("用户取消", "text");
}else{
$.toast("支付失败", "forbidden", function() {
window.location.reload();//刷新页面
});
}
}
);
},

授权获取openid这里就不多说了,amount为支付金额,我这里是为了方便测试改为了金额前端传输,实际肯定不是哈!当然金额有变之后需要重新走一遍统一下单的流程。

感悟

其实我们认为的所有坑大部分原因都是源自不细心,尽管文档等各种会让我们多走一些弯路,但是不能带着情绪去开发,遇到问题首先想自身不足,静下心来,写程序本身就需要有足够的耐心和十分的细心。去年做微信分享,也是有个签名,足足让我困了三天才搞定,有时候真的都怀疑人生了,但是最后都解决了,解决的那一瞬间,真的能感动自己!这回做微信支付,同样还是签名问题,也前前后后搞了差不多一天,当然这其中有很大部分原因是吸取了去年的经验,也就是上面所说。

参考资源

WXPayUtil

微信公众平台支付接口调试工具

微信支付之统一下单--JAVA版的更多相关文章

  1. springboot+微信小程序实现微信支付【统一下单】

    说明: 1)微信支付必须有营业执照才可以申请 2)微信支付官方api是全套的,我这是抽取其中的统一下单api,做了一个简单的封装 首先看看微信支付 商户系统和微信支付系统主要交互: 1.小程序内调用登 ...

  2. 第九篇 :微信公众平台开发实战Java版之如何实现自定义分享内容

    第一部分:微信JS-SDK介绍 微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包. 通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照.选图.语音.位置等手机系统 ...

  3. 第八篇 :微信公众平台开发实战Java版之如何网页授权获取用户基本信息

    第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...

  4. 第一篇:微信公众平台开发实战Java版之了解微信公众平台基础知识以及资料准备

    相信很多人或多或少听说了微信公众平台的火热.但是开发还是有一点门槛,鉴于挺多朋友问我怎么开发,问多了,自己平时也进行以下总结.所以下面给大家分享一下我的经验: 微信公众号是什么? 官网的介绍:再小的个 ...

  5. 第七篇 :微信公众平台开发实战Java版之如何获取微信用户基本信息

    在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同). 公众号可通过本接口来根据O ...

  6. 第六篇 :微信公众平台开发实战Java版之如何自定义微信公众号菜单

    我们来了解一下 自定义菜单创建接口: http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_to ...

  7. 第五篇 :微信公众平台开发实战Java版之如何获取公众号的access_token以及缓存access_token

    一.access_token简介 为了使第三方开发者能够为用户提供更多更有价值的个性化服务,微信公众平台 开放了许多接口,包括自定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等, 开 ...

  8. 第四篇 :微信公众平台开发实战Java版之完成消息接受与相应以及消息的处理

    温馨提示: 这篇文章是依赖前几篇的文章的. 第一篇:微信公众平台开发实战之了解微信公众平台基础知识以及资料准备 第二篇 :微信公众平台开发实战之开启开发者模式,接入微信公众平台开发 第三篇 :微信公众 ...

  9. 第三篇 :微信公众平台开发实战Java版之请求消息,响应消息以及事件消息类的封装

    微信服务器和第三方服务器之间究竟是通过什么方式进行对话的? 下面,我们先看下图: 其实我们可以简单的理解: (1)首先,用户向微信服务器发送消息: (2)微信服务器接收到用户的消息处理之后,通过开发者 ...

随机推荐

  1. CentOS7.2静默安装oracle11g

    http://www.centoscn.com/image-text/config/2015/0528/5552.html http://www.linuxidc.com/Linux/2016-04/ ...

  2. 深入理解Java常用类----String

         Java中字符串的操作可谓是最常见的操作了,String这个类它封装了有关字符串操作的大部分方法,从构建一个字符串对象到对字符串的各种操作都封装在该类中,本篇我们通过阅读String类的源码 ...

  3. Maven学习-构建项目

    创建项目 运行如下命令会创建一个简单的Maven项目. mvn archetype:create -DgroupId=com.netease.learn -DartifactId=simple -Dp ...

  4. 最近一些朋友问我,临近快毕业了专业不对口,想转行看到IT行业就业前景不错,但是编程语言众多不了解,不知道哪门语言能够快速入门掌握,短期能让我找到工作

    我做互联网前端后台开发也有四年多了,一路走过来,累并快乐着.快乐比艰辛更多,源自我的兴趣驱动.初中的一个偶然的机会我接触到了计算机,从那个时候就喜欢上开始经常到网吧上网.那个时候我对计算机领域的认识是 ...

  5. Linux下非root用户安装软件的一般流程:

    1. 获取源代码,一般是wget方式,ubuntu可以使用apt-get source来获取源代码. 2. 解压源代码,一般使用tar -zxvf xxx.tar.gz即可 3. 切换到解压后的目录, ...

  6. 如何用C#完成控制台日历?

    本题目的最终要就是根据用户输入的年和月在控制台输出单月的日历信息,附加范围年在1900-2100之间,月的范围在1-12之间,当用户输入不在范围时要给予错误信息提示:已知条件是1900年1月1日为星期 ...

  7. javaWeb学习总结(8)- JSP属性范围(5)

    所谓的属性范围就是一个属性设置之后,可以经过多少个其他页面后仍然可以访问的保存范围. 一.JSP属性范围 JSP中提供了四种属性范围,四种属性范围分别指以下四种: 当前页:一个属性只能在一个页面中取得 ...

  8. THREE笛卡尔右手坐标系详解

    1,正常的笛卡尔右手坐标系,以屏幕右方为+X轴,屏幕上方为+Y轴,垂直屏幕向外为+Z轴,如下图,xy轴组成的平面为屏幕面 但由于THREE里的相机并不总是从屏幕正前方视角,还可以设置坐标系任意一个轴为 ...

  9. MySQL定时逻辑备份

    当项目数据量不大时,备份可以采用逻辑备份. 数据库可以搭建一主一从,从库每天凌晨三点全量逻辑备份. 然后同时记录二进制文件,用来进行基于时间点的数据恢复. 其他备份方案详见我的思维导图:MySQL备份 ...

  10. Include promo/activity effect into the prediction (extended ARIMA model with R)

    I want to consider an approach of forecasting I really like and frequently use. It allows to include ...