diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AlipayCallbackController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AlipayCallbackController.java index 909de9e42..886baaeb9 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AlipayCallbackController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AlipayCallbackController.java @@ -6,10 +6,12 @@ import com.alipay.api.AlipayApiException; import com.alipay.api.internal.util.AlipaySignature; import com.ruoyi.common.core.controller.XktBaseController; import com.ruoyi.xkt.domain.AlipayCallback; +import com.ruoyi.xkt.domain.FinanceBill; import com.ruoyi.xkt.domain.StoreOrder; import com.ruoyi.xkt.enums.EProcessStatus; import com.ruoyi.xkt.manager.impl.AliPaymentMangerImpl; import com.ruoyi.xkt.service.IAlipayCallbackService; +import com.ruoyi.xkt.service.IFinanceBillService; import com.ruoyi.xkt.service.IStoreOrderService; import io.swagger.annotations.Api; import org.springframework.beans.factory.annotation.Autowired; @@ -38,6 +40,8 @@ public class AlipayCallbackController extends XktBaseController { @Autowired private IStoreOrderService storeOrderService; @Autowired + private IFinanceBillService financeBillService; + @Autowired private IAlipayCallbackService alipayCallbackService; @Autowired private AliPaymentMangerImpl paymentManger; @@ -69,17 +73,6 @@ public class AlipayCallbackController extends XktBaseController { if (signVerified) { AlipayCallback alipayCallback = trans2DO(params); //需要严格按照如下描述校验通知数据的正确性: - //1. 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。 - StoreOrder order = storeOrderService.getByOrderNo(alipayCallback.getOutTradeNo()); - if (order == null) { - logger.warn("支付宝回调订单匹配失败:{}", params); - return FAILURE; - } - //2. 判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。 - if (!NumberUtil.equals(order.getTotalAmount(), alipayCallback.getTotalAmount())) { - logger.warn("支付宝回调订单金额匹配失败:{}", params); - return FAILURE; - } //3. 校验通知中的 seller_id(或者 seller_email)是否为 out_trade_no 这笔单据的对应的操作方 // (有的时候,一个商家可能有多个 seller_id/seller_email)。 //4. 验证 app_id 是否为该商家本身。 @@ -87,32 +80,75 @@ public class AlipayCallbackController extends XktBaseController { logger.warn("支付宝回调应用ID异常:{}", params); return FAILURE; } - //5. 处理支付宝回调信息 - AlipayCallback info = alipayCallbackService.getByNotifyId(alipayCallback.getNotifyId()); - if (info == null) { - //保存到数据库 - info = alipayCallback; - alipayCallbackService.insertAlipayCallback(info); - } - if (!"TRADE_SUCCESS".equals(info.getTradeStatus())) { - //非交易支付成功的回调不处理 + //1. 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。 + StoreOrder order = storeOrderService.getByOrderNo(alipayCallback.getOutTradeNo()); + if (order != null) { + //2. 判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。 + if (!NumberUtil.equals(order.getTotalAmount(), alipayCallback.getTotalAmount())) { + logger.warn("支付宝回调订单金额匹配失败:{}", params); + return FAILURE; + } + //5. 处理支付宝回调信息 + AlipayCallback info = alipayCallbackService.getByNotifyId(alipayCallback.getNotifyId()); + if (info == null) { + //保存到数据库 + info = alipayCallback; + alipayCallbackService.insertAlipayCallback(info); + } + if (!"TRADE_SUCCESS".equals(info.getTradeStatus())) { + //非交易支付成功的回调不处理 + return SUCCESS; + } + if (info.getRefundFee() != null && !NumberUtil.equals(info.getRefundFee(), BigDecimal.ZERO)) { + //如果有退款金额,可能是部分退款的回调,这里不做处理 + return SUCCESS; + } + if (EProcessStatus.INIT.getValue().equals(info.getProcessStatus())) { + //更新回调状态和订单状态,创建收款单 + alipayCallbackService.processOrderPaid(info); + } else { + logger.warn("支付回调重复请求处理: {}", info.getId()); + } return SUCCESS; - } - if (info.getRefundFee() != null && !NumberUtil.equals(info.getRefundFee(), BigDecimal.ZERO)) { - //如果有退款金额,可能是部分退款的回调,这里不做处理 - return SUCCESS; - } - if (EProcessStatus.INIT.getValue().equals(info.getProcessStatus())) { - //更新回调状态和订单状态,创建收款单 - alipayCallbackService.processOrderPaid(info); } else { - logger.warn("支付回调重复请求处理: {}", info.getId()); + //充值业务 + //1. 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。 + FinanceBill bill = financeBillService.getByBillNo(alipayCallback.getOutTradeNo()); + if (bill != null) { + //2. 判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。 + if (!NumberUtil.equals(bill.getBusinessAmount(), alipayCallback.getTotalAmount())) { + logger.warn("支付宝回调订单金额匹配失败:{}", params); + return FAILURE; + } + //5. 处理支付宝回调信息 + AlipayCallback info = alipayCallbackService.getByNotifyId(alipayCallback.getNotifyId()); + if (info == null) { + //保存到数据库 + info = alipayCallback; + alipayCallbackService.insertAlipayCallback(info); + } + if (!"TRADE_SUCCESS".equals(info.getTradeStatus())) { + //非交易支付成功的回调不处理 + return SUCCESS; + } + if (info.getRefundFee() != null && !NumberUtil.equals(info.getRefundFee(), BigDecimal.ZERO)) { + //如果有退款金额,可能是部分退款的回调,这里不做处理 + return SUCCESS; + } + if (EProcessStatus.INIT.getValue().equals(info.getProcessStatus())) { + //充值到账 + financeBillService.entryRechargeCollectionBill(bill.getBillNo()); + } else { + logger.warn("支付回调重复请求处理: {}", info.getId()); + } + return SUCCESS; + } } - return SUCCESS; - } else { - logger.warn("支付宝验签未通过:{}", params); + logger.warn("支付宝回调订单匹配失败:{}", params); return FAILURE; } + logger.warn("支付宝验签未通过:{}", params); + return FAILURE; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AssetController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AssetController.java index 4a86d1238..fd8857d8a 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AssetController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/AssetController.java @@ -9,14 +9,16 @@ import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.web.controller.xkt.vo.BasePageVO; import com.ruoyi.web.controller.xkt.vo.account.*; import com.ruoyi.xkt.dto.account.*; +import com.ruoyi.xkt.dto.finance.RechargeAddDTO; +import com.ruoyi.xkt.dto.finance.RechargeAddResult; import com.ruoyi.xkt.enums.EAccountOwnerType; import com.ruoyi.xkt.enums.EPayChannel; +import com.ruoyi.xkt.enums.EPayPage; import com.ruoyi.xkt.manager.impl.AliPaymentMangerImpl; import com.ruoyi.xkt.service.IAssetService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -34,7 +36,6 @@ public class AssetController extends XktBaseController { @Autowired private AliPaymentMangerImpl aliPaymentManger; - @PreAuthorize("@ss.hasPermi('system:asset:query')") @ApiOperation(value = "档口资产") @GetMapping(value = "store/current") public R getCurrentStoreAsset() { @@ -42,7 +43,6 @@ public class AssetController extends XktBaseController { return success(BeanUtil.toBean(dto, AssetInfoVO.class)); } - @PreAuthorize("@ss.hasPermi('system:asset:query')") @ApiOperation(value = "卖家资产") @GetMapping(value = "user/current") public R getCurrentUserAsset() { @@ -50,7 +50,6 @@ public class AssetController extends XktBaseController { return success(BeanUtil.toBean(dto, AssetInfoVO.class)); } - @PreAuthorize("@ss.hasPermi('system:asset:add')") @ApiOperation(value = "档口绑定支付宝") @PostMapping(value = "store/alipay/bind") public R bindStoreAlipay(@Validated @RequestBody AlipayStoreBindVO vo) { @@ -60,7 +59,6 @@ public class AssetController extends XktBaseController { return success(BeanUtil.toBean(assetService.bindAlipay(dto), AssetInfoVO.class)); } - @PreAuthorize("@ss.hasPermi('system:asset:add')") @ApiOperation(value = "卖家绑定支付宝") @PostMapping(value = "user/alipay/bind") public R bindUserAlipay(@Validated @RequestBody AlipayUserBindVO vo) { @@ -70,7 +68,6 @@ public class AssetController extends XktBaseController { return success(BeanUtil.toBean(assetService.bindAlipay(dto), AssetInfoVO.class)); } - @PreAuthorize("@ss.hasPermi('system:asset:add')") @ApiOperation(value = "档口设置交易密码") @PostMapping(value = "store/transaction-password/set") public R setTransactionPassword(@Validated @RequestBody TransactionPasswordSetVO vo) { @@ -79,7 +76,6 @@ public class AssetController extends XktBaseController { return success(BeanUtil.toBean(assetService.setTransactionPassword(dto), AssetInfoVO.class)); } - @PreAuthorize("@ss.hasPermi('system:asset:add')") @ApiOperation(value = "档口支付宝提现") @PostMapping(value = "store/alipay/withdraw") public R withdrawByAlipay(@Validated @RequestBody WithdrawReqVO vo) { @@ -94,7 +90,6 @@ public class AssetController extends XktBaseController { return success(); } - @PreAuthorize("@ss.hasPermi('system:asset:list')") @ApiOperation(value = "档口交易明细") @PostMapping("store/trans-detail/page") public R> pageStoreTransDetail(@Validated @RequestBody BasePageVO vo) { @@ -104,7 +99,6 @@ public class AssetController extends XktBaseController { return success(PageVO.of(pageDTO, TransDetailStorePageItemVO.class)); } - @PreAuthorize("@ss.hasPermi('system:asset:list')") @ApiOperation(value = "卖家交易明细") @PostMapping("user/trans-detail/page") public R> pageUserTransDetail(@Validated @RequestBody BasePageVO vo) { @@ -114,5 +108,25 @@ public class AssetController extends XktBaseController { return success(PageVO.of(pageDTO, TransDetailUserPageItemVO.class)); } + @ApiOperation(value = "档口充值") + @PostMapping(value = "store/recharge") + public R rechargeByStore(@Validated @RequestBody StoreRechargeReqVO vo) { + RechargeAddDTO rechargeAddDTO = new RechargeAddDTO(); + rechargeAddDTO.setStoreId(SecurityUtils.getStoreId()); + rechargeAddDTO.setAmount(vo.getAmount()); + rechargeAddDTO.setPayChannel(EPayChannel.of(vo.getPayChannel())); + rechargeAddDTO.setPayPage(EPayPage.of(vo.getPayPage())); + RechargeAddResult result = assetService.rechargeByStore(rechargeAddDTO); + return success(new StoreRechargeRespVO(result.getFinanceBillExt().getFinanceBill().getBillNo(), + result.getPayRtnStr())); + } + + @ApiOperation(value = "档口充值结果") + @PostMapping(value = "store/recharge/result") + public R getRechargeResult(@Validated @RequestBody StoreRechargeResultReqVO vo) { + return success(new StoreRechargeResultRespVO(vo.getFinanceBillNo(), + assetService.isRechargeSuccess(vo.getFinanceBillNo()))); + } + } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java index 373d9e44f..3c304d618 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/StoreOrderController.java @@ -61,7 +61,7 @@ public class StoreOrderController extends XktBaseController { dto.setOrderUserId(SecurityUtils.getUserId()); //创建订单并根据参数决定是否发起支付 StoreOrderAddResult result = storeOrderService.createOrder(dto, vo.getBeginPay(), - EPayChannel.of(vo.getPayChannel()), EPayPage.of(vo.getPayFrom())); + EPayChannel.of(vo.getPayChannel()), EPayPage.of(vo.getPayPage())); //返回信息 StoreOrderPayRespVO respVO = new StoreOrderPayRespVO(result.getOrderExt().getOrder().getId(), result.getPayRtnStr()); @@ -88,7 +88,7 @@ public class StoreOrderController extends XktBaseController { //订单支付状态->支付中 StoreOrderExt orderExt = storeOrderService.preparePayOrder(vo.getStoreOrderId(), payChannel); //调用支付 - String rtnStr = paymentManager.payStoreOrder(orderExt, EPayPage.of(vo.getPayFrom())); + String rtnStr = paymentManager.payStoreOrder(orderExt, EPayPage.of(vo.getPayPage())); StoreOrderPayRespVO respVO = new StoreOrderPayRespVO(vo.getStoreOrderId(), rtnStr); return success(respVO); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeReqVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeReqVO.java new file mode 100644 index 000000000..b34d19702 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeReqVO.java @@ -0,0 +1,29 @@ +package com.ruoyi.web.controller.xkt.vo.account; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +/** + * @author liangyq + * @date 2025-05-09 + */ +@ApiModel +@Data +public class StoreRechargeReqVO { + + @NotNull(message = "充值金额不能为空") + @ApiModelProperty(value = "充值金额") + private BigDecimal amount; + + @NotNull(message = "支付渠道不能为空") + @ApiModelProperty(value = "支付渠道[1:支付宝]") + private Integer payChannel; + + @NotNull(message = "支付来源不能为空") + @ApiModelProperty(value = "支付来源[1:电脑网站 2:手机网站]") + private Integer payPage; +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeRespVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeRespVO.java new file mode 100644 index 000000000..ec3bf8e2f --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeRespVO.java @@ -0,0 +1,24 @@ +package com.ruoyi.web.controller.xkt.vo.account; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author liangyq + * @date 2025-05-09 + */ +@ApiModel +@Data +@AllArgsConstructor +@NoArgsConstructor +public class StoreRechargeRespVO { + + @ApiModelProperty(value = "付款单号") + private String financeBillNo; + + @ApiModelProperty(value = "支付渠道返回值: 跳转页面数据/签名字符串/支付跳转链接/预支付交易会话标识(根据选择的支付渠道和支付来源确定)") + private String payRtnStr; +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeResultReqVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeResultReqVO.java new file mode 100644 index 000000000..2735ef040 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeResultReqVO.java @@ -0,0 +1,20 @@ +package com.ruoyi.web.controller.xkt.vo.account; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; + +/** + * @author liangyq + * @date 2025-05-09 + */ +@ApiModel +@Data +public class StoreRechargeResultReqVO { + + @NotEmpty(message = "付款单号不能为空") + @ApiModelProperty(value = "付款单号") + private String financeBillNo; +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeResultRespVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeResultRespVO.java new file mode 100644 index 000000000..bbd8ebdd0 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/account/StoreRechargeResultRespVO.java @@ -0,0 +1,24 @@ +package com.ruoyi.web.controller.xkt.vo.account; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author liangyq + * @date 2025-05-09 + */ +@ApiModel +@Data +@AllArgsConstructor +@NoArgsConstructor +public class StoreRechargeResultRespVO { + + @ApiModelProperty(value = "付款单号") + private String financeBillNo; + + @ApiModelProperty(value = "是否成功") + private Boolean success; +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderAddReqVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderAddReqVO.java index 0aeb43165..dbf80c25e 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderAddReqVO.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderAddReqVO.java @@ -99,7 +99,7 @@ public class StoreOrderAddReqVO { @NotNull(message = "支付来源不能为空") @ApiModelProperty(value = "支付来源[1:电脑网站 2:手机网站]") - private Integer payFrom; + private Integer payPage; @ApiModel(value = "明细") @Data diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderPayReqVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderPayReqVO.java index a1266b042..3956dba30 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderPayReqVO.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderPayReqVO.java @@ -24,5 +24,5 @@ public class StoreOrderPayReqVO { @NotNull(message = "支付来源不能为空") @ApiModelProperty(value = "支付来源[1:电脑网站 2:手机网站]") - private Integer payFrom; + private Integer payPage; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderUpdateReqVO.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderUpdateReqVO.java index 5aace9e4b..f2944434d 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderUpdateReqVO.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/vo/order/StoreOrderUpdateReqVO.java @@ -95,18 +95,6 @@ public class StoreOrderUpdateReqVO { @ApiModelProperty(value = "明细列表") private List detailList; -// @NotNull(message = "是否发起支付不能为空") -// @ApiModelProperty(value = "是否发起支付") -// private Boolean beginPay; -// -// @NotNull(message = "支付渠道不能为空") -// @ApiModelProperty(value = "支付渠道[1:支付宝]") -// private Integer payChannel; -// -// @NotNull(message = "支付来源不能为空") -// @ApiModelProperty(value = "支付来源[1:电脑网站 2:手机网站]") -// private Integer payFrom; - @ApiModel(value = "明细") @Data public static class Detail { diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/finance/RechargeAddDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/finance/RechargeAddDTO.java new file mode 100644 index 000000000..e85d7d275 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/finance/RechargeAddDTO.java @@ -0,0 +1,31 @@ +package com.ruoyi.xkt.dto.finance; + +import com.ruoyi.xkt.enums.EPayChannel; +import com.ruoyi.xkt.enums.EPayPage; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * @author liangyq + * @date 2025-05-09 + */ +@Data +public class RechargeAddDTO { + /** + * 档口ID + */ + private Long storeId; + /** + * 金额 + */ + private BigDecimal amount; + /** + * 支付渠道 + */ + private EPayChannel payChannel; + /** + * 支付来源 + */ + private EPayPage payPage; +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/finance/RechargeAddResult.java b/xkt/src/main/java/com/ruoyi/xkt/dto/finance/RechargeAddResult.java new file mode 100644 index 000000000..6cfb2d330 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/finance/RechargeAddResult.java @@ -0,0 +1,24 @@ +package com.ruoyi.xkt.dto.finance; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author liangyq + * @date 2025-05-09 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RechargeAddResult { + /** + * 收款单信息 + */ + private FinanceBillExt financeBillExt; + /** + * 三方支付返回信息 + */ + private String payRtnStr; + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/enums/EFinBillSrcType.java b/xkt/src/main/java/com/ruoyi/xkt/enums/EFinBillSrcType.java index 3c5b85a7a..5f94224ec 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/enums/EFinBillSrcType.java +++ b/xkt/src/main/java/com/ruoyi/xkt/enums/EFinBillSrcType.java @@ -15,6 +15,9 @@ public enum EFinBillSrcType { STORE_ORDER_COMPLETED(2, "档口代发订单"), WITHDRAW(3, "提现"), STORE_ORDER_REFUND(4, "档口代发退货"), + RECHARGE(5, "充值"), + ADVERT_PAID(6, "推广费"), + ADVERT_REFUND(7, "推广费退款"), ; private final Integer value; diff --git a/xkt/src/main/java/com/ruoyi/xkt/manager/PaymentManager.java b/xkt/src/main/java/com/ruoyi/xkt/manager/PaymentManager.java index 374fa14b3..27c36baec 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/manager/PaymentManager.java +++ b/xkt/src/main/java/com/ruoyi/xkt/manager/PaymentManager.java @@ -19,14 +19,25 @@ public interface PaymentManager { */ EPayChannel channel(); + /** + * 支付 + * + * @param tradeNo + * @param amount + * @param subject + * @param payPage + * @return 跳转页面数据/签名字符串/支付跳转链接/预支付交易会话标识(根据支付渠道&支付来源确定) + */ + String pay(String tradeNo, BigDecimal amount, String subject, EPayPage payPage); + /** * 订单支付 * * @param order - * @param payFrom + * @param payPage * @return 跳转页面数据/签名字符串/支付跳转链接/预支付交易会话标识(根据支付渠道&支付来源确定) */ - String payStoreOrder(StoreOrderExt order, EPayPage payFrom); + String payStoreOrder(StoreOrderExt order, EPayPage payPage); /** * 订单退款 diff --git a/xkt/src/main/java/com/ruoyi/xkt/manager/impl/AliPaymentMangerImpl.java b/xkt/src/main/java/com/ruoyi/xkt/manager/impl/AliPaymentMangerImpl.java index a758f3811..7bdee5e2c 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/manager/impl/AliPaymentMangerImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/manager/impl/AliPaymentMangerImpl.java @@ -89,21 +89,20 @@ public class AliPaymentMangerImpl implements PaymentManager { } @Override - public String payStoreOrder(StoreOrderExt orderExt, EPayPage payFrom) { - Assert.notNull(orderExt); - Assert.notNull(payFrom); - if (!EPayStatus.PAYING.getValue().equals(orderExt.getOrder().getPayStatus())) { - throw new ServiceException("订单[" + orderExt.getOrder().getOrderNo() + "]支付状态异常"); - } + public String pay(String tradeNo, BigDecimal amount, String subject, EPayPage payPage) { + Assert.notEmpty(tradeNo); + Assert.notNull(amount); + Assert.notEmpty(subject); + Assert.notNull(payPage); AlipayReqDTO reqDTO = new AlipayReqDTO(); - reqDTO.setOutTradeNo(orderExt.getOrder().getOrderNo()); - reqDTO.setTotalAmount(orderExt.getOrder().getTotalAmount().toPlainString()); - reqDTO.setSubject("代发订单" + orderExt.getOrder().getOrderNo()); + reqDTO.setOutTradeNo(tradeNo); + reqDTO.setTotalAmount(amount.toPlainString()); + reqDTO.setSubject(subject); reqDTO.setProductCode(PAY_PRODUCT_CODE); //这个是固定的 String reqStr = JSON.toJSONString(reqDTO); AlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl, appId, privateKey, DEFAULT_FORMAT, charset, alipayPublicKey, signType); - switch (payFrom) { + switch (payPage) { case WEB: AlipayTradePagePayRequest webReq = new AlipayTradePagePayRequest(); webReq.setReturnUrl(returnUrl); @@ -131,6 +130,17 @@ public class AliPaymentMangerImpl implements PaymentManager { } } + @Override + public String payStoreOrder(StoreOrderExt orderExt, EPayPage payPage) { + Assert.notNull(orderExt); + Assert.notNull(payPage); + if (!EPayStatus.PAYING.getValue().equals(orderExt.getOrder().getPayStatus())) { + throw new ServiceException("订单[" + orderExt.getOrder().getOrderNo() + "]支付状态异常"); + } + return pay(orderExt.getOrder().getOrderNo(), orderExt.getOrder().getTotalAmount(), + "代发订单" + orderExt.getOrder().getOrderNo(), payPage); + } + @Override public void refundStoreOrder(StoreOrderRefund orderRefund) { Assert.notNull(orderRefund); diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IAssetService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IAssetService.java index 7c8da34b5..81fbcc573 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IAssetService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IAssetService.java @@ -2,6 +2,8 @@ package com.ruoyi.xkt.service; import com.github.pagehelper.Page; import com.ruoyi.xkt.dto.account.*; +import com.ruoyi.xkt.dto.finance.RechargeAddDTO; +import com.ruoyi.xkt.dto.finance.RechargeAddResult; import com.ruoyi.xkt.enums.EPayChannel; import java.math.BigDecimal; @@ -86,4 +88,36 @@ public interface IAssetService { */ Page pageUserTransDetail(TransDetailUserQueryDTO queryDTO); + /** + * 档口充值 + * + * @param rechargeAddDTO + * @return + */ + RechargeAddResult rechargeByStore(RechargeAddDTO rechargeAddDTO); + + /** + * 充值是否成功 + * + * @param finBillNo + * @return + */ + boolean isRechargeSuccess(String finBillNo); + + /** + * 支付推广费 + * + * @param storeId + * @param amount + * @param transactionPassword + */ + void payAdvertFee(Long storeId, BigDecimal amount, String transactionPassword); + + /** + * 退回推广费 + * + * @param storeId + * @param amount + */ + void refundAdvertFee(Long storeId, BigDecimal amount); } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IFinanceBillService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IFinanceBillService.java index b06d0b048..629c1321e 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IFinanceBillService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IFinanceBillService.java @@ -1,5 +1,6 @@ package com.ruoyi.xkt.service; +import com.ruoyi.xkt.domain.FinanceBill; import com.ruoyi.xkt.dto.finance.FinanceBillExt; import com.ruoyi.xkt.dto.order.StoreOrderExt; import com.ruoyi.xkt.enums.EPayChannel; @@ -12,6 +13,14 @@ import java.util.List; * @date 2025-04-08 21:14 */ public interface IFinanceBillService { + /** + * 获取单据 + * + * @param billNo + * @return + */ + FinanceBill getByBillNo(String billNo); + /** * 订单支付完成创建收款单(入账) * @@ -33,7 +42,6 @@ public interface IFinanceBillService { /** * 订单完成创建转移单(入账) * - * * @param orderExt * @param afterSaleOrderExts * @return @@ -72,5 +80,38 @@ public interface IFinanceBillService { */ void entryWithdrawPaymentBill(Long financeBillId); + /** + * 充值创建收款单(未入账) + * + * @param storeId + * @param amount + * @param payChannel + * @return + */ + FinanceBillExt createRechargeCollectionBill(Long storeId, BigDecimal amount, EPayChannel payChannel); + + /** + * 充值收款单入账 + * + * @param billNo + */ + void entryRechargeCollectionBill(String billNo); + + /** + * 内部转移单(已入账) + * + * @param inputAccountId + * @param outputAccountId + * @param amount + * @param srcType + * @param srcId + * @param relType + * @param relId + * @param remark + * @return + */ + FinanceBillExt createInternalTransferBill(Long inputAccountId, Long outputAccountId, BigDecimal amount, + Integer srcType, Long srcId, Integer relType, Long relId, String remark); + } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/AssetServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/AssetServiceImpl.java index eb45d315e..66465a22f 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/AssetServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/AssetServiceImpl.java @@ -7,15 +7,18 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; +import com.ruoyi.common.constant.Constants; import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.bean.BeanValidators; import com.ruoyi.xkt.domain.ExternalAccount; +import com.ruoyi.xkt.domain.FinanceBill; import com.ruoyi.xkt.domain.InternalAccount; import com.ruoyi.xkt.dto.account.*; import com.ruoyi.xkt.dto.finance.FinanceBillExt; -import com.ruoyi.xkt.enums.EAccountOwnerType; -import com.ruoyi.xkt.enums.EAccountStatus; -import com.ruoyi.xkt.enums.EAccountType; -import com.ruoyi.xkt.enums.EPayChannel; +import com.ruoyi.xkt.dto.finance.RechargeAddDTO; +import com.ruoyi.xkt.dto.finance.RechargeAddResult; +import com.ruoyi.xkt.enums.*; +import com.ruoyi.xkt.manager.PaymentManager; import com.ruoyi.xkt.service.IAssetService; import com.ruoyi.xkt.service.IExternalAccountService; import com.ruoyi.xkt.service.IFinanceBillService; @@ -26,6 +29,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; +import java.util.List; /** * @author liangyq @@ -41,6 +45,8 @@ public class AssetServiceImpl implements IAssetService { private IInternalAccountService internalAccountService; @Autowired private IExternalAccountService externalAccountService; + @Autowired + private List paymentManagers; @Transactional(rollbackFor = Exception.class) @@ -187,5 +193,72 @@ public class AssetServiceImpl implements IAssetService { return page; } + @Transactional(rollbackFor = Exception.class) + @Override + public RechargeAddResult rechargeByStore(RechargeAddDTO rechargeAddDTO) { + FinanceBillExt financeBillExt = financeBillService.createRechargeCollectionBill(rechargeAddDTO.getStoreId(), + rechargeAddDTO.getAmount(), rechargeAddDTO.getPayChannel()); + PaymentManager paymentManager = getPaymentManager(rechargeAddDTO.getPayChannel()); + String payRtnStr = paymentManager.pay(financeBillExt.getFinanceBill().getBillNo(), rechargeAddDTO.getAmount(), + "档口充值", rechargeAddDTO.getPayPage()); + return new RechargeAddResult(financeBillExt, payRtnStr); + } + + @Override + public boolean isRechargeSuccess(String finBillNo) { + FinanceBill financeBill = financeBillService.getByBillNo(finBillNo); + return BeanValidators.exists(financeBill) + && EFinBillStatus.SUCCESS.getValue().equals(financeBill.getBillStatus()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void payAdvertFee(Long storeId, BigDecimal amount, String transactionPassword) { + Assert.notNull(storeId); + Assert.notNull(amount); + InternalAccount internalAccount = internalAccountService.getAccountAndCheck(storeId, EAccountOwnerType.STORE); + if (!EAccountStatus.NORMAL.getValue().equals(internalAccount.getAccountStatus())) { + throw new ServiceException("档口账户已冻结"); + } + if (StrUtil.isEmpty(internalAccount.getTransactionPassword())) { + throw new ServiceException("请先设置支付密码"); + } + if (!StrUtil.equals(SecureUtil.md5(transactionPassword), internalAccount.getTransactionPassword())) { + throw new ServiceException("支付密码错误"); + } + financeBillService.createInternalTransferBill(Constants.PLATFORM_INTERNAL_ACCOUNT_ID, internalAccount.getId(), + amount, EFinBillSrcType.ADVERT_PAID.getValue(), null, null, null, "推广费"); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void refundAdvertFee(Long storeId, BigDecimal amount) { + Assert.notNull(storeId); + Assert.notNull(amount); + InternalAccount internalAccount = internalAccountService.getAccountAndCheck(storeId, EAccountOwnerType.STORE); + financeBillService.createInternalTransferBill(internalAccount.getId(), Constants.PLATFORM_INTERNAL_ACCOUNT_ID, + amount, EFinBillSrcType.ADVERT_REFUND.getValue(), null, null, null, + "推广费退款"); + + } + + /** + * 根据支付渠道匹配支付类 + * + * @param payChannel + * @return + */ + private PaymentManager getPaymentManager(EPayChannel payChannel) { + if (payChannel == null) { + throw new ServiceException("请先选择支付渠道"); + } + for (PaymentManager paymentManager : paymentManagers) { + if (paymentManager.channel() == payChannel) { + return paymentManager; + } + } + throw new ServiceException("未知支付渠道"); + } + } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/FinanceBillServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/FinanceBillServiceImpl.java index 6599095d3..26f63a394 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/FinanceBillServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/FinanceBillServiceImpl.java @@ -14,6 +14,7 @@ import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.bean.BeanValidators; import com.ruoyi.xkt.domain.FinanceBill; import com.ruoyi.xkt.domain.FinanceBillDetail; +import com.ruoyi.xkt.domain.InternalAccount; import com.ruoyi.xkt.domain.StoreOrderDetail; import com.ruoyi.xkt.dto.finance.FinanceBillExt; import com.ruoyi.xkt.dto.finance.TransInfo; @@ -51,6 +52,13 @@ public class FinanceBillServiceImpl implements IFinanceBillService { @Autowired private IExternalAccountService externalAccountService; + @Override + public FinanceBill getByBillNo(String billNo) { + Assert.notEmpty(billNo); + return financeBillMapper.selectOne(Wrappers.lambdaQuery(FinanceBill.class) + .eq(FinanceBill::getBillNo, billNo)); + } + @Transactional(rollbackFor = Exception.class) @Override public FinanceBillExt createOrderPaidCollectionBill(StoreOrderExt orderExt, Long payId, @@ -112,6 +120,7 @@ public class FinanceBillServiceImpl implements IFinanceBillService { @Transactional(rollbackFor = Exception.class) @Override public FinanceBillExt createOrderCompletedTransferBill(StoreOrderExt orderExt) { + //TODO 需要先批量获取内部账户避免出现死锁? Assert.notNull(orderExt); FinanceBill bill = new FinanceBill(); bill.setBillNo(generateBillNo(EFinBillType.TRANSFER)); @@ -169,6 +178,7 @@ public class FinanceBillServiceImpl implements IFinanceBillService { @Override public FinanceBillExt createOrderCompletedTransferBill(StoreOrderExt orderExt, List afterSaleOrderExts) { + //TODO 需要先批量获取内部账户避免出现死锁? Assert.notNull(orderExt); FinanceBill bill = new FinanceBill(); bill.setBillNo(generateBillNo(EFinBillType.TRANSFER)); @@ -347,6 +357,10 @@ public class FinanceBillServiceImpl implements IFinanceBillService { if (!BeanValidators.exists(financeBill)) { throw new ServiceException(CharSequenceUtil.format("售后订单[{}]对应付款单不存在", storeOrderId)); } + if (!EFinBillType.PAYMENT.getValue().equals(financeBill.getBillType()) + || !EFinBillSrcType.STORE_ORDER_REFUND.getValue().equals(financeBill.getSrcType())) { + throw new ServiceException(CharSequenceUtil.format("单据[{}]类型异常", financeBill.getId())); + } if (!EFinBillStatus.PROCESSING.getValue().equals(financeBill.getBillStatus())) { throw new ServiceException(CharSequenceUtil.format("付款单[{}]状态异常", financeBill.getId())); } @@ -416,6 +430,10 @@ public class FinanceBillServiceImpl implements IFinanceBillService { if (!BeanValidators.exists(financeBill)) { throw new ServiceException(CharSequenceUtil.format("提现付款单[{}]不存在", financeBillId)); } + if (!EFinBillType.PAYMENT.getValue().equals(financeBill.getBillType()) + || !EFinBillSrcType.WITHDRAW.getValue().equals(financeBill.getSrcType())) { + throw new ServiceException(CharSequenceUtil.format("单据[{}]类型异常", financeBillId)); + } if (!EFinBillStatus.PROCESSING.getValue().equals(financeBill.getBillStatus())) { throw new ServiceException(CharSequenceUtil.format("付款单[{}]状态异常", financeBill.getId())); } @@ -430,6 +448,120 @@ public class FinanceBillServiceImpl implements IFinanceBillService { externalAccountService.entryTransDetail(financeBill.getId(), EFinBillType.of(financeBill.getBillType())); } + @Transactional(rollbackFor = Exception.class) + @Override + public FinanceBillExt createRechargeCollectionBill(Long storeId, BigDecimal amount, EPayChannel payChannel) { + Assert.notNull(storeId); + Assert.notNull(amount); + Assert.notNull(payChannel); + FinanceBill bill = new FinanceBill(); + String billNo = generateBillNo(EFinBillType.COLLECTION); + bill.setBillNo(billNo); + bill.setBillType(EFinBillType.COLLECTION.getValue()); + //直接标记成功 + bill.setBillStatus(EFinBillStatus.PROCESSING.getValue()); + bill.setSrcType(EFinBillSrcType.RECHARGE.getValue()); + bill.setBusinessUniqueKey(billNo); + InternalAccount inputInternalAccount = internalAccountService.getAccount(storeId, EAccountOwnerType.STORE); + Assert.notNull(inputInternalAccount, "未创建档口账户"); + bill.setInputInternalAccountId(inputInternalAccount.getId()); + bill.setInputExternalAccountId(payChannel.getPlatformExternalAccountId()); + bill.setBusinessAmount(amount); + bill.setTransAmount(amount); + bill.setRemark(CharSequenceUtil.format("档口充值{}元", amount.toPlainString())); + bill.setVersion(0L); + bill.setDelFlag(Constants.UNDELETED); + financeBillMapper.insert(bill); + TransInfo transInfo = TransInfo.builder() + .srcBillId(bill.getId()) + .srcBillType(bill.getBillType()) + .transAmount(bill.getTransAmount()) + .transTime(new Date()) + .handlerId(null) + .remark("档口充值") + .build(); + //内部账户 + internalAccountService.addTransDetail(inputInternalAccount.getId(), transInfo, + ELoanDirection.DEBIT, EEntryStatus.WAITING); + //外部账户 + externalAccountService.addTransDetail(payChannel.getPlatformExternalAccountId(), transInfo, + ELoanDirection.DEBIT, EEntryStatus.WAITING); + return new FinanceBillExt(bill, ListUtil.empty()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void entryRechargeCollectionBill(String billNo) { + Assert.notEmpty(billNo); + //业务唯一键 + FinanceBill financeBill = financeBillMapper.selectOne(Wrappers.lambdaQuery(FinanceBill.class) + .eq(FinanceBill::getBillNo, billNo)); + if (!BeanValidators.exists(financeBill)) { + throw new ServiceException(CharSequenceUtil.format("充值收款单[{}]不存在", billNo)); + } + if (!EFinBillType.COLLECTION.getValue().equals(financeBill.getBillType()) + || !EFinBillSrcType.RECHARGE.getValue().equals(financeBill.getSrcType())) { + throw new ServiceException(CharSequenceUtil.format("单据[{}]类型异常", financeBill.getId())); + } + if (!EFinBillStatus.PROCESSING.getValue().equals(financeBill.getBillStatus())) { + throw new ServiceException(CharSequenceUtil.format("收款单[{}]状态异常", financeBill.getId())); + } + financeBill.setBillStatus(EFinBillStatus.SUCCESS.getValue()); + int r = financeBillMapper.updateById(financeBill); + if (r == 0) { + throw new ServiceException(Constants.VERSION_LOCK_ERROR_COMMON_MSG); + } + //内部账户入账 + internalAccountService.entryTransDetail(financeBill.getId(), EFinBillType.of(financeBill.getBillType())); + //外部账户入账 + externalAccountService.entryTransDetail(financeBill.getId(), EFinBillType.of(financeBill.getBillType())); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public FinanceBillExt createInternalTransferBill(Long inputAccountId, Long outputAccountId, BigDecimal amount, + Integer srcType, Long srcId, Integer relType, Long relId, + String remark) { + //TODO 需要先批量获取内部账户避免出现死锁? + Assert.notNull(inputAccountId); + Assert.notNull(outputAccountId); + Assert.notNull(amount); + FinanceBill bill = new FinanceBill(); + String billNo = generateBillNo(EFinBillType.TRANSFER); + bill.setBillNo(billNo); + bill.setBillType(EFinBillType.TRANSFER.getValue()); + //直接标记成功 + bill.setBillStatus(EFinBillStatus.SUCCESS.getValue()); + bill.setSrcType(srcType); + bill.setSrcId(srcId); + bill.setRelType(relType); + bill.setRelId(relId); + //业务唯一键 + bill.setBusinessUniqueKey(billNo); + bill.setInputInternalAccountId(inputAccountId); + bill.setOutputInternalAccountId(outputAccountId); + bill.setBusinessAmount(amount); + bill.setTransAmount(amount); + bill.setRemark(remark); + bill.setVersion(0L); + bill.setDelFlag(Constants.UNDELETED); + financeBillMapper.insert(bill); + TransInfo transInfo = TransInfo.builder() + .srcBillId(bill.getId()) + .srcBillType(bill.getBillType()) + .transAmount(bill.getTransAmount()) + .transTime(new Date()) + .handlerId(null) + .remark(remark) + .build(); + //内部账户 + internalAccountService.addTransDetail(outputAccountId, transInfo, + ELoanDirection.CREDIT, EEntryStatus.FINISH); + internalAccountService.addTransDetail(inputAccountId, transInfo, + ELoanDirection.DEBIT, EEntryStatus.FINISH); + return new FinanceBillExt(bill, ListUtil.empty()); + } + /** * 生成单号 diff --git a/xkt/src/main/resources/mapper/InternalAccountMapper.xml b/xkt/src/main/resources/mapper/InternalAccountMapper.xml index 160f84841..7a1bc1eca 100644 --- a/xkt/src/main/resources/mapper/InternalAccountMapper.xml +++ b/xkt/src/main/resources/mapper/InternalAccountMapper.xml @@ -22,7 +22,10 @@ CASE fb.src_type WHEN 2 THEN '档口代发订单' WHEN 3 THEN '提现' - ELSE '推广费' END trans_type, + WHEN 5 THEN '充值' + WHEN 6 THEN '推广费' + WHEN 7 THEN '推广费退款' + ELSE '其他' END trans_type, fb.remark, fb.bill_type, CASE fb.bill_type @@ -38,6 +41,7 @@ LEFT JOIN store_order so ON fb.rel_id = so.id AND fb.rel_type = 1 WHERE iatd.internal_account_id = #{id} + AND fb.bill_status = 3 AND iatd.del_flag = '0'