package com.ruoyi.system.ControllerUtil; import com.alibaba.fastjson2.JSONObject; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.InputStreamReader; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.*; /** * 微信支付工具类 * * 提供微信支付相关的完整功能实现 * 主要功能: * 1. 统一下单支付 * 2. 订单状态查询 * 3. 找朋友代付功能 * 4. 支付结果回调处理 * 5. 退款申请和查询 * 6. 签名生成和验证 * 7. 支付安全验证 * * @author Mr. Zhang Pan * @date 2025-01-03 * @version 1.0 */ public class WechatPayUtil { /** * 微信支付配置常量 * 注意:实际使用时需要在配置文件中配置这些值 */ private static final String WECHAT_APP_ID = "your_wechat_appid"; private static final String WECHAT_MCH_ID = "your_merchant_id"; private static final String WECHAT_API_KEY = "your_api_key"; private static final String WECHAT_CERT_PATH = "/path/to/cert.p12"; // 微信支付证书路径 /** * 微信支付API地址 */ private static final String WECHAT_PAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; private static final String WECHAT_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery"; private static final String WECHAT_REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund"; private static final String WECHAT_TRANSFER_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"; // 企业付款 /** * 其他配置常量 */ private static final String TRADE_TYPE_JSAPI = "JSAPI"; private static final String CURRENCY = "CNY"; // 货币类型 private static final String SUCCESS_CODE = "SUCCESS"; private static final String FAIL_CODE = "FAIL"; /** * RestTemplate实例 */ private static final RestTemplate restTemplate = new RestTemplate(); /** * 统一下单 - 微信小程序支付 * * @param orderInfo 订单信息 * @return 支付参数Map */ public static Map unifiedOrder(Map orderInfo) { Map result = new HashMap<>(); try { // 1. 参数验证 String validationError = validateOrderParams(orderInfo); if (validationError != null) { result.put("success", false); result.put("message", validationError); return result; } // 2. 构建统一下单参数 Map params = buildUnifiedOrderParams(orderInfo); // 3. 生成签名 String sign = generateSign(params, WECHAT_API_KEY); params.put("sign", sign); // 4. 发送请求 String xmlRequest = mapToXml(params); ResponseEntity response = restTemplate.postForEntity(WECHAT_PAY_URL, xmlRequest, String.class); // 5. 解析响应 Map responseMap = xmlToMap(response.getBody()); if (SUCCESS_CODE.equals(responseMap.get("return_code")) && SUCCESS_CODE.equals(responseMap.get("result_code"))) { // 6. 构建小程序支付参数 Map payParams = buildMiniProgramPayParams(responseMap.get("prepay_id")); result.put("success", true); result.put("payParams", payParams); result.put("prepayId", responseMap.get("prepay_id")); result.put("message", "统一下单成功"); } else { result.put("success", false); result.put("message", "统一下单失败:" + (responseMap.get("err_code_des") != null ? responseMap.get("err_code_des") : responseMap.get("return_msg"))); } } catch (Exception e) { result.put("success", false); result.put("message", "统一下单异常:" + e.getMessage()); } return result; } /** * 查询订单状态 * * @param orderNo 订单号 * @param transactionId 微信订单号(可选,与orderNo二选一) * @return 查询结果 */ public static Map queryOrder(String orderNo, String transactionId) { Map result = new HashMap<>(); try { // 1. 参数验证 if ((orderNo == null || orderNo.trim().isEmpty()) && (transactionId == null || transactionId.trim().isEmpty())) { result.put("success", false); result.put("message", "订单号和微信订单号不能同时为空"); return result; } // 2. 构建查询参数 Map params = new HashMap<>(); params.put("appid", WECHAT_APP_ID); params.put("mch_id", WECHAT_MCH_ID); params.put("nonce_str", generateNonceStr()); if (orderNo != null && !orderNo.trim().isEmpty()) { params.put("out_trade_no", orderNo.trim()); } if (transactionId != null && !transactionId.trim().isEmpty()) { params.put("transaction_id", transactionId.trim()); } // 3. 生成签名 String sign = generateSign(params, WECHAT_API_KEY); params.put("sign", sign); // 4. 发送请求 String xmlRequest = mapToXml(params); ResponseEntity response = restTemplate.postForEntity(WECHAT_QUERY_URL, xmlRequest, String.class); // 5. 解析响应 Map responseMap = xmlToMap(response.getBody()); if (SUCCESS_CODE.equals(responseMap.get("return_code")) && SUCCESS_CODE.equals(responseMap.get("result_code"))) { Map orderInfo = new HashMap<>(); orderInfo.put("tradeState", responseMap.get("trade_state")); orderInfo.put("tradeStateDesc", responseMap.get("trade_state_desc")); orderInfo.put("transactionId", responseMap.get("transaction_id")); orderInfo.put("outTradeNo", responseMap.get("out_trade_no")); orderInfo.put("totalFee", responseMap.get("total_fee")); orderInfo.put("cashFee", responseMap.get("cash_fee")); orderInfo.put("timeEnd", responseMap.get("time_end")); result.put("success", true); result.put("orderInfo", orderInfo); result.put("message", "查询订单成功"); } else { result.put("success", false); result.put("message", "查询订单失败:" + (responseMap.get("err_code_des") != null ? responseMap.get("err_code_des") : responseMap.get("return_msg"))); } } catch (Exception e) { result.put("success", false); result.put("message", "查询订单异常:" + e.getMessage()); } return result; } /** * 找朋友代付功能 - 生成代付订单 * * @param payForInfo 代付信息 * @return 代付订单结果 */ public static Map createPayForOrder(Map payForInfo) { Map result = new HashMap<>(); try { // 1. 参数验证 String validationError = validatePayForParams(payForInfo); if (validationError != null) { result.put("success", false); result.put("message", validationError); return result; } // 2. 生成代付订单号 String payForOrderNo = generatePayForOrderNo((String) payForInfo.get("orderNo")); // 3. 构建代付订单信息 Map payForOrderInfo = new HashMap<>(); payForOrderInfo.put("orderNo", payForOrderNo); payForOrderInfo.put("openid", payForInfo.get("payerOpenid")); payForOrderInfo.put("totalFee", payForInfo.get("totalFee")); payForOrderInfo.put("body", "代付-" + payForInfo.get("body")); payForOrderInfo.put("notifyUrl", payForInfo.get("notifyUrl")); payForOrderInfo.put("attach", buildPayForAttach(payForInfo)); // 4. 设置30分钟过期时间 Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE, 30); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); payForOrderInfo.put("timeExpire", sdf.format(calendar.getTime())); // 5. 调用统一下单 Map unifiedResult = unifiedOrder(payForOrderInfo); if ((Boolean) unifiedResult.get("success")) { result.put("success", true); result.put("payForOrderNo", payForOrderNo); result.put("payParams", unifiedResult.get("payParams")); result.put("prepayId", unifiedResult.get("prepayId")); result.put("expireTime", payForOrderInfo.get("timeExpire")); result.put("message", "代付订单创建成功"); } else { result.put("success", false); result.put("message", "代付订单创建失败:" + unifiedResult.get("message")); } } catch (Exception e) { result.put("success", false); result.put("message", "创建代付订单异常:" + e.getMessage()); } return result; } /** * 支付结果通知回调处理 * * @param request HTTP请求对象 * @return 处理结果 */ public static Map handlePayNotify(HttpServletRequest request) { Map result = new HashMap<>(); try { // 1. 读取请求数据 StringBuilder xmlData = new StringBuilder(); BufferedReader reader = new BufferedReader( new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)); String line; while ((line = reader.readLine()) != null) { xmlData.append(line); } reader.close(); // 2. 解析XML数据 Map notifyData = xmlToMap(xmlData.toString()); // 3. 验证签名 if (!verifySign(notifyData, WECHAT_API_KEY)) { result.put("success", false); result.put("responseXml", buildNotifyResponse(FAIL_CODE, "签名验证失败")); result.put("message", "签名验证失败"); return result; } // 4. 检查支付结果 if (!SUCCESS_CODE.equals(notifyData.get("return_code")) || !SUCCESS_CODE.equals(notifyData.get("result_code"))) { result.put("success", false); result.put("responseXml", buildNotifyResponse(FAIL_CODE, "支付失败")); result.put("message", "支付失败:" + notifyData.get("err_code_des")); return result; } // 5. 构建支付信息 Map paymentInfo = new HashMap<>(); paymentInfo.put("transactionId", notifyData.get("transaction_id")); paymentInfo.put("outTradeNo", notifyData.get("out_trade_no")); paymentInfo.put("totalFee", notifyData.get("total_fee")); paymentInfo.put("cashFee", notifyData.get("cash_fee")); paymentInfo.put("timeEnd", notifyData.get("time_end")); paymentInfo.put("attach", notifyData.get("attach")); paymentInfo.put("openid", notifyData.get("openid")); // 6. 判断是否为代付订单 String orderNo = notifyData.get("out_trade_no"); boolean isPayFor = isPayForOrder(orderNo); result.put("success", true); result.put("responseXml", buildNotifyResponse(SUCCESS_CODE, "OK")); result.put("paymentInfo", paymentInfo); result.put("isPayFor", isPayFor); result.put("message", "支付通知处理成功"); } catch (Exception e) { result.put("success", false); result.put("responseXml", buildNotifyResponse(FAIL_CODE, "处理异常")); result.put("message", "处理支付通知异常:" + e.getMessage()); } return result; } /** * 申请退款 * * @param refundInfo 退款信息 * @return 退款结果 */ public static Map refund(Map refundInfo) { Map result = new HashMap<>(); try { // 1. 参数验证 String validationError = validateRefundParams(refundInfo); if (validationError != null) { result.put("success", false); result.put("message", validationError); return result; } // 2. 构建退款参数 Map params = buildRefundParams(refundInfo); // 3. 生成签名 String sign = generateSign(params, WECHAT_API_KEY); params.put("sign", sign); // 4. 发送请求(需要证书) String xmlRequest = mapToXml(params); // 注意:退款接口需要使用客户端证书,这里简化处理 ResponseEntity response = restTemplate.postForEntity(WECHAT_REFUND_URL, xmlRequest, String.class); // 5. 解析响应 Map responseMap = xmlToMap(response.getBody()); if (SUCCESS_CODE.equals(responseMap.get("return_code")) && SUCCESS_CODE.equals(responseMap.get("result_code"))) { Map refundResult = new HashMap<>(); refundResult.put("refundId", responseMap.get("refund_id")); refundResult.put("outRefundNo", responseMap.get("out_refund_no")); refundResult.put("refundFee", responseMap.get("refund_fee")); refundResult.put("settlementRefundFee", responseMap.get("settlement_refund_fee")); result.put("success", true); result.put("refundInfo", refundResult); result.put("message", "申请退款成功"); } else { result.put("success", false); result.put("message", "申请退款失败:" + (responseMap.get("err_code_des") != null ? responseMap.get("err_code_des") : responseMap.get("return_msg"))); } } catch (Exception e) { result.put("success", false); result.put("message", "申请退款异常:" + e.getMessage()); } return result; } /** * 企业付款到零钱(用于代付完成后的资金转移) * * @param transferInfo 付款信息 * @return 付款结果 */ public static Map transferToUser(Map transferInfo) { Map result = new HashMap<>(); try { // 1. 参数验证 String validationError = validateTransferParams(transferInfo); if (validationError != null) { result.put("success", false); result.put("message", validationError); return result; } // 2. 构建付款参数 Map params = buildTransferParams(transferInfo); // 3. 生成签名 String sign = generateSign(params, WECHAT_API_KEY); params.put("sign", sign); // 4. 发送请求(需要证书) String xmlRequest = mapToXml(params); // 注意:企业付款接口需要使用客户端证书,这里简化处理 ResponseEntity response = restTemplate.postForEntity(WECHAT_TRANSFER_URL, xmlRequest, String.class); // 5. 解析响应 Map responseMap = xmlToMap(response.getBody()); if (SUCCESS_CODE.equals(responseMap.get("return_code")) && SUCCESS_CODE.equals(responseMap.get("result_code"))) { Map transferResult = new HashMap<>(); transferResult.put("partnerTradeNo", responseMap.get("partner_trade_no")); transferResult.put("paymentNo", responseMap.get("payment_no")); transferResult.put("paymentTime", responseMap.get("payment_time")); result.put("success", true); result.put("transferInfo", transferResult); result.put("message", "企业付款成功"); } else { result.put("success", false); result.put("message", "企业付款失败:" + (responseMap.get("err_code_des") != null ? responseMap.get("err_code_des") : responseMap.get("return_msg"))); } } catch (Exception e) { result.put("success", false); result.put("message", "企业付款异常:" + e.getMessage()); } return result; } // ========== 私有工具方法 ========== /** * 验证统一下单参数 */ private static String validateOrderParams(Map orderInfo) { if (orderInfo == null) return "订单信息不能为空"; if (orderInfo.get("orderNo") == null || orderInfo.get("orderNo").toString().trim().isEmpty()) { return "订单号不能为空"; } if (orderInfo.get("openid") == null || orderInfo.get("openid").toString().trim().isEmpty()) { return "用户openid不能为空"; } if (orderInfo.get("totalFee") == null) { return "支付金额不能为空"; } if (orderInfo.get("body") == null || orderInfo.get("body").toString().trim().isEmpty()) { return "商品描述不能为空"; } if (orderInfo.get("notifyUrl") == null || orderInfo.get("notifyUrl").toString().trim().isEmpty()) { return "回调地址不能为空"; } return null; } /** * 验证代付参数 */ private static String validatePayForParams(Map payForInfo) { if (payForInfo == null) return "代付信息不能为空"; if (payForInfo.get("orderNo") == null || payForInfo.get("orderNo").toString().trim().isEmpty()) { return "原订单号不能为空"; } if (payForInfo.get("payerOpenid") == null || payForInfo.get("payerOpenid").toString().trim().isEmpty()) { return "代付人openid不能为空"; } if (payForInfo.get("payeeOpenid") == null || payForInfo.get("payeeOpenid").toString().trim().isEmpty()) { return "被代付人openid不能为空"; } if (payForInfo.get("totalFee") == null) { return "代付金额不能为空"; } if (payForInfo.get("body") == null || payForInfo.get("body").toString().trim().isEmpty()) { return "商品描述不能为空"; } if (payForInfo.get("notifyUrl") == null || payForInfo.get("notifyUrl").toString().trim().isEmpty()) { return "回调地址不能为空"; } return null; } /** * 验证退款参数 */ private static String validateRefundParams(Map refundInfo) { if (refundInfo == null) return "退款信息不能为空"; if (refundInfo.get("orderNo") == null || refundInfo.get("orderNo").toString().trim().isEmpty()) { return "订单号不能为空"; } if (refundInfo.get("refundNo") == null || refundInfo.get("refundNo").toString().trim().isEmpty()) { return "退款单号不能为空"; } if (refundInfo.get("totalFee") == null) { return "订单总金额不能为空"; } if (refundInfo.get("refundFee") == null) { return "退款金额不能为空"; } return null; } /** * 验证企业付款参数 */ private static String validateTransferParams(Map transferInfo) { if (transferInfo == null) return "付款信息不能为空"; if (transferInfo.get("partnerTradeNo") == null || transferInfo.get("partnerTradeNo").toString().trim().isEmpty()) { return "商户订单号不能为空"; } if (transferInfo.get("openid") == null || transferInfo.get("openid").toString().trim().isEmpty()) { return "用户openid不能为空"; } if (transferInfo.get("amount") == null) { return "付款金额不能为空"; } if (transferInfo.get("desc") == null || transferInfo.get("desc").toString().trim().isEmpty()) { return "付款描述不能为空"; } return null; } /** * 构建统一下单参数 */ private static Map buildUnifiedOrderParams(Map orderInfo) { Map params = new HashMap<>(); params.put("appid", WECHAT_APP_ID); params.put("mch_id", WECHAT_MCH_ID); params.put("nonce_str", generateNonceStr()); params.put("body", orderInfo.get("body").toString()); params.put("out_trade_no", orderInfo.get("orderNo").toString()); params.put("total_fee", orderInfo.get("totalFee").toString()); params.put("spbill_create_ip", "127.0.0.1"); params.put("notify_url", orderInfo.get("notifyUrl").toString()); params.put("trade_type", TRADE_TYPE_JSAPI); params.put("openid", orderInfo.get("openid").toString()); if (orderInfo.get("attach") != null) { params.put("attach", orderInfo.get("attach").toString()); } if (orderInfo.get("timeExpire") != null) { params.put("time_expire", orderInfo.get("timeExpire").toString()); } return params; } /** * 构建退款参数 */ private static Map buildRefundParams(Map refundInfo) { Map params = new HashMap<>(); params.put("appid", WECHAT_APP_ID); params.put("mch_id", WECHAT_MCH_ID); params.put("nonce_str", generateNonceStr()); params.put("out_trade_no", refundInfo.get("orderNo").toString()); params.put("out_refund_no", refundInfo.get("refundNo").toString()); params.put("total_fee", refundInfo.get("totalFee").toString()); params.put("refund_fee", refundInfo.get("refundFee").toString()); if (refundInfo.get("refundDesc") != null) { params.put("refund_desc", refundInfo.get("refundDesc").toString()); } if (refundInfo.get("notifyUrl") != null) { params.put("notify_url", refundInfo.get("notifyUrl").toString()); } return params; } /** * 构建企业付款参数 */ private static Map buildTransferParams(Map transferInfo) { Map params = new HashMap<>(); params.put("mch_appid", WECHAT_APP_ID); params.put("mchid", WECHAT_MCH_ID); params.put("nonce_str", generateNonceStr()); params.put("partner_trade_no", transferInfo.get("partnerTradeNo").toString()); params.put("openid", transferInfo.get("openid").toString()); params.put("check_name", transferInfo.get("checkName") != null ? transferInfo.get("checkName").toString() : "NO_CHECK"); params.put("amount", transferInfo.get("amount").toString()); params.put("desc", transferInfo.get("desc").toString()); params.put("spbill_create_ip", "127.0.0.1"); if (transferInfo.get("reUserName") != null) { params.put("re_user_name", transferInfo.get("reUserName").toString()); } return params; } /** * 构建小程序支付参数 */ private static Map buildMiniProgramPayParams(String prepayId) { Map payParams = new HashMap<>(); String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); String nonceStr = generateNonceStr(); String packageStr = "prepay_id=" + prepayId; String signType = "MD5"; // 构建签名参数 Map signParams = new HashMap<>(); signParams.put("appId", WECHAT_APP_ID); signParams.put("timeStamp", timeStamp); signParams.put("nonceStr", nonceStr); signParams.put("package", packageStr); signParams.put("signType", signType); String paySign = generateSign(signParams, WECHAT_API_KEY); payParams.put("timeStamp", timeStamp); payParams.put("nonceStr", nonceStr); payParams.put("package", packageStr); payParams.put("signType", signType); payParams.put("paySign", paySign); return payParams; } /** * 构建代付附加数据 */ private static String buildPayForAttach(Map payForInfo) { JSONObject attach = new JSONObject(); attach.put("type", "payfor"); attach.put("originalOrderNo", payForInfo.get("orderNo")); attach.put("payerOpenid", payForInfo.get("payerOpenid")); attach.put("payeeOpenid", payForInfo.get("payeeOpenid")); if (payForInfo.get("remark") != null) { attach.put("remark", payForInfo.get("remark")); } return attach.toString(); } /** * 生成代付订单号 */ private static String generatePayForOrderNo(String originalOrderNo) { return "PF" + originalOrderNo + System.currentTimeMillis(); } /** * 判断是否为代付订单 */ private static boolean isPayForOrder(String orderNo) { return orderNo != null && orderNo.startsWith("PF"); } /** * 构建通知响应XML */ private static String buildNotifyResponse(String returnCode, String returnMsg) { StringBuilder xml = new StringBuilder(); xml.append(""); xml.append(""); xml.append(""); xml.append(""); return xml.toString(); } /** * 生成随机字符串 */ private static String generateNonceStr() { return UUID.randomUUID().toString().replace("-", "").substring(0, 32); } /** * 生成签名 */ private static String generateSign(Map params, String key) { try { // 1. 排序参数 Map sortedParams = new TreeMap<>(params); // 2. 拼接参数 StringBuilder stringBuilder = new StringBuilder(); for (Map.Entry entry : sortedParams.entrySet()) { if (entry.getValue() != null && !entry.getValue().isEmpty() && !"sign".equals(entry.getKey())) { stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&"); } } stringBuilder.append("key=").append(key); // 3. MD5加密 MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] digest = md5.digest(stringBuilder.toString().getBytes(StandardCharsets.UTF_8)); // 4. 转换为大写16进制字符串 StringBuilder result = new StringBuilder(); for (byte b : digest) { result.append(String.format("%02X", b)); } return result.toString(); } catch (Exception e) { throw new RuntimeException("生成签名失败", e); } } /** * 验证签名 */ private static boolean verifySign(Map params, String key) { String sign = params.get("sign"); if (sign == null || sign.isEmpty()) { return false; } Map paramsWithoutSign = new HashMap<>(params); paramsWithoutSign.remove("sign"); String generatedSign = generateSign(paramsWithoutSign, key); return sign.equals(generatedSign); } /** * Map转XML */ private static String mapToXml(Map params) { StringBuilder xml = new StringBuilder(); xml.append(""); for (Map.Entry entry : params.entrySet()) { xml.append("<").append(entry.getKey()).append(">"); } xml.append(""); return xml.toString(); } /** * XML转Map(简化实现) */ private static Map xmlToMap(String xml) { Map map = new HashMap<>(); try { // 简化的XML解析实现 xml = xml.replaceAll("", "").replaceAll("", ""); String[] elements = xml.split(""); for (String element : elements) { if (element.trim().isEmpty()) continue; int startTag = element.indexOf("<"); int endTag = element.indexOf(">"); if (startTag >= 0 && endTag > startTag) { String key = element.substring(startTag + 1, endTag); String value = element.substring(endTag + 1); // 处理CDATA if (value.startsWith("")) { value = value.substring(9, value.length() - 3); } map.put(key, value); } } } catch (Exception e) { throw new RuntimeException("XML解析失败", e); } return map; } }