微信公众平台开发(2) 微信公众号支付

支付业务流程时序图:

一、统一下单

  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         }
原文地址:https://www.cnblogs.com/zhangxianming/p/5767923.html