前后台分离微信JSAPI支付开发详细流程(普通商户)

1.首先需要进行微信授权(公众号支付需要获取用户openid)

  • 在公众号下设置微信授权域名(需要备案,不加http://或https://)如:www.xxxx.com或www.xxxx.com/wechat
  • 下载MP_verify_dTQWB2ARdTJ1v6IF.txt此文件并放置到授权之后的服务器下,
    http://www.xxxx.com/MP_verify_dTQWB2ARdTJ1v6IF.txt 
    或http://www.xxxx.com/wechat/MP_verify_dTQWB2ARdTJ1v6IF.txt可以直接访问。

前后台分离微信JSAPI支付开发详细流程(普通商户)_第1张图片

  • js获取code    
    redirect_uri = 这里设置了前台地址(也可以是后台地址)
    前台发起授权,用户点击同意授权后,微信会携带code回调你设置的页面,然后根据获取code请求后台接口(code只能使用一次且5分钟内有效)
         window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='+ WX_APPID +'&redirect_uri='+ redirect_uri +'&response_type=code&scope=snsapi_userinfo#wechat_redirect';
    • 后台根据code获取acc_token和openId 和用户信息
      public WechatUserVo getAccessToken(String authCode) {
          // snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo
          //(弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息
          // code说明 : code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
          // 公众号可通过下述接口来获取网页授权access_token。
          // 如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。
          // 获取code后,请求以下链接获取access_token
          try {
              JSONObject json_token = JSONObject.parseObject(HttpClientUtil.get(WeChatContants.OAUTH2_TOKEN_URL + "&code=" + authCode + "&grant_type=authorization_code"));
              WechatMembershipVO wechatMembershipVO = new WechatMembershipVO();
              String accessToken = json_token.get("access_token").toString();
              String refreshToken = json_token.get("refresh_token").toString();
              String getOpenid = json_token.get("openid").toString();
      ​​​
          }catch (Exception e){
              throw new BusinessException("使用code换取access_token出错");
          }
      }
      
      //获取微信用户信息
      try {
          // 拉取用户信息(需scope为 snsapi_userinfo)
          // 如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。
          JSONObject json_user = JSONObject.parseObject(HttpClientUtil.get("https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN"));
          if(null == json_user){
              throw new BusinessException("获取微信用户信息失败");
          }
          logger.info("微信用户信息:" + json_user.toJSONString());
          String openid = json_user.get("openid").toString();
          String nickname = json_user.get("nickname").toString();
          String sex = json_user.get("sex").toString();
          String province = json_user.get("province").toString();
          String city = json_user.get("city").toString();
          String country = json_user.get("country").toString();
          String headimgurl = json_user.get("headimgurl").toString();
           //数据库存取
      }catch (Exception e){
          throw new BusinessException("获取微信用户信息失败");
      }

2.申请普通类型商户

前后台分离微信JSAPI支付开发详细流程(普通商户)_第2张图片

3.配置支付相关配置

   支付需要的参数:

mch_appid: 公众号appid
mch_id: 商户号
mch_key: 商户秘钥
app_secret: 公众号开发者秘钥
notify_url: http://xxx/pay/notify(自定义微信支付异步回调地址)
prepay_url: https://api.mch.weixin.qq.com/pay/unifiedorder(获取预支付id)

下面几张图是需要的配置或获取参数位置.

前后台分离微信JSAPI支付开发详细流程(普通商户)_第3张图片

 前后台分离微信JSAPI支付开发详细流程(普通商户)_第4张图片

前后台分离微信JSAPI支付开发详细流程(普通商户)_第5张图片

4.获取参数和配置好之后就可以进行开发了

  • 前端发起请求获取预支付订单参数
  • 获取成功后,发起微信支付请求,同步获取支付状态,该状态不作为最终支付结果。https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6
  • 微信发起预支付参数中设置异步回调

//获取预支付id

public ResponseEntity createPrepayOrder(String orderCode,Long userId,String userName,HttpServletRequest request) {
   try{
       OrderVO order = orderService.selectOrderByOrderCode(orderCode);
        //验证订单相关信息
       if(null == order){
           return ResponseEntity.failed("订单记录不存在");
       }
       if(null != order.getUserId()){
           if(userId.longValue() != order.getUserId().longValue()){
               return ResponseEntity.failed("该订单所属人不是你,请勿支付");
           }
       }else{
           return ResponseEntity.failed("该订单所属人不是你,请勿支付");
       }
       if(OrderStauts.ORDER_STAUTS2.getIndex() == order.getOrderStatus()){
           return ResponseEntity.failed("该订单已经支付,请勿支付");
       }
       if(OrderStauts.ORDER_STAUTS3.getIndex() == order.getOrderStatus()){
           return ResponseEntity.failed("该订单已经完成,请勿支付");
       }
       if(OrderStauts.ORDER_STAUTS4.getIndex() == order.getOrderStatus()){
           return ResponseEntity.failed("该订单已经取消,请勿支付");
       }
       BigDecimal orderAmount = order.getOrderAmount();
       if(-1 == orderAmount.compareTo(BigDecimal.ZERO) || 0 == orderAmount.compareTo(BigDecimal.ZERO) ){
           return ResponseEntity.failed("订单金额不能小于0");
       }
       //验证用户信息
       Membership membership = membershipService.selectById(userId);
       if(null == membership){
           return ResponseEntity.failed("用户不存在");
       }
       if(StringUtils.isBlank(membership.getOpenid())){
           return ResponseEntity.failed("您还没有进行微信授权,请授权后再进行操作");
       }
       //支付金额 单位:分
       Integer totalFee = orderAmount.multiply(new BigDecimal(100)).intValue();
       String body = order.getParameters();
       // 生成请求预支付ID XML
       String prepayXML = WeiXinUtil.buildOrderPrepayXML(
               mch_appid,
               mch_id,
               totalFee,
               body,
               orderCode,
               notify_url,
               TRADE_TYPE,
               getIp(request),
               membership.getOpenid(),
               mch_key
       );
       // 请求微信预支付ID
       String prepayId;
       try {
           prepayId = getWeChatPrepayId(prepayXML);
       } catch (IOException e) {
           return  ResponseEntity.failed("获取预支付订单失败");
       }
       // 为前端生成支付XML
       String orderPayParams = JsonUtil.jsonify(WeiXinUtil.buildOrdePay(mch_appid, mch_key, prepayId));

        return ResponseEntity.ok(orderPayParams);
   }catch (Exception e){

   }
    return ResponseEntity.failed("支付失败");
}
private String getWeChatPrepayId(String prepayXML) throws IOException {
        try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(prepay_url);
            httpPost.setEntity(new StringEntity(prepayXML, "UTF-8"));
            ResponseHandler responseHandler = response -> {
                int status = response.getStatusLine().getStatusCode();
                if (status >= 200 && status < 300) {
                    HttpEntity entity = response.getEntity();
                    return entity != null ? EntityUtils.toString(entity,"UTF-8") : null;
                } else {
                    throw new ClientProtocolException("Unexpected response status: " + status);
                }
            };
            String responseBody = httpclient.execute(httpPost, responseHandler);
            if(responseBody == null) {
                throw new RuntimeException("微信回传的值不正确");
            }
            logger.info(responseBody);
            Map responseMap = new HashMap<>();
            try{
                responseMap  = WeiXinUtil.xmlToMap(responseBody);
//              WeiXinUtil.checkSign(responseMap, mch_key);
            }catch (Exception e){

            }
            if (!StringUtils.equals(responseMap.get("appid"),mch_appid)) {
                throw new RuntimeException("微信APP_ID回传不正确");
            }
            if (!StringUtils.equals(responseMap.get("mch_id"), mch_id)) {
                throw new RuntimeException("微信MCH_ID回传不正确");
            }
            return responseMap.get("prepay_id");
        }
    }
//微信支付工具类
package com.uitech.official.sales.util;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;

import com.uitech.common.core.util.digest.MD5;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.UnsupportedEncodingException;

public class WeiXinUtil {

    public static String getSign(Map map, String mchKey) {
        List keys = new ArrayList<>(map.keySet());
        // key排序
        Collections.sort(keys);
        StringBuilder info = new StringBuilder();
        for (String key : keys) {
            if (!"sign".equals(key)  && !"key".equals(key)) {
                String value = map.get(key);
                if (!StringUtils.isBlank(value)) {
                    info.append(buildKeyValue(key, value));
                    info.append("&");
                }
            }
        }
        info.append(buildKeyValue("key", mchKey));
        try {
            return MD5.getMessageDigest(info.toString().getBytes("UTF-8")).toUpperCase();
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    private static String buildKeyValue(String key, String value) {
        return key + "=" + value;
    }

    /**
     * 封装预支付xml
     * @param app_id
     * @param mch_id
     * @param totalFee
     * @param body
     * @param out_trade_no
     * @param notify_url
     * @param trade_type
     * @param spbill_create_ip
     * @return
     */
    public static String buildOrderPrepayXML(String app_id,String mch_id,
                                             Integer totalFee, String body, String out_trade_no,
                                             String notify_url,String trade_type,String spbill_create_ip,String open_id,String mch_key) {
        SortedMap packageParams = new TreeMap<>();
        String nonce_str = getNonceStr();
        packageParams.put("appid", app_id);
        packageParams.put("mch_id", mch_id);
        packageParams.put("nonce_str", nonce_str);
        packageParams.put("body", body);
        packageParams.put("out_trade_no", out_trade_no);
        packageParams.put("total_fee", totalFee.toString());
        packageParams.put("spbill_create_ip", spbill_create_ip);
        packageParams.put("notify_url", notify_url);
        packageParams.put("trade_type", trade_type);
        packageParams.put("openid", open_id);
        String sign = getSign(packageParams,mch_key);
        packageParams.put("sign",sign);
        return getRequestXml(packageParams);
    }

    /**
     * 获取随机字符串
     *
     * @return
     */
    public static String getNonceStr() {
        // 随机数
        String currTime = getCurrTime();
        // 8位日期
        String strTime = currTime.substring(8, currTime.length());
        // 四位随机数
        String strRandom = buildRandom(4) + "";
        // 10位序列号,可以自行调整。
        return strTime + strRandom;
    }

    public static Map buildOrdePay(String app_id, String mch_key,String prepayId){
        Map payMap = new HashMap<>();
        payMap.put("appId",app_id);
        payMap.put("timeStamp", String.valueOf(System.currentTimeMillis()/1000));
        payMap.put("nonceStr", getNonceStr());
        payMap.put("package","prepay_id="+prepayId);
        payMap.put("signType","MD5");
        payMap.put("paySign",getSign(payMap,mch_key));
        return payMap;
    }

    /**
     * Map转xml数据
     */
    public static String GetMapToXML(Map param){
        StringBuffer sb = new StringBuffer();
        sb.append("");
        for (Map.Entry entry : param.entrySet()) {
            sb.append("<"+ entry.getKey() +">");
            sb.append(entry.getValue());
            sb.append("");
        }
        sb.append("");
        return sb.toString();
    }

    public static Map xmlToMap(String strXML) throws Exception {
        try {
            Map data = new HashMap();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
            }
            return data;
        } catch (Exception ex) {
            throw ex;
        }
    }

    //请求支付xml组装
    public static String getRequestXml(SortedMap parameters){
        StringBuffer sb = new StringBuffer();
        sb.append("");
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            String key = (String)entry.getKey();
            String value = (String)entry.getValue();
            if ("attach".equalsIgnoreCase(key)||"body".equalsIgnoreCase(key)||"sign".equalsIgnoreCase(key)) {
                sb.append("<"+key+">"+"");
            }else {
                sb.append("<"+key+">"+value+"");
            }
        }
        sb.append("");
        return sb.toString();
    }

    /**
     * 验证回调签名
     * @return
     */
    public static boolean isTenpaySign(Map map,String mch_key) {
        String characterEncoding="utf-8";
        String charset = "utf-8";
        String signFromAPIResponse = map.get("sign");
        if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
            System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!");
            return false;
        }
        System.out.println("服务器回包里面的签名是:" + signFromAPIResponse);
        //过滤空 设置 TreeMap
        SortedMap packageParams = new TreeMap();

        for (String parameter : map.keySet()) {
            String parameterValue = map.get(parameter);
            String v = "";
            if (null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }

        StringBuffer sb = new StringBuffer();
        Set es = packageParams.entrySet();
        Iterator it = es.iterator();

        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            String k = (String)entry.getKey();
            String v = (String)entry.getValue();
            if(!"sign".equals(k) && null != v && !"".equals(v)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + mch_key);

        //将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
        //算出签名
        String resultSign = "";
        String tobesign = sb.toString();
        if (null == charset || "".equals(charset)) {
            resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
        }else{
            try{
                resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
            }catch (Exception e) {
                resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
            }
        }
        String tenpaySign = ((String)packageParams.get("sign")).toUpperCase();
        return tenpaySign.equals(resultSign);
    }

    /**
     * 获取当前时间 yyyyMMddHHmmss
     * @return String
     */
    public static String getCurrTime() {
        Date now = new Date();
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String s = outFormat.format(now);
        return s;
    }

    /**
     * 取出一个指定长度大小的随机正整数.
     *
     * @param length
     *            int 设定所取出随机数的长度。length小于11
     * @return int 返回生成的随机数。
     */
    public static int buildRandom(int length) {
        int num = 1;
        double random = Math.random();
        if (random < 0.1) {
            random = random + 0.1;
        }
        for (int i = 0; i < length; i++) {
            num = num * 10;
        }
        return (int) ((random * num));
    }

}
//MD5工具类
package com.uitech.official.sales.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Util {

   /**
    * 生成MD5加密密文
    * 
    * @param text
    *            明文
    * @return String 密文
    */
   public static String md5(String text) {
      MessageDigest msgDigest = null;
      try {
         msgDigest = MessageDigest.getInstance("MD5");
      } catch (NoSuchAlgorithmException e) {
         throw new IllegalStateException(
               "System doesn't support MD5 algorithm.");
      }
      msgDigest.update(text.getBytes());
      byte[] bytes = msgDigest.digest();

      byte tb;
      char low;
      char high;
      char tmpChar;
      String md5Str = new String();
      for (int i = 0; i < bytes.length; i++) {
         tb = bytes[i];
         tmpChar = (char) ((tb >>> 4) & 0x000f);
         if (tmpChar >= 10) {
            high = (char) (('a' + tmpChar) - 10);
         } else {
            high = (char) ('0' + tmpChar);
         }
         md5Str += high;
         tmpChar = (char) (tb & 0x000f);
         if (tmpChar >= 10) {
            low = (char) (('a' + tmpChar) - 10);
         } else {
            low = (char) ('0' + tmpChar);
         }
         md5Str += low;
      }

      return md5Str;
   }

   public static String MD5Encode(String origin, String charsetname) {
      String resultString = null;
      try {
         resultString = new String(origin);
         MessageDigest md = MessageDigest.getInstance("MD5");
         if (charsetname == null || "".equals(charsetname))
            resultString = byteArrayToHexString(md.digest(resultString
                  .getBytes()));
         else
            resultString = byteArrayToHexString(md.digest(resultString
                  .getBytes(charsetname)));
      } catch (Exception exception) {
      }
      return resultString;
   }

   private static String byteArrayToHexString(byte b[]) {
      StringBuffer resultSb = new StringBuffer();
      for (int i = 0; i < b.length; i++)
         resultSb.append(byteToHexString(b[i]));

      return resultSb.toString();
   }

   private static String byteToHexString(byte b) {
      int n = b;
      if (n < 0)
         n += 256;
      int d1 = n / 16;
      int d2 = n % 16;
      return hexDigits[d1] + hexDigits[d2];
   }

   private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
         "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

   public static void main(String[] args) {
      System.out.println(md5("111111"));
   }
}
/**
 * 微信支付异步回调
 * @Param
 */
@RequestMapping(value = "/notify",method = RequestMethod.POST)
@ApiOperation(value = "移动端 - 支付回调",tags = {"订单支付"})
public void notify(HttpServletRequest request, HttpServletResponse response){
    logger.info("移动端 -- 支付异步回调 pay/notify,当前时间:{}", dateFormat.format(new Date()));
    payService.notifyUrl(request,response);
}
//回调方法
public void notifyUrl(HttpServletRequest request, HttpServletResponse response) {
    String inputLine;
    String notityXml = "";
    String resXml = "";
    String errMessage = "";
    //解析微信给返回的数据
    try {
        while ((inputLine = request.getReader().readLine()) != null) {
            notityXml += inputLine;
        }
        request.getReader().close();
    } catch (Exception e) {
        resXml = returnXML("FAIL","xml解析失败");
    }
    try{
        errMessage = notityXml;
        Map map = WeiXinUtil.xmlToMap(notityXml);
        String out_trade_no = (String) map.get("out_trade_no");// 获取商户订单号
        String sign = (String) map.get("sign");// 获取签名
        String time_end = (String) map.get("time_end");//支付完成时间
        String transaction_id = (String) map.get("transaction_id");//微信支付订单号
        String result_code = (String) map.get("result_code");// 业务结果
        String return_code = (String) map.get("return_code");// SUCCESS/FAIL
        //验证签名
        String validSign = WeiXinUtil.getSign(map,mch_key);
        if(!sign.equals(validSign)){
            errMessage = "微信回调签名验证失败OrderCode:"+out_trade_no+" "+"xml:"+notityXml;
            resXml = returnXML("FAIL","签名验证失败");
        }else {
            if ("SUCCESS".equals(result_code) && "SUCCESS".equals(return_code)){
                //处理订单
                OrderVO order = orderService.selectOrderByOrderCode(out_trade_no);
                if(null == order){
                    resXml =  returnXML("FAIL","订单不存在");
                }else{
                    //支付成功 修改订单支付状态
                    
                    if(updateStatus){
                        resXml =  returnXML("SUCCESS","OK");
                    }else{
                        resXml = returnXML("FAIL","修改订单状态失败");
                    }
                }
            }else{
                resXml = returnXML("FAIL","支付回调失败");
            }
        }
    }catch (Exception e){
        resXml = returnXML("FAIL","回调失败");
    }
    try {
        BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
        out.write(resXml.getBytes());
        out.flush();
        out.close();
    } catch (Exception e) {

    }
}
private String returnXML(String return_code,String return_msg) {
    return  "" +
            "" +
            "" +
            "" +
            "" +
            "" +
            "";
}

你可能感兴趣的:(微信开发)