无关风月
2025-04-30 4adb656ffd2c3660e07d224dd483e7479d48b46e
cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java
@@ -1,221 +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.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
@Service
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);
        }
    }
}
//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);
//        }
//    }
//
//}