一、扫码支付介绍
扫码支付可分为两种模式,商户根据支付场景选择相应模式。
【模式一】:
商户后台系统根据微信支付规则链接生成二维码,链接中带固定参数productid(可定义为产品标识或订单号)。用户扫码后,微信支付系统将productid和用户唯一标识(openid)回调商户后台系统(需要设置支付回调URL),商户后台系统根据productid生成支付交易,最后微信支付系统发起用户支付流程。
商户支付回调URL设置指引:进入商户平台–>产品中心–>开发配置,进行配置和修改,如下图所示。
【模式二】:
商户后台系统调用微信支付【统一下单API】生成预付交易,将接口返回的链接生成二维码,用户扫码后输入密码完成支付交易。注意:该模式的预付单有效期为2小时,过期后无法支付,不依赖设置的回调支付URL。
流程图:
业务流程说明:
(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(12)商户确认订单已支付后给用户发货。
二、准备工作
包括申请微信商户、账号的配置,这里暂不详述。
三、java代码
1.controller层
/**
* 微信支付模式二(统一下单)返回微信订单二维码url。前端使用二维码库生成二维码(qrcode.min.js)
* @param request
* @param response
* @return
*/
@ResponseBody
@RequestMapping("/weiXinPay")
public JsonResult weiXinPay(HttpServletRequest request, HttpServletResponse response) {
return wxPayService.weiXinPay(request,response);
}
2.service层
//统一下单
UnifiedOrder ufo = new UnifiedOrder();
//appid
String appid = "";
//商户id
String mch_id ="";
//商户秘钥
String mch_key = "";
String nonce_str = UUID.randomUUID().toString().substring(0,20).replace("-","1");
//价格(单位:分)
String total_fee = "";
//商品名称
String body = "会员充值";
//日期
String date = String.valueOf(System.currentTimeMillis()/ 1000);
String randomA = String.valueOf((int)(Math.random()*100000000));
String randomB = String.valueOf((int)(Math.random()*100000000));
String randomC = String.valueOf((int)(Math.random()*100000000));
String str = (randomA+randomB+randomC).substring(0,15);
//订单号
String out_trade_no = date+str;
//获取当前电脑的ip
String spbill_create_ip = request.getRemoteAddr();
//回调地址(公众号上面配置的支付回调地址)
String notify_url= "";
String trade_type = "NATIVE";
ufo.setAppid(appid);
ufo.setBody(body);
ufo.setMch_id(mch_id);
ufo.setNonce_str(nonce_str);
ufo.setNotify_url(notify_url);
ufo.setOut_trade_no(out_trade_no);
ufo.setSpbill_create_ip(spbill_create_ip);
ufo.setTotal_fee(total_fee);
ufo.setTrade_type(trade_type);
ufo.setOpenid("");
//获取预支付Map 10个参数
Map<String,String> unifiedMap = ufo.toMap();
log.info("unifiedMap is::"+unifiedMap);
//获取预支付签名
String estimateSign = SignUtil.getSign(unifiedMap,mch_key);
ufo.setSign(estimateSign);
try {
//这里使用的是weixin4j框架,具体可查看 http://www.weixin4j.org/
UnifiedOrderResult ufor = weixin.pay().payUnifiedOrder(ufo);
ufor.setDevice_info(out_trade_no+":"+total_fee);
//根据业务需要记录自己的信息
json = new JsonResult(ufor);
} catch (WeixinException e) {
json = new JsonResult(new ServiceException("微信认证失败"));
e.printStackTrace();
}
return json;
}
/***
* 微信支付回调
* @param res
* @param rep
* @return
*/
public JsonResult payResult(HttpServletRequest res,HttpServletResponse rep) {
JsonResult json = null;
try {
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = res.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null) {
sb.append(s);
}
Map<String, String> map = new HashMap<String, String>();
//判定是支付成功的返回结果
if(sb.toString().indexOf("xml")!=-1) {
//將xml转化为map
Document doc;
doc = DocumentHelper.parseText(sb.toString());
Element root = doc.getRootElement();
List children = root.elements();
if (children != null && children.size() > 0) {
for (int i = 0; i < children.size(); i++) {
Element child = (Element) children.get(i);
map.put(child.getName(), child.getTextTrim());
}
}
}
//商户秘钥
String mch_key = "";
//微信返回的签名
String sign = map.get("sign");
map.remove("sign");
//自动生成的sign
String nsign = SignUtil.getSign(map,mch_key);
//验证签名通过
if(nsign.equals(sign)){
String resXml = "";
if(map.get("return_code").equals("SUCCESS")) {
log.info("返回结果:: "+ map.get("openid") +"用户支付成功");
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
String create_time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
//获取系统当前时间
map.put("create_time",create_time);
//商户号
String mch_id = map.get("mch_id");
//商户订单号
String out_trade_no = map.get("out_trade_no");
//查询订单信息
Map<String,Object> vipOrder = wxPayMapper.getPayList(out_trade_no,mch_id);
String state = vipOrder.get("state")==null?"":vipOrder.get("state").toString();
//订单中的金额
String total_fee = vipOrder.get("total_fee")==null?"":vipOrder.get("total_fee").toString();
//订单id
String order_id =vipOrder.get("order_id")==null?"":vipOrder.get("order_id").toString();
//会员id
String member_id = vipOrder.get("member_id")==null?"":vipOrder.get("member_id").toString();
//微信返回信息中的支付金额
String wxtotal_fee = map.get("total_fee");
//无订单信息 ,订单状态为已支付,支付金额不正确,返回微信支付成功!
if(null==vipOrder||!state.equals("0")||!total_fee.equals(wxtotal_fee)){
}else{
//实现自己的业务
}
}else {
log.info("支付失败,错误信息:" + map.get("err_code"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
BufferedOutputStream out = new BufferedOutputStream(rep.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
log.info("返回结果 map::" + map);
//json = new JsonResult(map);
//关闭连接
in.close();
inputStream.close();
}
} catch (Exception e) {
log.info("返回结果 Exception is::"+ e);
//json = new JsonResult(new Exception(e));
}
return json;
}
/**主动查询**/
@Override
public JsonResult checkPayResult(String out_trade_no) {
if(null==out_trade_no||"".equals(out_trade_no)){
return new JsonResult(new ServiceException("订单号不能为空!"));
}
//appid
String appid = "";
//商户id
String mch_id ="";
//商户秘钥
String mch_key = "";
String nonce_str = UUID.randomUUID().toString().substring(0,20).replace("-","1");
OrderQuery orderQuery = new OrderQuery();
orderQuery.setAppid(appid);
orderQuery.setMch_id(mch_id);
orderQuery.setNonce_str(nonce_str);
orderQuery.setOut_trade_no(out_trade_no);
//获取预支付Map 10个参数
Map<String,String> unifiedMap = orderQuery.toMap();
//获取预支付签名
String estimateSign = SignUtil.getSign(unifiedMap,mch_key);
orderQuery.setSign(estimateSign);
try {
OrderQueryResult odr = weixin.pay().payOrderQuery(orderQuery);
return new JsonResult(odr);
} catch (WeixinException e) {
e.printStackTrace();
}
return null;
}
3.前端页面:js
var interval;
$(function(){
$(".recharge-qrcode1").hide();
doGetObjects();
interval = setInterval(doCheckObjects,4000);
})
//计数
var count = 0;
function doGetObjects(){
$.ajax({
type:'post',
url:'/wxPay/weiXinPay',
success:function(result){
if(result.state ==1){
var out_trade_no =result.data.device_info.split(":")[0];
$("#out_trade_no").val(out_trade_no);
$("#recharge-num").html("订单编号:"+out_trade_no);
$("#recharge-type").html("订单类型:"+result.data.trade_type);
//二维码大小
var qrcode = new QRCode(document.getElementById("qrcode"), {
width : 150,
height : 150
});
qrcode.makeCode(result.data.code_url);
}else{
alert(result.message);
}
}
});
}
订单主动查询
利用定时器的方式实现主动查询订单支付状态
在我们对接微信支付的时候可能会出现用户支付了,但我们系统业务支付状态并没有 改变的情况,这是因为微信推送支付数据流的时候是后台通知交互时,
如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总共会发起10次通知,
通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m),但微信不保证通知最终一定能成功。
所以这里就需要我们自己主动去查询:接口详细说明地址(http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2)
function doCheckObjects(){
count++;
if(count > 20){
clearInterval(interval);
//发起关闭订单
CloseOrder();
}
var out_trade_no = $("#out_trade_no").val();
var params = {"out_trade_no":out_trade_no};
$.ajax({
type:'post',
url:'/wxPay/checkPayResult',
data:params,
success:function(result){
//跳转支付成功页面
if(result.data.trade_state == "SUCCESS"){
clearInterval(interval);
window.location.href="/wxPay/goToSuccess?out_trade_no="+out_trade_no;
}
}
});
}
//关闭订单
function CloseOrder(){
$(".recharge-qrcode").hide();
$(".recharge-qrcode1").show();
}