支付业务流程时序图:
一、统一下单
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 οnlοad="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 }