|
|
@@ -1,60 +1,44 @@
|
|
|
package com.fs.app.service.impl;
|
|
|
|
|
|
-import cn.hutool.json.JSONUtil;
|
|
|
-import com.alibaba.fastjson.JSON;
|
|
|
-import com.alipay.api.AlipayApiException;
|
|
|
-import com.alipay.api.AlipayClient;
|
|
|
-import com.alipay.api.DefaultAlipayClient;
|
|
|
-import com.alipay.api.diagnosis.DiagnosisUtils;
|
|
|
-import com.alipay.api.domain.AlipayTradeAppPayModel;
|
|
|
import com.alipay.api.internal.util.AlipaySignature;
|
|
|
-import com.alipay.api.request.AlipayTradeAppPayRequest;
|
|
|
-import com.alipay.api.response.AlipayTradeAppPayResponse;
|
|
|
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import com.fs.app.service.AppPayService;
|
|
|
import com.fs.common.core.domain.R;
|
|
|
import com.fs.common.exception.CustomException;
|
|
|
-import com.fs.common.utils.ServletUtils;
|
|
|
-import com.fs.common.utils.ip.IpUtils;
|
|
|
-import com.fs.core.config.WxPayProperties;
|
|
|
-import com.fs.core.utils.OrderCodeUtils;
|
|
|
+import com.fs.common.utils.DateUtils;
|
|
|
+import com.fs.core.utils.AppPayPlaySourceUtils;
|
|
|
+import com.fs.course.domain.FsCoursePlaySourceConfig;
|
|
|
+import com.fs.course.service.IFsCoursePlaySourceConfigService;
|
|
|
import com.fs.course.service.IFsUserCourseOrderService;
|
|
|
import com.fs.course.service.IFsUserVipOrderService;
|
|
|
-import com.fs.his.domain.*;
|
|
|
-import com.fs.his.dto.PayConfigDTO;
|
|
|
-import com.fs.his.enums.FsPackageOrderStatusEnum;
|
|
|
-import com.fs.his.mapper.FsPackageOrderMapper;
|
|
|
+import com.fs.his.domain.FsPayConfig;
|
|
|
+import com.fs.his.domain.FsStorePayment;
|
|
|
+import com.fs.his.domain.MerchantAppConfig;
|
|
|
import com.fs.his.mapper.FsStorePaymentMapper;
|
|
|
-import com.fs.his.mapper.FsUserWxMapper;
|
|
|
-import com.fs.his.param.FsPackageOrderDoPayParam;
|
|
|
-import com.fs.his.service.*;
|
|
|
-import com.fs.huifuPay.service.HuiFuService;
|
|
|
+import com.fs.his.service.IFsInquiryOrderService;
|
|
|
+import com.fs.his.service.IFsPackageOrderService;
|
|
|
+import com.fs.his.service.IFsStoreOrderService;
|
|
|
+import com.fs.his.service.IMerchantAppConfigService;
|
|
|
import com.fs.live.service.ILiveOrderService;
|
|
|
import com.fs.system.domain.SysConfig;
|
|
|
import com.fs.system.mapper.SysConfigMapper;
|
|
|
-import com.fs.system.service.ISysConfigService;
|
|
|
-import com.fs.tzBankPay.TzBankService.TzBankService;
|
|
|
-import com.fs.tzBankPay.doman.PayType;
|
|
|
-import com.fs.ybPay.domain.OrderResult;
|
|
|
-import com.fs.ybPay.dto.OrderQueryDTO;
|
|
|
-import com.fs.ybPay.service.IPayService;
|
|
|
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
|
|
|
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
|
|
-import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
|
|
|
+import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
|
|
|
+import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
|
|
|
import com.github.binarywang.wxpay.config.WxPayConfig;
|
|
|
import com.github.binarywang.wxpay.exception.WxPayException;
|
|
|
import com.github.binarywang.wxpay.service.WxPayService;
|
|
|
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
|
|
import com.google.gson.Gson;
|
|
|
+import com.hc.openapi.tool.fastjson.JSON;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.context.annotation.Lazy;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
-import org.springframework.transaction.annotation.Transactional;
|
|
|
-import org.springframework.transaction.interceptor.TransactionAspectSupport;
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
-import java.util.Date;
|
|
|
+import java.math.RoundingMode;
|
|
|
import java.util.Map;
|
|
|
|
|
|
@Slf4j
|
|
|
@@ -76,6 +60,12 @@ public class AppPayServiceImpl implements AppPayService {
|
|
|
private IFsUserVipOrderService vipOrderService;
|
|
|
@Autowired
|
|
|
private ILiveOrderService liveOrderService;
|
|
|
+ @Autowired
|
|
|
+ private IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
|
|
|
+ @Autowired
|
|
|
+ private IMerchantAppConfigService merchantAppConfigService;
|
|
|
+ @Autowired
|
|
|
+ private FsStorePaymentMapper fsStorePaymentMapper;
|
|
|
|
|
|
/**
|
|
|
* 支付宝回调
|
|
|
@@ -86,7 +76,7 @@ public class AppPayServiceImpl implements AppPayService {
|
|
|
SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.pay");
|
|
|
FsPayConfig fsPayConfig = new Gson().fromJson(sysConfig.getConfigValue(), FsPayConfig.class);
|
|
|
try {
|
|
|
- boolean flag = AlipaySignature.rsaCheckV1(params, fsPayConfig.getAliPublicKey(), "UTF-8","RSA2");
|
|
|
+ boolean flag = AlipaySignature.rsaCheckV1(params, fsPayConfig.getAliPublicKey(), "UTF-8", "RSA2");
|
|
|
if (!flag) {
|
|
|
log.info("支付宝回调验签失败:{}", params);
|
|
|
}
|
|
|
@@ -100,67 +90,168 @@ public class AppPayServiceImpl implements AppPayService {
|
|
|
String tradeNo = params.get("trade_no");
|
|
|
switch (arr[0]) {
|
|
|
case "inquiry":
|
|
|
- inquiryOrderService.payConfirm("", arr[1],"","",1,tradeNo,"");
|
|
|
+ inquiryOrderService.payConfirm("", arr[1], tradeNo, "", 1, tradeNo, null);
|
|
|
break;
|
|
|
case "store":
|
|
|
- storeOrderService.payConfirm("", arr[1],"","",1,tradeNo,"");
|
|
|
+ storeOrderService.payConfirm("", arr[1], tradeNo, "", 1, tradeNo, null);
|
|
|
break;
|
|
|
case "package":
|
|
|
- packageOrderService.payConfirm("", arr[1],"","",1,tradeNo,"");
|
|
|
+ packageOrderService.payConfirm("", arr[1], tradeNo, "", 1, tradeNo, null);
|
|
|
break;
|
|
|
case "appvip":
|
|
|
- vipOrderService.payConfirm("", arr[1],"","",1,tradeNo,"");
|
|
|
+ vipOrderService.payConfirm("", arr[1], tradeNo, "", 1, tradeNo, null);
|
|
|
break;
|
|
|
case "course":
|
|
|
- courseOrderService.payConfirm("", arr[1],"","",1,tradeNo,"");
|
|
|
+ courseOrderService.payConfirm("", arr[1], tradeNo, "", 1, tradeNo, null);
|
|
|
break;
|
|
|
case "applive":
|
|
|
liveOrderService.payConfirm(1, null, arr[1], tradeNo, tradeNo, null);
|
|
|
break;
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
} catch (Exception e) {
|
|
|
- log.info("支付宝回调异常:{}", e);
|
|
|
+ log.error("支付宝回调异常", e);
|
|
|
throw new RuntimeException(e);
|
|
|
}
|
|
|
-
|
|
|
return "success";
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public String wxNotify(WxPayOrderNotifyResult result) {
|
|
|
log.info("微信回调参数: {}", result);
|
|
|
- if (!"SUCCESS".equals(result.getReturnCode())){
|
|
|
+ if (!"SUCCESS".equals(result.getReturnCode())) {
|
|
|
return WxPayNotifyResponse.success("微信回调失败");
|
|
|
}
|
|
|
-
|
|
|
- if (!"SUCCESS".equals(result.getResultCode())){
|
|
|
+ if (!"SUCCESS".equals(result.getResultCode())) {
|
|
|
return WxPayNotifyResponse.success("交易失败");
|
|
|
}
|
|
|
|
|
|
String[] tradeNoArr = result.getOutTradeNo().split("-");
|
|
|
- switch (tradeNoArr[0]) {
|
|
|
- case "inquiry":
|
|
|
- inquiryOrderService.payConfirm("", tradeNoArr[1],"","",1,result.getTransactionId(),"");
|
|
|
- break;
|
|
|
- case "store":
|
|
|
- storeOrderService.payConfirm("", tradeNoArr[1],"","",1,result.getTransactionId(),"");
|
|
|
- break;
|
|
|
- case "package":
|
|
|
- packageOrderService.payConfirm("", tradeNoArr[1],"","",1,result.getTransactionId(),"");
|
|
|
- break;
|
|
|
- case "appvip":
|
|
|
- vipOrderService.payConfirm("", tradeNoArr[1],"","",1,result.getTransactionId(),"");
|
|
|
- break;
|
|
|
- case "course":
|
|
|
- courseOrderService.payConfirm("", tradeNoArr[1],"","",1,result.getTransactionId(),"");
|
|
|
- break;
|
|
|
- case "applive":
|
|
|
- liveOrderService.payConfirm(1, null, tradeNoArr[1], result.getTransactionId(), result.getTransactionId(), null);
|
|
|
- break;
|
|
|
+ String bizType = tradeNoArr[0];
|
|
|
+ String payCode = tradeNoArr.length > 1 ? tradeNoArr[1] : null;
|
|
|
+ String tradeNo = result.getTransactionId();
|
|
|
+ try {
|
|
|
+ switch (bizType) {
|
|
|
+ case "inquiry":
|
|
|
+ inquiryOrderService.payConfirm("", payCode, tradeNo, "", 1, tradeNo, null);
|
|
|
+ break;
|
|
|
+ case "store":
|
|
|
+ storeOrderService.payConfirm("", payCode, tradeNo, "", 1, tradeNo, null);
|
|
|
+ break;
|
|
|
+ case "package":
|
|
|
+ packageOrderService.payConfirm("", payCode, tradeNo, "", 1, tradeNo, null);
|
|
|
+ break;
|
|
|
+ case "appvip":
|
|
|
+ vipOrderService.payConfirm("", payCode, tradeNo, "", 1, tradeNo, null);
|
|
|
+ break;
|
|
|
+ case "course":
|
|
|
+ courseOrderService.payConfirm("", payCode, tradeNo, "", 1, tradeNo, null);
|
|
|
+ break;
|
|
|
+ case "applive":
|
|
|
+ liveOrderService.payConfirm(1, null, payCode, tradeNo, tradeNo, null);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ log.warn("未知的APP微信回调业务类型: {}", bizType);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return WxPayNotifyResponse.success("OK");
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 微信已扣款,但本地确认/生单失败:自动退款,避免客户丢钱
|
|
|
+ log.error("APP微信支付确认失败,准备自动退款 outTradeNo={}, transactionId={}, err={}",
|
|
|
+ result.getOutTradeNo(), tradeNo, e.getMessage(), e);
|
|
|
+ try {
|
|
|
+ autoRefundAfterConfirmFail(result, payCode, e.getMessage());
|
|
|
+ return WxPayNotifyResponse.success("CONFIRM_FAIL_REFUNDED");
|
|
|
+ } catch (Exception refundEx) {
|
|
|
+ log.error("APP微信自动退款失败,请人工处理 outTradeNo={}, transactionId={}",
|
|
|
+ result.getOutTradeNo(), tradeNo, refundEx);
|
|
|
+ // 退款失败时返回fail,让微信重试回调(或人工补退)
|
|
|
+ return WxPayNotifyResponse.fail("CONFIRM_FAIL_REFUND_FAIL");
|
|
|
+ }
|
|
|
}
|
|
|
- return WxPayNotifyResponse.success("OK");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 支付成功但业务确认失败时自动原路退款。
|
|
|
+ */
|
|
|
+ private void autoRefundAfterConfirmFail(WxPayOrderNotifyResult notifyResult, String payCode, String reason) throws WxPayException {
|
|
|
+ if (notifyResult == null || StringUtils.isBlank(notifyResult.getOutTradeNo())) {
|
|
|
+ throw new CustomException("自动退款失败:回调参数缺失");
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(payCode)) {
|
|
|
+ FsStorePayment payment = fsStorePaymentMapper.selectFsStorePaymentByPaymentCode(payCode);
|
|
|
+ if (payment != null && payment.getStatus() != null && payment.getStatus() == -1) {
|
|
|
+ log.info("支付单已退款,跳过重复退款 payCode={}", payCode);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ WxPayService payService = buildAppWxPayService(notifyResult.getAppid());
|
|
|
+ WxPayRefundRequest refundRequest = new WxPayRefundRequest();
|
|
|
+ refundRequest.setTransactionId(notifyResult.getTransactionId());
|
|
|
+ refundRequest.setOutTradeNo(notifyResult.getOutTradeNo());
|
|
|
+ // 固定退款单号,保证微信重试回调时退款幂等
|
|
|
+ refundRequest.setOutRefundNo("refund-" + notifyResult.getOutTradeNo());
|
|
|
+ Integer totalFee = notifyResult.getTotalFee();
|
|
|
+ if (totalFee == null || totalFee <= 0) {
|
|
|
+ throw new CustomException("自动退款失败:金额异常");
|
|
|
+ }
|
|
|
+ refundRequest.setTotalFee(totalFee);
|
|
|
+ refundRequest.setRefundFee(totalFee);
|
|
|
+ refundRequest.setRefundDesc(StringUtils.left("支付确认失败自动退款:" + StringUtils.defaultString(reason), 80));
|
|
|
+
|
|
|
+ log.info("发起APP微信自动退款 outTradeNo={}, transactionId={}, totalFee={}",
|
|
|
+ notifyResult.getOutTradeNo(), notifyResult.getTransactionId(), totalFee);
|
|
|
+ WxPayRefundResult refundResult = payService.refund(refundRequest);
|
|
|
+ log.info("APP微信自动退款结果: {}", refundResult);
|
|
|
+
|
|
|
+ if (StringUtils.isNotBlank(payCode)) {
|
|
|
+ FsStorePayment payment = fsStorePaymentMapper.selectFsStorePaymentByPaymentCode(payCode);
|
|
|
+ if (payment != null) {
|
|
|
+ FsStorePayment paymentMap = new FsStorePayment();
|
|
|
+ paymentMap.setPaymentId(payment.getPaymentId());
|
|
|
+ paymentMap.setStatus(-1);
|
|
|
+ paymentMap.setTradeNo(notifyResult.getTransactionId());
|
|
|
+ paymentMap.setRefundTime(DateUtils.getNowDate());
|
|
|
+ paymentMap.setRefundMoney(fenToYuan(totalFee));
|
|
|
+ paymentMap.setRemark(StringUtils.left("支付确认失败自动退款:" + StringUtils.defaultString(reason), 200));
|
|
|
+ fsStorePaymentMapper.updateFsStorePayment(paymentMap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private WxPayService buildAppWxPayService(String wxAppId) {
|
|
|
+ String realWxAppId = AppPayPlaySourceUtils.toWxAppId(wxAppId);
|
|
|
+ String playSourceAppId = AppPayPlaySourceUtils.toPlaySourceAppId(realWxAppId);
|
|
|
+ FsCoursePlaySourceConfig playSourceConfig =
|
|
|
+ fsCoursePlaySourceConfigService.selectCoursePlaySourceConfigByAppId(playSourceAppId);
|
|
|
+ if (playSourceConfig == null) {
|
|
|
+ throw new CustomException("自动退款失败:未找到播放源配置 " + playSourceAppId);
|
|
|
+ }
|
|
|
+ MerchantAppConfig merchantAppConfig =
|
|
|
+ merchantAppConfigService.selectMerchantAppConfigById(playSourceConfig.getMerchantConfigId());
|
|
|
+ if (merchantAppConfig == null || StringUtils.isBlank(merchantAppConfig.getDataJson())) {
|
|
|
+ throw new CustomException("自动退款失败:未找到商户配置 " + playSourceAppId);
|
|
|
+ }
|
|
|
+ FsPayConfig fsPayConfig = JSON.parseObject(merchantAppConfig.getDataJson(), FsPayConfig.class);
|
|
|
+ if (StringUtils.isBlank(fsPayConfig.getWxAppMchId()) || StringUtils.isBlank(fsPayConfig.getWxAppMchKey())) {
|
|
|
+ throw new CustomException("自动退款失败:APP微信商户号/密钥未配置");
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(fsPayConfig.getWxAppKeyPath())) {
|
|
|
+ throw new CustomException("自动退款失败:未配置APP微信退款证书路径(wxAppKeyPath)");
|
|
|
+ }
|
|
|
+
|
|
|
+ WxPayConfig payConfig = new WxPayConfig();
|
|
|
+ payConfig.setAppId(realWxAppId);
|
|
|
+ payConfig.setMchId(fsPayConfig.getWxAppMchId());
|
|
|
+ payConfig.setMchKey(fsPayConfig.getWxAppMchKey());
|
|
|
+ payConfig.setKeyPath(fsPayConfig.getWxAppKeyPath());
|
|
|
+ WxPayServiceImpl payService = new WxPayServiceImpl();
|
|
|
+ payService.setConfig(payConfig);
|
|
|
+ return payService;
|
|
|
+ }
|
|
|
+
|
|
|
+ private BigDecimal fenToYuan(Integer fen) {
|
|
|
+ return BigDecimal.valueOf(fen).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -171,5 +262,4 @@ public class AppPayServiceImpl implements AppPayService {
|
|
|
}
|
|
|
return R.ok(merchantAppConfig.getMerchantType());
|
|
|
}
|
|
|
-
|
|
|
}
|