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<String, String> 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<String, String> 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(), "退款回调验签失败");
|
}
|
}
|
}
|