package com.xuekairui.payment.service; import cn.hutool.core.util.IdUtil; import com.alipay.api.AlipayClient; import com.alipay.api.domain.AlipayTradeRefundModel; import com.alipay.api.request.AlipayTradeRefundRequest; import com.alipay.api.response.AlipayTradeRefundResponse; import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request; import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result; import com.github.binarywang.wxpay.service.WxPayService; import com.xuekairui.common.BusinessException; import com.xuekairui.common.ErrorCode; import com.xuekairui.payment.config.PaymentProperties; import com.xuekairui.payment.enums.PaymentChannel; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import java.math.BigDecimal; /** * 支付退款服务 * *
封装微信支付和支付宝的原路退款逻辑。
* 用于重复支付场景:同一订单多渠道支付成功后,对第二笔及之后的支付自动发起退款。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PaymentRefundService {
private final PaymentProperties paymentProperties;
@Qualifier("alipayClient")
private final ObjectProvider 调用微信支付 v3 退款接口,按原交易号原路退回。
* 需要商户已配置退款证书(apiclient_key.pem + apiclient_cert.pem)。
*/
private String wechatRefund(String orderNo, String tradeNo, BigDecimal amount, String refundNo) {
WxPayService wxPayService = wxPayServiceProvider.getIfAvailable();
if (wxPayService == null) {
throw new BusinessException(ErrorCode.PAY_CHANNEL_NOT_CONFIGURED, "WxPayService未初始化,无法退款");
}
try {
WxPayRefundV3Request request = new WxPayRefundV3Request();
request.setOutTradeNo(orderNo);
request.setOutRefundNo(refundNo);
request.setReason("重复支付自动退款");
int totalCents = amount.movePointRight(2).intValue();
WxPayRefundV3Request.Amount amountObj = new WxPayRefundV3Request.Amount();
amountObj.setRefund(totalCents);
amountObj.setTotal(totalCents);
amountObj.setCurrency("CNY");
request.setAmount(amountObj);
WxPayRefundV3Result result = wxPayService.refundV3(request);
log.info("微信退款成功: orderNo={}, tradeNo={}, refundNo={}, refundId={}",
orderNo, tradeNo, refundNo, result.getRefundId());
return result.getRefundId();
} catch (Exception e) {
log.error("微信退款失败: orderNo={}, tradeNo={}, refundNo={}", orderNo, tradeNo, refundNo, e);
throw new BusinessException(ErrorCode.REFUND_FAILED, "微信退款失败: " + e.getMessage());
}
}
/**
* 支付宝退款(alipay.trade.refund)
*
* 调用支付宝统一收单交易退款接口,按原交易号原路退回。
*/
private String alipayRefund(String orderNo, String tradeNo, BigDecimal amount, String refundNo) {
AlipayClient alipayClient = alipayClientProvider.getIfAvailable();
if (alipayClient == null) {
throw new BusinessException(ErrorCode.PAY_CHANNEL_NOT_CONFIGURED, "AlipayClient未初始化,无法退款");
}
try {
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setOutTradeNo(orderNo);
model.setTradeNo(tradeNo);
model.setRefundAmount(amount.toPlainString());
model.setOutRequestNo(refundNo);
model.setRefundReason("重复支付自动退款");
request.setBizModel(model);
boolean publicKeyMode = paymentProperties.getAlipay().getAlipayPublicKey() != null
&& !paymentProperties.getAlipay().getAlipayPublicKey().isBlank();
AlipayTradeRefundResponse response = publicKeyMode
? alipayClient.execute(request)
: alipayClient.certificateExecute(request);
if (!response.isSuccess()) {
log.error("支付宝退款失败: orderNo={}, tradeNo={}, refundNo={}, code={}, subCode={}, subMsg={}",
orderNo, tradeNo, refundNo, response.getCode(), response.getSubCode(), response.getSubMsg());
throw new BusinessException(ErrorCode.REFUND_FAILED,
"支付宝退款失败: " + response.getSubMsg());
}
log.info("支付宝退款成功: orderNo={}, tradeNo={}, refundNo={}, tradeNo={}",
orderNo, tradeNo, refundNo, response.getTradeNo());
return response.getTradeNo();
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("支付宝退款异常: orderNo={}, tradeNo={}, refundNo={}", orderNo, tradeNo, refundNo, e);
throw new BusinessException(ErrorCode.REFUND_FAILED, "支付宝退款异常: " + e.getMessage());
}
}
}