diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/config/WxPayConfig.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/config/WxPayConfig.java index ae1b748b..73e3dcdf 100644 --- a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/config/WxPayConfig.java +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/config/WxPayConfig.java @@ -52,6 +52,9 @@ public class WxPayConfig { @Value("${wx.pay.merchantSerialNumber}") private String merchantSerialNumber; + @Value("${wx.pay.transferNotifyUrl}") + private String transferNotifyUrl; + @Bean public AppService appService () { // 初始化商户配置 diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/NotifyEventType.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxNotifyEventType.java similarity index 84% rename from smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/NotifyEventType.java rename to smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxNotifyEventType.java index 9cf2bd61..bac6b30b 100644 --- a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/NotifyEventType.java +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxNotifyEventType.java @@ -1,4 +1,4 @@ -package com.ruoyi.common.pay.wx.domain; +package com.ruoyi.common.pay.wx.domain.enums; import lombok.AllArgsConstructor; import lombok.Getter; @@ -10,7 +10,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum NotifyEventType { +public enum WxNotifyEventType { TRANSACTION_SUCCESS("TRANSACTION.SUCCESS", "支付成功"), MCHTRANSFER_BATCH_FINISHED("MCHTRANSFER.BATCH.FINISHED", "商户转账批次完成通知"), diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/TransferBatchStatus.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxTransferBatchStatus.java similarity index 50% rename from smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/TransferBatchStatus.java rename to smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxTransferBatchStatus.java index 2c2ebb77..402ff68b 100644 --- a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/TransferBatchStatus.java +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxTransferBatchStatus.java @@ -1,6 +1,9 @@ -package com.ruoyi.common.pay.wx.domain; +package com.ruoyi.common.pay.wx.domain.enums; +import java.util.Arrays; +import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.Getter; @@ -11,7 +14,7 @@ import lombok.Getter; */ @AllArgsConstructor @Getter -public enum TransferBatchStatus { +public enum WxTransferBatchStatus { WAIT_PAY("WAIT_PAY", "待付款确认"), ACCEPTED("ACCEPTED", "已受理"), PROCESSING("PROCESSING", "转账中"), @@ -22,12 +25,16 @@ public enum TransferBatchStatus { private final String status; private final String msg; - public static TransferBatchStatus parse(String status) { - for (TransferBatchStatus obj : TransferBatchStatus.values()) { + public static WxTransferBatchStatus parse(String status) { + for (WxTransferBatchStatus obj : WxTransferBatchStatus.values()) { if (Objects.equals(obj.getStatus(), status)) { return obj; } } - throw new RuntimeException("不存在值为" + status + "的状态"); + return null; } -} \ No newline at end of file + + public static List asList(WxTransferBatchStatus...statuses) { + return Arrays.stream(statuses).map(WxTransferBatchStatus::getStatus).collect(Collectors.toList()); + } +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxTransferDetailStatus.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxTransferDetailStatus.java new file mode 100644 index 00000000..2cf673e2 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxTransferDetailStatus.java @@ -0,0 +1,30 @@ +package com.ruoyi.common.pay.wx.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author wjh + * 2024/8/12 + */ +@Getter +@AllArgsConstructor +public enum WxTransferDetailStatus { + + /** + * INIT 初始状态 明细的初始状态 无 + * WAIT_PAY 待商户确认 当前明细等待商户管理员确认付款 商户管理员确认转账,或撤销转账 + * PROCESSING 转账中 当前明细正在处理中,转账结果尚未明确 查询明细单,确认明细处理结果 + * SUCCESS 转账成功 当前明细转账已成功 向用户展现转账结果 + * FAIL 转账失败 当前明细转账已失败 确认失败原因,并决定是否重新发起当前明细转账(并非整个转账批次) + */ + WAIT_PAY("WAIT_PAY", "待商户确认"), + PROCESSING("PROCESSING", "转账中"), + SUCCESS("SUCCESS", "转账成功"), + FAIL("FAIL", "转账失败"), + INIT("INIT", "初始状态"); + + private final String status; + private final String msg; + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/TransferScene.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxTransferScene.java similarity index 93% rename from smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/TransferScene.java rename to smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxTransferScene.java index 18456c95..ad323031 100644 --- a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/TransferScene.java +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/enums/WxTransferScene.java @@ -12,7 +12,7 @@ import java.math.BigDecimal; */ @AllArgsConstructor @Getter -public enum TransferScene { +public enum WxTransferScene { WITHDRAW(new BigDecimal(500), "提现"); diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/request/MyInitiateBatchTransferRequest.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/request/MyInitiateBatchTransferRequest.java new file mode 100644 index 00000000..b149ca29 --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/domain/request/MyInitiateBatchTransferRequest.java @@ -0,0 +1,17 @@ +package com.ruoyi.common.pay.wx.domain.request; + +import com.google.gson.annotations.SerializedName; +import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest; +import lombok.Data; + +/** + * @author wjh + * 2024/8/12 + */ +@Data +public class MyInitiateBatchTransferRequest extends InitiateBatchTransferRequest { + + @SerializedName("notify_url") + private String notifyUrl; + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/service/WxPayService.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/service/WxPayService.java index a608ed4c..97379c76 100644 --- a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/service/WxPayService.java +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/service/WxPayService.java @@ -1,83 +1,116 @@ package com.ruoyi.common.pay.wx.service; -import com.ruoyi.common.pay.wx.domain.BatchTransferAble; -import com.ruoyi.common.pay.wx.domain.Payable; -import com.ruoyi.common.pay.wx.domain.RefundAble; -import com.ruoyi.common.pay.wx.domain.enums.TransferScene; -import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.config.WxPayConfig; +import com.ruoyi.common.pay.wx.domain.*; +import com.wechat.pay.java.service.payments.jsapi.JsapiService; +import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension; +import com.wechat.pay.java.service.payments.jsapi.model.*; import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.refund.RefundService; +import com.wechat.pay.java.service.refund.model.CreateRequest; import com.wechat.pay.java.service.refund.model.Refund; -import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse; -import com.wechat.pay.java.service.transferbatch.model.TransferDetailInput; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; -import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; -import java.util.List; /** - * 微信支付服务接口 + * 微信支付服务 * @author 辉 * 2024/3/11 */ -public interface WxPayService { +@Service +@Slf4j +public class WxPayService { - PrepayWithRequestPaymentResponse prepayWithRequestPayment(Payable payable); + @Autowired + private JsapiService jsapiService; - PrepayWithRequestPaymentResponse prepayWithRequestPayment(String billNo, BigDecimal money, String description, String wxOpenId, String attach); + @Autowired + private JsapiServiceExtension jsapiServiceExtension; + @Autowired + private WxPayConfig wxPayConfig; + + @Autowired + private RefundService refundService; + + private static final String CNY = "CNY"; + + public PrepayWithRequestPaymentResponse prepayWithRequestPayment(Payable payable) { + return this.prepayWithRequestPayment(payable.payableOutTradeNo(), payable.payableMoney(), payable.payableDescription(), payable.payableOpenId(), payable.payableAttach()); + } + + public PrepayWithRequestPaymentResponse prepayWithRequestPayment(String billNo, BigDecimal money, String description, String wxOpenId, String attach) { + // 获取JSAPI所需参数 + PrepayRequest request = new PrepayRequest(); + request.setAmount(getAmount(money)); + request.setOutTradeNo(billNo); + request.setAppid(wxPayConfig.getAppId()); + request.setMchid(wxPayConfig.getMerchantId()); + request.setDescription(description); + request.setNotifyUrl(wxPayConfig.getNotifyUrl()); + request.setPayer(getPayer(wxOpenId)); + request.setAttach(attach); + return jsapiServiceExtension.prepayWithRequestPayment(request); + } /** - * 关闭订单 + * 关闭支付订单 * @param billNo 平台订单编号 */ - void closeOrder(String billNo); + public void closeOrder(String billNo) { + CloseOrderRequest request = new CloseOrderRequest(); + request.setMchid(wxPayConfig.getMerchantId()); + request.setOutTradeNo(billNo); + jsapiService.closeOrder(request); + } - /** - * 通过微信订单id查询订单信息 - * @param prePayId 微信订单id - * @return 订单信息 - */ - Transaction queryOrderById(String prePayId); + public Transaction queryOrderById(String prePayId) { + QueryOrderByIdRequest request = new QueryOrderByIdRequest(); + request.setMchid(wxPayConfig.getMerchantId()); + request.setTransactionId(prePayId); + return jsapiService.queryOrderById(request); + } - /** - * 通过订单编号查询订单信息 - * @param billNo 订单编号 - * @return 订单信息 - */ - Transaction queryOrderByOutTradeNo(String billNo); - - boolean isSuccess(Transaction transaction); - - /** - * 转账到零钱 - */ - InitiateBatchTransferResponse batchTransfer(BatchTransferAble batchTransferAble); - - /** - * 微信商户支付通知 - */ - void wxTransferNotify(HttpServletRequest request); - - /** - * 构造给一个用户转账所需的明细列表,将限额分解成多个明细 - * @param totalAmount 转账总金额 - * @param openId 微信用户openId - * @param transferScene 转账场景 - */ - List buildTransferDetailList(BigDecimal totalAmount, String openId, TransferScene transferScene); + public Transaction queryOrderByOutTradeNo(String billNo) { + QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest(); + request.setMchid(wxPayConfig.getMerchantId()); + request.setOutTradeNo(billNo); + return jsapiService.queryOrderByOutTradeNo(request); + } /** * 发起退款 + * + * @param refund */ - Refund refund(RefundAble refund); + public Refund refund(RefundAble refund) { + CreateRequest request = new CreateRequest(); + request.setOutTradeNo(refund.refundOutTradeNo()); + request.setOutRefundNo(refund.refundOutRefundNo()); + request.setReason(refund.refundReason()); + request.setAmount(refund.refundAmount()); + request.setNotifyUrl(wxPayConfig.getRefundNotifyUrl()); + log.info("【退款】请求微信参数: {}", JSON.toJSONString(request)); + Refund res = refundService.create(request); + log.info("【退款】微信返回结果:【{}】",JSON.toJSONString(refund)); + return res; + } - /** - * 验签并解析 - */ - T checkAndParse(HttpServletRequest request, String body, Class clazz); + private Payer getPayer(String openId) { + Payer payer = new Payer(); + payer.setOpenid(openId); + return payer; + } + + private Amount getAmount(BigDecimal money) { + Amount amount = new Amount(); + amount.setTotal(money.intValue()); + amount.setCurrency(CNY); + return amount; + } - /** - * 转为业务对象 - */ - Transaction toTransaction(HttpServletRequest request); } diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/service/WxPayServiceImpl.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/service/WxPayServiceImpl.java deleted file mode 100644 index c441ffbe..00000000 --- a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/service/WxPayServiceImpl.java +++ /dev/null @@ -1,254 +0,0 @@ -package com.ruoyi.common.pay.wx.service; - -import com.alibaba.fastjson2.JSON; -import com.ruoyi.common.config.WxPayConfig; -import com.ruoyi.common.exception.ServiceException; -import com.ruoyi.common.pay.wx.domain.*; -import com.ruoyi.common.utils.ServiceUtil; -import com.ruoyi.common.utils.SnowFlakeUtil; -import com.ruoyi.common.utils.http.HttpUtils; -import com.ruoyi.common.pay.wx.domain.enums.TransferScene; -import com.wechat.pay.java.core.notification.Notification; -import com.wechat.pay.java.core.notification.NotificationParser; -import com.wechat.pay.java.core.notification.RequestParam; -import com.wechat.pay.java.service.payments.jsapi.JsapiService; -import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension; -import com.wechat.pay.java.service.payments.jsapi.model.*; -import com.wechat.pay.java.service.payments.model.Transaction; -import com.wechat.pay.java.service.refund.RefundService; -import com.wechat.pay.java.service.refund.model.CreateRequest; -import com.wechat.pay.java.service.refund.model.Refund; -import com.wechat.pay.java.service.transferbatch.TransferBatchService; -import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest; -import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse; -import com.wechat.pay.java.service.transferbatch.model.TransferDetailInput; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.servlet.http.HttpServletRequest; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - -/** - * 微信支付服务 - * @author 辉 - * 2024/3/11 - */ -@Service -@Slf4j -public class WxPayServiceImpl implements WxPayService { - - @Autowired - private JsapiService jsapiService; - - @Autowired - private JsapiServiceExtension jsapiServiceExtension; - - @Autowired - private WxPayConfig wxPayConfig; - - @Autowired - private NotificationParser notificationParser; - - @Autowired - private RefundService refundService; - - @Autowired - private TransferBatchService transferBatchService; - - private static final String CNY = "CNY"; - - @Override - public PrepayWithRequestPaymentResponse prepayWithRequestPayment(Payable payable) { - return this.prepayWithRequestPayment(payable.payableOutTradeNo(), payable.payableMoney(), payable.payableDescription(), payable.payableOpenId(), payable.payableAttach()); - } - - @Override - public PrepayWithRequestPaymentResponse prepayWithRequestPayment(String billNo, BigDecimal money, String description, String wxOpenId, String attach) { - // 获取JSAPI所需参数 - PrepayRequest request = new PrepayRequest(); - request.setAmount(getAmount(money)); - request.setOutTradeNo(billNo); - request.setAppid(wxPayConfig.getAppId()); - request.setMchid(wxPayConfig.getMerchantId()); - request.setDescription(description); - request.setNotifyUrl(wxPayConfig.getNotifyUrl()); - request.setPayer(getPayer(wxOpenId)); - request.setAttach(attach); - return jsapiServiceExtension.prepayWithRequestPayment(request); - } - - /** - * 关闭支付订单 - * @param billNo 平台订单编号 - */ - @Override - public void closeOrder(String billNo) { - CloseOrderRequest request = new CloseOrderRequest(); - request.setMchid(wxPayConfig.getMerchantId()); - request.setOutTradeNo(billNo); - jsapiService.closeOrder(request); - } - - @Override - public Transaction queryOrderById(String prePayId) { - QueryOrderByIdRequest request = new QueryOrderByIdRequest(); - request.setMchid(wxPayConfig.getMerchantId()); - request.setTransactionId(prePayId); - return jsapiService.queryOrderById(request); - } - - @Override - public Transaction queryOrderByOutTradeNo(String billNo) { - QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest(); - request.setMchid(wxPayConfig.getMerchantId()); - request.setOutTradeNo(billNo); - return jsapiService.queryOrderByOutTradeNo(request); - } - - @Override - public boolean isSuccess(Transaction transaction) { - return transaction != null && Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState()); - } - - /** - * 转账到零钱 - */ - @Override - public InitiateBatchTransferResponse batchTransfer(BatchTransferAble bill) { - InitiateBatchTransferRequest request = new InitiateBatchTransferRequest(); - request.setAppid(wxPayConfig.getAppId()); - request.setOutBatchNo(bill.transferOutBatchNo()); - request.setBatchName(bill.transferBatchName()); - request.setBatchRemark(bill.transferBatchRemark()); - request.setTotalAmount(bill.transferTotalAmount()); - request.setTotalNum(bill.transferDetailList().size()); - request.setTransferDetailList(bill.transferDetailList()); - return transferBatchService.initiateBatchTransfer(request); - } - - @Override - @Transactional - public void wxTransferNotify(HttpServletRequest request) { - throw new ServiceException("开发中"); -// String body = HttpUtils.getBody(request); -// // 解析通知数据 -// Notification notification = JSON.parseObject(body, Notification.class); -// -// // 判断是否重复通知,重复通知则忽略 -// if (isRepeatNotify(notification.getId())) { -// return; -// } -// -// // 商户转账成功通知 -// if (NotifyEventType.MCHTRANSFER_BATCH_FINISHED.getValue().equals(notification.getEventType())) { -// // 验签、解密并转换成 TransferBatchGet -// TransferBatchGet transferBatchGet = checkAndParse(request, body, TransferBatchGet.class); -// TransactionBill bill = transactionBillService.selectSmTransactionBillByBillNo(transferBatchGet.getOutBatchNo()); -// ServiceUtil.assertion(bill == null, "订单不存在"); -// -// // 修改订单状态 -// if (TransferBatchStatus.FINISHED.getStatus().equals(transferBatchGet.getBatchStatus())) { -// // 提现成功 -// transactionBillService.withdrawSuccess(bill.getBillId(), LocalDateTime.now()); -// } else if (TransferBatchStatus.CLOSED.getStatus().equals(transferBatchGet.getBatchStatus())) { -// // 提现失败 -// transactionBillService.withdrawFailed(bill.getBillId()); -// } -// } - - } - - @Override - public List buildTransferDetailList(BigDecimal totalAmount, String openId, TransferScene transferScene) { - ServiceUtil.assertion(BigDecimal.ZERO.compareTo(totalAmount) > 0, "转账总金额不允许小于0"); - ServiceUtil.assertion(StringUtils.isBlank(openId), "微信openId不允许为空"); - ServiceUtil.assertion(transferScene == null, "转账场景不允许为空"); - - List transferDetailList = new ArrayList<>(); - BigDecimal limitAmount = transferScene.getLimitAmount(); // 单笔限额 - BigDecimal payAmount = totalAmount; // 需要转账的金额 - while (BigDecimal.ZERO.compareTo(payAmount) < 0) { - BigDecimal detailPayAmount = payAmount.compareTo(limitAmount) > 0 ? limitAmount : payAmount; // 当前明细转账的金额 - TransferDetailInput input = new TransferDetailInput(); - input.setOutDetailNo(String.valueOf(SnowFlakeUtil.newId())); - input.setTransferAmount(detailPayAmount.longValue()); - input.setTransferRemark(transferScene.getRemark()); - input.setOpenid(openId); - transferDetailList.add(input); - payAmount = payAmount.subtract(detailPayAmount); - } - return transferDetailList; - } - - /** - * 发起退款 - * - * @param refund - */ - @Override - public Refund refund(RefundAble refund) { - CreateRequest request = new CreateRequest(); - request.setOutTradeNo(refund.refundOutTradeNo()); - request.setOutRefundNo(refund.refundOutRefundNo()); - request.setReason(refund.refundReason()); - request.setAmount(refund.refundAmount()); - request.setNotifyUrl(wxPayConfig.getRefundNotifyUrl()); - log.info("【退款】请求微信参数: {}", JSON.toJSONString(request)); - Refund res = refundService.create(request); - log.info("【退款】微信返回结果:【{}】",JSON.toJSONString(refund)); - return res; - } - - /** - * 验签并解析 - * @param request 请求 - * @param body 请求体 - * @param clazz 返回值类型 - */ - @Override - public T checkAndParse(HttpServletRequest request, String body, Class clazz) { - // 构造 RequestParam - RequestParam requestParam = new RequestParam.Builder() - .serialNumber(request.getHeader("Wechatpay-Serial")) - .nonce(request.getHeader("Wechatpay-Nonce")) - .signature(request.getHeader("Wechatpay-Signature")) - .timestamp(request.getHeader("Wechatpay-Timestamp")) - .body(body) - .build(); - // 验签 - return notificationParser.parse(requestParam, clazz); - } - - @Override - public Transaction toTransaction(HttpServletRequest request) { - // 获取原始报文body - String body = HttpUtils.getBody(request); - // 解析通知数据 - Notification notification = JSON.parseObject(body, Notification.class); - // 支付成功通知 - if (NotifyEventType.TRANSACTION_SUCCESS.getValue().equals(notification.getEventType())) { - // 验签、解密并转换成 Transaction - return checkAndParse(request, body, Transaction.class); - } - return null; - } - - private Payer getPayer(String openId) { - Payer payer = new Payer(); - payer.setOpenid(openId); - return payer; - } - - private Amount getAmount(BigDecimal money) { - Amount amount = new Amount(); - amount.setTotal(money.intValue()); - amount.setCurrency(CNY); - return amount; - } - -} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/service/WxTransferService.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/service/WxTransferService.java new file mode 100644 index 00000000..d336caaf --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/service/WxTransferService.java @@ -0,0 +1,60 @@ +package com.ruoyi.common.pay.wx.service; + +import com.ruoyi.common.config.WxPayConfig; +import com.ruoyi.common.pay.wx.domain.BatchTransferAble; +import com.ruoyi.common.pay.wx.domain.request.MyInitiateBatchTransferRequest; +import com.wechat.pay.java.service.transferbatch.TransferBatchService; +import com.wechat.pay.java.service.transferbatch.model.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author wjh + * 2024/8/12 + */ +@Service +public class WxTransferService { + + @Autowired + private WxPayConfig wxPayConfig; + + @Autowired + private TransferBatchService transferBatchService; + + /** + * 转账到零钱 + */ + public InitiateBatchTransferResponse batchTransfer(BatchTransferAble batchTransferAble) { + MyInitiateBatchTransferRequest request = new MyInitiateBatchTransferRequest(); + request.setAppid(wxPayConfig.getAppId()); + request.setOutBatchNo(batchTransferAble.transferOutBatchNo()); + request.setBatchName(batchTransferAble.transferBatchName()); + request.setBatchRemark(batchTransferAble.transferBatchRemark()); + request.setTotalAmount(batchTransferAble.transferTotalAmount()); + request.setTotalNum(batchTransferAble.transferDetailList().size()); + request.setTransferDetailList(batchTransferAble.transferDetailList()); + request.setNotifyUrl(wxPayConfig.getTransferNotifyUrl()); + return transferBatchService.initiateBatchTransfer(request); + } + + /** 通过微信批次单号查询批次单 */ + public TransferBatchEntity getTransferBatchByNo(GetTransferBatchByNoRequest request) { + return transferBatchService.getTransferBatchByNo(request); + } + + /** 通过商家批次单号查询批次单 */ + public TransferBatchEntity getTransferBatchByOutNo(GetTransferBatchByOutNoRequest request) { + return transferBatchService.getTransferBatchByOutNo(request); + } + + /** 通过微信明细单号查询明细单 */ + public TransferDetailEntity getTransferDetailByNo(GetTransferDetailByNoRequest request ) { + return transferBatchService.getTransferDetailByNo(request); + } + + /** 通过商家明细单号查询明细单 */ + public TransferDetailEntity getTransferDetailByOutNo(GetTransferDetailByOutNoRequest request ) { + return transferBatchService.getTransferDetailByOutNo(request); + } + +} diff --git a/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/util/WxPayUtil.java b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/util/WxPayUtil.java new file mode 100644 index 00000000..428bb70c --- /dev/null +++ b/smart-switch-ruoyi/smart-switch-common/src/main/java/com/ruoyi/common/pay/wx/util/WxPayUtil.java @@ -0,0 +1,108 @@ +package com.ruoyi.common.pay.wx.util; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.pay.wx.domain.enums.WxNotifyEventType; +import com.ruoyi.common.utils.http.HttpUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.wechat.pay.java.core.notification.Notification; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.core.notification.RequestParam; +import com.wechat.pay.java.service.payments.model.Transaction; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author wjh + * 2024/8/12 + */ +public class WxPayUtil { + + public static final NotificationParser NOTIFICATION_PARSER = SpringUtils.getBean(NotificationParser.class); + + + /** + * 验签并解析 + */ + public static T checkAndParse(HttpServletRequest request, String body, Class clazz) { + + // 构造 RequestParam + RequestParam requestParam = new RequestParam.Builder() + .serialNumber(request.getHeader("Wechatpay-Serial")) + .nonce(request.getHeader("Wechatpay-Nonce")) + .signature(request.getHeader("Wechatpay-Signature")) + .timestamp(request.getHeader("Wechatpay-Timestamp")) + .body(body) + .build(); + // 验签 + return NOTIFICATION_PARSER.parse(requestParam, clazz); + } + + /** + * 是否支付成功 + */ + public static boolean isSuccess(Transaction transaction) { + return transaction != null && Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState()); + } + + /** + * 验签并转为业务对象 + */ + public static Transaction toTransaction(HttpServletRequest request) { + // 获取原始报文body + String body = HttpUtils.getBody(request); + // 解析通知数据 + Notification notification = JSON.parseObject(body, Notification.class); + // 支付成功通知 + if (WxNotifyEventType.TRANSACTION_SUCCESS.getValue().equals(notification.getEventType())) { + // 验签、解密并转换成 Transaction + return checkAndParse(request, body, Transaction.class); + } + return null; + } + + + public static String getTransferCLoseReason(String code) { + switch (code) { + case "OVERDUE_CLOSE": return "系统超时关闭,可能原因账户余额不足或其他错误"; + case "TRANSFER_SCENE_INVALID": return "付款确认时,转账场景已不可用,系统做关单处理"; + default: return "其他关闭原因"; + } + } + + /** + * 获取转账明细失败原因 + */ + public static String getTransferDetailErrorMsg(String code){ + switch (code){ + case "NAME_NOT_CORRECT": + return "收款人姓名校验不通过,请核实信息"; + case "ACCOUNT_FROZEN": + return "该用户账户被冻结"; + case "REAL_NAME_CHECK_FAIL": + return "收款人未实名认证,需要用户完成微信实名认证"; + case "OPENID_INVALID": + return "微信用户编号格式错误或者不属于商家公众账号"; + case "TRANSFER_QUOTA_EXCEED": + return "超过用户单笔收款额度"; + case "DAY_RECEIVED_QUOTA_EXCEED": + return "超过用户单日收款额度"; + case "MONTH_RECEIVED_QUOTA_EXCEED": + return "超过用户单月收款额度"; + case "DAY_RECEIVED_COUNT_EXCEED": + return "超过用户单日收款次数"; + case "PRODUCT_AUTH_CHECK_FAIL": + return "未开通该权限或权限被冻结"; + case "OVERDUE_CLOSE": + return "超过系统重试期,系统自动关闭"; + case "ID_CARD_NOT_CORRECT": + return "收款人身份证校验不通过,请核实信息"; + case "ACCOUNT_NOT_EXIST": + return "该用户账户不存在"; + case "TRANSFER_RISK": + return "该笔转账可能存在风险,已被微信拦截"; + default: + return "其它失败原因"; + } + } + +} diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/timeBill/service/impl/TimeBillServiceImpl.java b/smart-switch-service/src/main/java/com/ruoyi/ss/timeBill/service/impl/TimeBillServiceImpl.java index b85aaa01..e8ea0a52 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/timeBill/service/impl/TimeBillServiceImpl.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/timeBill/service/impl/TimeBillServiceImpl.java @@ -24,7 +24,7 @@ import com.ruoyi.ss.timeBill.domain.dto.TimeBillPayDTO; import com.ruoyi.ss.timeBill.domain.enums.TimeBillStatus; import com.ruoyi.ss.timeBill.service.TimeBillConverter; import com.ruoyi.ss.transactionBill.domain.enums.TransactionBillPayType; -import com.ruoyi.common.pay.wx.service.WxPayServiceImpl; +import com.ruoyi.common.pay.wx.service.WxPayService; import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -66,7 +66,7 @@ public class TimeBillServiceImpl implements TimeBillService private PayBillService payBillService; @Autowired - private WxPayServiceImpl wxPayService; + private WxPayService wxPayService; @Autowired private PayBillConverter payBillConverter; diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/domain/vo/TransactionBillVO.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/domain/vo/TransactionBillVO.java index b3a1e880..c873919e 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/domain/vo/TransactionBillVO.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/domain/vo/TransactionBillVO.java @@ -2,16 +2,12 @@ package com.ruoyi.ss.transactionBill.domain.vo; import com.fasterxml.jackson.annotation.JsonView; import com.ruoyi.common.core.domain.JsonViewProfile; -import com.ruoyi.common.pay.wx.domain.BatchTransferAble; -import com.ruoyi.common.pay.wx.domain.enums.TransferScene; import com.ruoyi.ss.suit.domain.enums.SuitTimeUnit; import com.ruoyi.ss.transactionBill.domain.TransactionBill; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import java.math.BigDecimal; - /** * @author 辉 * 2024/3/4 diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillServiceImpl.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillServiceImpl.java index 51f00e94..64d7e215 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillServiceImpl.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillServiceImpl.java @@ -5,6 +5,7 @@ import com.ruoyi.common.core.redis.RedisLock; import com.ruoyi.common.core.redis.enums.RedisLockKey; import com.ruoyi.common.enums.WithdrawServiceType; import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.pay.wx.util.WxPayUtil; import com.ruoyi.common.utils.*; import com.ruoyi.common.utils.collection.CollectionUtils; import com.ruoyi.ss.account.domain.AccountVO; @@ -39,7 +40,10 @@ import com.ruoyi.ss.transactionBill.domain.enums.*; import com.ruoyi.ss.transactionBill.mapper.TransactionBillMapper; import com.ruoyi.ss.transactionBill.service.TransactionBillService; import com.ruoyi.ss.transactionBill.service.TransactionBillValidator; +import com.ruoyi.ss.transfer.domain.Transfer; +import com.ruoyi.ss.transfer.domain.TransferQuery; import com.ruoyi.ss.transfer.domain.TransferVO; +import com.ruoyi.ss.transfer.interfaces.AfterTransfer; import com.ruoyi.ss.transfer.service.TransferConverter; import com.ruoyi.ss.transfer.service.TransferService; import com.ruoyi.ss.user.domain.SmUserVo; @@ -73,7 +77,7 @@ import java.util.stream.Collectors; */ @Service("transactionBillService") @Slf4j -public class TransactionBillServiceImpl implements TransactionBillService { +public class TransactionBillServiceImpl implements TransactionBillService, AfterTransfer { @Autowired private TransactionBillMapper transactionBillMapper; @@ -708,7 +712,7 @@ public class TransactionBillServiceImpl implements TransactionBillService { if (TransactionBillPayType.WECHAT.getType().equals(bill.getChannelId())) { // 微信支付 Transaction transaction = wxPayService.queryOrderByOutTradeNo(billNo); - return wxPayService.isSuccess(transaction); + return WxPayUtil.isSuccess(transaction); } // ... TODO 其他支付方式 @@ -1138,4 +1142,62 @@ public class TransactionBillServiceImpl implements TransactionBillService { public int selectSimpleCount(TransactionBillQuery query) { return transactionBillMapper.selectSimpleCount(query); } + + /** + * 转账成功后 + */ + @Override + public int onTransferSuccess(Long bstId) { + if (bstId == null) { + return 0; + } + + Integer result = transactionTemplate.execute(status -> { + // 修改提现状态 + TransactionBill data = new TransactionBill(); + data.setStatus(TransactionBillStatus.WITHDRAW_SUCCESS.getStatus()); + TransactionBillQuery query = new TransactionBillQuery(); + query.setStatus(TransactionBillStatus.WITHDRAW_PAYING.getStatus()); + query.setBillId(bstId); + int update = this.updateByQuery(data, query); + ServiceUtil.assertion(update != 1, "修改提现状态失败,提现状态已发生改变"); + return update; + }); + + return result == null ? 0 : result; + } + + /** + * 转账失败后 + */ + @Override + public int onTransferFail(Long bstId) { + if (bstId == null) { + return 0; + } + + TransactionBillVO withdraw = this.selectWithdrawById(bstId); + if (withdraw == null) { + return 0; + } + + Integer result = transactionTemplate.execute(status -> { + + // 修改提现状态 + TransactionBill data = new TransactionBill(); + data.setStatus(TransactionBillStatus.WITHDRAW_FAIL.getStatus()); + TransactionBillQuery query = new TransactionBillQuery(); + query.setStatus(TransactionBillStatus.WITHDRAW_PAYING.getStatus()); + query.setBillId(bstId); + int update = this.updateByQuery(data, query); + ServiceUtil.assertion(update != 1, "修改提现状态失败,提现状态已发生改变"); + + // 将提现金额退回用户 + userService.addBalance(withdraw.getUserId(), withdraw.getMoney(), String.format("提现%s打款失败", withdraw.getBillNo()), RecordBalanceBstType.WITHDRAW, bstId); + + return update; + }); + + return result == null ? 0 : result; + } } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/Transfer.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/Transfer.java index 93f7555a..27bf7b74 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/Transfer.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/Transfer.java @@ -53,4 +53,7 @@ public class Transfer extends BaseEntity @ApiModelProperty("批次总金额(元)") private BigDecimal totalAmount; + @ApiModelProperty("关闭原因") + private String closeReason; + } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/enums/TransferBstType.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/enums/TransferBstType.java index be1e03d4..3f54b228 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/enums/TransferBstType.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/enums/TransferBstType.java @@ -1,5 +1,7 @@ package com.ruoyi.ss.transfer.domain.enums; +import com.ruoyi.ss.transactionBill.service.impl.TransactionBillServiceImpl; +import com.ruoyi.ss.transfer.interfaces.AfterTransfer; import lombok.AllArgsConstructor; import lombok.Getter; @@ -12,9 +14,19 @@ import lombok.Getter; @AllArgsConstructor public enum TransferBstType { - WITHDRAW("1", "提现"); + WITHDRAW("1", "提现", TransactionBillServiceImpl.class); private final String type; private final String name; + private final Class afterTransfer; + + public static TransferBstType parse(String type) { + for (TransferBstType value : TransferBstType.values()) { + if (value.getType().equals(type)) { + return value; + } + } + return null; + } } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/enums/TransferStatus.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/enums/TransferStatus.java index ef10c33e..80ddc2f5 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/enums/TransferStatus.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/domain/enums/TransferStatus.java @@ -11,10 +11,11 @@ import lombok.Getter; @AllArgsConstructor public enum TransferStatus { - TRANSFER_ING("1", "转账中"), - TRANSFER_SUCCESS("2", "已转账"), - TRANSFER_PART_SUCCESS("3", "部分成功"), - TRANSFER_FAIL("4", "转账失败"); + WAIT_TRANSFER("1", "待转账"), + TRANSFER_ING("2", "转账中"), + TRANSFER_SUCCESS("3", "已转账"), + TRANSFER_PART_SUCCESS("4", "部分成功"), + TRANSFER_FAIL("5", "转账失败"); private final String status; private final String msg; diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/interfaces/AfterTransfer.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/interfaces/AfterTransfer.java new file mode 100644 index 00000000..6a4c7d16 --- /dev/null +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/interfaces/AfterTransfer.java @@ -0,0 +1,16 @@ +package com.ruoyi.ss.transfer.interfaces; + +/** + * 转账结束后处理 + * @author wjh + * 2024/8/12 + */ +public interface AfterTransfer { + + int onTransferSuccess(Long bstId); + + default int onTransferPartSuccess(Long bstId) {return 1;} + + int onTransferFail(Long bstId); + +} diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/mapper/TransferMapper.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/mapper/TransferMapper.java index 6302ca65..a2206f2a 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/mapper/TransferMapper.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/mapper/TransferMapper.java @@ -66,4 +66,9 @@ public interface TransferMapper * 批次单号查询 */ TransferVO selectByBatchNo(String batchNo); + + /** + * 条件修改 + */ + int updateByQuery(@Param("data") Transfer data, @Param("query") TransferQuery query); } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/mapper/TransferMapper.xml b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/mapper/TransferMapper.xml index 21939b2d..4047baf0 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/mapper/TransferMapper.xml +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/mapper/TransferMapper.xml @@ -17,7 +17,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" st.batch_name, st.batch_remark, st.create_time, - st.total_amount + st.total_amount, + st.close_reason from ss_transfer st @@ -64,6 +65,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" batch_remark, create_time, total_amount, + close_reason, #{batchNo}, @@ -75,25 +77,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{batchRemark}, #{createTime}, #{totalAmount}, + #{closeReason}, update ss_transfer - batch_no = #{data.batchNo}, - account_type = #{data.accountType}, - bst_type = #{data.bstType}, - bst_id = #{data.bstId}, - `status` = #{data.status}, - batch_name = #{data.batchName}, - batch_remark = #{data.batchRemark}, - create_time = #{data.createTime}, - total_amount = #{data.totalAmount}, + where batch_id = #{data.batchId} + + batch_no = #{data.batchNo}, + account_type = #{data.accountType}, + bst_type = #{data.bstType}, + bst_id = #{data.bstId}, + `status` = #{data.status}, + batch_name = #{data.batchName}, + batch_remark = #{data.batchRemark}, + create_time = #{data.createTime}, + total_amount = #{data.totalAmount}, + close_reason = #{data.closeReason}, + + + + update ss_transfer st + + + + + + + + delete from ss_transfer where batch_id = #{batchId} diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/TransferAssembler.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/TransferAssembler.java new file mode 100644 index 00000000..005e6d40 --- /dev/null +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/TransferAssembler.java @@ -0,0 +1,18 @@ +package com.ruoyi.ss.transfer.service; + +import com.ruoyi.ss.transfer.domain.TransferVO; + +import java.util.List; + +/** + * @author wjh + * 2024/8/12 + */ +public interface TransferAssembler { + + /** + * 拼接明细 + */ + void assembleDetail(List list); + +} diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/TransferService.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/TransferService.java index 4b79726d..35fe9fe6 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/TransferService.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/TransferService.java @@ -78,4 +78,19 @@ public interface TransferService * @param batchNo */ TransferVO selectByBatchNo(String batchNo); + + /** + * 根据查询条件更新 + */ + int updateByQuery(Transfer data, TransferQuery query); + + /** + * 刷新转账单状态 + */ + int refreshTransferStatus(TransferVO transfer); + + /** + * 刷新转账状态 + */ + int refreshTransferStatus(Long batchId); } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferAssemblerImpl.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferAssemblerImpl.java new file mode 100644 index 00000000..10ebe62e --- /dev/null +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferAssemblerImpl.java @@ -0,0 +1,51 @@ +package com.ruoyi.ss.transfer.service.impl; + +import com.ruoyi.common.utils.collection.CollectionUtils; +import com.ruoyi.ss.transfer.domain.TransferVO; +import com.ruoyi.ss.transfer.service.TransferAssembler; +import com.ruoyi.ss.transferDetail.domain.TransferDetailQuery; +import com.ruoyi.ss.transferDetail.domain.TransferDetailVO; +import com.ruoyi.ss.transferDetail.service.ITransferDetailService; +import com.ruoyi.ss.transferDetail.service.impl.TransferDetailServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author wjh + * 2024/8/12 + */ +@Service +public class TransferAssemblerImpl implements TransferAssembler { + + @Autowired + private ITransferDetailService transferDetailService; + + /** + * 拼接明细 + */ + @Override + public void assembleDetail(List list) { + if (CollectionUtils.isEmptyElement(list)) { + return; + } + TransferDetailQuery query = new TransferDetailQuery(); + query.setTransferIds(CollectionUtils.map(list, TransferVO::getBatchId)); + Map> group = transferDetailService.selectTransferDetailList(query).stream() + .collect(Collectors.groupingBy(TransferDetailVO::getTransferId)); + + for (TransferVO transfer : list) { + List detailList = group.get(transfer.getBatchId()); + if (CollectionUtils.isNotEmptyElement(detailList)) { + transfer.setDetailList(detailList); + } else { + transfer.setDetailList(Collections.emptyList()); + } + } + + } +} diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferConverterImpl.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferConverterImpl.java index 41ebacaf..840ebfcb 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferConverterImpl.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferConverterImpl.java @@ -52,7 +52,7 @@ public class TransferConverterImpl implements TransferConverter { } vo.setBstType(TransferBstType.WITHDRAW.getType()); vo.setBstId(bill.getBillId()); - vo.setStatus(TransferStatus.TRANSFER_ING.getStatus()); + vo.setStatus(TransferStatus.WAIT_TRANSFER.getStatus()); vo.setBatchName("提现转账"); vo.setBatchRemark(String.format("提现申请%s转账", bill.getBillNo())); vo.setTotalAmount(bill.getArrivalAmount()); @@ -60,9 +60,10 @@ public class TransferConverterImpl implements TransferConverter { // 生成明细 List detailList = new ArrayList<>(); TransferDetailVO detail = new TransferDetailVO(); - detail.setStatus(TransferDetailStatus.TRANSFER_ING.getStatus()); + detail.setStatus(TransferDetailStatus.WAIT_TRANSFER.getStatus()); detail.setAmount(bill.getArrivalAmount()); detail.setAccountNo(bill.getAccountNo()); + detail.setRemark(String.format("提现申请%s转账", bill.getBillNo())); detailList.add(detail); vo.setDetailList(detailList); diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferServiceImpl.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferServiceImpl.java index 0a5365af..467b2654 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferServiceImpl.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transfer/service/impl/TransferServiceImpl.java @@ -1,12 +1,34 @@ package com.ruoyi.ss.transfer.service.impl; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Objects; + +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.pay.wx.domain.enums.WxTransferBatchStatus; +import com.ruoyi.common.pay.wx.domain.enums.WxTransferDetailStatus; +import com.ruoyi.common.pay.wx.service.WxPayService; +import com.ruoyi.common.pay.wx.service.WxTransferService; +import com.ruoyi.common.pay.wx.util.WxPayUtil; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.ServiceUtil; import com.ruoyi.common.utils.SnowFlakeUtil; import com.ruoyi.common.utils.collection.CollectionUtils; +import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.ss.account.domain.enums.AccountType; +import com.ruoyi.ss.transfer.domain.enums.TransferBstType; +import com.ruoyi.ss.transfer.domain.enums.TransferStatus; +import com.ruoyi.ss.transfer.interfaces.AfterTransfer; +import com.ruoyi.ss.transfer.service.TransferAssembler; +import com.ruoyi.ss.transferDetail.domain.TransferDetail; +import com.ruoyi.ss.transferDetail.domain.TransferDetailQuery; import com.ruoyi.ss.transferDetail.domain.TransferDetailVO; +import com.ruoyi.ss.transferDetail.domain.enums.TransferDetailStatus; import com.ruoyi.ss.transferDetail.service.ITransferDetailService; +import com.wechat.pay.java.service.transferbatch.model.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.ss.transfer.mapper.TransferMapper; @@ -23,6 +45,7 @@ import org.springframework.transaction.support.TransactionTemplate; * @date 2024-08-09 */ @Service +@Slf4j public class TransferServiceImpl implements TransferService { @Autowired @@ -34,6 +57,15 @@ public class TransferServiceImpl implements TransferService @Autowired private ITransferDetailService transferDetailService; + @Autowired + private WxPayService wxPayService; + + @Autowired + private TransferAssembler transferAssembler; + + @Autowired + private WxTransferService wxTransferService; + /** * 查询转账 * @@ -131,6 +163,7 @@ public class TransferServiceImpl implements TransferService @Override public boolean requestTransfer(String batchNo) { TransferVO transfer = this.selectByBatchNo(batchNo); + transferAssembler.assembleDetail(Collections.singletonList(transfer)); return this.requestTransfer(transfer); } @@ -138,23 +171,208 @@ public class TransferServiceImpl implements TransferService * 请求转账 */ private boolean requestTransfer(TransferVO transfer) { - if (transfer == null) { + if (transfer == null || transfer.getBatchId() == null) { return false; } - // TODO 请求转账 - transactionTemplate.execute(status -> { + // 请求转账 + Boolean result = transactionTemplate.execute(status -> { + // 修改状态 + Transfer data = new Transfer(); + data.setStatus(TransferStatus.TRANSFER_ING.getStatus()); + TransferQuery query = new TransferQuery(); + query.setBatchId(transfer.getBatchId()); + query.setStatus(TransferStatus.WAIT_TRANSFER.getStatus()); + int update = this.updateByQuery(data, query); + ServiceUtil.assertion(update != 1, "更新转账状态失败"); - // TODO 修改状态 + // 更新子表状态 + TransferDetail detailData = new TransferDetail(); + detailData.setStatus(TransferDetailStatus.TRANSFER_ING.getStatus()); + TransferDetailQuery detailQuery = new TransferDetailQuery(); + detailQuery.setTransferId(transfer.getBatchId()); + detailQuery.setStatus(TransferDetailStatus.WAIT_TRANSFER.getStatus()); + int updateDetail = transferDetailService.updateByQuery(detailData, detailQuery); + ServiceUtil.assertion(updateDetail != 1, "更新转账明细状态失败"); + // 发起转账 + if (AccountType.WECHAT.getType().equals(transfer.getAccountType())) { + wxTransferService.batchTransfer(transfer); + } else { + throw new ServiceException("暂不支持该方式进行线上转账"); + } - // TODO 发起转账 - + return true; }); + + return result != null && result; } @Override public TransferVO selectByBatchNo(String batchNo) { return transferMapper.selectByBatchNo(batchNo); } + + @Override + public int updateByQuery(Transfer data, TransferQuery query) { + return transferMapper.updateByQuery(data, query); + } + + @Override + public int refreshTransferStatus(TransferVO transfer) { + if (transfer == null) { + return 0; + } + // 微信 + if (AccountType.WECHAT.getType().equals(transfer.getAccountType())) { + return this.refreshTransferStatusWx(transfer); + } else { + throw new ServiceException("该付款方式不支持刷新操作"); + } + } + + @Override + public int refreshTransferStatus(Long batchId) { + TransferVO transfer = selectTransferByBatchId(batchId); + transferAssembler.assembleDetail(Collections.singletonList(transfer)); + return this.refreshTransferStatus(transfer); + } + + private int refreshTransferStatusWx(TransferVO transfer) { + if (transfer == null) { + return 0; + } + // 主表信息 + GetTransferBatchByOutNoRequest request = new GetTransferBatchByOutNoRequest(); + request.setOutBatchNo(transfer.getBatchNo()); + request.setNeedQueryDetail(false); + TransferBatchEntity transferBatchEntity = wxTransferService.getTransferBatchByOutNo(request); + if (transferBatchEntity == null || transferBatchEntity.getTransferBatch() == null) { + return 0; + } + // 批次信息 + TransferBatchGet transferBatch = transferBatchEntity.getTransferBatch(); + String batchStatus = transferBatch.getBatchStatus(); + + // 仅处理完成和关单的状态 + log.info("【刷新微信转账状态】批次信息:{}", transferBatch); + if (!WxTransferBatchStatus.asList(WxTransferBatchStatus.FINISHED, WxTransferBatchStatus.CLOSED).contains(batchStatus)) { + return 1; + } + + // 待更新的数据 + Transfer data = new Transfer(); + List detaiList = new ArrayList<>(); + + // 已完成:所有明细都处理完成了 + if (WxTransferBatchStatus.FINISHED.getStatus().equals(transferBatch.getBatchStatus())) { + if (transferBatch.getSuccessNum() != null && Objects.equals(transferBatch.getSuccessNum(), transferBatch.getTotalNum())) { + // 全部成功 + data.setStatus(TransferStatus.TRANSFER_SUCCESS.getStatus()); + } else if (transferBatch.getFailNum() != null && Objects.equals(transferBatch.getFailNum(), transferBatch.getTotalNum())) { + // 全部失败 + data.setStatus(TransferStatus.TRANSFER_FAIL.getStatus()); + } else { + // 部分成功 + data.setStatus(TransferStatus.TRANSFER_PART_SUCCESS.getStatus()); + } + + // 处理明细信息 + detaiList.addAll(this.buildRefreshDetailList(transfer.getDetailList())); + } + // 关单:失败,并给出关单原因 + else if (WxTransferBatchStatus.CLOSED.getStatus().equals(transferBatch.getBatchStatus())) { + data.setStatus(TransferStatus.TRANSFER_FAIL.getStatus()); + data.setCloseReason(WxPayUtil.getTransferCLoseReason(transferBatch.getCloseReason().name())); + // 所有明细都修改为失败 + for (TransferDetailVO detail : transfer.getDetailList()) { + TransferDetail failDetail = new TransferDetail(); + failDetail.setDetailId(detail.getDetailId()); + failDetail.setStatus(TransferDetailStatus.TRANSFER_FAIL.getStatus()); + detaiList.add(failDetail); + } + } + + // 保存到数据库 + Integer result = transactionTemplate.execute(status -> { + // 修改主表信息 + TransferQuery query = new TransferQuery(); + query.setStatus(TransferStatus.TRANSFER_ING.getStatus()); + query.setBatchId(transfer.getBatchId()); + int update = this.updateByQuery(data, query); + ServiceUtil.assertion(update != 1, "修改转账批次失败,状态已发生变化"); + + // 修改明细信息 + for (TransferDetail detail : detaiList) { + TransferDetailQuery detailQuery = new TransferDetailQuery(); + detailQuery.setDetailId(detail.getDetailId()); + detailQuery.setStatus(TransferStatus.TRANSFER_ING.getStatus()); + int detailUpdate = transferDetailService.updateByQuery(detail, detailQuery); + ServiceUtil.assertion(detailUpdate != 1, "转账明细修改失败,状态已发生变化"); + } + + // 修改业务信息 + TransferBstType bstType = TransferBstType.parse(transfer.getBstType()); + ServiceUtil.assertion(bstType == null, "业务类型不存在"); + ServiceUtil.assertion(bstType.getAfterTransfer() == null, "业务处理器不存在"); + + AfterTransfer afterTransfer = SpringUtils.getBean(bstType.getAfterTransfer()); + + // 成功 + int bstResult = 0; + if (TransferStatus.TRANSFER_SUCCESS.getStatus().equals(data.getStatus())) { + bstResult = afterTransfer.onTransferSuccess(transfer.getBstId()); + } + // 部分成功 + else if (TransferStatus.TRANSFER_PART_SUCCESS.getStatus().equals(data.getStatus())) { + bstResult = afterTransfer.onTransferPartSuccess(transfer.getBstId()); + } + // 失败 + else if (TransferStatus.TRANSFER_FAIL.getStatus().equals(data.getStatus())) { + bstResult = afterTransfer.onTransferFail(transfer.getBstId()); + } + ServiceUtil.assertion(bstResult == 0, "业务执行失败"); + + return update; + }); + + return result == null ? 0 : result; + } + + private List buildRefreshDetailList(List detailList) { + List updateList = new ArrayList<>(); + + if (CollectionUtils.isEmptyElement(detailList)) { + return updateList; + } + + for (TransferDetailVO detail : detailList) { + GetTransferDetailByOutNoRequest detailRequest = new GetTransferDetailByOutNoRequest(); + detailRequest.setOutBatchNo(detail.getTransferBatchNo()); + detailRequest.setOutDetailNo(detail.getDetailNo()); + TransferDetailEntity transferDetailEntity = wxTransferService.getTransferDetailByOutNo(detailRequest); + log.info("【刷新微信转账状态】明细批次信息:{}", transferDetailEntity); + if (transferDetailEntity == null) { + continue; + } + + TransferDetail updateDetail = new TransferDetail(); + updateDetail.setDetailId(detail.getDetailId()); + + // 转账成功 + if (WxTransferDetailStatus.SUCCESS.getStatus().equals(transferDetailEntity.getDetailStatus())) { + updateDetail.setStatus(TransferDetailStatus.TRANSFER_SUCCESS.getStatus()); + updateList.add(updateDetail); + } + // 转账失败 + else if (WxTransferDetailStatus.FAIL.getStatus().equals(transferDetailEntity.getDetailStatus())){ + updateDetail.setStatus(TransferDetailStatus.TRANSFER_FAIL.getStatus()); + updateDetail.setFailReason(WxPayUtil.getTransferDetailErrorMsg(transferDetailEntity.getFailReason().name())); + updateList.add(updateDetail); + } + } + return updateList; + } + + } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetail.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetail.java index b4122ccb..a70a6c69 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetail.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetail.java @@ -45,4 +45,8 @@ public class TransferDetail extends BaseEntity @ApiModelProperty("收款用户姓名") private String userName; + @Excel(name = "收款用户姓名") + @ApiModelProperty("失败原因") + private String failReason; + } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetailQuery.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetailQuery.java index 2a0d37a4..8a99c70b 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetailQuery.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetailQuery.java @@ -1,11 +1,18 @@ package com.ruoyi.ss.transferDetail.domain; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; +import java.util.List; + /** * @author wjh * 2024/8/9 */ @Data public class TransferDetailQuery extends TransferDetailVO{ + + @ApiModelProperty("批次ID列表") + private List transferIds; + } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetailVO.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetailVO.java index f631afb0..2f1b9859 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetailVO.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/TransferDetailVO.java @@ -1,5 +1,6 @@ package com.ruoyi.ss.transferDetail.domain; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** @@ -8,4 +9,8 @@ import lombok.Data; */ @Data public class TransferDetailVO extends TransferDetail{ + + @ApiModelProperty("批次单号") + private String transferBatchNo; + } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/enums/TransferDetailStatus.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/enums/TransferDetailStatus.java index 8754a3af..5e62da06 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/enums/TransferDetailStatus.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/domain/enums/TransferDetailStatus.java @@ -12,9 +12,10 @@ import lombok.Getter; @AllArgsConstructor public enum TransferDetailStatus { - TRANSFER_ING("1", "转账中"), - TRANSFER_SUCCESS("2", "已转账"), - TRANSFER_FAIL("3", "转账失败"); + WAIT_TRANSFER("1", "待转账"), + TRANSFER_ING("2", "转账中"), + TRANSFER_SUCCESS("3", "已转账"), + TRANSFER_FAIL("4", "转账失败"); private final String status; private final String msg; diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/mapper/TransferDetailMapper.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/mapper/TransferDetailMapper.java index f0d1f736..20852496 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/mapper/TransferDetailMapper.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/mapper/TransferDetailMapper.java @@ -67,4 +67,8 @@ public interface TransferDetailMapper */ int batchInsert(@Param("list") List list); + /** + * 条件更新 + */ + int updateByQuery(@Param("data") TransferDetail data, @Param("query") TransferDetailQuery query); } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/mapper/TransferDetailMapper.xml b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/mapper/TransferDetailMapper.xml index 56af9dab..fafecc19 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/mapper/TransferDetailMapper.xml +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transferDetail/mapper/TransferDetailMapper.xml @@ -16,18 +16,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" std.remark, std.account_no, std.user_name, - std.create_time + std.create_time, + std.fail_reason, + st.batch_no as transfer_batch_no from ss_transfer_detail std + left join ss_transfer st on st.batch_id = std.transfer_id and std.detail_id = #{query.detailId} and std.detail_no like concat('%', #{query.detailNo}, '%') and std.transfer_id = #{query.transferId} + and st.batch_no like concat('%', #{query.transferBatchNo}, '%') and std.status = #{query.status} and std.remark like concat('%', #{query.remark}, '%') and std.account_no like concat('%', #{query.accountNo}, '%') and std.user_name like concat('%', #{query.userName}, '%') + + and std.transfer_id in + + #{item} + +