功能描述:用户下单支付时可以选择微信支付,并调用微信端接口实现支付完成
微信支付的种类
- 微信扫描二维码支付
- 微信端吊起H5进行支付
- 微信公众号吊起支付
- app吊起微信支付
本篇讲的是针对微信公众号和app吊起支付的流程
首先是图文流程
模块代码
一、申请微信商户平台
二、进入到商户平台,设置一些需要的参数
三、前台执行ajax请求,提交到后台吊起微信支付接口。用户在前台选择想要购买的商品,点击支付按钮时,前台ajax获取商品的具体信息(id,num,name),然后请求后台的支付方法,后台执行完成后给前台返回需要的参数并跳转到微信支付页面执行输入密码操作
function wechatDoPay({
//获取商品名称,价格,商品数量
$.ajax({
type:"post",
url:"http://www.xyz.cn/wechatTest/wechat/unifiedorder",
success:function(data){
if(data.status=="success"){
wx.chooseWXPay(data);
}
}
})
});
wx.chooseWXPay({
timestamp: 0, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: '', // 支付签名随机串,不长于 32 位
package: '', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
signType: '', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: '', // 支付签名
success: function (res) {
// 支付成功后的回调函数
}
});
备注:prepay_id 通过微信支付统一下单接口拿到,paySign 采用统一的微信支付 Sign 签名生成方法,注意这里 appId 也要参与签名,appId 与 config 中传入的 appId 一致,即最后参与签名的参数有appId, timeStamp, nonceStr, package, signType。
四、后台controller层代码,校验用户的登陆状态,拿着openid和前台传来的参数去service层执行微信支付的具体方法
@RequestMapping("unifiedorder")
@ResponseBody
public String unifiedorder(HttpServletRequest request, String goodsId, int countNum){
//在这里做一下校验,直接校验用户有没有登录,如果没有登录,就不允许吊起下单操作
//接受前台的token防止用户重复提交
String openid = "";//openid是微信用户在公众号appid下的唯一用户标识
JSONObject jsonObject = service.unifiedorder(openid, goodsId,countNum);
jsonObject.put("status", "success");
return jsonObject.toString();
}
五、后台service层
处理主要的支付业务,根据前台传来的商品id获取详细商品信息,以及购买总价格,然后吊起微信统一下单的地址,在吊起之前,需要对传递的数据进行一些操作,首先需要appid和openid,商户ID,随即生成的字符串等等,还要设置一下支付完成后,微信如何通知后台管理用户已经完成了支付的路径,这些参数设置号以后,将它们转化成xml格式,并且调用支付接口,随后微信端返回对应的xml结果回来,我们在根据前台需要将传递回来的参数,进行json格式的转换以及paysign签名的重新生成,随后把拼接好的json字符串返回给前台
public JSONObject unifiedorder(String openid, String goodsId, int countNum) {
GoodsOrder goodsOrder = new GoodsOrder();
//根据用户id查询用户权限,看看用户是否是VIP
//根据商品id查询出商品信息
//根据商品数量和商品价格,计算出要付的价格
//4、调用微信下单操作,开始执行微信下单功能
//获取预支付号
String out_trade_no = UUID.randomUUID().toString();//需要插入订单表的订单ID
String prepayId = "";//第一次生成的预支付订单
SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();//使用sortedmap给下面插入的数据进行排序
parameters.put("appid", WechatUtils.appid);//微信支付分配的公众账号ID
parameters.put("mch_id", WechatUtils.mch_id);//微信支付分配的商户号,注册完公众号后开通微信支付接口时给的id
parameters.put("nonce_str", WechatUtils.CreateNoncestr());//随即生成的16位的字符串
parameters.put("body", "我是从商品表中拿出来的商品名字");
// parameters.put("body", goodsOrder.getId());
parameters.put("out_trade_no", out_trade_no);
//110 等于一块一毛钱
//10000 等于一百
//如果数据库存的是double类型的,价格是10.5怎么传入微信里,10.5*100=1050
// parameters.put("total_fee", String.valueOf(goods.getPrice())+"00");
parameters.put("total_fee", String.valueOf(10)+"00");//我是十块
parameters.put("spbill_create_ip", WechatUtils.spbill_create_ip);//调用微信支付API的机器IP
parameters.put("notify_url", WechatUtils.WECHAT_NOTIFY_URL);//支付完成后回返回后台系统的调用的请求路径
parameters.put("trade_type", "JSAPI");//交易类型
//生成签名的地方需要一个openid,所以把这个openid加上
//openid是微信用户在公众号appid下的唯一用户标识(appid不同,则获取到的openid就不同),
// 可用于永久标记一个用户,同时也是微信JSAPI支付的必传参数
parameters.put("openid", openid);
parameters.put("timeStamp", WechatUtils.create_timestamp());//生成时间戳
//我的作用是为了生成一个签名
String sign = WechatUtils.createSign(parameters);
parameters.put("sign", sign);
//把map类型的参数转换成xml格式的
String requestXML = WechatUtils.getRequestXml(parameters);
//开始请求支付接口,返回微信的订单结果
String preparyIdXml = CommonUtil.httpRequestJson(WechatUtils.unifiedorder, "POST", requestXML);
//把返回的订单结果转换为map类型的
Map<String, String> prepayIdMap =new HashMap<String, String>();
try {
prepayIdMap = XMLUtil.doXMLParse(preparyIdXml);
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (null != prepayIdMap) {
prepayId =prepayIdMap.get("prepay_id");
}
}
这是微信端需要的参数格式,都是以xml的形式传递
<xml>
<appid>wx2421b1c4370ec43b</appid>
<attach>支付测试</attach>
<body>JSAPI支付测试</body>
<mch_id>10000100</mch_id>
<detail><![CDATA[{
"goods_detail":[
{
"goods_id":"iphone6s_16G",
"wxpay_goods_id":"1001",
"goods_name":"iPhone6s 16G",
"quantity":1,
"price":528800,
"goods_category":"123456",
"body":"苹果手机"
},
{
"goods_id":"iphone6s_32G",
"wxpay_goods_id":"1002",
"goods_name":"iPhone6s 32G",
"quantity":1,
"price":608800,
"goods_category":"123789",
"body":"苹果手机"
}
]
}]]></detail>
<nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str>
<notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url>
<openid>oUpF8uMuAJO_M2pxb1Q9zNjWeS6o</openid>
<out_trade_no>1415659990</out_trade_no>
<spbill_create_ip>14.23.150.211</spbill_create_ip>
<total_fee>1</total_fee>
<trade_type>JSAPI</trade_type>
<sign>0CB01533B8C1EF103065174F50BCA001</sign>
</xml>
这是第二次返回的xml结果,需要我们后台进行转换以及拼接成json字符串返回给前台
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wx2421b1c4370ec43b]]></appid>
<mch_id><![CDATA[10000100]]></mch_id>
<nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
<openid><![CDATA[oUpF8uMuAJO_M2pxb1Q9zNjWeS6o]]></openid>
<sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>
这是前台需要的参数类型,后台需要拼接成这种类似的格式,前台才能引用
wx.chooseWXPay({
timestamp: 0, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: ‘’, // 支付签名随机串,不长于 32 位
package: ‘’, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
signType: ‘’, // 签名方式,默认为’SHA1’,使用新版支付需传入’MD5’
paySign: ‘’, // 支付签名
success: function (res) {
// 支付成功后的回调函数
}
});
后台controller层,负责接收微信端的回调数据,主要是解析用户有没有完成支付,并进行更进一步的业务判断和处理
@RequestMapping("wechatNotifyUrl")
@ResponseBody
public String wechatNotifyUrl(HttpServletRequest request){
try {
//用户发送了一条消息或关注了我的公众号,都会发一条消息,消息是流形式
InputStream inStream = request.getInputStream();
//把消息流转换成文本类型
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
String result = new String(outSteam.toByteArray(), "UTF-8");
System.out.println("result==="+result);
//SAX解析xml
Map<String, String> map = null;
//通过sax解析xml技术,把xml文本解析成map对象
try {
map = XMLUtil.doXMLParse(result);
} catch (JDOMException e) {
e.printStackTrace();
}
//必须要给微信一个回复,否则微信端会一直请求你的接口
return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}catch (Exception e){
}
return null;
}
这里需要注意的是,一定要给微信端返回
“<return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg>”;
这种形式的数据,不然微信端会一直调用你的后台接口确认你有没有接受到数据
总结
前台调用后台接口→后台调用微信统一下单接口,后台返回调用微信统一下单后返回的内容给前台→前台根据后台返回的内容调用微信浏览器内置JS弹出支付→支付后有两种处理 ①前台支付成功后的页面 ②微信回调url(一般处理业务逻辑)
支付的幂等性
什么是支付的幂等性?
(1)用户多次请求造成出现多个订单
(2)支付完成后,被拦截或网络不好,造成收不到回调通知或是收到了回调通知,但是这个回调通知的金额和支付信息不匹配,说明数据有问题
可以参考以下博客来进行问题的解决
https://blog.csdn.net/github_36032947/article/details/78386551
https://www.cnblogs.com/leechenxiang/p/6626629.html
https://blog.csdn.net/aly1989/article/details/52352726