• 微信公众平台开发(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         }
  • 相关阅读:
    错误:Authentication with old password no longer supported, use 4.1 style passwords.
    百度有钱 邀请码
    [datatables杂记] sAjaxSource 数据源 Search 后 fnInitComplete 不执行。
    uploadify onComplete 不执行?
    VS 2013 简体中文 专业版 下载地址。
    C# 导出图片到Word (通过XML实现)
    使用OutLook发送一封带附件的邮件
    WebBrower使用 Http 代理访问网页
    .net SMTP 发送邮件
    C# 汉字转拼音 方法(汉字的发音不过400多种(不算声调))
  • 原文地址:https://www.cnblogs.com/zhangxianming/p/5767923.html
Copyright © 2020-2023  润新知