支付业务流程时序图:
一、统一下单
API地址: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
根据商户系统的订单,将参数封装成xml格式,调用微信统一下单接口,如果成功返回prepay_id。
1、支付实体
1 public class PayInfo implements Serializable{ 2 3 /** 4 * @Fields serialVersionUID : TODO 5 */ 6 private static final long serialVersionUID = 1L; 7 private String appid;//公众账号ID 8 private String mch_id;//商户号 9 private String device_info;//设备号。PC端或公众号支付穿WEB 10 private String nonce_str;//随机字符串 11 private String sign;//签名 12 private String body;//商品描述 13 private String detail;//商品详情 14 private String attach;//附加数据 15 /** 16 * 商品订单号:商户支付的订单号由商户自定义生成,微信支付要求商户订单号保持唯一性(建议根据当前系统时间加随机序列来生成订单号)。 17 * 重新发起一笔支付要使用原订单号,避免重复支付; 18 * 已支付过或已调用关单、撤销的订单号不能重新发起支付。 19 */ 20 private String out_trade_no; 21 private String fee_type;//货币类型,默认人民币:CNY。可不传 22 private int total_fee;//总金额,不能为小数,单位是分 23 private String spbill_create_ip;//终端IP,网页支付提交用户端ip 24 private String time_start;//交易起始时间 25 private String time_expire;//交易结束时间:最短时间间隔必须大于5分钟 26 private String goods_tag;//商品标记 27 private String notify_url;//通知地址:接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。 28 private String trade_type;//交易类型:公众号支付传JSAPI 29 private String product_id;//商品ID:公众号支付该参数可不传 30 private String limit_pay;//指定支付方式:no_credit--指定不能使用信用卡支付 31 private String openid;//用户标识 32 //省略getter、setter方法 33 }
2、创建统一下单的支付实体
1 /** 2 * 创建统一下单的xml的java对象 3 * @param params UniformOrderParams 4 * @return 5 */ 6 public static PayInfo createPayInfo(UniformOrderParams params) { 7 PayInfo payInfo = new PayInfo(); 8 payInfo.setAppid("appid");//商户的appid 9 payInfo.setDevice_info("WEB"); 10 payInfo.setMch_id("mach_id");//商户mach_id 11 payInfo.setNonce_str(new StringWidthWeightRandom().getNextString(32));//获得32位随机数 12 payInfo.setBody(params.getBody()); 13 payInfo.setAttach(params.getAttach()); 14 payInfo.setOut_trade_no(params.getOut_trade_no()); 15 payInfo.setTotal_fee(params.getTotal_fee()); 16 payInfo.setSpbill_create_ip(params.getSpbill_create_ip()); 17 payInfo.setNotify_url("notify_url");//支付后,微信回调商户系统的地址,系统根据回调结果处理订单。 18 payInfo.setTrade_type("JSAPI"); 19 payInfo.setOpenid(params.getOpenid());//用户的openId 20 return payInfo; 21 }
3、统一下单,获得预付Id
1 /** 2 * 统一订单 3 * @param params 根据商户订单,封装的统一下单所必须的参数 4 */ 5 public String uniformOrder(UniformOrderParams params) { 6 //获得支付实体 7 PayInfo payInfo = CommonUtil.createPayInfo(params); 8 9 //将bean转换为SortedMap,转为sortedMap方便签名时排序 10 SortedMap<Object,Object> paras = CommonUtil.convertBean(payInfo); 11 String sgin = CommonUtil.createSgin(paras); 12 13 payInfo.setSign(sgin); 14 15 String xml = CommonUtil.beanToXML(payInfo).replace("__", "_"). 16 replace("<![CDATA[", "").replace("]]>", ""); 17 if (log.isDebugEnabled()) { 18 log.debug(xml); 19 } 20 Map<String, String> map = CommonUtil.httpsRequestToXML( 21 "https://api.mch.weixin.qq.com/pay/unifiedorder","POST",xml,false); 22 23 String return_code = map.get("return_code");//此字段是通信标识 24 String result_code = ""; //业务处理标示 25 //当return_code为SUCCESS时,result_code有返回。return_code、result_code同时为SUCCESS时,取出prepay_id返回 26 if(StringUtils.isNotBlank(return_code) && return_code.equals("SUCCESS")){ 27 result_code = map.get("result_code"); 28 } 29 if(StringUtils.isNotBlank(result_code) && !result_code.equals("SUCCESS")) { 30 return "统一下单错误!"; 31 }else{ 32 return map.get("prepay_id"); 33 } 34 }
拿到预付id后,就可以调用JSAPI接口请求支付了。
二、调用微信JS完成支付
1、微信支付对象
1 /** 2 * 微信支付对象 3 * @author rory.wu 4 * 5 */ 6 public class WxPay implements Serializable { 7 private static final long serialVersionUID = 3843862351717555525L; 8 9 private String appId;//商户appId 10 private String paySign;//签名 11 private String prepayId;//预付id 12 private String nonceStr;//一次性字符串 13 private String timeStamp;//时间戳 14 15 //省略getter、setter方法 16 }
2、签名
1 /** 2 * 获取页面上weixin支付JS所需签名 3 * @param wxPay 4 * @return sgin 5 */ 6 public static String getJspaySgin(WxPay wxPay){ 7 String args = "appId="+wxPay.getAppId() 8 +"&nonceStr="+wxPay.getNonceStr() 9 +"&package="+wxPay.getPrepayId() 10 +"&signType=MD5" 11 +"&timeStamp="+wxPay.getTimeStamp() 12 +"&key="+wxConfig.getWxKey(); 13 14 String sgin=MD5.sign(args); 15 return sgin; 16 }
3、创建页面调用微信JS所需的实体
1 /** 2 * 获取页面上weixin支付JS所需的参数 3 * @param map 4 * @return 5 */ 6 public WxPay getWxPayInfo(String prepay_id) { 7 8 String nonce = new StringWidthWeightRandom().getNextString(32); 9 String timeStamp = UtilDate.getDateLong(); 10 String newPrepay_id = "prepay_id="+prepay_id; 11 WeixinConfig wxConfig = WeixinConfig.getInstance(); 12 WxPay wxPay = new WxPay(); 13 wxPay.setAppId(wxConfig.getAppid()); 14 wxPay.setNonceStr(nonce); 15 wxPay.setPrepayId(newPrepay_id); 16 wxPay.setTimeStamp(timeStamp); 17 18 //再算签名 19 String paySign = SginUtil.getJspaySgin(wxPay); 20 wxPay.setPaySign(paySign); 21 22 return wxPay; 23 }
商品系统支付的api
/** * 商户系统支付api * @return */ public String weixinpay(){ //商户订单 Trade trade = tradeService.findById(id); //授权信息 Oauth oauth = oauthService.findByUserId(userId); //构建统一下单参数 UniformOrderParams params = PaymentWeixinHelper.buildUniformOrderParams(trade,null,oauth,request); //统一下单,获得预付id String prePayId = weixinPayService.uniformOrder(params); //根据预付id,获得网页支付js所需的参数(封装为wxPay) WxPay wxpay = weixinPayService.getWxPayInfo(prePayId); request.setAttribute("context", wxpay); return "weixinpay"; }
4、网页调用微信js支付
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML> <html> <head> <title>微信支付</title> </head> <body onload="start()"> 正在打开微信支付 <script> var wxPayConfig = { appId:"${context.appId}", timeStamp:"${context.timeStamp}", nonceStr:"${context.nonceStr}", package:"${context.prepayId}", signType:"MD5", paySign:"${context.paySign}", }; function start(){ callpay(); } function onBridgeReady(){ //noinspection TypeScriptUnresolvedVariable WeixinJSBridge.invoke( 'getBrandWCPayRequest',wxPayConfig, function(res){ //alert(JSON.stringify(res)); if(res.err_msg == "get_brand_wcpay_request:ok" ) { window.location.href="${clientPath}#/page/trade/detail/"+tradeId; }else{ window.location.href="${clientPath}#/page/trade/detail/"+tradeId; } // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 }); } function callpay(){ var doc =document; //noinspection TypeScriptUnresolvedVariable if (typeof WeixinJSBridge == "undefined"){ if( doc.addEventListener ){ doc.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); }else if (doc.attachEvent){ doc.attachEvent('WeixinJSBridgeReady', onBridgeReady); doc.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } }else{ onBridgeReady(); } } </script> </body> </html>
三、处理支付后,微信的回调结果
支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。
1、商户同步后返回给微信的参数
1 public class ReturnNotify { 2 /** 3 * 返回状态码:SUCCESS/FAIL,SUCCESS表示商户接收通知成功并校验成功 4 */ 5 private String return_code; 6 /** 7 * 返回信息返回信息,如非空,为错误原因:签名失败、参数格式校验错误 8 */ 9 private String return_msg; 10 11 public String getReturn_code() { 12 return return_code; 13 } 14 public void setReturn_code(String return_code) { 15 this.return_code = return_code; 16 } 17 public String getReturn_msg() { 18 return return_msg; 19 } 20 public void setReturn_msg(String return_msg) { 21 this.return_msg = return_msg; 22 } 23 24 }
2、处理支付结果。微信要求:商户系统必须能够正确处理重复的通知。商户系统对于支付结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”,造成资金损失。
签名验证:
/** * 将一个 Map<Object,Object> 对象转化为一个 SortedMap<Object,Object> */ public static SortedMap<Object,Object> convertMap(Map<Object,Object> map) { SortedMap<Object,Object> returnMap = new TreeMap<Object,Object>(); for(Map.Entry<Object, Object> entry : map.entrySet()){ returnMap.put(entry.getKey(), entry.getValue()); } return returnMap; } private boolean checkSign(Map<Object,Object> map){ String sign = (String)map.get("sign"); map.remove("sign"); SortedMap<Object, Object> paramMap = CommonUtil.convertMap(map); String tempSgin = SginUtil.createSgin(paramMap); if(!sign.equals(tempSgin)){ return false; }else{ return true; } }
处理支付结果:
public synchronized ReturnNotify dealWxPayNotify(FlowContext context) { ReturnNotify returnNotify = new ReturnNotify(); //校验签名 if(!checkSign(context.getParams())){ returnNotify.setReturn_code("FAIL"); returnNotify.setReturn_msg("订单支付支付失败"); return returnNotify; } //订单状态 String return_code = context.getString("return_code"); String result_code = context.getString("result_code"); String out_trade_no = context.getString("out_trade_no");//即为系统中的订单号tradeNo int total_fee = context.getInt("total_fee"); //处理重复通知 if("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)){ Trade trade = tradeHelper.findByNo(out_trade_no);//本地的订单号 if(null==trade){ //订单不存在 //返回失败 returnNotify.setReturn_code("FAIL"); returnNotify.setReturn_msg("订单不存在"); return returnNotify; }else{ if(trade.getStatus() == TradeStatusEnum.WAIT_BUYER_PAY || trade.getPayStatus() == PaymentStatusEnum.WAIT_PAY || trade.getPayStatus() == PaymentStatusEnum.WAIT_HANDLER){ int needPay = trade.getPayAmount().multiply(BigDecimal.valueOf(100)).intValue(); if(total_fee==needPay) { //省略处理订单系统订单的支付状态代码 returnNotify.setReturn_code("SUCCESS"); returnNotify.setReturn_msg("OK"); return returnNotify; }else{ //返回失败,扣款金额不对 returnNotify.setReturn_code("FAIL"); //支付金额不一致 String msg = "支付金额与本地订单验证不一致:本地="+trade.getPayAmount()+",微信="+total_fee; returnNotify.setReturn_msg(msg); return returnNotify; } }else{ //订单已经处理,直接返回成功 returnNotify.setReturn_code("SUCCESS"); returnNotify.setReturn_msg("OK"); return returnNotify; } } } else{ //失败 returnNotify.setReturn_code("FAIL"); returnNotify.setReturn_msg("订单支付支付失败"); return returnNotify; } }
3、商户处理微信回调结果
1 //获的回调结果 2 Map<Object, Object> map = CommonUtil.parseXml(request); 3 //处理回调结果 4 ReturnNotify returnNotify = PaymentWeixinHelper.dealWxPayNotify(new FlowContext(map)); 5 6 try { 7 //将商户的处理结果写会给微信 8 response.getWriter().write(CommonUtil.beanToXML(returnNotify)); 9 } catch (IOException e) { 10 e.printStackTrace(); 11 }