7.微信支付-如何对接接口和回调
微信开发文档
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_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加密)
-
发送请求url,使用Map<S,S>接收
- 使用工具类发送请求
- url头是配置中的orderUrl
- 参数时 1 中拼接好的xml字符串
- encoding是常量中的utf-8
-
自定义返回数据集合并判断是否可用
- return_code是否为SUCCESS(SUCCESS是定义好的常量)
- result_code是否为SUCCESS(SUCCESS是定义好的常量)
-
拿到返回的数据
- 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分钱
- 完成官方统一查询订单接口,可以支付一笔订单,然后到微信官方查询该订单的支付结果(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.