feat: 提现
parent
7ee4abd483
commit
a2cc166908
|
|
@ -47,6 +47,10 @@ public class ExternalAccount extends SimpleEntity {
|
|||
* 归属人认证通过
|
||||
*/
|
||||
private Boolean accountAuthAccess;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ public class ExternalAccountDTO {
|
|||
* 归属人认证通过
|
||||
*/
|
||||
private Boolean accountAuthAccess;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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("无法获取与支付通道匹配的账户类型");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("支付宝转账失败");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
/**
|
||||
* 添加交易明细
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成单号
|
||||
|
|
|
|||
Loading…
Reference in New Issue