package com.doumee.config.wx; import com.doumee.core.constants.ResponseStatus; import com.doumee.core.exception.BusinessException; import com.doumee.core.utils.ID; import com.wechat.pay.java.core.notification.RequestParam; import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest; import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; import com.wechat.pay.java.service.refund.model.AmountReq; import com.wechat.pay.java.service.refund.model.CreateRequest; import com.wechat.pay.java.service.refund.model.Refund; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; /** * 微信支付V3版本服务(JSAPI下单 + 退款 + 回调验签) * * @author rk * @date 2026/04/20 */ @Service @Slf4j public class WxPayV3Service { @Autowired private WxPayProperties wxPayProperties; /** * JSAPI下单,返回前端调起支付所需的参数 * * @param outTradeNo 商户订单号 * @param description 商品描述 * @param totalCents 金额(分) * @param openid 用户openid * @param notifyUrl 支付回调地址 * @param attach 附加数据(回调时原样返回,用于路由不同业务) * @return 前端调起支付参数(appId, timeStamp, nonceStr, package, signType, paySign) */ public Map createOrder(String outTradeNo, String description, Long totalCents, String openid, String notifyUrl, String attach) { if (WxMiniConfig.v3JsapiService == null) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "微信支付V3未初始化"); } try { PrepayRequest request = new PrepayRequest(); request.setAppid(wxPayProperties.getAppId()); request.setMchid(wxPayProperties.getMchId()); request.setDescription(description); request.setOutTradeNo(outTradeNo); request.setNotifyUrl(notifyUrl); if (attach != null && !attach.isEmpty()) { request.setAttach(attach); } com.wechat.pay.java.service.payments.jsapi.model.Amount amount = new com.wechat.pay.java.service.payments.jsapi.model.Amount(); amount.setTotal(1);//totalCents.intValue()); amount.setCurrency("CNY"); request.setAmount(amount); com.wechat.pay.java.service.payments.jsapi.model.Payer payer = new com.wechat.pay.java.service.payments.jsapi.model.Payer(); payer.setOpenid(openid); request.setPayer(payer); PrepayWithRequestPaymentResponse response = WxMiniConfig.v3JsapiService.prepayWithRequestPayment(request); Map result = new HashMap<>(); result.put("appId", response.getAppId()); result.put("timeStamp", response.getTimeStamp()); result.put("nonceStr", response.getNonceStr()); result.put("package", response.getPackageVal()); result.put("signType", response.getSignType()); result.put("paySign", response.getPaySign()); return result; } catch (Exception e) { log.error("微信支付V3下单失败: {}", e.getMessage(), e); throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "支付调起失败:" + e.getMessage()); } } /** * V3退款 * * @param outTradeNo 商户订单号 * @param totalCents 原订单金额(分) * @param refundCents 退款金额(分) * @param reason 退款原因 * @param notifyUrl 退款回调地址 * @return Refund 退款结果(包含 outRefundNo、status 等) */ public Refund refund(String outTradeNo, Long totalCents, Long refundCents, String reason, String notifyUrl) { if (WxMiniConfig.v3RefundService == null) { throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "微信支付V3未初始化"); } try { String outRefundNo = ID.nextGUID(); CreateRequest request = new CreateRequest(); request.setOutTradeNo(outTradeNo); request.setOutRefundNo(outRefundNo); request.setReason(reason); request.setNotifyUrl(notifyUrl); AmountReq amount = new AmountReq(); amount.setRefund(1L);//refundCents); amount.setTotal(1L);//totalCents); amount.setCurrency("CNY"); request.setAmount(amount); Refund result = WxMiniConfig.v3RefundService.create(request); log.info("微信支付V3退款申请结果, outTradeNo={}, outRefundNo={}, status={}", outTradeNo, outRefundNo, result.getStatus()); return result; } catch (Exception e) { log.error("微信支付V3退款失败: {}", e.getMessage(), e); throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "退款失败:" + e.getMessage()); } } /** * 解析V3支付回调通知 * * @param serialNumber 证书序列号(Wechatpay-Serial header) * @param timestamp 时间戳(Wechatpay-Timestamp header) * @param nonce 随机串(Wechatpay-Nonce header) * @param signature 签名(Wechatpay-Signature header) * @param body 请求体JSON * @return 解析后的Transaction对象 */ public com.wechat.pay.java.service.payments.model.Transaction parsePayNotify( String serialNumber, String timestamp, String nonce, String signature, String body) { try { RequestParam requestParam = new RequestParam.Builder() .serialNumber(serialNumber) .timestamp(timestamp) .nonce(nonce) .signature(signature) .body(body) .build(); return WxMiniConfig.v3NotificationParser.parse(requestParam, com.wechat.pay.java.service.payments.model.Transaction.class); } catch (Exception e) { log.error("微信支付V3回调验签失败: {}", e.getMessage(), e); throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "回调验签失败"); } } /** * 解析V3退款回调通知 * * @param serialNumber 证书序列号 * @param timestamp 时间戳 * @param nonce 随机串 * @param signature 签名 * @param body 请求体JSON * @return 解析后的RefundNotification对象 */ public com.wechat.pay.java.service.refund.model.RefundNotification parseRefundNotify( String serialNumber, String timestamp, String nonce, String signature, String body) { try { RequestParam requestParam = new RequestParam.Builder() .serialNumber(serialNumber) .timestamp(timestamp) .nonce(nonce) .signature(signature) .body(body) .build(); return WxMiniConfig.v3NotificationParser.parse(requestParam, com.wechat.pay.java.service.refund.model.RefundNotification.class); } catch (Exception e) { log.error("微信支付V3退款回调验签失败: {}", e.getMessage(), e); throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "退款回调验签失败"); } } }