From 9486766c806fe1d9e082b2fd02ea1cc558f1b443 Mon Sep 17 00:00:00 2001 From: 无关风月 <443237572@qq.com> Date: 星期四, 08 五月 2025 09:21:57 +0800 Subject: [PATCH] bug修改 --- cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java | 444 +++++++++++++++++++++++++++--------------------------- 1 files changed, 222 insertions(+), 222 deletions(-) diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java index 0b243e3..bd24df0 100644 --- a/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java +++ b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java @@ -1,222 +1,222 @@ -//package com.dsh.activity.util.wx; -// -//import com.dsh.activity.util.wx.WXPayConstants; -//import com.dsh.activity.util.wx.WXPaySignatureCertificateUtil; -//import com.dsh.activity.util.wx.WxV3PayConfig; -//import com.fasterxml.jackson.databind.JsonNode; // 引入Jackson库处理JSON -//import com.fasterxml.jackson.databind.ObjectMapper; -//import com.fasterxml.jackson.databind.node.ObjectNode; -//import org.apache.http.client.methods.CloseableHttpResponse; -//import org.apache.http.client.methods.HttpPost; -//import org.apache.http.entity.StringEntity; -//import org.apache.http.impl.client.CloseableHttpClient; -//import org.apache.http.util.EntityUtils; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.stereotype.Component; -//import org.springframework.stereotype.Service; -// -//import java.io.IOException; -//import java.math.BigDecimal; -//import java.nio.charset.StandardCharsets; -//import java.util.HashMap; -//import java.util.Map; -// -//@Component -//public class WxAppPayService { -// -// private static final Logger log = LoggerFactory.getLogger(WxAppPayService.class); -// -// private final ObjectMapper objectMapper; // 用于JSON序列化和反序列化 -// private final CloseableHttpClient wechatPayClient; // 注入通过工具类创建的HTTP客户端 -// -// @Autowired -// public WxAppPayService(ObjectMapper objectMapper) throws IOException { -// this.objectMapper = objectMapper; -// // 在构造函数中初始化带有签名验证功能的HTTP客户端 -// this.wechatPayClient = WXPaySignatureCertificateUtil.getWechatPayClient(); -// log.info("微信支付V3 HTTP客户端初始化完成。"); -// } -// -// /** -// * 创建APP支付预付单 (统一下单) -// * -// * @param description 商品描述 -// * @param outTradeNo 商户订单号 (要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一) -// * @param totalAmount 支付总金额 (单位:元) -// * @return 用于调起APP支付的参数 Map (appid, partnerid, prepayid, package, noncestr, timestamp, sign) -// * @throws IOException 网络或IO异常 -// * @throws RuntimeException 支付请求失败或处理异常 -// */ -// public Map<String, String> createOrder(String description, String outTradeNo, BigDecimal totalAmount) -// throws IOException { -// -// // 1. 构建请求URL -// String url = WXPayConstants.DOMAIN_API + WXPayConstants.PAY_TRANSACTIONS_APP; -// HttpPost httpPost = new HttpPost(url); -// httpPost.addHeader("Accept", "application/json"); -// httpPost.addHeader("Content-type", "application/json; charset=utf-8"); -// -// // 2. 构建请求体 JSON -// ObjectNode rootNode = objectMapper.createObjectNode(); -//// rootNode.put("appid", WxV3PayConfig.APP_ID);//服务商不需要该字段 -//// rootNode.put("mchid", WxV3PayConfig.Mch_ID);//服务商不需要该字段 -// rootNode.put("description", description); -// rootNode.put("out_trade_no", outTradeNo); -// rootNode.put("notify_url", WXPayConstants.WECHAT_PAY_NOTIFY_URL); // 使用常量中的回调地址 -// -// ObjectNode amountNode = objectMapper.createObjectNode(); -// // 微信支付金额单位为分,需要转换 -// amountNode.put("total", totalAmount.multiply(new BigDecimal("100")).intValue()); -// amountNode.put("currency", "CNY"); // 货币类型,默认人民币 -// rootNode.set("amount", amountNode); -// -// // 如果你是服务商模式,需要添加子商户信息 -// rootNode.put("sp_mchid", WxV3PayConfig.Mch_ID); -// rootNode.put("sub_mchid", "123"); -// rootNode.put("sp_appid", "wx41d32f362ba0f911"); -// // rootNode.put("sub_appid", "子商户AppID"); // 如果子商户需要用自己的AppID拉起支付 -// -// String requestBody = rootNode.toString(); -// log.info("微信APP支付下单请求体: {}", requestBody); -// httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8)); -// -// // 3. 发送请求 -// try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) { -// int statusCode = response.getStatusLine().getStatusCode(); -// String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); -// log.info("微信APP支付下单响应状态码: {}, 响应体: {}", statusCode, responseBody); -// -// if (statusCode == 200) { // HTTP状态码 200 表示成功 -// JsonNode responseNode = objectMapper.readTree(responseBody); -// String prepayId = responseNode.get("prepay_id").asText(); -// log.info("成功获取预支付交易会话标识 (prepay_id): {}", prepayId); -// -// // 4. 生成调起支付所需的参数 -// Map<String, String> payParams = WXPaySignatureCertificateUtil.buildAppPayParams(prepayId); -// log.info("生成APP支付参数: {}", payParams); -// return payParams; -// -// } else { -// // 处理错误情况 -// log.error("微信APP支付下单失败,状态码: {}, 响应: {}", statusCode, responseBody); -// // 可以根据 responseBody 中的 code 和 message 进一步分析错误 -// throw new RuntimeException("微信APP支付下单失败: " + responseBody); -// } -// } catch (Exception e) { -// log.error("微信APP支付下单请求执行异常", e); -// throw new IOException("微信APP支付下单请求执行异常", e); -// } -// } -// -// /** -// * 关闭订单 (根据商户订单号) -// * -// * @param outTradeNo 商户订单号 -// * @return true 如果关闭成功或订单已关闭/不存在 (根据微信文档,成功是204 No Content) -// * @throws IOException 网络或IO异常 -// * @throws RuntimeException 关闭请求失败或处理异常 -// */ -// public boolean closeOrder(String outTradeNo) throws IOException { -// // 1. 构建请求URL, 注意替换占位符 -// String url = WXPayConstants.DOMAIN_API -// + WXPayConstants.PAY_TRANSACTIONS_OUT_TRADE_NO.replace("{}", outTradeNo); -// -// HttpPost httpPost = new HttpPost(url); -// httpPost.addHeader("Accept", "application/json"); -// httpPost.addHeader("Content-type", "application/json; charset=utf-8"); -// -// // 2. 构建请求体 JSON (只需要商户号) -// ObjectNode rootNode = objectMapper.createObjectNode(); -// rootNode.put("mchid", WxV3PayConfig.Mch_ID); -// // 如果是服务商模式,则用 sp_mchid -// rootNode.put("sp_mchid", WxV3PayConfig.Mch_ID); -// -// String requestBody = rootNode.toString(); -// log.info("微信关单请求 URL: {}, 请求体: {}", url, requestBody); -// httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8)); -// -// // 3. 发送请求 -// try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) { -// int statusCode = response.getStatusLine().getStatusCode(); -// // 关单成功时,微信返回 HTTP 状态码 204 No Content -// log.info("微信关单响应状态码: {}", statusCode); -// if (statusCode == 204) { -// log.info("订单 {} 关闭成功。", outTradeNo); -// return true; -// } else { -// String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); -// log.error("微信关单失败,商户订单号: {}, 状态码: {}, 响应: {}", outTradeNo, statusCode, responseBody); -// // 根据需要可以解析responseBody获取错误详情 -// return false; // 或者抛出异常,根据业务决定 -// } -// } catch (Exception e) { -// log.error("微信关单请求执行异常, 商户订单号: {}", outTradeNo, e); -// throw new IOException("微信关单请求执行异常", e); -// } -// } -// -// -// // --- 退款相关方法 (可选) --- -// -// /** -// * 申请退款 -// * -// * @param outTradeNo 原商户订单号 -// * @param outRefundNo 商户退款单号 (商户系统内部的退款单号,要求唯一) -// * @param reason 退款原因 (可选) -// * @param refundAmount 退款金额 (单位:元) -// * @param totalAmount 原订单总金额 (单位:元) -// * @return 退款处理结果,可以返回微信返回的JSON节点或自定义对象 -// * @throws IOException 网络或IO异常 -// * @throws RuntimeException 退款请求失败或处理异常 -// */ -// public JsonNode createRefund(String outTradeNo, String outRefundNo, String reason, -// BigDecimal refundAmount, BigDecimal totalAmount) throws IOException { -// -// String url = WXPayConstants.DOMAIN_API + WXPayConstants.REFUND_DOMESTIC_REFUNDS; -// HttpPost httpPost = new HttpPost(url); -// httpPost.addHeader("Accept", "application/json"); -// httpPost.addHeader("Content-type", "application/json; charset=utf-8"); -// -// ObjectNode rootNode = objectMapper.createObjectNode(); -// // 如果是服务商模式,需要添加子商户号 -// // rootNode.put("sub_mchid", "子商户号"); -// rootNode.put("out_trade_no", outTradeNo); -// rootNode.put("out_refund_no", outRefundNo); -// if (reason != null && !reason.isEmpty()) { -// rootNode.put("reason", reason); -// } -// // 设置退款回调地址 -// rootNode.put("notify_url", WXPayConstants.WECHAT_REFUNDS_NOTIFY_URL); -// -// ObjectNode amountNode = objectMapper.createObjectNode(); -// amountNode.put("refund", refundAmount.multiply(new BigDecimal("100")).intValue()); // 退款金额,分 -// amountNode.put("total", totalAmount.multiply(new BigDecimal("100")).intValue()); // 原订单金额,分 -// amountNode.put("currency", "CNY"); -// rootNode.set("amount", amountNode); -// -// String requestBody = rootNode.toString(); -// log.info("微信申请退款请求体: {}", requestBody); -// httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8)); -// -// try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) { -// int statusCode = response.getStatusLine().getStatusCode(); -// String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); -// log.info("微信申请退款响应状态码: {}, 响应体: {}", statusCode, responseBody); -// -// if (statusCode == 200) { // 成功受理 -// log.info("微信退款申请成功受理。"); -// return objectMapper.readTree(responseBody); -// } else { -// log.error("微信申请退款失败,状态码: {}, 响应: {}", statusCode, responseBody); -// throw new RuntimeException("微信申请退款失败: " + responseBody); -// } -// } catch (Exception e) { -// log.error("微信申请退款请求执行异常", e); -// throw new IOException("微信申请退款请求执行异常", e); -// } -// } -// -//} \ No newline at end of file +package com.dsh.activity.util.wx; + +import com.dsh.activity.util.wx.WXPayConstants; +import com.dsh.activity.util.wx.WXPaySignatureCertificateUtil; +import com.dsh.activity.util.wx.WxV3PayConfig; +import com.fasterxml.jackson.databind.JsonNode; // 引入Jackson库处理JSON +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +@Component +public class WxAppPayService { + + private static final Logger log = LoggerFactory.getLogger(WxAppPayService.class); + + private final ObjectMapper objectMapper; // 用于JSON序列化和反序列化 + private final CloseableHttpClient wechatPayClient; // 注入通过工具类创建的HTTP客户端 + + @Autowired + public WxAppPayService(ObjectMapper objectMapper) throws IOException { + this.objectMapper = objectMapper; + // 在构造函数中初始化带有签名验证功能的HTTP客户端 + this.wechatPayClient = WXPaySignatureCertificateUtil.getWechatPayClient(); + log.info("微信支付V3 HTTP客户端初始化完成。"); + } + + /** + * 创建APP支付预付单 (统一下单) + * + * @param description 商品描述 + * @param outTradeNo 商户订单号 (要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一) + * @param totalAmount 支付总金额 (单位:元) + * @return 用于调起APP支付的参数 Map (appid, partnerid, prepayid, package, noncestr, timestamp, sign) + * @throws IOException 网络或IO异常 + * @throws RuntimeException 支付请求失败或处理异常 + */ + public Map<String, String> createOrder(String description, String outTradeNo, BigDecimal totalAmount) + throws IOException { + + // 1. 构建请求URL + String url = WXPayConstants.DOMAIN_API + WXPayConstants.PAY_TRANSACTIONS_APP; + HttpPost httpPost = new HttpPost(url); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-type", "application/json; charset=utf-8"); + + // 2. 构建请求体 JSON + ObjectNode rootNode = objectMapper.createObjectNode(); +// rootNode.put("appid", WxV3PayConfig.APP_ID);//服务商不需要该字段 +// rootNode.put("mchid", WxV3PayConfig.Mch_ID);//服务商不需要该字段 + rootNode.put("description", description); + rootNode.put("out_trade_no", outTradeNo); + rootNode.put("notify_url", WXPayConstants.WECHAT_PAY_NOTIFY_URL); // 使用常量中的回调地址 + + ObjectNode amountNode = objectMapper.createObjectNode(); + // 微信支付金额单位为分,需要转换 + amountNode.put("total", totalAmount.multiply(new BigDecimal("100")).intValue()); + amountNode.put("currency", "CNY"); // 货币类型,默认人民币 + rootNode.set("amount", amountNode); + + // 如果你是服务商模式,需要添加子商户信息 + rootNode.put("sp_mchid", WxV3PayConfig.Mch_ID); + rootNode.put("sub_mchid", "123"); + rootNode.put("sp_appid", "wx41d32f362ba0f911"); + // rootNode.put("sub_appid", "子商户AppID"); // 如果子商户需要用自己的AppID拉起支付 + + String requestBody = rootNode.toString(); + log.info("微信APP支付下单请求体: {}", requestBody); + httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8)); + + // 3. 发送请求 + try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) { + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + log.info("微信APP支付下单响应状态码: {}, 响应体: {}", statusCode, responseBody); + + if (statusCode == 200) { // HTTP状态码 200 表示成功 + JsonNode responseNode = objectMapper.readTree(responseBody); + String prepayId = responseNode.get("prepay_id").asText(); + log.info("成功获取预支付交易会话标识 (prepay_id): {}", prepayId); + + // 4. 生成调起支付所需的参数 + Map<String, String> payParams = WXPaySignatureCertificateUtil.buildAppPayParams(prepayId); + log.info("生成APP支付参数: {}", payParams); + return payParams; + + } else { + // 处理错误情况 + log.error("微信APP支付下单失败,状态码: {}, 响应: {}", statusCode, responseBody); + // 可以根据 responseBody 中的 code 和 message 进一步分析错误 + throw new RuntimeException("微信APP支付下单失败: " + responseBody); + } + } catch (Exception e) { + log.error("微信APP支付下单请求执行异常", e); + throw new IOException("微信APP支付下单请求执行异常", e); + } + } + + /** + * 关闭订单 (根据商户订单号) + * + * @param outTradeNo 商户订单号 + * @return true 如果关闭成功或订单已关闭/不存在 (根据微信文档,成功是204 No Content) + * @throws IOException 网络或IO异常 + * @throws RuntimeException 关闭请求失败或处理异常 + */ + public boolean closeOrder(String outTradeNo) throws IOException { + // 1. 构建请求URL, 注意替换占位符 + String url = WXPayConstants.DOMAIN_API + + WXPayConstants.PAY_TRANSACTIONS_OUT_TRADE_NO.replace("{}", outTradeNo); + + HttpPost httpPost = new HttpPost(url); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-type", "application/json; charset=utf-8"); + + // 2. 构建请求体 JSON (只需要商户号) + ObjectNode rootNode = objectMapper.createObjectNode(); + rootNode.put("mchid", WxV3PayConfig.Mch_ID); + // 如果是服务商模式,则用 sp_mchid + rootNode.put("sp_mchid", WxV3PayConfig.Mch_ID); + + String requestBody = rootNode.toString(); + log.info("微信关单请求 URL: {}, 请求体: {}", url, requestBody); + httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8)); + + // 3. 发送请求 + try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) { + int statusCode = response.getStatusLine().getStatusCode(); + // 关单成功时,微信返回 HTTP 状态码 204 No Content + log.info("微信关单响应状态码: {}", statusCode); + if (statusCode == 204) { + log.info("订单 {} 关闭成功。", outTradeNo); + return true; + } else { + String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + log.error("微信关单失败,商户订单号: {}, 状态码: {}, 响应: {}", outTradeNo, statusCode, responseBody); + // 根据需要可以解析responseBody获取错误详情 + return false; // 或者抛出异常,根据业务决定 + } + } catch (Exception e) { + log.error("微信关单请求执行异常, 商户订单号: {}", outTradeNo, e); + throw new IOException("微信关单请求执行异常", e); + } + } + + + // --- 退款相关方法 (可选) --- + + /** + * 申请退款 + * + * @param outTradeNo 原商户订单号 + * @param outRefundNo 商户退款单号 (商户系统内部的退款单号,要求唯一) + * @param reason 退款原因 (可选) + * @param refundAmount 退款金额 (单位:元) + * @param totalAmount 原订单总金额 (单位:元) + * @return 退款处理结果,可以返回微信返回的JSON节点或自定义对象 + * @throws IOException 网络或IO异常 + * @throws RuntimeException 退款请求失败或处理异常 + */ + public JsonNode createRefund(String outTradeNo, String outRefundNo, String reason, + BigDecimal refundAmount, BigDecimal totalAmount) throws IOException { + + String url = WXPayConstants.DOMAIN_API + WXPayConstants.REFUND_DOMESTIC_REFUNDS; + HttpPost httpPost = new HttpPost(url); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-type", "application/json; charset=utf-8"); + + ObjectNode rootNode = objectMapper.createObjectNode(); + // 如果是服务商模式,需要添加子商户号 + // rootNode.put("sub_mchid", "子商户号"); + rootNode.put("out_trade_no", outTradeNo); + rootNode.put("out_refund_no", outRefundNo); + if (reason != null && !reason.isEmpty()) { + rootNode.put("reason", reason); + } + // 设置退款回调地址 + rootNode.put("notify_url", WXPayConstants.WECHAT_REFUNDS_NOTIFY_URL); + + ObjectNode amountNode = objectMapper.createObjectNode(); + amountNode.put("refund", refundAmount.multiply(new BigDecimal("100")).intValue()); // 退款金额,分 + amountNode.put("total", totalAmount.multiply(new BigDecimal("100")).intValue()); // 原订单金额,分 + amountNode.put("currency", "CNY"); + rootNode.set("amount", amountNode); + + String requestBody = rootNode.toString(); + log.info("微信申请退款请求体: {}", requestBody); + httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8)); + + try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) { + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + log.info("微信申请退款响应状态码: {}, 响应体: {}", statusCode, responseBody); + + if (statusCode == 200) { // 成功受理 + log.info("微信退款申请成功受理。"); + return objectMapper.readTree(responseBody); + } else { + log.error("微信申请退款失败,状态码: {}, 响应: {}", statusCode, responseBody); + throw new RuntimeException("微信申请退款失败: " + responseBody); + } + } catch (Exception e) { + log.error("微信申请退款请求执行异常", e); + throw new IOException("微信申请退款请求执行异常", e); + } + } + +} \ No newline at end of file -- Gitblit v1.7.1