feat: 充值&推广费

pull/1121/head
梁宇奇 2025-05-09 18:22:37 +08:00
parent 8322fa6f3d
commit bfa14eeb1a
20 changed files with 573 additions and 75 deletions

View File

@ -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;
}

View File

@ -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<AssetInfoVO> 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<AssetInfoVO> 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<AssetInfoVO> 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<AssetInfoVO> 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<AssetInfoVO> 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<PageVO<TransDetailStorePageItemVO>> 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<PageVO<TransDetailUserPageItemVO>> 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<StoreRechargeRespVO> 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<StoreRechargeResultRespVO> getRechargeResult(@Validated @RequestBody StoreRechargeResultReqVO vo) {
return success(new StoreRechargeResultRespVO(vo.getFinanceBillNo(),
assetService.isRechargeSuccess(vo.getFinanceBillNo())));
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -99,7 +99,7 @@ public class StoreOrderAddReqVO {
@NotNull(message = "支付来源不能为空")
@ApiModelProperty(value = "支付来源[1:电脑网站 2:手机网站]")
private Integer payFrom;
private Integer payPage;
@ApiModel(value = "明细")
@Data

View File

@ -24,5 +24,5 @@ public class StoreOrderPayReqVO {
@NotNull(message = "支付来源不能为空")
@ApiModelProperty(value = "支付来源[1:电脑网站 2:手机网站]")
private Integer payFrom;
private Integer payPage;
}

View File

@ -95,18 +95,6 @@ public class StoreOrderUpdateReqVO {
@ApiModelProperty(value = "明细列表")
private List<Detail> 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 {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
/**
* 退

View File

@ -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);

View File

@ -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<TransDetailUserPageItemDTO> 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);
}

View File

@ -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);
}

View File

@ -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<PaymentManager> 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("未知支付渠道");
}
}

View File

@ -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<StoreOrderExt> 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());
}
/**
*

View File

@ -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'
</select>