首页 > 技术文章 > JAVA项目实战 -微信支付开发

zhucj-java 2019-11-28 14:35 原文

微信支付丰富着人们日常生活,下面我们来依据微信提供的API文档尝试着学习编写微信支付工具类,避免重复造轮子。

 1.JSAPI: 

         JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:

  1. ◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
  2. ◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
  3. ◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付

 2.Native支付

      Native支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。

 3.H5支付

      H5支付主要是在手机、ipad等移动设备中通过浏览器来唤起微信支付的支付产品。

 4.小程序支付

     小程序支付是专门被定义使用在小程序中的支付产品。目前在小程序中能且只能使用小程序支付的方式来唤起微信支付。

 5.App支付

     APP支付又称移动端支付,是商户通过在移动端应用APP中集成开放SDK调起微信支付模块完成支付的模式。

注:微信支付功能开发 :1.必须拥有一个微信公众号;2.具备开通微信支付功能;3登录微信商户平台(https://pay.weixin.qq.com/index.php/core/home/login),设置商户号,支付秘钥和下载证书(退款接口需要携带证书请求)。

===========================================================================

                                       Utils工具类代码

================================================================

package com.sf.detectprocess.util.pay;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.sf.detectprocess.controller.param.*;
import com.sf.detectprocess.core.constant.SystemConstants;
import com.sf.detectprocess.core.constant.WXPayConstants;
import com.sf.detectprocess.core.exception.WeChatPayException;
import com.sf.detectprocess.entity.Order;
import com.sf.detectprocess.entity.OrderPay;
import com.sf.detectprocess.mapper.OrderMapper;
import com.sf.detectprocess.mapper.OrderPayMapper;
import com.sf.detectprocess.service.LimsService;
import com.sf.detectprocess.util.FuntionUtils;
import com.sf.detectprocess.util.R;
import com.sun.org.apache.regexp.internal.RE;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;

/**
 * @description: 微信支付接口工具类
 * @author: zhucj  
 * @date: 2019-09-25 10:37
 */
@Slf4j
@Component
public class WeChatPayUtils {

    /**
     * 公众号或小程序或app端 appid
     */
    @Value("${wx.pay.appId}")
    private String appId;
    /**
     * 公众号或小程序 key
     */
    @Value("${wx.pay.key}")
    private String key;
    /**
     * 商户号 (微信支付商户号)
     */
    @Value("${wx.pay.mchNo}")
    private String mchNo;
    /**
     * 通知地址
     */
    @Value("${wx.pay.notifyUrl}")
    private String notifyUrl;
    /**
     * 安全证书密码(默认为商户号)
     */
    @Value("${wx.pay.lcePassWord}")
    private String lcePassWord;


    @ApiOperation(value = "请求微信统一支付接口" )
    @ApiImplicitParams({
            @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType= "String"),
            @ApiImplicitParam(name = "totalFee", required = true, value = "标价金额", dataType = "String"),
            @ApiImplicitParam(name = "body", required = true, value = "商品描述", dataType = "String"),
            @ApiImplicitParam(name = "tradeType", required = true, value = "交易类型 JSAPI:JSAPI支付(或小程序支付)," +
                    "NATIVE:Native支付, APP:app支付, MWEB:H5支付", dataType = "String"),
            @ApiImplicitParam(name = "openId", required = false, value = "用户openId(JSAPI/小程序/必传)",dataType = "String"),
            @ApiImplicitParam(name = "spbillCreateIp", required = false, value = "APP和网页支付提交用户端ip,Native支付不需要传)", dataType = "String"),
            @ApiImplicitParam(name = "productId", required = false, value = "二维码中包含商品Id(Native支付必传,其他支付不传)", dataType = "String")

    })
    public R unifiedOrder(UnfiedOrderParam map) throws WeChatPayException {

        /* 签名参数的map对象 */
        SortedMap<String, String> packageParams = new TreeMap<String, String>();
        // 请求预下单对象
        UnifiedOrderRequest unifiedOrderRequest = new UnifiedOrderRequest();
        //=>1.appId 商户Id
        unifiedOrderRequest.setAppid(appId);

        //TODO:判断当前微信支付类型 APP端/JSAPI/H5/Native/MWEB
        if (Objects.equals(WXPayConstants.APP,map.getTradeType())){

            //TODO:App端支付 必传用户终端IP 和单独的appId(应用Id)
            if (isNull(map.getSpbillCreateIp())){
                throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE, WXPayConstants.SPBILL_CREATE_APP_ERROR);
            }
            // => 2.终端Ip app支付由前端传
            unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp());

        } else if (Objects.equals(WXPayConstants.JSAPI,map.getTradeType())) {

            //TODO:JSAPI支付 必传openId和用户终端IP
            if (isNull(map.getOpenId())) {
                throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.OPENID_JSAPI_ERROR);
            }
            if (isNull(map.getSpbillCreateIp())){
                throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.SPBILL_CREATE_JSAPI_ERROR);
            }
            unifiedOrderRequest.setOpenid(map.getOpenId());
            // => 2.终端Ip 小程序或公众号支付由前端传
            unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp());
            //仅 JSAPI支付才传openId
            packageParams.put("openid", unifiedOrderRequest.getOpenid());

        }else if (Objects.equals(WXPayConstants.NATIVE,map.getTradeType())){

            //TODO:Native扫码支付 必传prductId(商品Id)
            if (isNull(map.getProductId())){
                throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.PRODUCT_NATIVE_ERROR);
            }
            unifiedOrderRequest.setProduct_id(map.getProductId());
            packageParams.put("product_id", unifiedOrderRequest.getProduct_id());
            //TODO:Native 本地获取终端Ip
            String localIp = WXPayUtil.getLocalIp();
            //=> 2.终端Ip
            unifiedOrderRequest.setSpbill_create_ip(localIp);

        }else if (Objects.equals(WXPayConstants.MWEB,map.getTradeType())){

            //TODO:H5 前端必传Ip
            if (isNull(map.getSpbillCreateIp())){
                throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,WXPayConstants.SPBILL_CREATE_HWEB_ERROR);
            }
            // => 2.终端Ip H5支付由前端传
            unifiedOrderRequest.setSpbill_create_ip(map.getSpbillCreateIp());

        }else {
            throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,"交易类型tradeType="+map.getTradeType()+"暂时不支持,请核对参数是否正确!");
        }
        // => 3.商品描述
        unifiedOrderRequest.setBody(map.getBody());
        // => 4.商户号
        unifiedOrderRequest.setMchId(mchNo);
        // 生成随机字符串 => 5.随机字符串
        unifiedOrderRequest.setNonceStr(WXPayUtil.generateNonceStr());
        // => 6.异步通知地址
        unifiedOrderRequest.setNotify_url(notifyUrl);
        // => 7.商户订单号
        unifiedOrderRequest.setOut_trade_no(map.getOutTradeNo());
        // => 8.支付金额 需要扩大100倍(1代表支付时是0.01)
        unifiedOrderRequest.setTotal_fee(WXPayUtil.changeY2F(map.getTotalFee()));
        // => 9.交易类型
        unifiedOrderRequest.setTrade_type(map.getTradeType());
        //TODO: 封装签名map
        packageParams.put("appid", unifiedOrderRequest.getAppid());
        packageParams.put("body", unifiedOrderRequest.getBody());
        packageParams.put("mch_id", unifiedOrderRequest.getMchId());
        packageParams.put("nonce_str", unifiedOrderRequest.getNonceStr());
        packageParams.put("notify_url", unifiedOrderRequest.getNotify_url());
        packageParams.put("out_trade_no", unifiedOrderRequest.getOut_trade_no());
        packageParams.put("spbill_create_ip", unifiedOrderRequest.getSpbill_create_ip());
        packageParams.put("total_fee", unifiedOrderRequest.getTotal_fee());
        packageParams.put("trade_type", unifiedOrderRequest.getTrade_type());
        try {
            String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5);
            // =>10.签名
            unifiedOrderRequest.setSign(signature);
            packageParams.put("sign",signature);
        } catch (Exception e) {
            log.error("签名异常:{}",e.getMessage());
            throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR);
        }

        try {
            //调统一下单接口
            String s = WXPayUtil.doPost(1, WXPayUtil.mapToXml(packageParams), false, null);
            UnifiedOrderResponse resposeStr = JSON.parseObject(s, UnifiedOrderResponse.class);
            //预下单后 返回前端封装map
            TreeMap respMap = new TreeMap();
            if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){
                if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){
                  
                    //TODO: 依据交易类型返回前端参数
                    switch (resposeStr.getTrade_type()) {
                        case WXPayConstants.NATIVE: {
                            //Native扫码直接返回二维码链接
                            respMap.put("codeUrl", resposeStr.getCode_url());
                            break;
                        }
                        case WXPayConstants.APP : {
                            //App端 需要商户号进行签名 返回
                            respMap.put("appid", resposeStr.getAppid());
                            respMap.put("partnerid", resposeStr.getMch_id());
                            respMap.put("prepayid", resposeStr.getPrepay_id());
                            respMap.put("timeStamp",String.valueOf(WXPayUtil.getCurrentTimestamp()));
                            respMap.put("nonce_str", WXPayUtil.generateNonceStr());
                            respMap.put("package","Sign=WXPay");
                            respMap.put("sign", WXPayUtil.generateSignature(respMap,key,WXPayConstants.SignType.MD5));
                            break;
                        }
                        case WXPayConstants.JSAPI: {
                            respMap.put("appId", resposeStr.getAppid());
                            respMap.put("timeStamp",String.valueOf(WXPayUtil.getCurrentTimestamp()));
                            respMap.put("nonceStr",resposeStr.getNonce_str());
                            respMap.put("package","prepay_id="+resposeStr.getPrepay_id());
                            respMap.put("signType",WXPayConstants.MD5);
                            respMap.put("paySign",WXPayUtil.generateSignature(respMap,key,WXPayConstants.SignType.MD5));
                            respMap.put("prepayid",resposeStr.getPrepay_id());
                            break;
                        }
                        case WXPayConstants.MWEB : {
                            // h5支付链接地址
                            respMap.put("payUrl", resposeStr.getMweb_url());
                            break;
                        }
                        default:
                            break;
                    }
                    return R.ok(respMap);

                }else {
                    return R.error(resposeStr.getErr_code_des());
                }


            }else {
                return R.error(resposeStr.getReturn_msg());

            }

        } catch (Exception e) {
            log.error("预订单异常:{}"+e.getMessage());
            throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"预订单异常");
        }
    }


    @ApiOperation(value = "请求微信订单查询接口" )
    @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType= "String")
    public R orderQuery(String outTradeNo) throws WeChatPayException {
        /* 签名map */
        SortedMap<String, String> packageParams = new TreeMap<String, String>();
        packageParams.put("appid",appId);
        packageParams.put("mch_id",mchNo);
        packageParams.put("out_trade_no",outTradeNo);
        packageParams.put("nonce_str",WXPayUtil.generateNonceStr());
        //获得签名
        try {
            String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5);
            packageParams.put("sign",signature);
        } catch (Exception e) {
            log.error("签名异常:{}",e.getMessage());
            throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR);
        }

        try {
            //调微信查询订单服务接口
            String s = WXPayUtil.doPost(2, WXPayUtil.mapToXml(packageParams), false, null);
            OrderQueryResponse resposeStr = JSON.parseObject(s, OrderQueryResponse.class);
            //查询返回结果封装map
            Map respMap = new HashMap();
            if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){
                if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){
                    respMap.put("statusDesc",resposeStr.getTrade_state_desc());
                    respMap.put("data",resposeStr);
                    /**
                     * 自己设置的status => 1:已支付 ,2:转入退款 3:未支付 4:已关闭 5:已撤销 6:支付失败
                     * */
                    if (Objects.equals(WXPayConstants.TradeState.SUCCESS.getName(),resposeStr.getTrade_state())){
                        respMap.put("status",1);
                    }else if (Objects.equals(WXPayConstants.TradeState.REFUND.getName(),resposeStr.getTrade_state())){
                        respMap.put("status",2);
                    }else if (Objects.equals(WXPayConstants.TradeState.NOTPAY.getName(),resposeStr.getTrade_state())){
                        respMap.put("status",3);
                    }else if (Objects.equals(WXPayConstants.TradeState.CLOSED.getName(),resposeStr.getTrade_state())){
                        respMap.put("status",4);
                    }else if (Objects.equals(WXPayConstants.TradeState.REVOKED.getName(),resposeStr.getTrade_state())){
                        respMap.put("status",5);
                    }else if (Objects.equals(WXPayConstants.TradeState.PAYERROR.getName(),resposeStr.getTrade_state())){
                        respMap.put("status",6);
                    }

                    return R.ok(respMap,"查询成功");
                }else {
                    return R.error(resposeStr.getErr_code_des());
                }
            }else {
                return R.error(resposeStr.getReturn_msg());
            }


        } catch (Exception e) {
            log.error("查询订单异常:{}"+e.getMessage());
            throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"查询订单异常");
        }

    }


    @ApiOperation(value = "请求微信关闭订单接口" )
    @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号(32个字符内)", dataType = "String")
    public R closeOrder(String outTradeNo) throws WeChatPayException {
        /* 签名map */
        SortedMap<String, String> packageParams = new TreeMap<String, String>();
        packageParams.put("appid",appId);
        packageParams.put("mch_id",mchNo);
        packageParams.put("out_trade_no",outTradeNo);
        packageParams.put("nonce_str",WXPayUtil.generateNonceStr());
        //获得签名
        try {
            String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5);
            packageParams.put("sign",signature);
        } catch (Exception e) {
            log.error("签名异常:{}",e.getMessage());
            throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR);
        }

        try {
            //请求微信关闭订单服务接口
            String s = WXPayUtil.doPost(4, WXPayUtil.mapToXml(packageParams), false, null);
            UnifiedOrderResponse resposeStr = JSON.parseObject(s,UnifiedOrderResponse.class);
            if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){
                if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){
                    return R.ok(null,"订单关闭成功");
                }else {
                    return R.error(resposeStr.getErr_code_des());
                }
            }else {
                return R.error(resposeStr.getReturn_msg());
            }
        } catch (Exception e) {
            log.error("关闭订单异常:{}"+e.getMessage());
            throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"关闭订单异常");
        }
    }

    @ApiOperation(value = "请求微信退款订单接口" )
    @ApiImplicitParams({
            @ApiImplicitParam(name = "transactionId", required = true, value = "微信订单号", dataType = "String"),
            @ApiImplicitParam(name = "outRefundNo", required = true, value = "商户退款单号(32个字符内 )", dataType = "String"),
            @ApiImplicitParam(name = "totalFee", required = true, value = "订单金额", dataType = "String"),
            @ApiImplicitParam(name = "refundFee", required = true, value = "退款金额", dataType = "String"),
            @ApiImplicitParam(name = "refundDesc", required = false, value = "退款原因", dataType = "String")
    })
    public R refund(RefundParam map) throws WeChatPayException {
        /* 签名map */
        SortedMap<String, String> packageParams = new TreeMap<String, String>();
        packageParams.put("appid",appId);
        packageParams.put("mch_id",mchNo);
        packageParams.put("nonce_str",WXPayUtil.generateNonceStr());
        packageParams.put("out_refund_no",map.getOutRefundNo());
        packageParams.put("total_fee",WXPayUtil.changeY2F(map.getTotalFee()));
        packageParams.put("refund_fee",WXPayUtil.changeY2F(map.getRefundFee()));
        packageParams.put("transaction_id",map.getTransactionId());
        //获得签名
        try {
            String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5);
            packageParams.put("sign",signature);
        } catch (Exception e) {
            log.error("签名异常:{}",e.getMessage());
            throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR);
        }

        try {
            //调统一请求退款服务接口
            String s = WXPayUtil.doPost(3,WXPayUtil.mapToXml(packageParams), true,lcePassWord);
            RefundResp resposeStr = JSON.parseObject(s, RefundResp.class);
            if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){
                if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){
                    /**
                     * ##订单已退款 此处更新数据库订单状态和退款信息 ###
                     */
                    return R.ok(null,"订单退款成功");
                }else {
                    return R.error(resposeStr.getErr_code_des());
                }
            }else {
                return R.error(resposeStr.getReturn_msg());
            }
        } catch (Exception e) {
            log.error("订单退款异常:{}"+e.getMessage());
            throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"订单退款异常");
        }
    }


    @ApiOperation(value = "请求微信退款查询接口" )
    @ApiImplicitParam(name = "outTradeNo", required = true, value = "商户订单号", dataType= "String")
    public R refundQuery(String outTradeNo) throws WeChatPayException {
        /* 签名map */
        SortedMap<String, String> packageParams = new TreeMap<String, String>();
        packageParams.put("appid",appId);
        packageParams.put("mch_id",mchNo);
        packageParams.put("out_trade_no",outTradeNo);
        packageParams.put("nonce_str",WXPayUtil.generateNonceStr());
        //获得签名
        try {
            String signature=WXPayUtil.generateSignature(packageParams,key,WXPayConstants.SignType.MD5);
            packageParams.put("sign",signature);
        } catch (Exception e) {
            log.error("签名异常:{}",e.getMessage());
            throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,WXPayConstants.SIGN_EXCEPTION_ERROR);
        }

        try {
            //统一请求退款服务接口
            String s = WXPayUtil.doPost(5, WXPayUtil.mapToXml(packageParams), false, null);
            RefundQueryResponse resposeStr = JSON.parseObject(s,RefundQueryResponse.class);
            //返回结果封装map
            Map respMap = new HashMap();
            if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getReturn_code())){
                if (Objects.equals(WXPayConstants.SUCCESS,resposeStr.getResult_code())){
                    /**
                     * ##订单查询成功 此处更新数据库退款订单状态
                     * 退款状态:
                     * SUCCESS—退款成功  status = 1
                     * REFUNDCLOSE—退款关闭 status = 2
                     * PROCESSING—退款处理中 status = 3
                     * CHANGE—退款异常  status = 4
                     */

                    respMap.put("statusDesc",resposeStr.getRefund_status_0());
                    respMap.put("data",resposeStr);
                    if (Objects.equals(WXPayConstants.RefundState.SUCCESS.getName(),resposeStr.getRefund_status_0())){
                        respMap.put("status",1);
                    }else if (Objects.equals(WXPayConstants.RefundState.REFUNDCLOS.getName(),resposeStr.getRefund_status_0())){
                        respMap.put("status",2);

                    }else if (Objects.equals(WXPayConstants.RefundState.PROCESSING.getName(),resposeStr.getRefund_status_0())){
                        respMap.put("status",3);
                    }else if (Objects.equals(WXPayConstants.RefundState.CHANGE.getName(),resposeStr.getRefund_status_0())){
                        respMap.put("status",4);
                    }
                    return R.ok(respMap,"订单退款查询成功");
                }else {
                    return R.error(resposeStr.getErr_code_des());
                }
            }else {
                return R.error(resposeStr.getReturn_msg());
            }
        } catch (Exception e) {
            log.error("查询退款订单异常:{}"+e.getMessage());
            throw new WeChatPayException(WXPayConstants.EXCEPTION_ERROR_CODE,"查询退款订单异常");
        }
    }


    /**
     * ####
     * 1、商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户的订单金额一致
     * ,防止数据泄漏导致出现“假通知”,造成资金损失。
     * 2、当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再
     * 进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制
     * ,以避免函数重入造成的数据混乱。
     */
@ApiOperation(value = "微信支付异步通知")
public void notify(HttpServletRequest req, HttpServletResponse resp){
    //定义一个通知微信消息
    String noticeStr = null;
    String notifyString = WXPayUtil.getNotifyUtils(req, resp);
    //解析xml成map
    Map<String, String> map = new HashMap<>();
    try {
        map = WXPayUtil.xmlToMap(notifyString);
    } catch (Exception e) {
        e.printStackTrace();
    }
    //获取异步通知请求 将map转成指定对象
    OrderQueryResponse orderResp = JSON.parseObject(JSON.toJSONString(map), OrderQueryResponse.class);
    //判断return_code状态
    if (Objects.equals(WXPayConstants.SUCCESS,orderResp.getReturn_code())){

        if (Objects.equals(WXPayConstants.SUCCESS,orderResp.getResult_code())){
            //TODO 验签
            try {

                if (WXPayUtil.isSignatureValid(notifyString,key)){

                    //TODO 新增验证预支付订单金额和异步回调支付金额是否匹配
                    /**
                     * ## 此处步骤
                     * 1.异步回调的商户订单号或微信订单号 调用查询接口
                     * 2.判断此订单是否真实支付成功
                     * 3.验证返回的支付金额和数据库中预支付订单金额是否相等
                     * 4.鉴定完毕才更新数据库数据
                     * 5.跳转至成功页面
                     */
                    log.info("------------验签成功 开始下一步----------");
                    //调用微信支付查询订单接口
                    R r = orderQuery(orderResp.getOut_trade_no());
                    if (r.getSuccess()){
                        Map respMap =(Map) r.getData();
                        OrderQueryResponse data =(OrderQueryResponse) respMap.get("data");

                        if (Objects.equals(WXPayConstants.SUCCESS,data.getTrade_state())){

                            //## 此处调用此商户订单号查询数据库预订单的付款金额(注意单位:分)
                          
                            noticeStr = setXML(WXPayConstants.SUCCESS, WXPayConstants.OK);
                        }else {
                            log.info("商户订单号:{},未支付成功状态",orderResp.getOut_trade_no());
                            noticeStr = setXML(WXPayConstants.FAIL, orderResp.getOut_trade_no()+"商户订单号状态是未支付成功状态");
                        }

                    }else {
                        log.error("订单查询失败:{}",r.getMsg());
                    }


                }else {
                    log.error("验签错误 非法访问");
                    noticeStr = setXML(WXPayConstants.FAIL, "非法访问");
                }

            } catch (Exception e) {
                e.printStackTrace();
                log.error("验签异常:{}",e.getMessage());
                noticeStr = setXML(WXPayConstants.FAIL, "验签异常");
            }

        }else {
            log.error("错误信息:{}",orderResp.getErr_code_des());
            noticeStr = setXML(WXPayConstants.FAIL, orderResp.getReturn_msg());
        }

    }else {
        log.error("异常信息:{}",orderResp.getReturn_msg());
        noticeStr = setXML(WXPayConstants.FAIL, orderResp.getReturn_msg());

    }
    PrintWriter writer = null;
    try {
        writer = resp.getWriter();
    } catch (IOException e) {
        e.printStackTrace();
    }
    writer.write(noticeStr);
    writer.flush();
}



    /**
     * 判断对象是否非空
     * @param object
     * @return
     */
    public Boolean isNull(Object object){
        if (Objects.isNull(object)||Objects.equals("",object)){
            return true;
        }
        return false;
    }

    /**
     * 拼接Xml字符串
     * @param return_code
     * @param return_msg
     * @return
     */
    public static String setXML(String return_code, String return_msg) {
        return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
    }

    public  String notifyReqUrl(Integer code) throws WeChatPayException {
        String reqUrl = null;
        switch (code){
            case 1:
                reqUrl = refusalRefundUrl;
                break;
            case 2:
                reqUrl = notPickRefundUrl;
                break;
            case 3:
                reqUrl = detectionlRefundUrl;
                break;
                default:
                    throw new WeChatPayException(WXPayConstants.PARAM_ERROR_CODE,"参数未被定义");
        }
        return reqUrl;
    }
}
==============================================================================================
微信支付工具包
===============================================================================================

package com.sf.vsolution.hb.sfce.util.wechat.pay;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;

/**
 * @Desc: 微信支付工具包
 * @Author: zhucj
 * @Date: 2019/5/23 13:29
 */
@Slf4j
@Component
public class WXPayUtil implements InitializingBean {

   /**
    * 安全证书位置
    */
   @Value("${wx.pay.certPath}")
   private String certPath;

   public static String CERT_PATH;

   private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

   private static final Random RANDOM = new SecureRandom();

   /**
    * ##连接超时时间,默认10秒
    */
   private static int socketTimeout = 10000;
   /**
    * ## 传输超时时间,默认30秒
    */
   private static int connectTimeout = 30000;
   /**
    * ## 请求器的配置
    */
   private static RequestConfig requestConfig;
   /**
    * ## HTTP请求器
    */
   private static CloseableHttpClient httpClient;


   /**
     * 作用:统一请求地址<br>
    * 场景:公共号支付、扫码支付、APP支付
    * @param reqUrlNub 请求地址编号  1:下单 2:查询订单 3:申请退款 4:关闭订单 5:查询退款
    * @param xmlDate 发送内容 xml字符串
    * @param isLoadCert 是否需要证书
    * @param lcePassword 证书密码(默认商户Id) isLoadCert=true必传  如果isLoadCert=false 传null即可
    * @throws Exception
    * @return JSON格式字符串对象
    */

   public static String doPost(Integer reqUrlNub, String xmlDate, boolean isLoadCert, String lcePassword) throws Exception{

      if (Objects.isNull(reqUrlNub) || Objects.equals("",reqUrlNub)){return null;}
      //获取请求地址
      String url = getReqUrl(reqUrlNub);
      log.info("POST请求参数,请求URL:{},请求XML信息:{}",url,xmlDate);
      HttpPost httpPost=new HttpPost(url);
      // 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
      httpPost.addHeader("Content-Type", "text/xml");
      httpPost.setEntity(new StringEntity(xmlDate,"UTF-8"));
      // 根据默认超时限制初始化requestConfig
      requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
      // 设置请求器的配置
      httpPost.setConfig(requestConfig);
      CloseableHttpResponse execute = null;
      if(isLoadCert) {
         // 加载证书
         try {
            initCert(lcePassword,2);
         } catch (Exception e) {
            e.printStackTrace();
         }
         //发送含有证书的http请求
          execute = httpClient.execute(httpPost);
      }else {
          execute = HttpClients.custom().build().execute(httpPost);
      }

      return getJsonString(execute);
   }


   /**
    * 返回结果解析成Json字符串
    * @param response
    * @return
    */
   public static String getJsonString(HttpResponse response) throws Exception{
      HttpEntity entity = null;
      StringBuilder sb = new StringBuilder();
      try {
         entity = response.getEntity();
         String text;
         if (entity != null) {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
            while (true) { if (!((text = bufferedReader.readLine()) != null)) break;sb.append(text); }
         }else {
            return null;
         }
      }catch (Exception e){
         e.printStackTrace();
         log.error("--------读取异常---------"+e.getMessage());

      } finally {
         try {
            EntityUtils.consume(entity);
         } catch (IOException ex) {
            ex.printStackTrace();
            log.error("net io exception");
         }
      }
      //将xml转成map对象
      Map<String, String> stringMap = xmlToMap(sb.toString());
      log.info("响应参数内容:{}", JSON.toJSONString(stringMap));
      return JSON.toJSONString(stringMap);
   }


   /**
    * 加载证书 (apiclient_cert.p12)文件
    * @param lcePsw 证书密码(默认是商户Id)
    * @param loadType 加载证书方法 1:存放在项目下 2:存放本地磁盘中
    * @throws Exception
    */
   private static void initCert(String lcePsw,Integer loadType) throws Exception {
      KeyStore keyStore  = KeyStore.getInstance("PKCS12");
      if (Objects.isNull(loadType) || Objects.equals("",loadType) || Objects.equals(1,loadType)){
         //方式一:默认存放在项目下
         ClassPathResource cl = new ClassPathResource(CERT_PATH);
         try {
            keyStore.load(cl.getInputStream(), lcePsw.toCharArray());
         } finally {
            cl.getInputStream().close();
         }
      }else if (Objects.equals(2,loadType)){
         //方式二:存放在本地磁盘 读取本机存放的PKCS12证书文件
         FileInputStream instream = new FileInputStream(new File(CERT_PATH));
         try {
            keyStore.load(instream, lcePsw.toCharArray());
         } finally {
            instream.close();
         }
      }else {throw new WeChatPayException(400,"loadType参数格式错误,请核对");}

      // 信任自己的CA和所有自签名证书
      SSLContext sslcontext = SSLContexts.custom()
            .loadKeyMaterial(keyStore,lcePsw.toCharArray())
            .build();
      // 只允许TLSv1协议
      SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
            sslcontext,
            new String[]{"TLSv1"},
            null,
            SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
      httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
   }


   /**
    * 作用:获取请求地址<br>
    * 场景:1:下单 2:查询订单 3:申请退款 4:关闭订单 5:查询退款
    * @param reqUrlNub
    * @return 请求地址字符串
    * @throws Exception
    */
   public static String getReqUrl(Integer reqUrlNub) throws WeChatPayException {
      StringBuilder reqUrl = new StringBuilder();
      reqUrl.append(WXPayConstants.BASE_URL);
      switch (reqUrlNub){
         case 1:
            reqUrl.append( WXPayConstants.UNIFIED_ORDER_URL);
            break;
         case 2:
            reqUrl.append(WXPayConstants.ORDER_QUERY_URL);
            break;
         case 3:
            reqUrl.append(WXPayConstants.REFUND_URL);
            break;
         case 4:
            reqUrl.append(WXPayConstants.CLOSEORDER_URL_SUFFIX);
            break;
         case 5:
            reqUrl.append(WXPayConstants.REFUNDQUERY_URL_SUFFIX);
            break;
         default:
            throw new WeChatPayException(400,"传入的请求地址编号暂不支持");
      }
      return reqUrl.toString();
   }

   /**
    * 获取随机字符串 Nonce Str
    *
    * @return String 随机字符串
    */
   public static String generateNonceStr() {
      char[] nonceChars = new char[32];
      for (int index = 0; index < nonceChars.length; ++index) {
         nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
      }
      return new String(nonceChars);
   }


   /**
    * 获取本机Ip地址
    * @return
    */
   public static String getLocalIp(){
      try {
         InetAddress addr = InetAddress.getLocalHost();
         //获取本机ip
         return addr.getHostAddress();
      } catch (UnknownHostException e) {
         e.printStackTrace();
         return null;
      }
   }


   /**
    * 判断签名是否正确
    *
    * @param xmlStr XML格式数据
    * @param key API密钥
    * @return 签名是否正确
    * @throws Exception
    */
   public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
      Map<String, String> data = xmlToMap(xmlStr);
      if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
         return false;
      }
      String sign = data.get(WXPayConstants.FIELD_SIGN);

      return generateSignature(data, key,WXPayConstants.SignType.MD5).equals(sign);
   }


   /**
    * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
    *
    * @param data 待签名数据
    * @param key API密钥
    * @param signType 签名方式
    * @return 签名
    */
   public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
      Set<String> keySet = data.keySet();
      String[] keyArray = keySet.toArray(new String[keySet.size()]);
      Arrays.sort(keyArray);
      StringBuilder sb = new StringBuilder();
      for (String k : keyArray) {
         if (k.equals(WXPayConstants.FIELD_SIGN)) {
            continue;
         }
         if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
         {sb.append(k).append("=").append(data.get(k).trim()).append("&");}
      }
      sb.append("key=").append(key);
      if (WXPayConstants.SignType.MD5.equals(signType)) {
         return MD5(sb.toString()).toUpperCase();
      }
      else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
         return HMACSHA256(sb.toString(), key);
      }
      else {
         throw new Exception(String.format("Invalid sign_type: %s", signType));
      }
   }



   /**
    * 生成 MD5
    *
    * @param data 待处理数据
    * @return MD5结果
    */
   public static String MD5(String data) throws Exception {
      MessageDigest md = MessageDigest.getInstance("MD5");
      byte[] array = md.digest(data.getBytes("UTF-8"));
      StringBuilder sb = new StringBuilder();
      for (byte item : array) {
         sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
      }
      return sb.toString().toUpperCase();
   }

   /**
    * 生成 HMACSHA256
    * @param data 待处理数据
    * @param key 密钥
    * @return 加密结果
    * @throws Exception
    */
   public static String HMACSHA256(String data, String key) throws Exception {
      Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
      SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
      sha256_HMAC.init(secret_key);
      byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
      StringBuilder sb = new StringBuilder();
      for (byte item : array) {
         sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
      }
      return sb.toString().toUpperCase();
   }





   /**
    * 获取当前时间戳,单位秒
    * @return
    */
   public static long getCurrentTimestamp() {
      return System.currentTimeMillis()/1000;
   }


   /**
     * 支付金额单位:元转分 (12.23元 >>> 1223分)
    * @param amount
     * @return
     */
   public static String changeY2F(String amount) {
      String currency = amount.replaceAll("\\$|\\¥|\\,", "");
      // 或者$的金额
      int index = currency.indexOf(".");
      int length = currency.length();
      Long amLong = 0l;
      if (index == -1) {
         amLong = Long.valueOf(currency + "00");
      } else if (length - index >= 3) {
         amLong = Long.valueOf((currency.substring(0, index + 3)).replace(".", ""));
      } else if (length - index == 2) {
         amLong = Long.valueOf((currency.substring(0, index + 2)).replace(".", "") + 0);
      } else {
         amLong = Long.valueOf((currency.substring(0, index + 1)).replace(".", "") + "00");
      }
      return amLong.toString();
   }


   /**
    * XML格式字符串转换为Map
    *
    * @param strXML XML字符串
    * @return XML数据转换后的Map
    * @throws Exception
    */
   public static Map<String, String> xmlToMap(String strXML) throws Exception {
      try {
         Map<String, String> data = new HashMap<String, String>();
         DocumentBuilder documentBuilder = newDocumentBuilder();
         InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
         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) {
            // do nothing
         }
         return data;
      } catch (Exception ex) {
         log.warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
         throw ex;
      }

   }


   /**
    * 将Map转换为XML格式的字符串
    *
    * @param data Map类型数据
    * @return XML格式的字符串
    * @throws Exception
    */
   public static String mapToXml(Map<String, String> data) throws Exception {
      Document document = newDocument();
      org.w3c.dom.Element root = document.createElement("xml");
      document.appendChild(root);
      for (String key: data.keySet()) {
         String value = data.get(key);
         if (value == null) {
            value = "";
         }
         value = value.trim();
         org.w3c.dom.Element filed = document.createElement(key);
         filed.appendChild(document.createTextNode(value));
         root.appendChild(filed);
      }
      TransformerFactory tf = TransformerFactory.newInstance();
      Transformer transformer = tf.newTransformer();
      DOMSource source = new DOMSource(document);
      transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
      StringWriter writer = new StringWriter();
      StreamResult result = new StreamResult(writer);
      transformer.transform(source, result);
      String output = writer.getBuffer().toString();
      try {
         writer.close();
      }
      catch (Exception ex) {
      }
      return output;
   }

   public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
      DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
      documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
      documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
      documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
      documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
      documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
      documentBuilderFactory.setXIncludeAware(false);
      documentBuilderFactory.setExpandEntityReferences(false);

      return documentBuilderFactory.newDocumentBuilder();
   }

   public static Document newDocument() throws ParserConfigurationException {
      return newDocumentBuilder().newDocument();
   }

   /**
    * 将微信异步通知消息解析成map对象
    * @param req
    * @param response
    * @return
    */
   public static String getNotifyUtils(HttpServletRequest req, HttpServletResponse response){
      //返回字符串内容
      String noticeStr = null;
      InputStream inputStream ;
      StringBuffer sb = new StringBuffer();
      try{
         inputStream = req.getInputStream();
         String s ;
         BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
         while ((s = in.readLine()) != null){
            sb.append(s);
         }
         in.close();
         inputStream.close();
         log.info("异步通知结果=>xml值:"+sb.toString());
         //解析xml成map
         return sb.toString();
      }catch (Exception e){
         log.error("解析异步消息异常",e.getMessage());
         return null;
      }
   }



    /**
     * 注入的属性转静态属性
    * @throws Exception
    */
   @Override
   public void afterPropertiesSet() throws Exception {
      CERT_PATH = this.certPath;
   }


}
=======================================================================================================
微信支付相关参数(请求参数,响应参数,枚举,异常类)
=======================================================================================================
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 微信下单前端传递参数
 * @author: zhucj 
 * @date: 2019-09-26 11:08
 */

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class UnfiedOrderParam {
    /**
     * 商户订单号(32个字符内)
     */
    private String outTradeNo;

    /**
     * 标价金额
     */
    private String totalFee;

    /**
     * 商品描述
     */
    private String body;

    /**
     * 交易类型
     */
    private String tradeType;

    /**
     * 用户openId
     */
    private String openId;

    /**
     * 终端IP
     */
    private String spbillCreateIp;

    /**
     * 产品Id
     */
    private String productId;
}
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 统一下单实体
 * @Author:zhucj
 * @Date: 2019/5/23 13:29
 */

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class UnifiedOrderRequest {


   /**
    * 公众账号ID
    * 必填  String(32)
    * wxd678efh567hg6787微信支付分配的公众账号ID(企业号corpid即为此appId)
    */
   private String appid;
   /**
    * 商户号
    * 必填  String(32)
    * 1230000109  微信支付分配的商户号
    */
   private String mchId;
   /**
    * 设备号
    * 否
    * String(32)
    * 013467007045764 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
    */
   private String deviceInfo;
   /**
    * 随机字符串
    * 必填  String(32)
    * 5K8264ILTKCH16CQ2502SI8ZNMTM67VS    随机字符串,长度要求在32位以内。推荐随机数生成算法
    */
   private String nonceStr;
   /**
    * 签名
    * 必填 String(32)
    * C380BEC2BFD727A4B6845133519F3AD6    通过签名算法计算得出的签名值,详见签名生成算法MD5加密
    */
   private String sign;
   /**
    * 签名类型
    * 否   String(32)
    * HMAC-SHA256 签名类型,默认为MD5,支持HMAC-SHA256和MD5。
    */
   private String sign_type;
   /**
    * 商品描述
    * 必填  String(128)
    * 腾讯充值中心-QQ会员充值  商品简单描述,该字段请按照规范传递,具体请见参数规定
    */
   private String body;
   /**
    * 商品详情
    * 否   String(6000)
    * 单品优惠字段(暂未上线)
    */
   private String detail;
   /**
    * 商品详情
    * 否   String(6000)
    * 单品优惠字段(暂未上线)
    */
   private String attach;
   /**
    * 商户订单号
    * 必传  String(32)
    * 20150806125346  商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。详见商户订单号
    */
   private String out_trade_no;
   /**
    * 标价币种
    * 否   String(16)
    * CNY 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型
    */
   private String fee_type;
   /**
    * 标价金额
    * 必传  Int    88 订单总金额,单位为分,详见支付金额
    */
   private String total_fee;
   /**
    * 终端IP
    * 必传  String(16)
    * 123.12.12.123   APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
    */
   private String spbill_create_ip;
   /**
    * 交易起始时间
    * 否   String(14)
    * 20091225091010  订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
    */
   private String time_start;
   /**
    * 交易结束时间
    * 否   String(14)
    * 20091227091010  订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则   注意:最短失效时间间隔必须大于5分钟
    */
   private String time_expire;
   /**
    * 订单优惠标记
    * 否   String(32)
    * WXG 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠
    */
   private String goods_tag;
   /**
    * 通知地址
    * 必传  String(32)
    * 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
    */
   private String notify_url;
   /**
    * 交易类型
    * 必传  String(16)
    * JSAPI   取值如下:JSAPI,NATIVE,APP等,说明详见参数规定
    */
   private String trade_type;
   /**
    * 商品ID
    * 否   String(32)
    * 12235413214070356458058 trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。
    */
   private String product_id;
   /**
    * 指定支付方式
    * 否   String(32)上传此参数no_credit--可限制用户不能使用信用卡支付
    */
   private String limit_pay;
   /**
    * 用户标识
    *否    String(128)
    * oUpF8uMuAJO_M2pxb1Q9zNjWeS6o    trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,
    * 可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换
    */
   private String openid;

}
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * @description 统一下单返回参数
 * @author zhucj
 * @Date: 2019/5/23 13:29
 */

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class UnifiedOrderResponse {

    /**
     *  返回状态码
     *  SUCCESS/FAIL
     * 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
     */
    private String return_code;

    /**
     *返回信息
     * 返回信息,如非空,为错误原因 签名失败
     * 参数格式校验错误
     */
    private String return_msg;

    //TODO 以下字段在return_code为SUCCESS的时候有返回
    /**
     * 公众账号ID
     */
    private String appid;
    /**
     * 商户号
     */
    private String mch_id;
    /**
     * 设备号
     */
    private String device_info;
    /**
     * 随机字符串
     */
    private String nonce_str;
    /**
     * 签名
     */
    private String sign;
    /**
     * 业务结果
     */
    private String result_code;
    /**
     * 错误代码
     */
    private String err_code;

    /**
     * 错误代码描述
     */
    private String err_code_des;

    //TODO 以下字段在return_code 和result_code都为SUCCESS的时候有返回

    /**
     * 交易类型
     */
    private String trade_type;
    /**
     * 预支付交易会话标识
     */
    private String prepay_id;
    /**
     * 二维码链接
     */
    private String code_url;

    /**
     *H5支付跳转页面
     */
    private String mweb_url;

    /**
     * 商户支付订单号
     */
    private String out_trade_no;

}
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 微信退款参数
 * @author:zhucj
 * @date: 2019-09-26 11:21
 */

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RefundParam {


    /**
     * 微信订单号
     */
    private String transactionId;

    /**
     * 退款订单号
     */
    private String outRefundNo;

    /**
     * 订单总金额
     */
    private String totalFee;

    /**
     * 退款金额
     */
    private String refundFee;

    /**
     * 退款信息描述
     */
    private String refundDesc;

    /**
     * 退款异步地址编号 1:为拒签退款异步地址 2:不接样退款地址 3:检测完成退款地址
     */
    private Integer refundStatus;
}
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 退款回调信息
 * @author: zhucj
 * @date: 2019-10-24 17:53
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RefundInfoParam {

    private String return_code;

    private String return_msg;

    private String appid;

    private String mch_id;

    private String nonce_str;

    private String req_info;
}
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 退款查询返回视图
 * @author: zhucj
 * @date: 2019-07-24 18:30
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RefundQueryResponse extends UnifiedOrderResponse {


    //TODO 处理退款结果查询参数,如果通过微信单号或商户支付订单号查询,
    //     可能出现多次退款情况。

    /**
     * 微信退款单号.....
     */
    private String refund_id_0;
    private String refund_id_1;
    private String refund_id_2;
    private String refund_id_3;
    private String refund_id_4;

    /**
     * 微信单号
     */
    private String transaction_id;

    /**
     * 商户退款单号 ......
     */
    private String out_refund_no_0;
    private String out_refund_no_1;
    private String out_refund_no_2;
    private String out_refund_no_3;
    private String out_refund_no_4;

    /**
     * 商户订单号
     */
    private String out_trade_no;


    /**
     * 现金支付金额
     */
    private Integer cash_fee;

    /**
     * 订单总金额
     */
    private Integer total_fee;

    /**
     * 退款笔数
     */
    private String refund_count;

    /**
     * 退款金额......
     */
    private String refund_fee_0;
    private String refund_fee_1;
    private String refund_fee_2;
    private String refund_fee_3;
    private String refund_fee_4;


    /**
     * 退款状态 ......
     */
    private String refund_status_0;
    private String refund_status_1;
    private String refund_status_2;
    private String refund_status_3;
    private String refund_status_4;

    /**
     * 退入账号.....
     */
    private String refund_recv_accout_0;
    private String refund_recv_accout_1;
    private String refund_recv_accout_2;
    private String refund_recv_accout_3;
    private String refund_recv_accout_4;

    /**
     * 退款成功时间 .....
     */
    private String refund_success_time_0;
    private String refund_success_time_1;
    private String refund_success_time_2;
    private String refund_success_time_3;
    private String refund_success_time_4;
}
package com.sf.vsolution.hb.sfce.util.wechat.pay.param;

import lombok.*;

/**
 * @description: 退款返回参数
 * @author: zhucj
 * @date: 2019-10-23 17:54
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class RefundResp extends UnifiedOrderResponse {

    /**
     * 微信订单号
     */
    private String transaction_id;

    /**
     * 商户订单号
     */
    private String out_trade_no;

    /**
     * 商户退款单号
     */
    private String out_refund_no;

    /**
     * 微信退款单号
     */
    private String refund_id;

    /**
     * 退款金额
     */
    private String refund_fee;

    /**
     * 订单总金额
     */
    private String total_fee;

    /**
     * 现金支付金额
     */
    private String cash_fee;

    private String refund_status;

    private String success_time;

}
package com.sf.vsolution.hb.sfce.util.wechat.pay;

/**
 * @author :zhucj
 * @date :Created in 2019/6/17 17:46
 * @description: 自定义微信支付异常类
 * @modified By:
 */
public class WeChatPayException extends Exception {
    private static final long serialVersionUID = -5317007026578376164L;

    /**
     * 错误码
     */
    private Integer errorCode;
    /**
     * 错误描述
     */
    private String errorMsg;

    /**
     * @param errorCode
     * @param errorMsg
     */
    public WeChatPayException(Integer errorCode, String errorMsg) {
        super(errorMsg);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public Integer getErrorCode() {
        return errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }
}
package com.sf.vsolution.hb.sfce.util.wechat.pay;

/**
 * @Author: zhucj
 * @Date: 2019/5/23 13:29
 * @apiNote: 微信支付常量
 */
public class WXPayConstants {


    /**
     * 签名枚举
    */
   public static enum SignType {
      MD5,

      HMACSHA256;

      private SignType() {
      }
   }

   /**
    * 公众号、小程序支付
    */
   public static final String JSAPI = "JSAPI" ;

   /**
    * NATIVE 扫码支付
    */
   public static final String NATIVE = "NATIVE";

   /**
    * app支付
    */
   public static final String APP = "APP";

   /**
    * 小程序支付
    */
   public static final String MWEB = "MWEB";


   public enum TradeState{
      /**
       * 交易状态
       */
      SUCCESS("SUCCESS","支付成功",1),
      REFUND("REFUND","转入退款",2),
      NOTPAY("NOTPAY","未支付",3),
      CLOSED("CLOSED","已关闭",4),
      REVOKED("REVOKED","已撤销",5),
      PAYERROR("PAYERROR","支付失败",6);

      private String name;

      private String values;

      private Integer index;

      TradeState(String name, String values, int index) {
         this.name = name;
         this.values = values;
         this.index = index;
      }

      public String getName() {
         return name;
      }

      public void setName(String name) {
         this.name = name;
      }

      public String getValues() {
         return values;
      }

      public void setValues(String values) {
         this.values = values;
      }

      public Integer getIndex() {
         return index;
      }

      public void setIndex(Integer index) {
         this.index = index;
      }
   }

   /**
    * 退款状态
    */
   public enum RefundState{

      SUCCESS("SUCCESS","退款成功",1),
      REFUNDCLOS("REFUNDCLOS","退款关闭",2),
      PROCESSING("PROCESSING","退款处理中",3),
      CHANGE("CHANGE","退款异常",4);

      private String name;

      private String values;

      private Integer index;

      RefundState(String name, String values, int index) {
         this.name = name;
         this.values = values;
         this.index = index;
      }

      public String getName() {
         return name;
      }

      public void setName(String name) {
         this.name = name;
      }

      public String getValues() {
         return values;
      }

      public void setValues(String values) {
         this.values = values;
      }

      public Integer getIndex() {
         return index;
      }

      public void setIndex(Integer index) {
         this.index = index;
      }
   }


   //TODO:  返回状态码

   public static final String FAIL = "FAIL";
   public static final String SUCCESS = "SUCCESS";

   //TODO: 验签加密方法

   public static final String HMACSHA256 = "HMAC-SHA256";
   public static final String MD5 = "MD5";

    //TODO: 签名

   public static final String FIELD_SIGN = "sign";
   public static final String FIELD_SIGN_TYPE = "sign_type";

   public static final String OK = "OK";

   //TODO:微信支付服务名

   /* 系统地址 */
   public static final String BASE_URL = "https://api.mch.weixin.qq.com";

   /* 统一下单地址 */
   public static final String UNIFIED_ORDER_URL = "/pay/unifiedorder";
   /* 查询订单 */
   public static final String ORDER_QUERY_URL = "/pay/orderquery";
   /* 关闭订单 */
   public static final String CLOSEORDER_URL_SUFFIX = "/pay/closeorder";
   /* 申请退款 */
   public static final String REFUND_URL = "/secapi/pay/refund";
   /* 查询退款 */
   public static final String REFUNDQUERY_URL_SUFFIX = "/pay/refundquery";

   //TODO:请求状态码
   public static final Integer PARAM_ERROR_CODE = 400;

   public static final Integer EXCEPTION_ERROR_CODE = 500;

   //TODO: 异常信息状态

   public static final String SPBILL_CREATE_APP_ERROR = "选择APP支付时,用户终端IP不为空";

   public static final String SPBILL_CREATE_JSAPI_ERROR = "选择JSAPI支付时,用户终端IP不为空";

   public static final String SPBILL_CREATE_HWEB_ERROR = "选择HWEB支付时,用户终端IP不为空";

   public static final String OPENID_JSAPI_ERROR = "选择JSAPI支付时,用户openId不为空";

   public static final String PRODUCT_NATIVE_ERROR = "选择NATIVE支付时,商品Id不为空";

   public static final String SIGN_EXCEPTION_ERROR = "签名异常";

}
package com.sf.vsolution.hb.sfce.util.wechat.pay;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.ToString;

import java.io.Serializable;

/**
 * 返回前端格式类型
 * @author choleece
 * @date 2018/9/27
 */
@ApiModel
@ToString
public class R<T> implements Serializable {

    private static final long serialVersionUID = -6287952131441663819L;

    /**
     * 编码
     */
    @ApiModelProperty(value = "响应码", example = "200")
    private int code = 200;

    /**
     * 成功标志
     */
    @ApiModelProperty(value = "成功标志", example = "true")
    private Boolean success;

    /**
     * 返回消息
     */
    @ApiModelProperty(value = "返回消息说明", example = "操作成功")
    private String msg="操作成功";

    /**
     * 返回数据
     */
    @ApiModelProperty(value = "返回数据")
    private T data;

    /**
     * 创建实例
     * @return
     */
    public static R instance() {
        return new R();
    }

    public int getCode() {
        return code;
    }

    public R setCode(int code) {
        this.code = code;
        return this;
    }

    public Boolean getSuccess() {
        return success;
    }

    public R setSuccess(Boolean success) {
        this.success = success;
        return this;
    }

    public String getMsg() {
        return msg;
    }

    public R setMsg(String msg) {
        this.msg = msg;
        return this;
    }

    public T getData() {
        return data;
    }
    public R setData(T data) {
        this.data = data;
        return this;
    }

    public static R ok() {
        return R.instance().setSuccess(true);
    }

    public static R ok(Object data) {
        return ok().setData(data);
    }

    public static R ok(Object data, String msg) {
        return ok(data).setMsg(msg);
    }

    public static R error() {
        return R.instance().setSuccess(false);
    }

    public static R error(String msg) {
        return error().setMsg(msg);
    }

    /**
     * 无参
     */
    public R() {
    }

    public R(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public R(int code, T data){
        this.code = code;
        this.data = data;
    }

    /**
     * 有全参
     * @param code
     * @param msg
     * @param data
     * @param success
     */
    public R(int code, String msg, T data, Boolean success) {
        this.code = code;
        this.msg = msg;
        this.data = data;
        this.success = success;
    }

    /**
     * 有参
     * @param code
     * @param msg
     * @param data
     */
    public R(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

        private int code;
        private String msg;

        ResultEnum(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }

        public int getCode() {
            return code;
        }

        public void setCode(int code) {
            this.code = code;
        }

        public String getMsg() {
            return msg;
        }

        public void setMsg(String msg) {
            this.msg = msg;
        }
    }
}

====================================================================================
支付查询定时任务
===================================================================================
package com.sf.vsolution.hb.sfce.util.wechat.pay.task;

import com.sf.vsolution.hb.sfce.util.wechat.pay.WeChatPayUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * @description: 定时任务-订单支付状态查询
 * @author: zhucj
 * @date: 2019-08-28 10:18
 */

@Slf4j
@Component
@Order(value = 1)
public class OrderPayTask implements CommandLineRunner {

    @Autowired
    private WeChatPayUtils weChatPayUtils;

    @Scheduled(cron= "${task.cron.order}")
    public void orderTask(){
        SimpleDateFormat s  = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        log.info(s.format(new Date())+"=>开始执行支付订单查询定时任务");
        //当前时间点
        Date date = new Date();
        //10分钟之前点
        String fastFormat = s.format(date.getTime()-600000);
        String lastFormat = s.format(date);
        /**
         * #### 订单状态查询定时任务 #####
         * 1.设置每隔10分钟查询一次条件为
         *  type:支付订单
         *  status:未支付状态的数据库预订单数据
         * 2.通过商户支付订单号,调用微信查询接口 获取支付状态
         * 3.判断是否状态一致,如不一致修改
         */
    }

    @Override
    public void run(String... args) throws Exception {
       this.orderTask();
    }
}
==================================================================================
yml配置信息
==================================================================================
#微信支付参数
wx:
  pay:
    #微信平台appId
    appId: *******
    #支付商户号
    mchNo: *******
    #支付成功,异步回调地址
    notifyUrl: **********
    #支付秘钥
    key: *****************
    #证书密码
    lcePassWord: *************
    #证书存放路径
    certPath:  ****/apiclient_cert.p12
#定时任务配置
task:
  cron:
    #支付订单推送:每隔10分钟查询一次
    order: 0 */10 * * * ?
===================================================================================
依赖maven jar包
===================================================================================
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.2</version>
    <scope>provided</scope>
</dependency>

<!--htttpclient依赖-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.5</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.55</version>
</dependency>
<!-- swagger API -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.6.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.6.0</version>
</dependency>

 



 

 

推荐阅读