From a2cc16690802bc2de716ddcee595e1de2b0f8bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E5=AE=87=E5=A5=87?= Date: Tue, 22 Apr 2025 22:35:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8F=90=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/xkt/domain/ExternalAccount.java | 4 ++ .../xkt/dto/finance/ExternalAccountDTO.java | 4 ++ .../com/ruoyi/xkt/enums/EAccountType.java | 42 +++++++++++ .../com/ruoyi/xkt/manager/PaymentManager.java | 12 ++++ .../manager/impl/AliPaymentMangerImpl.java | 66 ++++++++++++++--- .../ruoyi/xkt/service/IAccountService.java | 30 ++++++++ .../xkt/service/IExternalAccountService.java | 14 +++- .../xkt/service/IFinanceBillService.java | 19 +++++ .../xkt/service/impl/AccountServiceImpl.java | 70 +++++++++++++++++++ .../impl/ExternalAccountServiceImpl.java | 22 ++++-- .../service/impl/FinanceBillServiceImpl.java | 65 ++++++++++++++++- 11 files changed, 330 insertions(+), 18 deletions(-) create mode 100644 xkt/src/main/java/com/ruoyi/xkt/enums/EAccountType.java create mode 100644 xkt/src/main/java/com/ruoyi/xkt/service/IAccountService.java create mode 100644 xkt/src/main/java/com/ruoyi/xkt/service/impl/AccountServiceImpl.java diff --git a/xkt/src/main/java/com/ruoyi/xkt/domain/ExternalAccount.java b/xkt/src/main/java/com/ruoyi/xkt/domain/ExternalAccount.java index e25553ec5..b44ccf234 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/domain/ExternalAccount.java +++ b/xkt/src/main/java/com/ruoyi/xkt/domain/ExternalAccount.java @@ -47,6 +47,10 @@ public class ExternalAccount extends SimpleEntity { * 归属人认证通过 */ private Boolean accountAuthAccess; + /** + * 密码 + */ + private String password; /** * 备注 */ diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/finance/ExternalAccountDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/finance/ExternalAccountDTO.java index a1ebc60c9..aeb9f8a8c 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/dto/finance/ExternalAccountDTO.java +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/finance/ExternalAccountDTO.java @@ -48,6 +48,10 @@ public class ExternalAccountDTO { * 归属人认证通过 */ private Boolean accountAuthAccess; + /** + * 密码 + */ + private String password; /** * 备注 */ diff --git a/xkt/src/main/java/com/ruoyi/xkt/enums/EAccountType.java b/xkt/src/main/java/com/ruoyi/xkt/enums/EAccountType.java new file mode 100644 index 000000000..329d4d6a3 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/enums/EAccountType.java @@ -0,0 +1,42 @@ +package com.ruoyi.xkt.enums; + +import com.ruoyi.common.exception.ServiceException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * @author liangyq + * @date 2025-04-22 13:42 + */ +@Getter +@AllArgsConstructor +public enum EAccountType { + + ALI_PAY(1, "支付宝"), + ; + + private final Integer value; + private final String label; + + public static EAccountType of(Integer value) { + for (EAccountType e : EAccountType.values()) { + if (e.getValue().equals(value)) { + return e; + } + } + return null; + } + + /** + * 获取与支付通道匹配的账户类型 + * + * @param payChannel + * @return + */ + public static EAccountType getByChannel(EPayChannel payChannel) { + if (EPayChannel.ALI_PAY == payChannel) { + return ALI_PAY; + } + throw new ServiceException("无法获取与支付通道匹配的账户类型"); + } +} 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 cece7dfce..374fa14b3 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/manager/PaymentManager.java +++ b/xkt/src/main/java/com/ruoyi/xkt/manager/PaymentManager.java @@ -5,6 +5,8 @@ import com.ruoyi.xkt.dto.order.StoreOrderRefund; import com.ruoyi.xkt.enums.EPayChannel; import com.ruoyi.xkt.enums.EPayPage; +import java.math.BigDecimal; + /** * @author liangyq * @date 2025-04-06 19:36 @@ -42,4 +44,14 @@ public interface PaymentManager { */ boolean isStoreOrderPaid(String orderNo); + /** + * 转账 + * + * @param bizNo + * @param identity + * @param realName + * @param amount + */ + void transfer(String bizNo, String identity, String realName, BigDecimal amount); + } 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 2969c49f7..a758f3811 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 @@ -1,19 +1,18 @@ package com.ruoyi.xkt.manager.impl; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.NumberUtil; import com.alibaba.fastjson2.JSON; import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.diagnosis.DiagnosisUtils; +import com.alipay.api.domain.AlipayFundTransUniTransferModel; import com.alipay.api.domain.AlipayTradeQueryModel; import com.alipay.api.domain.AlipayTradeRefundModel; -import com.alipay.api.request.AlipayTradePagePayRequest; -import com.alipay.api.request.AlipayTradeQueryRequest; -import com.alipay.api.request.AlipayTradeRefundRequest; -import com.alipay.api.request.AlipayTradeWapPayRequest; +import com.alipay.api.domain.Participant; +import com.alipay.api.request.*; +import com.alipay.api.response.AlipayFundTransUniTransferResponse; import com.alipay.api.response.AlipayTradeQueryResponse; import com.alipay.api.response.AlipayTradeRefundResponse; import com.ruoyi.common.exception.ServiceException; @@ -150,9 +149,9 @@ public class AliPaymentMangerImpl implements PaymentManager { model.setTradeNo(orderRefund.getOriginOrder().getPayTradeNo()); // 设置退款金额 BigDecimal amount = BigDecimal.ZERO; - for (StoreOrderDetail orderDetail:orderRefund.getRefundOrderDetails()){ + for (StoreOrderDetail orderDetail : orderRefund.getRefundOrderDetails()) { //TODO 暂时商品金额+快递费一起退,需调整为实际退款金额 - amount = NumberUtil.add(amount,orderDetail.getTotalAmount()); + amount = NumberUtil.add(amount, orderDetail.getTotalAmount()); } model.setRefundAmount(amount.toPlainString()); // 设置退款原因说明 @@ -161,12 +160,12 @@ public class AliPaymentMangerImpl implements PaymentManager { model.setOutRequestNo(orderRefund.getRefundOrder().getOrderNo()); try { AlipayTradeRefundResponse response = alipayClient.execute(request); - log.info("支付宝退款:{}",response.getBody()); - if (response.isSuccess()){ + log.info("支付宝退款:{}", response.getBody()); + if (response.isSuccess()) { //TODO 沙箱环境接口不通 return; } - }catch (Exception e){ + } catch (Exception e) { log.error("退款异常", e); } throw new ServiceException("退款失败"); @@ -215,4 +214,51 @@ public class AliPaymentMangerImpl implements PaymentManager { throw new ServiceException("查询订单支付结果失败"); } + @Override + public void transfer(String bizNo, String identity, String realName, BigDecimal amount) { + Assert.notEmpty(bizNo); + Assert.notEmpty(identity); + Assert.notEmpty(realName); + Assert.notNull(amount); + AlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl, appId, privateKey, DEFAULT_FORMAT, charset, + alipayPublicKey, signType); + // 构造请求参数以调用接口 + AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest(); + AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel(); + // 设置商家侧唯一订单号 + model.setOutBizNo(bizNo); + // 设置订单总金额 + model.setTransAmount(amount.toPlainString()); + // 设置描述特定的业务场景 + model.setBizScene("DIRECT_TRANSFER"); + // 设置业务产品码 + model.setProductCode("TRANS_ACCOUNT_NO_PWD"); + // 设置收款方信息 + Participant payeeInfo = new Participant(); + payeeInfo.setIdentity(identity); + payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); + payeeInfo.setName(realName); + model.setPayeeInfo(payeeInfo); + // 设置业务备注 + model.setRemark("档口提现"); + // 设置转账业务请求的扩展参数 + model.setBusinessParams("{\"payer_show_name_use_alias\":\"true\"}"); + request.setBizModel(model); + AlipayFundTransUniTransferResponse response; + try { + response = alipayClient.certificateExecute(request); + if (response.isSuccess()) { + //TODO 接口未调通 + return; + } else { + //获取诊断链接 + String diagnosisUrl = DiagnosisUtils.getDiagnosisUrl(response); + log.warn("支付宝转账异常: {}", diagnosisUrl); + } + } catch (Exception e) { + log.error("支付宝转账异常", e); + } + throw new ServiceException("支付宝转账失败"); + } + } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IAccountService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IAccountService.java new file mode 100644 index 000000000..053146b0f --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IAccountService.java @@ -0,0 +1,30 @@ +package com.ruoyi.xkt.service; + +import com.ruoyi.xkt.dto.finance.FinanceBillExt; +import com.ruoyi.xkt.enums.EPayChannel; + +import java.math.BigDecimal; + +/** + * @author liangyq + * @date 2025-04-22 21:06 + */ +public interface IAccountService { + /** + * 档口提现准备 + * + * @param storeId + * @param amount + * @param password + * @param payChannel + * @return + */ + FinanceBillExt prepareWithdraw(Long storeId, BigDecimal amount, String password, EPayChannel payChannel); + + /** + * 档口提现成功 + * + * @param financeBillId + */ + void withdrawSuccess(Long financeBillId); +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IExternalAccountService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IExternalAccountService.java index f9e903303..3bae1d982 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IExternalAccountService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IExternalAccountService.java @@ -2,9 +2,7 @@ package com.ruoyi.xkt.service; import com.ruoyi.xkt.domain.ExternalAccount; import com.ruoyi.xkt.dto.finance.TransInfo; -import com.ruoyi.xkt.enums.EEntryStatus; -import com.ruoyi.xkt.enums.EFinBillType; -import com.ruoyi.xkt.enums.ELoanDirection; +import com.ruoyi.xkt.enums.*; /** * @author liangyq @@ -19,6 +17,16 @@ public interface IExternalAccountService { */ ExternalAccount getById(Long id); + /** + * 获取外部账户 + * + * @param ownerId + * @param ownerType + * @param accountType + * @return + */ + ExternalAccount getExternalAccount(Long ownerId, EAccountOwnerType ownerType, EAccountType accountType); + /** * 添加交易明细 * 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 220c900ab..f482a8eee 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IFinanceBillService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IFinanceBillService.java @@ -4,6 +4,8 @@ import com.ruoyi.xkt.dto.finance.FinanceBillExt; import com.ruoyi.xkt.dto.order.StoreOrderExt; import com.ruoyi.xkt.enums.EPayChannel; +import java.math.BigDecimal; + /** * @author liangyq * @date 2025-04-08 21:14 @@ -42,5 +44,22 @@ public interface IFinanceBillService { */ void entryRefundOrderPaymentBill(Long storeOrderId); + /** + * 提现创建付款单(未入账) + * + * @param storeId + * @param amount + * @param payChannel + * @return + */ + FinanceBillExt createWithdrawPaymentBill(Long storeId, BigDecimal amount, EPayChannel payChannel); + + /** + * 提现付款单入账 + * + * @param financeBillId + */ + void entryWithdrawPaymentBill(Long financeBillId); + } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/AccountServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/AccountServiceImpl.java new file mode 100644 index 000000000..870b31db9 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/AccountServiceImpl.java @@ -0,0 +1,70 @@ +package com.ruoyi.xkt.service.impl; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.xkt.domain.ExternalAccount; +import com.ruoyi.xkt.domain.InternalAccount; +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.service.IAccountService; +import com.ruoyi.xkt.service.IExternalAccountService; +import com.ruoyi.xkt.service.IFinanceBillService; +import com.ruoyi.xkt.service.IInternalAccountService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; + +/** + * @author liangyq + * @date 2025-04-22 21:07 + */ +@Slf4j +@Service +public class AccountServiceImpl implements IAccountService { + + @Autowired + private IFinanceBillService financeBillService; + @Autowired + private IInternalAccountService internalAccountService; + @Autowired + private IExternalAccountService externalAccountService; + + + @Transactional(rollbackFor = Exception.class) + @Override + public FinanceBillExt prepareWithdraw(Long storeId, BigDecimal amount, String password, EPayChannel payChannel) { + Assert.notNull(storeId); + Assert.notEmpty(password); + Assert.isTrue(NumberUtil.isLessOrEqual(amount, BigDecimal.ZERO), "提现金额异常"); + InternalAccount internalAccount = internalAccountService.getInternalAccount(storeId, EAccountOwnerType.STORE); + ExternalAccount externalAccount = externalAccountService.getExternalAccount(storeId, EAccountOwnerType.STORE, + EAccountType.getByChannel(payChannel)); + if (!EAccountStatus.NORMAL.getValue().equals(internalAccount.getAccountStatus()) + || !EAccountStatus.NORMAL.getValue().equals(externalAccount.getAccountStatus())) { + throw new ServiceException("档口账户已冻结"); + } + if (StrUtil.isEmpty(externalAccount.getPassword())) { + throw new ServiceException("请先设置支付密码"); + } + if (!StrUtil.equals(password, externalAccount.getPassword())) { + throw new ServiceException("支付密码错误"); + } + return financeBillService.createWithdrawPaymentBill(storeId, amount, payChannel); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void withdrawSuccess(Long financeBillId) { + financeBillService.entryWithdrawPaymentBill(financeBillId); + } + + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/ExternalAccountServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/ExternalAccountServiceImpl.java index b0da28cb8..581e04a7a 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/ExternalAccountServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/ExternalAccountServiceImpl.java @@ -1,6 +1,7 @@ package com.ruoyi.xkt.service.impl; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.util.NumberUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.ruoyi.common.constant.Constants; @@ -10,10 +11,7 @@ import com.ruoyi.common.utils.bean.BeanValidators; import com.ruoyi.xkt.domain.ExternalAccount; import com.ruoyi.xkt.domain.ExternalAccountTransDetail; import com.ruoyi.xkt.dto.finance.TransInfo; -import com.ruoyi.xkt.enums.EAccountStatus; -import com.ruoyi.xkt.enums.EEntryStatus; -import com.ruoyi.xkt.enums.EFinBillType; -import com.ruoyi.xkt.enums.ELoanDirection; +import com.ruoyi.xkt.enums.*; import com.ruoyi.xkt.mapper.ExternalAccountMapper; import com.ruoyi.xkt.mapper.ExternalAccountTransDetailMapper; import com.ruoyi.xkt.service.IExternalAccountService; @@ -47,6 +45,22 @@ public class ExternalAccountServiceImpl implements IExternalAccountService { return externalAccountMapper.selectById(id); } + @Override + public ExternalAccount getExternalAccount(Long ownerId, EAccountOwnerType ownerType, EAccountType accountType) { + Assert.notNull(ownerId); + Assert.notNull(ownerType); + Assert.notNull(accountType); + ExternalAccount account = externalAccountMapper.selectOne(Wrappers.lambdaQuery(ExternalAccount.class) + .eq(ExternalAccount::getOwnerId, ownerId) + .eq(ExternalAccount::getOwnerType, ownerType.getValue()) + .eq(ExternalAccount::getAccountType, accountType.getValue())); + if (!BeanValidators.exists(account)) { + throw new ServiceException(CharSequenceUtil.format("{}[{}]未设置{}账户", ownerType.getLabel(), + ownerId, accountType.getLabel())); + } + return account; + } + @Transactional(rollbackFor = Exception.class) @Override public int addTransDetail(Long externalAccountId, TransInfo transInfo, ELoanDirection loanDirection, 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 bb4f0cc5e..a0a93c3be 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 @@ -1,5 +1,7 @@ package com.ruoyi.xkt.service.impl; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Assert; import cn.hutool.core.text.CharSequenceUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.NumberUtil; @@ -19,7 +21,6 @@ import com.ruoyi.xkt.mapper.FinanceBillMapper; import com.ruoyi.xkt.service.IExternalAccountService; import com.ruoyi.xkt.service.IFinanceBillService; import com.ruoyi.xkt.service.IInternalAccountService; -import io.jsonwebtoken.lang.Assert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -223,6 +224,68 @@ public class FinanceBillServiceImpl implements IFinanceBillService { internalAccountService.entryTransDetail(financeBill.getId(), EFinBillType.of(financeBill.getBillType())); } + @Transactional(rollbackFor = Exception.class) + @Override + public FinanceBillExt createWithdrawPaymentBill(Long storeId, BigDecimal amount, EPayChannel payChannel) { + Assert.notNull(storeId); + Assert.isTrue(NumberUtil.isLessOrEqual(amount, BigDecimal.ZERO), "提现金额异常"); + Assert.notNull(payChannel); + FinanceBill bill = new FinanceBill(); + bill.setBillNo(generateBillNo(EFinBillType.PAYMENT)); + bill.setBillType(EFinBillType.PAYMENT.getValue()); + //执行中 + bill.setBillStatus(EFinBillStatus.PROCESSING.getValue()); + bill.setSrcType(EFinBillSrcType.WITHDRAW.getValue()); + bill.setSrcId(storeId); + //业务唯一键 + String businessUniqueKey = IdUtil.simpleUUID(); + bill.setBusinessUniqueKey(businessUniqueKey); + Long outputInternalAccountId = internalAccountService.getInternalAccount(storeId, EAccountOwnerType.STORE) + .getId(); + Long inputExternalAccountId = externalAccountService.getExternalAccount(storeId, EAccountOwnerType.STORE, + EAccountType.getByChannel(payChannel)).getId(); + bill.setOutputInternalAccountId(outputInternalAccountId); + bill.setInputExternalAccountId(inputExternalAccountId); + bill.setOutputExternalAccountId(payChannel.getPlatformExternalAccountId()); + bill.setBusinessAmount(amount); + bill.setTransAmount(amount); + 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(outputInternalAccountId, transInfo, + ELoanDirection.CREDIT, EEntryStatus.WAITING); + //外部账户 + externalAccountService.addTransDetail(inputExternalAccountId, transInfo, + ELoanDirection.DEBIT, EEntryStatus.WAITING); + externalAccountService.addTransDetail(payChannel.getPlatformExternalAccountId(), transInfo, + ELoanDirection.CREDIT, EEntryStatus.WAITING); + return new FinanceBillExt(bill, ListUtil.empty()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void entryWithdrawPaymentBill(Long financeBillId) { + Assert.notNull(financeBillId); + //业务唯一键 + FinanceBill financeBill = financeBillMapper.selectById(financeBillId); + if (!BeanValidators.exists(financeBill)) { + throw new ServiceException(CharSequenceUtil.format("提现付款单[{}]不存在", financeBillId)); + } + //内部账户入账 + internalAccountService.entryTransDetail(financeBill.getId(), EFinBillType.of(financeBill.getBillType())); + //外部账户入账 + externalAccountService.entryTransDetail(financeBill.getId(), EFinBillType.of(financeBill.getBillType())); + } + /** * 生成单号