springboot微信支付+退款全流程

之前写了支付接口,现在补充一下 微信退款接口



import com.alibaba.fastjson.JSON;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.xhsoft.device.entity.DeviceOrdersEntity;
import com.xhsoft.device.feign.IDeviceOrdersClient;
import com.xhsoft.device.vo.DeviceOrdersVO;
import com.xhsoft.mp.config.PayConfig;
import com.xhsoft.mp.entity.OrderInfo;
import com.xhsoft.mp.service.OrderInfoService;
import com.xhsoft.mp.vo.OrderInfoVO;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import org.springblade.core.social.props.SocialProperties;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springblade.core.tool.api.R;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 小程序支付
 * @Author Lenovo
 * @Date 2022/4/6 11:21
 */
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/v1/order")
@Api(value = "首页显示", tags = "查询结果接口")
public class PayController {

	private final OrderInfoService orderInfoService;

	private final SocialProperties socialProperties;

	@Resource
	private PayConfig payConfig;

	@Resource
	private IDeviceOrdersClient deviceOrdersClient;


	/**
	 * 调用统一下单接口,并组装生成支付所需参数对象.
	 *
	 * @param orderInfoVO 统一下单请求参数
	 */
	@PostMapping("/unifiedOrder")
	public R unifiedOrder(HttpServletRequest request, @RequestBody DeviceOrdersEntity orderInfoVO) {
		return orderInfoService.unifiedOrder(request, orderInfoVO);
	}

	/**
	 * 支付回调(微信)
	 *
	 * @param xmlData  微信XML数据
	 * @param orderId  订单id
	 * @return 返回支付结果
	 */
	@PostMapping("/notify-order-wx/{orderId}")
	public String notifyOrderWx(HttpServletRequest request, HttpServletResponse response,
								@RequestBody String xmlData,
								@PathVariable("orderId") Long orderId
								) throws IOException {
		AuthConfig config = socialProperties.getOauth().get(AuthDefaultSource.WECHAT_MP);



		log.info("支付回调(微信):" + xmlData);
		if( orderId == null ){
			System.out.println("验签失败");
			response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
		}
		String resXml = "";
		try {
			//通过商家id查询支付配置
//			PayConfig payConfig = payConfigService.selectPayConfigByTenantId(tenantId,"1");
			Map<String, Object> ResultMap = orderInfoService.WeChatPayCallback(xmlData, payConfig.getMchKey());
			Map<String, String> WxResultMapData = new HashMap<>();
			if (ResultMap.get("Verify").equals("YES")) {
				//验签成功
				System.out.println("验签成功");
				WxResultMapData = (Map<String, String>) ResultMap.get("data");
				System.out.println("WxResultMapData:" + JSON.toJSONString(WxResultMapData));
				log.info("收到微信回调:{}", JSON.toJSONString(WxResultMapData));
				log.info("查询订单");
				log.info("查询订单id:{}", orderId);
				R<DeviceOrdersVO> deviceOrdersVOR = deviceOrdersClient.detailById(orderId);
				if (!deviceOrdersVOR.isSuccess() || deviceOrdersVOR.getData() == null) {
					log.info("查询订单失败");
					log.info("无法变更订单状态,通知微信验签失败");
					return WxPayNotifyResponse.fail("失败");
				}
				System.out.println("通知微信验签成功");
				//自信业务逻辑处理
				log.info("自己的业务处理");
				DeviceOrdersVO ordersVO = deviceOrdersVOR.getData();
				// 验证金额
//				BigDecimal payAmount = new BigDecimal(WxResultMapData.get("total_fee"));
//				BigDecimal amount = ordersVO.getAmount();
//				if(payAmount == null || amount == null || amount.compareTo(payAmount) != 0) {
//					log.error("微信支付回调时,支付金额{}与待支付金额{}不符",payAmount,amount);
//					return WxPayNotifyResponse.fail("支付金额不符");
//				}
				ordersVO.setPayStatus(1);// 支付状态 已支付
				ordersVO.setPayTime(new Date());// 支付时间
				// 输出 更新的订单信息
				log.info("回调方法,要更新的订单信息:{}", JSON.toJSONString(ordersVO));
				R updateR = deviceOrdersClient.submit(ordersVO);
				if (!updateR.isSuccess()) {
					log.info("无法变更订单状态,通知微信验签失败");
					return WxPayNotifyResponse.fail("失败");
				}else {
					log.info("变更订单状态成功");
				}
//				orderInfoService.notifyOrder(orderInfo,tenantId,orderId,typeId,WxResultMapData);
				resXml = String.valueOf(ResultMap.get("returnWeChat"));
			} else if (ResultMap.get("Verify").equals("NO")) {
				resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[" + WxResultMapData.get("err_code_des") + "]]></return_msg>" + "</xml> ";
			}
		}catch (Exception e) {
			throw new RuntimeException(e);
		}
		return WxPayNotifyResponse.success("成功");
	}

	/**
	 * 微信退款接口
	 *
	 * @param orderId     订单id
	 * @param refundTotal 退款金额
	 * @return 退款结果
	 */
	@PostMapping("/refund/{orderId}/{refundTotal}")
	public R refund(@PathVariable("orderId") Long orderId, @PathVariable("refundTotal") BigDecimal refundTotal) {
		return orderInfoService.refund(orderId, refundTotal);
	}

	/**
	 * 退款回调(微信)
	 *
	 * @param xmlData 微信XML数据
	 * @return 返回退款结果
	 */
	@PostMapping("/notify-refund-wx")
	public String notifyRefundWx(@RequestBody String xmlData) {
		try {
			Map<String, Object> resultMap = orderInfoService.WeChatRefundCallback(xmlData, payConfig.getMchKey());
			if ("YES".equals(resultMap.get("Verify"))) {
				// 验签成功,处理业务逻辑
				log.info("退款回调验签成功,处理业务逻辑");
				return WxPayNotifyResponse.success("成功");
			} else {
				log.info("退款回调验签失败");
				return WxPayNotifyResponse.fail("失败");
			}
		} catch (Exception e) {
			log.error("退款回调处理异常", e);
			return WxPayNotifyResponse.fail("失败");
		}
	}


}
public interface OrderInfoService {


	R unifiedOrder(HttpServletRequest request, DeviceOrdersEntity orderInfoVO);

	 Map<String, Object> WeChatPayCallback(String xmlData, String apiKey);


	R refund(Long orderId, BigDecimal refundTotal);

	Map<String, Object> WeChatRefundCallback(String xmlData, String mchKey);
}


import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import com.xhsoft.device.entity.DeviceOrdersEntity;
import com.xhsoft.device.feign.IDeviceOrdersClient;
import com.xhsoft.device.vo.DeviceOrdersVO;
import com.xhsoft.mp.config.PayConfig;
import com.xhsoft.mp.entity.WechatUser;
import com.xhsoft.mp.service.OrderInfoService;
import com.xhsoft.mp.util.*;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.config.AuthDefaultSource;
import org.springblade.common.constant.HeaderConstant;
import org.springblade.core.social.props.SocialProperties;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.ObjectUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class OrderInfoServiceImpl implements OrderInfoService {

	@Resource
	private SocialProperties socialProperties;

	@Resource
	private IDeviceOrdersClient deviceOrdersClient;

	@Resource
	private WechatUserServiceImpl wechatUserService;

	@Autowired
	private PayConfig payConfig;
	@Override
	public R unifiedOrder(HttpServletRequest request, DeviceOrdersEntity orderInfoVO) {
		String openid = request.getHeader(HeaderConstant.TOKEN);
		WechatUser user = wechatUserService.getOne(Wrappers.lambdaQuery(WechatUser.class).eq(WechatUser::getOpenid,openid));
		if (user == null) {
			return R.fail("用户信息不存在");
		}


		AuthConfig config = socialProperties.getOauth().get(AuthDefaultSource.WECHAT_MP);
		String clientId = config.getClientId(); // 小程序id



		//根据typeId查询支付金额  根据自己的业务逻辑自行处理
//		SysFunctionType sysFunctionType = sysFunctionTypeMapper.selectFunctionTypeById(orderInfoVO.getTypeId());
		//通过customId 查询用户信息  根据自己的业务逻辑自行处理
//		SysCustom sysCustom = sysCustomMapper.selectSysCustomById(orderInfoVO.getCustomId());

		//根据自己的业务逻辑自行处理 OrderInfo为我自己业务中的实体类
		DeviceOrdersEntity orderInfo = new DeviceOrdersEntity();
		// 下单商品id
		orderInfo.setGoodsId(orderInfoVO.getGoodsId());
		// 生成雪花id
		orderInfo.setId(IdUtil.getSnowflake(0,0).nextId());
		// 下单用户
		orderInfo.setOpenid(openid);
		// 充值给哪个用户
		orderInfo.setUserId(orderInfoVO.getUserId());
		orderInfo.setAmount(orderInfoVO.getAmount());// 订单金额
		orderInfo.setPayStatus(0);// 0 未支付 1 已支付
		//支付类型
		orderInfo.setPayMethod(1);// 1 微信支付 2 支付宝支付
		// 单价 折扣 购买天数 需要用户传来 后台与之匹配
		orderInfo.setUnitPrice(orderInfoVO.getUnitPrice());
		orderInfo.setDiscount(orderInfoVO.getDiscount());
		orderInfo.setOrderDays(orderInfoVO.getOrderDays());

//		orderInfo.setPaymentPrice(sysFunctionType.getPrice());
		String body = "测试支付下单商品";
		body = body.length() > 40 ? body.substring(0,39) : body;
		//更新编号防止不同终端微信报重复订单号
		orderInfo.setOrderNumber(IdUtil.getSnowflake(0,0).nextIdStr());
		Map<String, String> req = new HashMap<>();
		// JSAPI支付必须传openid]
		req.put("openid", openid);
		//公众号
		req.put("appid", clientId);
		// 商户号
		req.put("mch_id", payConfig.getMchId());
		// 32位随机字符串
		req.put("nonce_str", WXPayUtil.generateNonceStr());
		// 商品描述
		req.put("body", body);
		// 商户订单号
		req.put("out_trade_no", orderInfo.getOrderNumber());
		// 标价金额(分)
		String total_fee = String.valueOf(MoneyUtils.yuanToFen(String.valueOf(orderInfo.getAmount())));
		log.info("传入的金额=》getAmount:{}", orderInfo.getAmount());
		log.info("标价金额=》total_fee:{}", total_fee);
		req.put("total_fee", total_fee);
		// 终端IP
		req.put("spbill_create_ip", request.getRemoteAddr());
		// 回调地址+携带的返回参数  domain 为配置的域名[不可为ip地址]
		req.put("notify_url", payConfig.getDomain()+"/api/xxxxxx/xx/xx/notify-order-wx"+"/"+orderInfo.getId());
		// 交易类型
		req.put("trade_type", "JSAPI"); //JSAPI 是微信公众号支付或小程序支付的一种交易类型 //NATIVE 交易类型主要用于线下扫码支付场景
		try {
			// 签名
			req.put("sign", WXPayUtil.generateSignature(req, payConfig.getMchKey(), WXPayConstants.SignType.MD5));
			String xmlBody = WXPayUtil.generateSignedXml(req, payConfig.getMchKey());
			System.err.println(String.format("微信支付预下单请求 xml 格式:\n%s", xmlBody));
			String result = WxChatPayCommonUtil.httpsRequest(WeChatPayUrl.Uifiedorder, "POST", xmlBody);
			log.info("保存订单信息");
			// 保存订单信息
			R submitR = deviceOrdersClient.submit(orderInfo);
			if (submitR.isSuccess()) {
				log.info("保存订单信息成功");
			} else {
				log.info("保存订单信息失败");
			}


			System.err.println(String.format("%s", result));
			Map<String, String> WxResultMap = WXPayUtil.xmlToMap(result);
			WxResultMap.put("orderNo",orderInfo.getOrderNumber());
			if (ObjectUtil.isNotEmpty(WxResultMap.get("return_code")) && WxResultMap.get("return_code").equals("SUCCESS")) {
				if (WxResultMap.get("result_code").equals("SUCCESS")) {
					System.out.println("预下单成功");
					System.out.println("QrCode:"+WxResultMap.get("code_url"));

					// 二次签名 让前端唤起支付
					SignInfo signInfo = new SignInfo();
					signInfo.setAppId(config.getClientId());
					long time = System.currentTimeMillis() / 1000;
					signInfo.setTimeStamp(String.valueOf(time));
					signInfo.setNonceStr(RandomStringGenerator.getRandomStringByLength(32));
					signInfo.setRepay_id("prepay_id=" + WxResultMap.get("prepay_id"));
					signInfo.setSignType("MD5");
					//生成签名
					String sign1 = Signature.getSign(signInfo, payConfig.getMchKey());
					Map<String, String> payInfo = new HashMap<>();
					payInfo.put("timeStamp", signInfo.getTimeStamp());
					payInfo.put("nonceStr", signInfo.getNonceStr());
					payInfo.put("package", signInfo.getRepay_id());
					payInfo.put("signType", signInfo.getSignType());
					payInfo.put("paySign", sign1);
					// 直接返回二次签名给前端
					return R.data(payInfo);
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		return R.fail("下单失败");
	}

	@Override
	public Map<String, Object> WeChatPayCallback(String xmlData, String apiKey) {
		Map<String, Object> ResultMap = new HashMap<String, Object>();
		//解析到微信返回过来的xml数据
		try {
			//xml转Map
			Map<String, String> WxResultMap = WXPayUtil.xmlToMap(xmlData);
			//验证签名
			boolean SignStatus = WXPayUtil.isSignatureValid(WxResultMap, apiKey);
			if (SignStatus) {
				//验证成功
				//要返回给微信的xml数据
				String returnWeChat = WxChatPayCommonUtil.setReturnXml("SUCCESS", "OK");
				ResultMap.put("Verify", "YES");
				ResultMap.put("returnWeChat", returnWeChat);
				ResultMap.put("data", WxResultMap);
			} else {
				//验证失败(表示可能接口被他人调用  需要留意)
				ResultMap.put("Verify", "NO");
				ResultMap.put("msg", "验签失败。");
			}
			return ResultMap;
		} catch (IOException e) {
			throw new RuntimeException(e);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}


	@Override
	public R refund(Long orderId, BigDecimal refundTotal) {
		R<DeviceOrdersVO> deviceOrdersVOR = deviceOrdersClient.detailById(orderId);
		if (!deviceOrdersVOR.isSuccess() || deviceOrdersVOR.getData() == null) {
			return R.fail("查询订单失败");
		}
		DeviceOrdersVO ordersVO = deviceOrdersVOR.getData();
		if (ordersVO.getPayStatus() != 1) {
			return R.fail("订单未支付,不能退款");
		}
		AuthConfig config = socialProperties.getOauth().get(AuthDefaultSource.WECHAT_MP);
		String clientId = config.getClientId();
		Map<String, String> req = new HashMap<>();
		req.put("appid", clientId);
		req.put("mch_id", payConfig.getMchId());
		req.put("nonce_str", WXPayUtil.generateNonceStr());
		req.put("out_trade_no", ordersVO.getOrderNumber());
		req.put("out_refund_no", IdUtil.getSnowflake(0, 0).nextIdStr());
		req.put("total_fee", String.valueOf(MoneyUtils.yuanToFen(String.valueOf(ordersVO.getAmount()))));
		req.put("refund_fee", String.valueOf(MoneyUtils.yuanToFen(String.valueOf(refundTotal))));
		req.put("notify_url", payConfig.getDomain() + "/api/xxxxx/xx/xx/notify-refund-wx");
		try {
			req.put("sign", WXPayUtil.generateSignature(req, payConfig.getMchKey(), WXPayConstants.SignType.MD5));
			String xmlBody = WXPayUtil.generateSignedXml(req, payConfig.getMchKey());
			System.err.println(String.format("微信退款请求 xml 格式:\n%s", xmlBody));
			String result = WxChatPayCommonUtil.httpsRequest(WeChatPayUrl.Refund, "POST", xmlBody);
			System.err.println(String.format("%s", result));
			Map<String, String> WxResultMap = WXPayUtil.xmlToMap(result);
			if ("SUCCESS".equals(WxResultMap.get("return_code")) && "SUCCESS".equals(WxResultMap.get("result_code"))) {
				ordersVO.setPayStatus(2); // 退款状态 已退款
				// TODO: 2025/4/6   后续需要完善业务逻辑  退款并且恢复用户使用时长等 
				R updateR = deviceOrdersClient.submit(ordersVO);
				if (updateR.isSuccess()) {
					log.info("变更订单状态为已退款成功");
					return R.data(WxResultMap);
				} else {
					log.info("变更订单状态为已退款失败");
					return R.fail("变更订单状态为已退款失败");
				}
			} else {
				return R.fail("退款失败: " + WxResultMap.get("err_code_des"));
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public Map<String, Object> WeChatRefundCallback(String xmlData, String apiKey) {
		Map<String, Object> ResultMap = new HashMap<>();
		try {
			Map<String, String> WxResultMap = WXPayUtil.xmlToMap(xmlData);
			boolean signStatus = WXPayUtil.isSignatureValid(WxResultMap, apiKey);
			if (signStatus) {
				String returnWeChat = WxChatPayCommonUtil.setReturnXml("SUCCESS", "OK");
				ResultMap.put("Verify", "YES");
				ResultMap.put("returnWeChat", returnWeChat);
				ResultMap.put("data", WxResultMap);
			} else {
				ResultMap.put("Verify", "NO");
				ResultMap.put("msg", "验签失败。");
			}
			return ResultMap;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}



}

需要注意的是  退款 必须要加上 证书  而 支付不需要,所以在发送请求的时候  需要修改请求方法:

package com.xhsoft.mp.util;

import com.xhsoft.mp.config.PayConfig;
import org.apache.commons.lang3.StringUtils;
import javax.net.ssl.*;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;

/**
 * 微信支付工具类
 */
public class WxChatPayCommonUtil {

	private static PayConfig payConfig;

	public static void setPayConfig(PayConfig config) {
		payConfig = config;
	}
	/**
	 * 发送 http 请求
	 * @param requestUrl    请求路径
	 * @param requestMethod 请求方式(GET/POST/PUT/DELETE/...)
	 * @param outputStr     请求参数体
	 * @return 结果信息
	 */
	public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
		try {
			// 加载证书
			KeyStore keyStore = KeyStore.getInstance("PKCS12");
			// 使用类加载器获取证书输入流
			InputStream instream = WxChatPayCommonUtil.class.getClassLoader().getResourceAsStream("apiclient_cert.p12");
			if (instream == null) {
				throw new FileNotFoundException("未找到 apiclient_cert.p12 证书文件");
			}
			try {
				// 请将此处替换为你的商户ID,通常证书密码和商户ID相同
				keyStore.load(instream, payConfig.getMchId().toCharArray());
			} finally {
				instream.close();
			}

			// 创建SSL上下文
			SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
			KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
			// 请将此处替换为你的商户ID,通常证书密码和商户ID相同
			kmf.init(keyStore, payConfig.getMchId().toCharArray());
			TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
				@Override
				public java.security.cert.X509Certificate[] getAcceptedIssuers() {
					return null;
				}

				@Override
				public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
				}

				@Override
				public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
				}
			}};
			sslContext.init(kmf.getKeyManagers(), trustAllCerts, new SecureRandom());

			URL url = new URL(requestUrl);
			HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
			conn.setSSLSocketFactory(sslContext.getSocketFactory());
			conn.setHostnameVerifier((hostname, session) -> true);

			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setUseCaches(false);
			// 设置请求方式(GET/POST)
			conn.setRequestMethod(requestMethod);
			conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
			// 当outputStr不为null时向输出流写数据
			if (null != outputStr) {
				OutputStream outputStream = conn.getOutputStream();
				// 注意编码格式
				outputStream.write(outputStr.getBytes("UTF-8"));
				outputStream.close();
			}
			// 从输入流读取返回内容
			InputStream inputStream = conn.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String str = null;
			StringBuffer buffer = new StringBuffer();
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			// 释放资源
			bufferedReader.close();
			inputStreamReader.close();
			inputStream.close();
			inputStream = null;
			conn.disconnect();
			return buffer.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 获取ip
	 * @param request 请求
	 * @return ip 地址
	 */
	public static String getIp(HttpServletRequest request) {
		if (request == null) {
			return "";
		}
		String ip = request.getHeader("X-Requested-For");
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("X-Forwarded-For");
		}
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("HTTP_CLIENT_IP");
		}
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("HTTP_X_FORWARDED_FOR");
		}
		if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}

	/**
	 * 从流中读取微信返回的xml数据
	 * @param httpServletRequest
	 * @return
	 * @throws IOException
	 */
	public static String readXmlFromStream(HttpServletRequest httpServletRequest) throws IOException {
		InputStream inputStream = httpServletRequest.getInputStream();
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
		final StringBuffer sb = new StringBuffer();
		String line = null;
		try {
			while ((line = bufferedReader.readLine()) != null) {
				sb.append(line);
			}
		} finally {
			bufferedReader.close();
			inputStream.close();
		}
		return sb.toString();
	}

	/**
	 * 设置返回给微信服务器的xml信息
	 * @param returnCode
	 * @param returnMsg
	 * @return
	 */
	public static String setReturnXml(String returnCode, String returnMsg) {
		return "<xml><return_code><![CDATA[" + returnCode + "]]></return_code><return_msg><![CDATA[" + returnMsg + "]]></return_msg></xml>";
	}
}

还有就是 注入配置的问题,你需要拿到咱们的商户id:

@Data
@Component
@ConfigurationProperties(prefix = "pay")
public class PayConfig {

	private String mchId;

	private String mchKey;

	// 域名
	private String domain;
}
@Configuration
public class MpConfiguration {

	@Autowired
	private PayConfig payConfig;

	/**
	 *  PostConstruct注解表示在依赖注入完成后,在对象的初始化阶段调用该方法。
	 *  这里的目的是在PayConfig实例被成功注入后,将其设置到WxChatPayCommonUtil工具类中,
	 *  以便WxChatPayCommonUtil类在后续执行httpsRequest方法(例如加载微信支付证书时)
	 *  时能够获取到有效的PayConfig配置信息,进而正确地使用其中的商户ID等配置来进行操作。
	 */
	@PostConstruct
	public void setupWxPayUtil() {
		WxChatPayCommonUtil.setPayConfig(payConfig);
	}
}

下面 分享一下 支付所需要的工具类:

public class HexUtils {
	public static String bytesToHexStr(byte[] bytes) {
		StringBuilder hexString = new StringBuilder();
		for (byte b : bytes) {
			String hex = Integer.toHexString(0xff & b);
			if (hex.length() == 1) hexString.append('0');
			hexString.append(hex);
		}
		return hexString.toString();
	}
}


package com.xhsoft.mp.util;

import java.security.MessageDigest;

/**
 * @author rsz
 * @version 1.0.0
 * @ClassName MD5.java
 * @Description MD5工具类
 * @createTime 2022年04月09日 12:58:00
 */
public class MD5 {
	private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7",
		"8", "9", "a", "b", "c", "d", "e", "f"};

	/**
	 * 转换字节数组为16进制字串
	 *
	 * @param b 字节数组
	 * @return 16进制字串
	 */
	public static String byteArrayToHexString(byte[] b) {
		StringBuilder resultSb = new StringBuilder();
		for (byte aB : b) {
			resultSb.append(byteToHexString(aB));
		}
		return resultSb.toString();
	}

	/**
	 * 转换byte到16进制
	 *
	 * @param b 要转换的byte
	 * @return 16进制格式
	 */
	private static String byteToHexString(byte b) {
		int n = b;
		if (n < 0) {
			n = 256 + n;
		}
		int d1 = n / 16;
		int d2 = n % 16;
		return hexDigits[d1] + hexDigits[d2];
	}

	/**
	 * MD5编码
	 *
	 * @param origin 原始字符串
	 * @return 经过MD5加密之后的结果
	 */
	public static String MD5Encode(String origin) {
		String resultString = null;
		try {
			resultString = origin;
			MessageDigest md = MessageDigest.getInstance("MD5");
			resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return resultString;
	}

}
package com.xhsoft.mp.util;




import java.math.BigDecimal;

/**
 * 钱 工具类
 *
 * Created by YouHan on 2019-06-28 09:12:00
 * Copyright © 2019 YouHan All rights reserved.
 */
public class MoneyUtils {
	public static final String YUAN = "元";
	public static final String FEN = "分";

	/**
	 * 元转分
	 *
	 * @param s
	 * @return java.lang.Integer
	 * @date 2020/9/10 9:03
	 * @author YouHan
	 */
	public static Integer yuanToFen(String s) {
		if (!YouNumberUtil.isNumber(s)) {
			return 0;
		}

		return new BigDecimal(s).multiply(new BigDecimal(100)).intValue();
	}

	/**
	 * 处理分
	 *
	 * @param s
	 * @return java.lang.Integer
	 * @author YouHan
	 * @date 2022/7/23
	 */
	public static Integer handleFen(String s) {
		if (!YouNumberUtil.isNumber(s)) {
			return 0;
		}

		return new BigDecimal(s).intValue();
	}

	/**
	 * 分转元
	 *      可以为正负小数(这里保留2位小数)
	 *
	 * @param s
	 * @return java.lang.String
	 * @date 2020/9/10 9:01
	 * @author YouHan
	 */
	public static String fenToYuan(String s) {
		if (!YouNumberUtil.isNumber(s) || "0".equals(s) || "-0".equals(s)) {
			return "0.00";
		}

		return new BigDecimal(s)
			.divide(new BigDecimal(100))
			.setScale(2, BigDecimal.ROUND_DOWN)
			.toString();
	}

	/**
	 * 处理元
	 *      可以为正负小数(这里保留2位小数)
	 *
	 * @param s
	 * @return java.lang.String
	 * @author YouHan
	 * @date 2022/7/23
	 */
	public static String handleYuan(String s) {
		if (!YouNumberUtil.isNumber(s) || "0".equals(s) || "-0".equals(s)) {
			return "0.00";
		}

		return new BigDecimal(s)
			.setScale(2, BigDecimal.ROUND_DOWN)
			.toString();
	}

}

package com.xhsoft.mp.util;


import com.thoughtworks.xstream.annotations.XStreamAlias;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;

/**
 */
public class Signature {

	/**
	 * 签名算法
	 *
	 * @param o 要参与签名的数据对象
	 * @return 签名
	 * @throws IllegalAccessException
	 */
	public static String getSign(Object o,String mchKey) throws IllegalAccessException {
		ArrayList<String> list = new ArrayList<String>();
		Class cls = o.getClass();
		Field[] fields = cls.getDeclaredFields();
		for (Field f : fields) {
			f.setAccessible(true);
			if (f.get(o) != null && f.get(o) != "") {
				String name = f.getName();
				XStreamAlias anno = f.getAnnotation(XStreamAlias.class);
				if (anno != null)
					name = anno.value();
				list.add(name + "=" + f.get(o) + "&");
			}
		}
		int size = list.size();
		String[] arrayToSort = list.toArray(new String[size]);
		Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < size; i++) {
			sb.append(arrayToSort[i]);
		}
		String result = sb.toString();
		result += "key=" + mchKey;
		System.out.println("签名数据:" + result);
		result = MD5.MD5Encode(result).toUpperCase();
		return result;
	}

	public static String getSign(Map<String, Object> map,String mchKey) {
		ArrayList<String> list = new ArrayList<String>();
		for (Map.Entry<String, Object> entry : map.entrySet()) {
			if (entry.getValue() != "") {
				list.add(entry.getKey() + "=" + entry.getValue() + "&");
			}
		}
		int size = list.size();
		String[] arrayToSort = list.toArray(new String[size]);
		Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < size; i++) {
			sb.append(arrayToSort[i]);
		}
		String result = sb.toString();
		result += "key=" + mchKey;
		//Util.log("Sign Before MD5:" + result);
		result = MD5.MD5Encode(result).toUpperCase();
		//Util.log("Sign Result:" + result);
		return result;
	}
}
/**
 * 微信支付接口地址
 *
 */
public class WeChatPayUrl {
	//统一下单预下单接口url
	public static final String Uifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder";
	//订单状态查询接口URL
	public static final String Orderquery = "https://api.mch.weixin.qq.com/pay/orderquery";
	//订单申请退款
	public static final String Refund = "https://api.mch.weixin.qq.com/secapi/pay/refund";
	//付款码 支付
	public static final String MicroPay = "https://api.mch.weixin.qq.com/pay/micropay";
	//微信网页授权 获取“code”请求地址
	public static final String GainCodeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize";

}
package com.xhsoft.mp.util;



import org.apache.commons.lang3.StringUtils;

import java.util.concurrent.ThreadLocalRandom;

/**
 * 数字 client
 *
 * Created by YouHan on 2020-09-09 13:28:40
 * Copyright © 2021 YouHan All rights reserved.
 */
public class YouNumberUtil {
	/**
	 * 整数正则表达式
	 */
	public static final String INTEGER_REGEX = "^[-\\+]?[\\d]*$";

	/**
	 * 数字正则表达式
	 */
	public static final String NUMBER_REGEX = "^-?\\d+(\\.\\d+)?$";

	/**
	 * 是否是整数
	 *
	 * @param s
	 * @return java.lang.Boolean
	 * @date 2020/9/12 8:38
	 * @author YouHan
	 */
	public static boolean isInteger(String s) {
		if (StringUtils.isBlank(s)) {
			return false;
		}

		return s.matches(INTEGER_REGEX);
	}

	/**
	 * 判断给定字符串是否为十六进制数
	 *
	 * @param value 值
	 * @return 是否为十六进制
	 */
	public static boolean isHex(String value) {
		final int index = (value.startsWith("-") ? 1 : 0);
		if (value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index)) {
			try {
				Long.decode(value);
			} catch (NumberFormatException e) {
				return false;
			}
			return true;
		}

		return false;
	}

	/**
	 * 是否是数字(包括小数)
	 *
	 * @param s
	 * @return java.lang.Boolean
	 * @date 2020/9/9 14:01
	 * @author YouHan
	 */
	public static boolean isNumber(String s) {
		if (StringUtils.isBlank(s)) {
			return false;
		}

		return s.matches(NUMBER_REGEX);
	}

	/**
	 * 十进制转十六进制
	 *
	 * @param n 十进制数
	 * @return java.lang.String
	 * @date 2019/4/8 09:22
	 * @author YouHan
	 */
	public static String intToHex(Integer n) {
		if (null == n) {
			return null;
		}

		return String.format("%X", n);
	}

	/**
	 * 十进制转十六进制
	 *
	 * @param n 十进制数
	 * @return java.lang.String
	 * @date 2019/4/8 09:22
	 * @author YouHan
	 */
	public static String longToHex(Long n) {
		if (null == n) {
			return null;
		}

		return String.format("%X", n);
	}

	/**
	 * 十进制转十六进制
	 *
	 * @param n
	 * @param length
	 * @return java.lang.String
	 * @date 2019-08-12 09:56
	 * @author YouHan
	 */
	public static String intToHexPrefix(Integer n, Integer length) {
		if (null == n) {
			return null;
		}
		if (null == length || length <= 0) {
			return null;
		}

		String result = String.format("%X", n);
		if (result.length() < length) {
			result = YouStringUtil.appendPrefixContent(result, "0", length - result.length());
		}

		return result;
	}

	/**
	 * 十进制转十六进制
	 *
	 * @param n
	 * @param length
	 * @return java.lang.String
	 * @date 2019-08-12 09:56
	 * @author YouHan
	 */
	public static String longToHexPrefix(Long n, Integer length) {
		if (null == n) {
			return null;
		}
		if (null == length || length <= 0) {
			return null;
		}

		String result = String.format("%X", n);
		if (result.length() < length) {
			result = YouStringUtil.appendPrefixContent(result, "0", length - result.length());
		}

		return result;
	}

	/**
	 * 十进制转十六进制
	 *
	 * @param n 十进制数
	 * @return java.lang.String
	 * @date 2019/4/8 09:22
	 * @author YouHan
	 */
	public static String intToHexSuffix(Integer n, Integer length) {
		if (null == n) {
			return null;
		}
		if (null == length || length <= 0) {
			return null;
		}

		String result = String.format("%X", n);
		if (result.length() < length) {
			result = YouStringUtil.appendSuffixContent(result, "0", length - result.length());
		}

		return result;
	}

	/**
	 * 十进制转十六进制
	 *
	 * @param n 十进制数
	 * @return java.lang.String
	 * @date 2019/4/8 09:22
	 * @author YouHan
	 */
	public static String longToHexSuffix(Long n, Integer length) {
		if (null == n) {
			return null;
		}
		if (null == length || length <= 0) {
			return null;
		}

		String result = String.format("%X", n);
		if (result.length() < length) {
			result = YouStringUtil.appendSuffixContent(result, "0", length - result.length());
		}

		return result;
	}

	/**
	 * 十六进制转十进制
	 *
	 * @param hex
	 * @return java.lang.Integer
	 * @date 2019/4/8 09:49
	 * @author YouHan
	 */
	public static Integer hexToInt(String hex) {
		Long n = hexToLong(hex);
		if (null == n) {
			return null;
		}

		// 超出整数最大值,不予处理
		if (Integer.MAX_VALUE < n) {
			return null;
		}

		return Integer.valueOf(String.valueOf(n));
	}

	/**
	 * 十六进制转十进制
	 *
	 * @param hex
	 * @return java.lang.Integer
	 * @date 2019/4/8 09:49
	 * @author YouHan
	 */
	public static Long hexToLong(String hex) {
		if (StringUtils.isBlank(hex)) {
			return null;
		}

		// 去除前缀为 0 的 十六进制
		if (hex.length() > 1 && hex.startsWith("0")) {
			hex = hex.substring(1);
		}

		return Long.valueOf(hex, 16);
	}

	/**
	 * 字符串转十六进制
	 *
	 * @param s
	 * @return
	 */
	public static String stringToHex(String s) {
		char[] chars = "0123456789ABCDEF".toCharArray();
		StringBuilder sb = new StringBuilder("");
		byte[] bs = s.getBytes();
		int bit;
		for (int i = 0; i < bs.length; i++) {
			bit = (bs[i] & 0x0f0) >> 4;
			sb.append(chars[bit]);
			bit = bs[i] & 0x0f;
			sb.append(chars[bit]);
		}
		return sb.toString().trim();
	}

	/**
	 * 十六进制转字符串
	 *
	 * @param s
	 * @return
	 */
	public static String hexToString(String s) {
		String str = "0123456789ABCDEF";
		char[] hexs = s.toCharArray();
		byte[] bytes = new byte[s.length() / 2];
		int n;
		for (int i = 0; i < bytes.length; i++) {
			n = str.indexOf(hexs[2 * i]) * 16;
			n += str.indexOf(hexs[2 * i + 1]);
			bytes[i] = (byte) (n & 0xff);
		}

		return new String(bytes);
	}

	/**
	 * 去除末尾多余的 0
	 *
	 * @param s
	 * @return java.lang.String
	 * @author YouHan
	 * @date 2021/7/2 15:39
	 */
	public static String stripTrailingZeros(String s) {
		if (StringUtils.isBlank(s)) {
			return "0";
		}

		if (!isNumber(s)) {
			return "0";
		}

		if (!s.contains(".")) {
			return s;
		}

		while (s.endsWith("0")) {
			s = s.substring(0, s.length() - 1);
		}
		if (s.endsWith(".")) {
			s = s.substring(0, s.length() - 1);
		}

		return s;
	}

	/**
	 * int 转 String
	 * 1024以内高效,超出后,正常转换
	 */
	static int cacheSize = 1024;
	static String[] caches = new String[cacheSize];

	static {
		for (int i = 0; i < cacheSize; i++) {
			caches[i] = String.valueOf(i);
		}
	}

	public static String int2String(int data) {
		if (data < cacheSize) {
			return caches[data];
		}
		return String.valueOf(data);
	}

	/**
	 * 获取几位的 int 随机数
	 *
	 * @param length
	 * @return int
	 * @author YouHan
	 * @date 2021/12/19
	 */
	public static int getRandomInt(int length) {
		return (int) ((Math.random() * 9 + 1) * 10 * length);
	}

	/**
	 * 获取几位的 long 随机数
	 *
	 * @param length
	 * @return long
	 * @author YouHan
	 * @date 2021/12/19
	 */
	public static long getRandomLong(long length) {
		return (long) ((Math.random() * 9 + 1) * 10 * length);
	}

	/**
	 * 获取随机数
	 *
	 * @param
	 * @return java.client.concurrent.ThreadLocalRandom
	 * @author YouHan
	 * @date 2021/6/3 10:29
	 */
	public static ThreadLocalRandom getRandom() {
		return ThreadLocalRandom.current();
	}

	/**
	 * 获取缓存穿透时间(单位秒),最长不超过 5 分钟
	 *
	 * @param
	 * @return java.lang.Long
	 * @date 2021/4/26 9:50
	 * @author YouHan
	 */
	public static Long getCachePenetrationTime() {
		return Long.valueOf(int2String(getRandom().nextInt(300)));
	}

	/**
	 * 获取数据库缓存时间(单位秒),最长不超过 1 小时
	 *
	 * @param
	 * @return java.lang.Long
	 * @date 2021/4/26 9:50
	 * @author YouHan
	 */
	public static Long getDBCacheTime() {
		return Long.valueOf(int2String(getRandom().nextInt(3600)));
	}

	/**
	 * 十六进制高低位转换
	 *
	 * @param hexString
	 * @return java.lang.String
	 * @author YouHan
	 * @date 2021/12/11
	 */
	public static String hexHighLowPositionConvert(String hexString) {
		if (StringUtils.isBlank(hexString) || hexString.length() % 2 != 0) {
			return null;
		}

		StringBuilder result = new StringBuilder();
		for (int i = hexString.length() - 2; i >= 0; i = i - 2) {
			result.append(hexString.substring(i, i + 2));
		}

		return result.toString();
	}

}

package com.xhsoft.mp.util;


import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * String client
 *
 * Created by YouHan on 2019-09-11 08:57:56
 * Copyright © 2019 YouHan All rights reserved.
 */
public class YouStringUtil {

	/**
	 * 下划线
	 */
	public static final Pattern LINE = Pattern.compile("_(\\w)");

	/**
	 * 驼峰
	 */
	public static final Pattern HUMP = Pattern.compile("[A-Z]");

	/**
	 * 添加内容
	 *
	 * @param content
	 * @param length
	 * @return java.lang.String
	 * @author YouHan
	 * @date 2021/6/17 9:59
	 */
	public static String appendContent(String content, int length) {
		if (length <= 0) {
			return "";
		}

		StringBuilder sb = new StringBuilder();

		for (int i = 0; i < length; i ++) {
			sb.append(content);
		}

		return sb.toString();
	}

	/**
	 * 添加前缀内容
	 *
	 * @param s
	 * @param content
	 * @param length
	 * @return java.lang.String
	 * @date 2019-08-12 09:53
	 * @author YouHan
	 */
	public static String appendPrefixContent(String s, String content, int length) {

		if (length <= 0) {
			return null;
		}

		StringBuilder sb = new StringBuilder(s);
		for (int i = 0; i < length; i ++) {
			sb.append(content, 0, content.length());
		}

		return sb.toString();
	}

	/**
	 * 添加后缀内容
	 *
	 * @param s
	 * @param content
	 * @param length
	 * @return java.lang.String
	 * @date 2019-08-12 09:56
	 * @author YouHan
	 */
	public static String appendSuffixContent(String s, String content, int length) {
		if (length <= 0) {
			return null;
		}

		StringBuilder sb = new StringBuilder(s);

		for (int i = 0; i < length; i ++) {
			sb.append(content);
		}

		return sb.toString();
	}

	/**
	 * Set 转 String
	 *
	 * @param stringSet
	 * @return java.lang.String
	 * @author YouHan
	 * @date 2021/6/3 9:26
	 */
	public static String setToString(Set<String> stringSet) {
		return setToString(stringSet, null);
	}

	/**
	 * Set 转 String
	 *
	 * @param stringSet
	 * @param regex
	 * @return java.lang.String
	 * @date 2021/6/3 9:21
	 * @author YouHan
	 */
	public static String setToString(Set<String> stringSet, String regex) {
		// 参数校验
		if (CollectionUtils.isEmpty(stringSet)) {
			return null;
		}
		if (StringUtils.isBlank(regex)) {
			regex = ",";
		}

		// List to String
		StringBuilder sb = new StringBuilder(stringSet.size());
		for (String s : stringSet) {
			sb.append(s).append(regex);
		}

		// 返回结果
		return sb.substring(0, sb.length() - 1);
	}

	/**
	 *  字符串列表转字符串
	 *
	 * @author YouHan
	 * @generatedDate: 2018/10/9 17:25
	 * @param stringList 要转换的字符串列表
	 * @return
	 */
	public static String listToString(List<String> stringList) {
		return listToString(stringList, null);
	}

	/**
	 *  字符串列表转字符串
	 *
	 * @author YouHan
	 * @generatedDate: 2018/10/9 17:25
	 * @param stringList 要转换的字符串列表
	 * @return
	 */
	public static String listToString(List<String> stringList, String regex) {
		// 参数校验
		if (CollectionUtils.isEmpty(stringList)) {
			return null;
		}
		if (StringUtils.isBlank(regex)) {
			regex = ",";
		}

		// List to String
		StringBuilder sb = new StringBuilder(stringList.size());
		for (String s : stringList) {
			sb.append(s).append(regex);
		}

		// 返回结果
		return sb.substring(0, sb.length() - 1);
	}

	/**
	 * 字符串转列表
	 *
	 * @param s
	 * @return java.client.List<java.lang.String>
	 * @date 2019-09-11 09:11
	 * @author YouHan
	 */
	public static List<String> stringToList(String s) {
		/**
		 * 参数校验
		 */
		if (StringUtils.isBlank(s)) {
			return null;
		}

		return stringToList(s, null);
	}

	/**
	 * 字符串转列表
	 *
	 * @param s
	 * @param regex 分割规则,默认为逗号
	 * @return java.client.List<java.lang.String>
	 * @date 2019-09-11 09:11
	 * @author YouHan
	 */
	public static List<String> stringToList(String s, String regex) {
		/**
		 * 参数校验
		 */
		if (StringUtils.isBlank(s)) {
			return null;
		}

		/**
		 * 默认逗号隔开
		 */
		if (StringUtils.isBlank(regex)) {
			regex = ",";
		}

		/**
		 * 去除首尾空格
		 */
		String blankString = " ";
		while (s.startsWith(blankString)) {
			s = s.substring(1);
		}
		while (s.endsWith(blankString)) {
			s = s.substring(0, s.length() -1);
		}

		/**
		 * 返回结果列表
		 */
		List<String> resultList = new ArrayList<>();

		/**
		 * 只有单个元素
		 */
		if (!s.contains(regex)) {
			resultList.add(s);
			return resultList;
		}

		String[] strings = s.split(regex);
		for (String e : strings) {
			resultList.add(e);
		}

		return resultList;
	}

	/**
	 * 过滤逗号
	 * @param s
	 * @return
	 */
	public static String filterCommaString(String s) {
		// 数据为空校验
		if (StringUtils.isEmpty(s)) {
			return null;
		}

		// 去除 并列逗号
		s = s.replace(",,", ",");

		// 去除 首逗号
		if (s.startsWith(",")) {
			s = s.substring(1, s.length() - 1);
		}

		// 去除 尾逗号
		if (s.endsWith(",")) {
			s = s.substring(0, s.length() - 1);
		}

		return s;
	}

	/**
	 * 是否包含中文(包括中文标点符号和空格)
	 *
	 * @param s
	 * @return boolean
	 * @date 2020/9/9 13:30
	 * @author YouHan
	 */
	public static Boolean isContainChinese(String s) {
		/**
		 * 参数校验
		 */
		if (StringUtils.isBlank(s)) {
			return false;
		}

		if (s.contains(" ")) {
			return true;
		}

		/**
		 * 中文正则表达式
		 */
		String regex = "[\u4e00-\u9fa5]";

		if (s.matches(regex)) {
			return Boolean.TRUE;
		}

		/**
		 * 中文标点符号处理
		 */
		char[] chars = s.toCharArray();
		for (char c : chars) {
			if (isChinesePunctuation(c)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * 过滤中文(包括标点符号和空格)
	 *
	 * @param s
	 * @return java.lang.String
	 * @date 2020/9/9 14:08
	 * @author YouHan
	 */
	public static String filterChinese(String s) {
		/**
		 * 参数校验
		 */
		if (StringUtils.isBlank(s)) {
			return "";
		}

		s = s.replace(" ", "");

		if (!isContainChinese(s)) {
			return s;
		}

		/**
		 * 过滤中文字符
		 */
		char[] chars = s.toCharArray();
		StringBuilder sb = new StringBuilder(chars.length);
		for (char c : chars) {
			if (isContainChinese(String.valueOf(c))) {
				continue;
			}
			sb.append(c);
		}

		return sb.toString();
	}

	/**
	 * 判断是否为中文标点符号
	 *
	 * @param c
	 * @return java.lang.Boolean
	 * @date 2020/9/9 13:43
	 * @author YouHan
	 */
	public static boolean isChinesePunctuation(char c) {
		Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
		if (ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
			|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
			|| ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
			|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS
			|| ub == Character.UnicodeBlock.VERTICAL_FORMS) {
			return true;
		}

		return false;
	}

	/**
	 * 获取 UUID
	 *
	 * @param
	 * @return java.lang.String
	 * @date 2021/4/9 14:08
	 * @author YouHan
	 */
	public static String getUUID() {
		return UUID.randomUUID().toString().replace("-", "");
	}

	/**
	 * 安全比较(可防止时序攻击 timing attack)
	 */
	public static boolean safeEqual(String a, String b) {
		if (StringUtils.isBlank(a) || StringUtils.isBlank(b)) {
			return false;
		}
		if (a.length() != b.length()) {
			return false;
		}
		int equal = 0;
		for (int i = 0; i < a.length(); i++) {
			equal |= a.charAt(i) ^ b.charAt(i);
		}
		return equal == 0;
	}

	/**
	 * 驼峰转下划线
	 *
	 * @param s
	 * @return java.lang.String
	 * @date 2021/5/6 22:20
	 * @author YouHan
	 */
	public static String humpToLine(String s) {
		Matcher matcher = HUMP.matcher(s);
		StringBuffer sb = new StringBuffer();
		while (matcher.find()) {
			matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
		}
		if (sb.toString().startsWith("_")) {
			sb.deleteCharAt(0);
		}

		matcher.appendTail(sb);
		return sb.toString();
	}

	/**
	 * 下划线转驼峰
	 *
	 * @param s
	 * @return java.lang.String
	 * @date 2021/5/6 22:21
	 * @author YouHan
	 */
	public static String lineToHump(String s) {
		s = s.toLowerCase();
		Matcher matcher = LINE.matcher(s);
		StringBuffer sb = new StringBuffer();
		while (matcher.find()) {
			matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
		}
		matcher.appendTail(sb);
		return sb.toString();
	}

	/**
	 * 生成加密的内容
	 *
	 * @param s
	 * @return java.lang.String
	 * @author YouHan
	 * @date 2021/6/17 10:10
	 */
	public static String hide(String s) {
		/**
		 * 1
		 * 1*
		 * 1**
		 * 1***
		 * 1***5
		 * 12***6
		 * 12***67
		 * 123***78
		 * 123***789
		 * 123****890
		 * 123*****8901
		 */
		if (s.isEmpty() || s.length() == 1) {
			return s;
		}

		if (s.length() == 2) {
			return s.substring(0, 1) + "*";
		}

		if (s.length() == 3 || s.length() == 4) {
			return s.substring(0, 1) + appendContent("*", s.length() - 1);
		}

		if (s.length() == 5) {
			return s.substring(0, 1) + "***" + s.substring(4);
		}

		if (s.length() == 6 || s.length() == 7) {
			return s.substring(0, 2) + appendContent("*", 3) + s.substring(5);
		}

		if (s.length() == 8) {
			return s.substring(0, 3) + "***" + s.substring(6);
		}

		return s.substring(0, 3) + appendContent("*", s.length() - 6) + s.substring(s.length() - 3);
	}
}

<think>好的,用户想知道如何在Spring Boot项目中实现微信支付退款功能,需要教程和示例代码。首先,我需要回忆一下之前提供的引用内容。引用[4]提到Spinrg boot集成微信支付插件,里面提到了退款申请的步骤。另外,引用[1]中的demo模块支持微信退款方式,可能相关。 用户可能需要先了解前置条件,比如已经接入了微信支付,因为退款需要之前的支付订单信息。根据引用[1],创建支付订单是退款的前提,所以需要确认用户已经完成了支付接入。然后,需要配置微信支付的相关参数,比如商户号、API密钥、证书等,这些在配置文件中可能会有体现。 接下来,根据引用[4]的步骤,退款申请可能涉及到调用微信的API,需要构造请求参数,比如商户订单号、退款单号、金额等。示例代码可能需要使用微信支付插件提供的类和方法,比如WxPayService。此外,处理回调也是重要的一环,确保退款状态能够正确更新。 还要注意异常处理和事务管理,确保退款过程中的数据一致性。可能需要引用相关的依赖,如微信支付的SDK或者Spring Boot插件。同时,需要提醒用户关于证书的配置,因为微信支付退款通常需要双向证书,这可能需要在配置文件中指定路径或者通过类路径加载。 最后,根据用户的问题,生成相关问题,比如如何查询退款状态、处理回调验证等,帮助用户进一步了解整个流程。需要确保回答结构清晰,分步骤讲解,并提供代码示例,同时引用相关的资料作为参考。</think>### Spring Boot实现微信支付退款功能分步指南 在Spring Boot项目中接入微信支付退款功能需要以下关键步骤: --- #### 一、前置准备 1. 已完成微信支付接入(需已实现下单支付功能) 2. 获取商户证书文件(`apiclient_cert.p12`) 3. 准备以下配置参数: - 微信支付商户号(mchId) - API密钥(apiKey) - 应用ID(appId) - 证书存放路径 参考微信支付文档[^3]关于退款接口的规范要求 --- #### 二、配置文件 ```yaml # application.yml wx: pay: mch-id: 1230000109 api-key: your_wechat_pay_api_key cert-path: classpath:certs/apiclient_cert.p12 http-connect-timeout-ms: 5000 http-read-timeout-ms: 10000 ``` --- #### 三、核心代码实现 1. **退款请求方法** ```java @Service public class WxPayRefundService { @Autowired private WxPayService wxPayService; public RefundResult refund(RefundRequest request) throws WxPayException { // 构造退款请求 WxPayRefundRequest wxRequest = new WxPayRefundRequest(); wxRequest.setOutTradeNo(request.getOrderNo()); // 原支付订单号 wxRequest.setOutRefundNo(request.getRefundNo()); // 退款单号 wxRequest.setTotalFee(request.getTotalFee()); // 订单总金额(分) wxRequest.setRefundFee(request.getRefundFee()); // 退款金额(分) wxRequest.setNotifyUrl("/api/refund/notify"); // 退款结果通知地址 // 执行退款请求 WxPayRefundResult result = wxPayService.refund(wxRequest); return new RefundResult( result.getRefundId(), result.getRefundStatus(), result.getCashFee() ); } } ``` 2. **退款回调处理** ```java @RestController @RequestMapping("/api/refund") public class RefundNotifyController { @PostMapping("/notify") public String handleRefundNotify(@RequestBody String xmlData) { try { WxPayRefundNotifyResult result = wxPayService.parseRefundNotifyResult(xmlData); if ("SUCCESS".equals(result.getReturnCode())) { // 处理退款成功逻辑 String refundStatus = result.getRefundStatus(); String orderNo = result.getOutTradeNo(); // 更新订单状态等业务操作 orderService.updateRefundStatus(orderNo, refundStatus); return "<xml><return_code>SUCCESS</return_code></xml>"; } } catch (WxPayException e) { logger.error("退款通知处理异常", e); } return "<xml><return_code>FAIL</return_code></xml>"; } } ``` --- #### 四、注意事项 1. 金额单位需统一使用分(整数) 2. 退款单号需保证唯一性(推荐使用日期+随机数生成) 3. 需处理证书加载异常(建议将证书文件放在resources/certs目录) 4. 生产环境需要添加事务管理和重试机制 5. 建议实现退款状态查询接口做结果校验[^4] --- #### 五、测试验证 1. 使用微信支付沙箱环境测试 2. 验证不同金额的退款场景 3. 模拟异常情况(如重复退款、金额超限等) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值