部分支付

localizer
ljw 2025-09-27 12:26:00 +08:00
parent 8236f0d7aa
commit 8c34ebb528
11 changed files with 327 additions and 6 deletions

View File

@ -11,6 +11,7 @@ import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
*
@ -77,7 +78,7 @@ public class ExpenseItemsConfigEntity {
* ids
*/
@TableField(typeHandler = JsonTypeHandler.class)
private String companyIds;
private List<String> companyIds;
/**
*

View File

@ -31,6 +31,11 @@ public class MoneyChangeDetailEntity {
*/
private Long userId;
/**
* id
*/
private Long orderId;
/**
* Id
*/

View File

@ -0,0 +1,19 @@
package com.njzscloud.supervisory.order.contant;
import com.njzscloud.common.core.ienum.DictStr;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* payment_status
*
*/
@Getter
@RequiredArgsConstructor
public enum PaymentWay implements DictStr {
COMPANY("company", "公司支付"),
WX("wx", "微信"),
;
private final String val;
private final String txt;
}

View File

@ -0,0 +1,181 @@
package com.njzscloud.supervisory.order.controller;
import com.google.common.base.Strings;
import com.njzscloud.common.core.ex.Exceptions;
import com.njzscloud.common.core.utils.R;
import com.njzscloud.supervisory.money.contant.MoneyChangeCategory;
import com.njzscloud.supervisory.money.pojo.entity.MoneyAccountEntity;
import com.njzscloud.supervisory.money.pojo.entity.MoneyChangeDetailEntity;
import com.njzscloud.supervisory.money.service.MoneyAccountService;
import com.njzscloud.supervisory.money.service.MoneyChangeDetailService;
import com.njzscloud.supervisory.order.contant.PaymentStatus;
import com.njzscloud.supervisory.order.contant.PaymentWay;
import com.njzscloud.supervisory.order.pojo.entity.OrderExpenseItemsEntity;
import com.njzscloud.supervisory.order.pojo.param.PaymentItemParam;
import com.njzscloud.supervisory.order.pojo.param.PaymentParam;
import com.njzscloud.supervisory.order.pojo.result.PaymentContextResult;
import com.njzscloud.supervisory.order.service.OrderExpenseItemsService;
import com.njzscloud.supervisory.order.service.OrderInfoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
/**
*
*/
@Slf4j
@RestController
@RequestMapping("/payment")
@RequiredArgsConstructor
public class PaymentController {
private final OrderExpenseItemsService orderExpenseItemsService;
private final OrderInfoService orderInfoService;
private final MoneyAccountService moneyAccountService;
private final MoneyChangeDetailService moneyChangeDetailService;
/**
*
*/
@PostMapping("/pay")
@Transactional(rollbackFor = Exception.class)
public R<?> pay(@RequestBody PaymentParam paymentParam) {
// 入参校验占位支付方式、订单ID、支付清单不能为空
if (paymentParam == null
|| paymentParam.getOrderId() == null
|| Strings.isNullOrEmpty(paymentParam.getPaymentCategory())
|| paymentParam.getSettleTotalMoney() == null
|| paymentParam.getSettleTotalMoney().signum() < 0
|| paymentParam.getItems() == null) {
throw Exceptions.clierr("参数不完整或支付总金额小于0");
}
// 校验支付清单id、名称、金额
boolean invalidItem = paymentParam.getItems().stream().anyMatch(it ->
it == null
|| it.getId() == null
|| it.getExpenseItemName() == null || it.getExpenseItemName().length() == 0
|| it.getSettleMoney() == null || it.getSettleMoney().signum() < 0
);
if (invalidItem) {
throw Exceptions.clierr("支付清单项不合法id/名称/金额)");
}
// 校验支付方式
if (paymentParam.getPaymentCategory() == null ||
!("wx".equals(paymentParam.getPaymentCategory()) ||
"company".equals(paymentParam.getPaymentCategory()))) {
throw Exceptions.clierr("支付方式不合法");
}
// 校验数据库中结算金额是否与请求一致,并统计总金额
BigDecimal dbTotalAmount = BigDecimal.ZERO;
for (PaymentItemParam it : paymentParam.getItems()) {
OrderExpenseItemsEntity entity = orderExpenseItemsService.getById(it.getId());
if (entity == null) {
throw Exceptions.clierr("支付清单项不存在id=" + it.getId());
}
BigDecimal dbSettle = entity.getSettleMoney();
BigDecimal reqSettle = it.getSettleMoney();
if (dbSettle == null) {
throw Exceptions.clierr("系统结算金额缺失id=" + it.getId());
}
if (dbSettle.compareTo(reqSettle) != 0) {
throw Exceptions.clierr("结算金额不一致【" + it.getExpenseItemName() + "】(id=" + it.getId()
+ "),系统:" + dbSettle + ",请求:" + reqSettle);
}
// 累加数据库中的结算金额
dbTotalAmount = dbTotalAmount.add(dbSettle);
}
// 验证总金额是否与参数中的结算总金额一致
if (paymentParam.getSettleTotalMoney() == null ||
paymentParam.getSettleTotalMoney().compareTo(dbTotalAmount) != 0) {
throw Exceptions.clierr("结算总金额与清单金额不一致");
}
// 验证总金额是否与参数中的结算总金额一致(这里已经是第二层验证了)
// 第一层验证在循环中已经完成:每个清单项的金额与数据库一致
// 第二层验证:参数中的结算总金额与数据库统计总金额一致
// 查询订单及相关支付上下文(公司、司机账户)
PaymentContextResult ctx = orderInfoService.paymentContext(paymentParam.getOrderId());
if (ctx == null || ctx.getOrderId() == null) {
throw Exceptions.clierr("订单不存在");
}
// 根据支付方式处理支付
if (PaymentWay.COMPANY.getVal().equals(paymentParam.getPaymentCategory())) {
// 公司支付:根据订单 trans_company_id -> biz_company.user_id -> money_account
if (ctx.getTransCompanyId() == null || ctx.getCompanyUserId() == null) {
throw Exceptions.clierr("订单未关联清运公司,无法公司支付");
}
if (ctx.getCompanyBalance() == null || ctx.getCompanyBalance().compareTo(dbTotalAmount) < 0) {
throw Exceptions.clierr("公司账户余额不足");
}
// 直接扣减公司账户余额
deductCompanyBalance(ctx, dbTotalAmount, paymentParam.getOrderId());
} else if (PaymentWay.WX.getVal().equals(paymentParam.getPaymentCategory())) {
// 微信支付:当前不接入,模拟失败以触发回滚
throw Exceptions.clierr("微信支付失败(占位):已回滚之前扣款");
} else {
throw Exceptions.clierr("不支持的支付方式");
}
// 3. 更新订单支付状态为已支付
orderInfoService.lambdaUpdate()
.eq(com.njzscloud.supervisory.order.pojo.entity.OrderInfoEntity::getId, paymentParam.getOrderId())
.set(com.njzscloud.supervisory.order.pojo.entity.OrderInfoEntity::getPaymentStatus, PaymentStatus.YiZhiFu)
.update();
log.info("订单支付成功订单ID{},支付方式:{},支付金额:{}",
paymentParam.getOrderId(), paymentParam.getPaymentCategory(), dbTotalAmount);
return R.success();
}
/**
*
*/
private void deductCompanyBalance(PaymentContextResult ctx, BigDecimal amount, Long orderId) {
// 验证资金账户信息
if (ctx.getCompanyAccountId() == null) {
throw Exceptions.clierr("公司资金账户不存在");
}
BigDecimal oldBalance = ctx.getCompanyBalance();
BigDecimal newBalance = oldBalance.subtract(amount);
// 更新账户余额
MoneyAccountEntity companyAccount = new MoneyAccountEntity()
.setId(ctx.getCompanyAccountId())
.setMoney(newBalance);
moneyAccountService.updateById(companyAccount);
// 记录资金变动明细
MoneyChangeDetailEntity changeDetail = new MoneyChangeDetailEntity()
.setUserId(ctx.getCompanyUserId())
.setOrderId(orderId)
.setMoneyAccountId(companyAccount.getId())
.setOldMoney(oldBalance)
.setDelta(amount.negate()) // 扣减为负数
.setNewMoney(newBalance)
.setMoneyChangeCategory(MoneyChangeCategory.DingDanKouKuan)
.setMemo("订单支付扣款订单ID" + orderId);
moneyChangeDetailService.save(changeDetail);
log.info("公司账户扣减成功用户ID{},扣减金额:{},余额:{} -> {}",
ctx.getCompanyUserId(), amount, oldBalance, newBalance);
}
}

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njzscloud.supervisory.order.pojo.entity.OrderInfoEntity;
import com.njzscloud.supervisory.order.pojo.result.OrderPagingResult;
import com.njzscloud.supervisory.order.pojo.result.PaymentContextResult;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -27,4 +28,6 @@ public interface OrderInfoMapper extends BaseMapper<OrderInfoEntity> {
void busyDriver(@Param("driverId") Long driverId, @Param("busy") Boolean busy);
void busyTruck(@Param("truckId") Long truckId, @Param("busy") Boolean busy);
PaymentContextResult paymentContext(@Param("orderId") Long orderId);
}

View File

@ -11,6 +11,7 @@ import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
*
@ -82,7 +83,7 @@ public class OrderExpenseItemsEntity {
* ids
*/
@TableField(typeHandler = JsonTypeHandler.class)
private String companyIds;
private List<String> companyIds;
/**
*

View File

@ -0,0 +1,32 @@
package com.njzscloud.supervisory.order.pojo.param;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class PaymentItemParam {
/**
* Idorder_expense_items.id
*/
private Long id;
/**
*
*/
private String expenseItemName;
/**
* >= 0
*/
private BigDecimal settleMoney;
}

View File

@ -0,0 +1,39 @@
package com.njzscloud.supervisory.order.pojo.param;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.util.List;
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class PaymentParam {
/**
* Id
*/
private Long orderId;
/**
* wx / company
*/
private String paymentCategory;
/**
* >= 0
*/
private BigDecimal settleTotalMoney;
/**
*
*/
private List<PaymentItemParam> items;
}

View File

@ -0,0 +1,25 @@
package com.njzscloud.supervisory.order.pojo.result;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class PaymentContextResult {
private Long orderId;
private Long transCompanyId;
private Long companyUserId;
private Long companyAccountId;
private BigDecimal companyBalance;
}

View File

@ -11,7 +11,6 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njzscloud.common.core.ex.Exceptions;
import com.njzscloud.common.mp.support.PageParam;
@ -37,8 +36,8 @@ import com.njzscloud.supervisory.order.pojo.param.*;
import com.njzscloud.supervisory.order.pojo.result.OrderCertificateResult;
import com.njzscloud.supervisory.order.pojo.result.OrderPagingResult;
import com.njzscloud.supervisory.order.pojo.result.TrainBillResult;
import com.njzscloud.supervisory.order.pojo.result.PaymentContextResult;
import com.njzscloud.supervisory.order.pojo.entity.OrderExpenseItemsEntity;
import com.njzscloud.supervisory.order.mapper.OrderExpenseItemsMapper;
import com.njzscloud.supervisory.goods.contant.MoneyStrategy;
import com.njzscloud.supervisory.expense.pojo.entity.ExpenseItemsConfigEntity;
import com.njzscloud.supervisory.expense.contant.Scope;
@ -65,7 +64,7 @@ import static com.njzscloud.supervisory.constant.Constant.ROLE_JG;
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEntity> implements IService<OrderInfoEntity> {
public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEntity> {
private final BizAuditConfigService bizAuditConfigService;
private final OrderGoodsService orderGoodsService;
@ -113,6 +112,10 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
}
}
public PaymentContextResult paymentContext(Long orderId) {
return this.baseMapper.paymentContext(orderId);
}
/**
*
*/
@ -747,7 +750,7 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
return bizObj == BizObj.QiYe || bizObj == BizObj.GeTi;
}
if (scope == Scope.CUSTOMER) {
String companyIds = cfg.getCompanyIds();
List<String> companyIds = cfg.getCompanyIds();
return companyIds != null && transCompanyId != null && companyIds.contains(String.valueOf(transCompanyId));
}
return false;

View File

@ -191,4 +191,16 @@
FROM biz_driver
WHERE user_id = #{userId}
</select>
<select id="paymentContext" resultType="com.njzscloud.supervisory.order.pojo.result.PaymentContextResult">
SELECT
a.id AS orderId,
a.trans_company_id AS transCompanyId,
bc.user_id AS companyUserId,
ma1.id AS companyAccountId,
ma1.money AS companyBalance
FROM order_info a
LEFT JOIN biz_company bc ON bc.id = a.trans_company_id
LEFT JOIN money_account ma1 ON ma1.user_id = bc.user_id
WHERE a.id = #{orderId}
</select>
</mapper>