| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- 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;
- /**
- * 支付退款服务
- *
- * <p>封装微信支付和支付宝的原路退款逻辑。
- * 用于重复支付场景:同一订单多渠道支付成功后,对第二笔及之后的支付自动发起退款。
- */
- @Slf4j
- @Service
- @RequiredArgsConstructor
- public class PaymentRefundService {
- private final PaymentProperties paymentProperties;
- @Qualifier("alipayClient")
- private final ObjectProvider<AlipayClient> alipayClientProvider;
- private final ObjectProvider<WxPayService> wxPayServiceProvider;
- /**
- * 发起原路退款
- *
- * @param channel 支付渠道:WECHAT/ALIPAY
- * @param orderNo 商户订单号
- * @param tradeNo 第三方交易号(微信 transaction_id / 支付宝 trade_no)
- * @param amount 退款金额(元)
- * @return 退款交易号(第三方退款单号)
- */
- public String refund(String channel, String orderNo, String tradeNo, BigDecimal amount) {
- String refundNo = "RF" + IdUtil.getSnowflake(1, 1).nextIdStr();
- if (paymentProperties.isMock()) {
- log.info("【模拟】重复支付退款成功: orderNo={}, tradeNo={}, refundNo={}, amount={}元",
- orderNo, tradeNo, refundNo, amount);
- return refundNo;
- }
- PaymentChannel ch = PaymentChannel.fromCode(channel);
- if (ch == null) {
- throw new BusinessException(ErrorCode.PAY_CHANNEL_NOT_SUPPORT, "不支持的退款渠道: " + channel);
- }
- return switch (ch) {
- case WECHAT -> wechatRefund(orderNo, tradeNo, amount, refundNo);
- case ALIPAY -> alipayRefund(orderNo, tradeNo, amount, refundNo);
- };
- }
- /**
- * 微信支付退款(API v3)
- *
- * <p>调用微信支付 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)
- *
- * <p>调用支付宝统一收单交易退款接口,按原交易号原路退回。
- */
- 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());
- }
- }
- }
|