7.微信支付-如何对接接口和回调


微信开发文档

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1


微信支付流程:

  1. 拼接参数xml

    • appid:公众账号ID(配置参数)
    • mch_id:商户号(配置参数)
    • device_info:设备号(PC网页或公众号内支付可以传"WEB")
    • nonce_str:随机字符串(工具生成)
    • body:商品描述(前端传值)
    • out_trade_no:商户订单号(工具生成)
    • total_fee:标价金额(前端传值)(商品价格)
    • spbill_create_ip:终端ip(控制层传值)(基础控制层获取)
    • notify_url:通知地址(配置参数)
    • trade_type:交易类型(常量)
    • product_id:商品ID(工具生成)
    • sign:签名处理(工具生成)(以上参数根据mchkey进行MD5加密)
  2. 发送请求url,使用Map<S,S>接收

    • 使用工具类发送请求
    • url头是配置中的orderUrl
    • 参数时 1 中拼接好的xml字符串
    • encoding是常量中的utf-8
  3. 自定义返回数据集合并判断是否可用

    • return_code是否为SUCCESS(SUCCESS是定义好的常量)
    • result_code是否为SUCCESS(SUCCESS是定义好的常量)
  4. 拿到返回的数据

    • trade_type:交易类型
    • prepay_id:预支付交易会话标识
    • code_url:二维码链接

主要文件:

配置类:

  • WxpayConfig:配置参数读取

常量类:

  • SystemConstant:系统常量
  • WxpayConstant:微信支付常量

工具类:

  • HttpClient4Util:发送HTTP请求工具
  • WxpayUtil:微信支付工具
  • WXPayXmlUtil:微信支付工具的工具
  • RequestResult:统一返回工具
  • ResultBuildUtil:统一返回工具

控制类:

  • BaseController:基础控制层,基本校验以及IP获取
  • WxpayController:微信支付控制层,实现支付流程

服务类:

  • WxpayService及其实现类:微信支付实现

    • 拼接参数xml

    • 发送请求url

依赖:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.3.6</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

配置文件

# 端口号
server.port=8089
#应用名称
spring.application.name=springboot-13-wxpay
# 微信支付配置
# 应用ID
wechat.pay.appId=wxab8acb865bb1637e

# 商户ID
wechat.pay.mchId=11473623

# 商户支付秘钥KEY
wechat.pay.mchKey=2ab9071b06b9f739b950ddb41db2690d

# 统一下单URL
wechat.pay.orderUrl=https://api.mch.weixin.qq.com/pay/unifiedorder

# 支付结果回调URL
wechat.pay.notifyUrl=http://githme.natappfree.cc/wxchatPay/resultNotify

配置类读取配置信息

package com.lcywings.sbt.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * Created on 2021/8/10.
 * <p>
 * Author: Lcywings
 * <p>
 * Description: 微信支付统一配置类
 */
@Component
@Data
@ConfigurationProperties(prefix = "wechat.pay")
public class WxpayConfig {
    /**
     * 应用id
     */
    private String appId;

    /**
     * 商户id
     */
    private String mchId;

    /**
     * 支付api密钥
     */
    private String mchkey;

    /**
     * 统一下单URL
     */
    private String orderUrl;

    /**
     * 支付结果回调URL
     */
    private String notifyUrl;
}

系统常量

package com.lcywings.sbt.constant;

/**
 * Created on 2021/7/27.
 * <p>
 * Author : Lcywings
 * <p>
 * Description : 系统常量类
 */
public class SystemConstant {
    /**
     * 统一成功返回状态码
     */
    public static final String SYS_UNIFY_RETURN_SUCCESS_CODE = "0000";
    /**
     * 统一成功返回状态说明
     */
    public static final String SYS_UNIFY_RETURN_SUCCESS_MSG = "SUCCESS";
    /**
     * 统一失败返回状态码
     */
    public static final String SYS_UNIFY_RETURN_FAIL_CODE = "9999";
    /**
     * 统一失败返回状态说明
     */
    public static final String SYS_UNIFY_RETURN_FAIL_MSG = "FAIL";
    /**
     * 默认统一分页当前页码
     */
    public static final int SYS_INIT_PAGE_NO = 1;
    /**
     * 默认统一分页页面容量
     */
    public static final int SYS_INIT_PAGE_SIZE = 3;

}

微信支付常量

package com.lcywings.sbt.constant;

/**
 * Created on 2021/8/10.
 * <p>
 * Author: wsw
 * <p>
 * Description: 微信支付常量类
 */
public class WxpayConstant {
    /**
     * 生成字符串的原始字符,数字,小写,大写字母
     */
    public static final String WXPAY_SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    /**
     * 商户订单号的统一前缀
     */
    public static final String WXPAY_ORDER_PREFIX = "T";

    /**
     * 微信支付接口交易类型
     */
    public static final String WXPAY_ORDER_NATIVE = "NATIVE";

    /**
     * 微信支付接口签名方式-MD5
     */
    public static final String WXPAY_SIGN_TYPE_MD5 = "MD5";

    /**
     * 微信支付接口签名参数
     */
    public static final String WXPAY_FIELD_SIGN = "sign";

    /**
     * 微信支付请求字符集编码
     */
    public static final String WXPAY_ENCODING_UTF8 = "UTF-8";

    /**
     * 微信支付请求返回成功标识
     */
    public static final String WXPAY_RETURN_CODE_SUCCESS = "SUCCESS";

}

基础控制层

package com.lcywings.sbt.controller;

import org.apache.catalina.servlet4preview.http.HttpServletRequest;

/**
 * Created on 2021-08-10.
 * <p>
 * Author: zhukang
 * <p>
 * Description: 所有控制器的父类,提供一些公共方法
 */
public class BaseController {

    /**
     * @author : zhukang
     * @date : 2021-08-10
     * @return :
     * @description : 从request中获取参数,如果没有,默认为空字符串
     */
    protected String getParameter(HttpServletRequest request, String key){
        return request.getParameter(key) == null ? "" : request.getParameter(key);
    }

    /**
     * @author : zhukang
     * @date : 2021-08-10
     * @return :
     * @description : 从request中获取参数,如果没有,指定默认值
     */
    protected String getParameterUseDefValue(HttpServletRequest request, String key, String defautValue){
        return request.getParameter(key) == null ? defautValue : request.getParameter(key);
    }

    /**
     * @author : zhukang
     * @date : 2021-08-10
     * @return : java.lang.String
     * @description : 获取请求的来源ip
     */
    protected String getRemoteIp(HttpServletRequest request) {
        // 获取ip
        String ip = request.getHeader("X-Real-IP");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("x-forwarded-for");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        return ip;
    }
}

微信支付控制层

package com.lcywings.sbt.controller;

import com.lcywings.sbt.constant.WxpayConstant;
import com.lcywings.sbt.service.WxpayService;
import com.lcywings.sbt.utils.RequestResult;
import com.lcywings.sbt.utils.ResultBuildUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import org.apache.catalina.servlet4preview.http.HttpServletRequest;

import java.util.HashMap;
import java.util.Map;

/**
 * Created on 2021/8/10.
 * <p>
 * Author: wsw
 * <p>
 * Description: 微信支付操作入口
 */
@RestController
@Slf4j
@RequestMapping("/wechatPay")
public class WxpayController extends BaseController {
    @Autowired
    private WxpayService wxpayService;

    /**
     * @return :
     * @author : wsw
     * @date : 2021/8/10
     * @description :  请求微信官方,统一下单(返回支付链接,拿到链接才可以扫码支付)
     */
    @RequestMapping("/unifiedOrder")
    public RequestResult<Map<String, String>> wechatPayUnifiedorder(HttpServletRequest request, @RequestParam String body, @RequestParam int totalFree) throws Exception {
        // 封装微信统一下单的参数(xml格式)
        String wxpayOrderXml = wxpayService.getWxpayOrderParamsXml(body, totalFree, this.getRemoteIp(request));

        log.info("------ 1 请求微信官方统一下单参数:{} ------", wxpayOrderXml);

        // 请求微信官方进行统一下单
        Map<String, String> wxpayOrderReturnMap = wxpayService.sendToWxpayUnifiedOrder(wxpayOrderXml);

        log.info("------ 2  请求微信官方统一下单返回结果:{} ------", wxpayOrderReturnMap);

        // 自定义返回数据集合
        Map<String, String> wxpayOrderData = new HashMap<>();

        // 结果解析和返回
        if (WxpayConstant.WXPAY_RETURN_CODE_SUCCESS.equals(wxpayOrderReturnMap.get("return_code"))
                && WxpayConstant.WXPAY_RETURN_CODE_SUCCESS.equals(wxpayOrderReturnMap.get("result_code"))) {
            // 交易类型
            wxpayOrderData.put("trade_type", wxpayOrderReturnMap.get("trade_type"));

            // 预支付交易会话标识
            wxpayOrderData.put("prepay_id", wxpayOrderReturnMap.get("prepay_id"));

            // 二维码链接
            wxpayOrderData.put("code_url", wxpayOrderReturnMap.get("code_url"));

            return ResultBuildUtil.success(wxpayOrderData);
        }

        // 返回微信下单接口错误结果
        return ResultBuildUtil.fail(wxpayOrderReturnMap);
    }

}

微信支付服务接口

package com.lcywings.sbt.service;

import java.util.Map;

/**
 * Created on 2021/8/10.
 * <p>
 * Author: lcywings
 * <p>
 * Description: 微信支付业务接口
 */
public interface WxpayService {
    /**
     * @return :
     * @author : wsw
     * @date : 2021/8/10
     * @description :  封装微信统一下单接口参数,注意:要求参数的顺序必须是按照字典序排列,结要转换为xml格式字符串
     */
    String getWxpayOrderParamsXml(String body, int totalFree, String fromIp) throws Exception;

    /**
     * @author : Lcywings
     * @date : 2021/8/10 16:23
     * @acl : true
     * @description : 请求微信官方进行统一下单,获取下单结果
     */
    Map<String, String> sendToWxpayUnifiedOrder(String wxpayOrderXml) throws Exception;
}

微信支付服务实现

package com.lcywings.sbt.service.impl;

import com.lcywings.sbt.config.WxpayConfig;
import com.lcywings.sbt.constant.WxpayConstant;
import com.lcywings.sbt.service.WxpayService;
import com.lcywings.sbt.utils.HttpClient4Util;
import com.lcywings.sbt.utils.WxpayUtil;
import org.apache.http.client.HttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.TreeMap;

/**
 * Created on 2021/8/10.
 * <p>
 * Author: wsw
 * <p>
 * Description:
 */
@Service
public class WxpayServiceImpl implements WxpayService {

    @Autowired
    private WxpayConfig wxpayConfig;

    @Override
    public String getWxpayOrderParamsXml(String body, int totalFree, String fromIp) throws Exception {

        // 微信统一下单接口,要求参数按照参数名的字典序排列,使用treemap
        Map<String, String> paramsMap = new TreeMap<>();

        // 公众账号ID  appid
        paramsMap.put("appid", wxpayConfig.getAppId());

        // 商户号 mch_id
        paramsMap.put("mch_id", wxpayConfig.getMchId());

        // 设备号 device_info PC网页或公众号内支付可以传"WEB"
        paramsMap.put("device_info", "WEB");

        // 随机字符串 nonce_str
        paramsMap.put("nonce_str", WxpayUtil.generateNonceStrUseRandom());

        // 商品描述 body
        paramsMap.put("body", body);

        // 商户订单号 out_trade_no
        paramsMap.put("out_trade_no", WxpayUtil.generateOutTrade());

        // 标价金额 total_fee
        paramsMap.put("total_fee", String.valueOf(totalFree));

        // 终端ip  spbill_create_ip
        paramsMap.put("spbill_create_ip", fromIp);

        // 通知地址 notify_url
        paramsMap.put("notify_url", wxpayConfig.getNotifyUrl());

        // 交易类型 trade_type
        paramsMap.put("trade_type", WxpayConstant.WXPAY_ORDER_NATIVE);

        // 商品ID product_id trade_type=NATIVE时,此参数必传。(实际应该是对应商品的id值)
        paramsMap.put("product_id", WxpayUtil.generateNonceStrUseUUID(9));

        // 签名处理,签名一般都是在最后进行,必须使用到上面所有的参数
        // 注意:sign参数是不参与签名逻辑,但是它是接口参数,必须将此参数传给
        paramsMap.put("sign", WxpayUtil.generateSignature(paramsMap, wxpayConfig.getMchkey()));

        // 将map参数转换为xml字符串
        return WxpayUtil.mapToXml(paramsMap);
    }

    @Override
    public Map<String, String> sendToWxpayUnifiedOrder(String wxpayOrderXml) throws Exception {
        String wxpayOrderReturnXml = HttpClient4Util.getResponse4PostByString(wxpayConfig.getOrderUrl(), wxpayOrderXml, WxpayConstant.WXPAY_ENCODING_UTF8);
        return WxpayUtil.xmlToMap(wxpayOrderReturnXml);
    }
}

HttpClient4Util:发送HTTP请求工具

package com.lcywings.sbt.utils;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Created on 2020-09-29.
 * <p>
 * Author: zhukang
 * <p>
 * Description: http请求工具类
 */
public final class HttpClient4Util {
    /**
     * 注释内容
     */
    private static Logger log = LoggerFactory.getLogger(HttpClient4Util.class);

    /**
     * 连接超时时间
     */
    public static final int CONNECT_TIMEOUT = 10000;

    /**
     * 读取超时时间
     */
    public static final int SO_TIMEOUT = 30000;

    private HttpClient4Util() {
    }

    /**
     * <HttpClient直接连接接口> <功能详细描述>
     *
     * @param url      接口URL
     * @param params   NameValuePair参数
     * @param encoding 编码
     * @return
     * @see [类、类#方法、类#成员]
     */
    public static String getResponse4PostByMap(String url, Map<String, Object> params, String encoding) {
        return post(url, params, encoding);
    }

    /**
     * <HttpClient直接连接接口> <功能详细描述>
     *
     * @param url      接口URL
     * @param params   NameValuePair参数
     * @param encoding 编码
     * @return
     * @see [类、类#方法、类#成员]
     */
    public static String getResponse4PostByString(String url, String params, String encoding) {
        return post(url, params, encoding);
    }

    /**
     * <HttpClient直接连接接口> <功能详细描述>
     *
     * @param url
     * @param encoding
     * @return
     * @throws Exception
     * @see [类、类#方法、类#成员]
     */
    public static String getResponse4GetAsString(String url, String encoding) {
        return get(url, encoding);
    }

    /**
     * HttpClient直接连接接口,直接返回数据
     *
     * @param url 接口URL
     * @param params NameValuePair参数
     * @param encoding 编码
     * @return
     * @throws Exception
     */
    private static String post(String url, Map<String, Object> params, String encoding) {
        log.info("执行Http Post请求,地址: " + url + ",参数: " + params);

        String response = null;
        CloseableHttpClient httpClient = null;
        try {
            httpClient = HttpClientBuilder.create().build();
            HttpPost httpPost = new HttpPost(url);

            List<NameValuePair> nvps = new ArrayList<>(params.size());
            if (!params.isEmpty()) {
                Set<Entry<String, Object>> entrySet = params.entrySet();
                for (Entry<String, Object> entry : entrySet) {
                    nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue().toString()));
                }
            }

            // 请求方式为key=value的方式
            httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
            httpPost.addHeader("Accept-Language", "zh-cn");
            // 及时释放连接,不缓存连接(防止close_wait)
            httpPost.addHeader("Connection", "close");

            httpPost.setEntity(new UrlEncodedFormEntity(nvps, encoding));

            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(SO_TIMEOUT).setConnectTimeout(CONNECT_TIMEOUT).build();
            httpPost.setConfig(requestConfig);

            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                response = EntityUtils.toString(httpEntity, encoding).replaceAll("\r\n", "");
                EntityUtils.consume(httpEntity);
            }

            httpPost.abort();
        } catch (Exception e) {
            log.error("执行Http Post请求失败! Exception: " + e.getMessage());
        } finally {
            if(null != httpClient){
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        log.info("Http Post执行后响应内容: " + response);
        return response;
    }

    /**
     * HttpClient直接连接接口,直接返回数据,
     *
     * @param url 接口URL
     * @param params xml字符串参数
     * @param encoding 编码
     * @return
     * @throws Exception
     */
    private static String post(String url, String params, String encoding) {
        log.info("执行Http Post请求,地址: " + url + ",参数: " + params);

        String response = null;
        CloseableHttpClient httpClient = null;
        try {
            httpClient = HttpClients.createDefault();
            HttpPost httpPost = new HttpPost(url);

            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(SO_TIMEOUT).setConnectTimeout(CONNECT_TIMEOUT).build();
            httpPost.setConfig(requestConfig);

            StringEntity postEntity = new StringEntity(params, "UTF-8");

            //httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
            // httpPost.addHeader("Content-Type", "application/json");
            httpPost.addHeader("Content-Type", "text/xml");
            // 及时释放连接,不缓存连接(防止close_wait)
            httpPost.addHeader("Connection", "close");
            httpPost.setEntity(postEntity);

            HttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                response = EntityUtils.toString(httpEntity, encoding).replaceAll("\r\n", "");
                EntityUtils.consume(httpEntity);
            }

            httpPost.abort();
        } catch (Exception e) {
            log.error("执行Http Post请求失败! Exception: " + e.getMessage());
        } finally {
            if(null != httpClient){
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        log.info("Http Post执行后响应内容: " + response);
        return response;
    }


    /**
     * HttpClient直接连接接口,直接返回数据
     *
     * @param url 接口URL
     * @param encoding 编码
     * @return
     * @throws Exception
     */
    private static String get(String url, String encoding) {
        log.info("执行Http get请求,地址: " + url );

        String response = null;
        HttpClient httpClient = null;
        try {
            httpClient = HttpClientBuilder.create().build();
            HttpGet httpGet = new HttpGet(url);

            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(SO_TIMEOUT).setConnectTimeout(CONNECT_TIMEOUT).build();
            httpGet.setConfig(requestConfig);

            httpGet.addHeader("Content-Type", "application/x-www-form-urlencoded");
            httpGet.addHeader("Accept-Language", "zh-cn");
            // 及时释放连接,不缓存连接(防止close_wait)
            httpGet.addHeader("Connection", "close");


            HttpResponse httpResponse = httpClient.execute(httpGet);
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                response = EntityUtils.toString(httpEntity, encoding).replaceAll("\r\n", "");
                EntityUtils.consume(httpEntity);
            }

            httpGet.abort();
        } catch (Exception e) {
            e.printStackTrace();
            log.error("执行Http GET请求失败! Exception: " + e.getMessage());
        }
        log.info("Http GET执行后响应内容: " + response);
        return response;
    }

}

WxpayUtil:微信支付工具

package com.lcywings.sbt.utils;

import com.lcywings.sbt.constant.WxpayConstant;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
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.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * Created on 2021/8/10.
 * <p>
 * Author: wsw
 * <p>
 * Description: 微信支付自定义工具类
 */
public class WxpayUtil {

    /**
     * 随机对象
     */
    private static final Random RANDOM = new SecureRandom();

    /**
     * @return :
     * @author : wsw
     * @date : 2021/8/10
     * @description :  生成32位随机字符串
     */
    public static String generateNonceStrUseRandom() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = WxpayConstant.WXPAY_SYMBOLS.charAt(RANDOM.nextInt(WxpayConstant.WXPAY_SYMBOLS.length()));
        }
        return new String(nonceChars);
    }

    /**
     * @return :
     * @author : wsw
     * @date : 2021/8/10
     * @description :  生成指定长度随机字符串,使用UUID
     */
    public static String generateNonceStrUseUUID(int length) {

        // TODO length长度的有效性校验,不能超出32位等
        return UUID.randomUUID().toString().replace("-", "").substring(0, length);
    }

    /**
     * @return :
     * @author : wsw
     * @date : 2021/8/10
     * @description :  生成微信支付上浮交易订单号
     */
    public static String generateOutTrade() {
        // java8日期格式化
        DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
        LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        return WxpayConstant.WXPAY_ORDER_PREFIX + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + generateNonceStrUseUUID(6);
    }

    /**
     * @author : Lcywings
     * @date : 2021/8/10 15:25
     * @acl : true
     * @description : 生成签名
     */
    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, WxpayConstant.WXPAY_SIGN_TYPE_MD5);
    }

    /**
     * @author : Lcywings
     * @date : 2021/8/10 15:29
     * @acl : true
     * @description : 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     */
    public static String generateSignature(final Map<String, String> data, String key, String 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(WxpayConstant.WXPAY_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 (WxpayConstant.WXPAY_SIGN_TYPE_MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        } else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }

    /**
     * @author : Lcywings
     * @date : 2021/8/10 15:35
     * @acl : true
     * @description : 生成 MD5
     */
    public static String MD5(String data) throws Exception {
        java.security.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();
    }

    /**
     * @author : Lcywings
     * @date : 2021/8/10 15:41
     * @acl : true
     * @description : 将Map转换为XML格式的字符串
     */
    public static String mapToXml(Map<String, String> data) throws Exception {
        org.w3c.dom.Document document = WXPayXmlUtil.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(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        } catch (Exception ex) {
        }
        return output;
    }

    /**
     * @author : Lcywings
     * @date : 2021/8/10 16:34
     * @acl : true
     * @description : XML格式字符串转换为Map
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WXPayXmlUtil.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) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            throw ex;
        }

    }

}

WXPayXmlUtil:微信支付工具的工具

package com.lcywings.sbt.utils;

import org.w3c.dom.Document;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
 * 2018/7/3
 */
public final class WXPayXmlUtil {
    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();
    }
}

课后作业:

  1. 完成上课统一下单接口,可以返回支付链接,并可以扫码支付,注意金额:1代表1分钱
  2. 完成官方统一查询订单接口,可以支付一笔订单,然后到微信官方查询该订单的支付结果(out_trade_no在日志里拷贝出来)

核心Url:(配置中添加)

# 统一查询订单URL
wechat.pay.orderQuery=https://api.mch.weixin.qq.com/pay/orderquery

追加业务:

控制层接口

 /**
     * @author : Lcywings
     * @date : 2021/8/10 17:58
     * @acl : true
     * @description : 查询订单
     */
    @RequestMapping("/orderquery")
    public RequestResult<Map<String, String>> wechatPayOrderquery(@RequestParam String out_trade_no) throws Exception {
        // 封装微信统一查询订单的参数(xml格式)
        String wxpayOrderQueryXml = wxpayService.getWxpayOrderQueryXml(out_trade_no);

        log.info("------ 1 请求微信官方统一查询订单参数:{} ------", wxpayOrderQueryXml);

        // 请求微信官方进行统一查询订单
        Map<String, String> wxpayOrderQueryReturnMap = wxpayService.sendToWxpayOrderQuery(wxpayOrderQueryXml);

        log.info("------ 2  请求微信官方统一查询订单返回结果:{} ------", wxpayOrderQueryReturnMap);

//        // 自定义返回数据集合
//        Map<String, String> wxpayOrderQueryData = new HashMap<>();

        // 结果解析和返回
        if (WxpayConstant.WXPAY_RETURN_CODE_SUCCESS.equals(wxpayOrderQueryReturnMap.get("return_code"))
                && WxpayConstant.WXPAY_RETURN_CODE_SUCCESS.equals(wxpayOrderQueryReturnMap.get("result_code"))) {
//            // 交易类型
//            wxpayOrderQueryData.put("trade_type", wxpayOrderQueryReturnMap.get("trade_type"));
//
//            // 预支付交易会话标识
//            wxpayOrderQueryData.put("prepay_id", wxpayOrderQueryReturnMap.get("prepay_id"));
//
//            // 二维码链接
//            wxpayOrderQueryData.put("code_url", wxpayOrderQueryReturnMap.get("code_url"));

            return ResultBuildUtil.success(wxpayOrderQueryReturnMap);
        }

        return ResultBuildUtil.fail(wxpayOrderQueryReturnMap);
    }

服务接口

/**
 * @author : Lcywings
 * @date : 2021/8/10 16:23
 * @acl : true
 * @description : 封装微信统一查询订单接口参数,注意:要求参数的顺序必须是按照字典序排列,结要转换为xml格式字符串
 */
String getWxpayOrderQueryXml(String out_trade_no) throws Exception;

/**
 * @author : Lcywings
 * @date : 2021/8/10 16:23
 * @acl : true
 * @description : 请求微信官方进行统一查询订单,获取查询结果
 */
Map<String, String> sendToWxpayOrderQuery(String wxpayOrderQueryXml) throws Exception;

服务实现

@Override
public String getWxpayOrderQueryXml(String out_trade_no) throws Exception {
    // 微信统一查询单号接口,要求参数按照参数名的字典序排列,使用treemap
    Map<String, String> paramsMap = new TreeMap<>();

    // 公众账号ID  appid
    paramsMap.put("appid", wxpayConfig.getAppId());

    // 商户号 mch_id
    paramsMap.put("mch_id", wxpayConfig.getMchId());

    // 商户订单号 mch_id
    paramsMap.put("out_trade_no", out_trade_no);

    // 随机字符串 mch_id
    paramsMap.put("nonce_str", WxpayUtil.generateNonceStrUseRandom());

    // 签名 mch_id
    paramsMap.put("sign", WxpayUtil.generateSignature(paramsMap, wxpayConfig.getMchkey()));

    //map to xml并返回
    return WxpayUtil.mapToXml(paramsMap);
}

@Override
public Map<String, String> sendToWxpayOrderQuery(String wxpayOrderQueryXml) throws Exception {
    String wxpayOrderReturnXml = HttpClient4Util.getResponse4PostByString(wxpayConfig.getOrderQuery(), wxpayOrderQueryXml, WxpayConstant.WXPAY_ENCODING_UTF8);
    return WxpayUtil.xmlToMap(wxpayOrderReturnXml);
}

Q.E.D.