添加分账请求接口;

This commit is contained in:
terrfly 2021-08-27 11:19:01 +08:00
parent fa6e00c3ee
commit 0be119cc1c
19 changed files with 630 additions and 262 deletions

View File

@ -54,6 +54,9 @@ public class PayOrderDivisionMQ extends AbstractMQ {
/** 支付订单号 **/
private String payOrderId;
/** 是否使用默认分组 **/
private Byte useSysAutoDivisionReceivers;
/**
* 分账接受者列表 字段值为空表示系统默认配置项
* 格式{receiverId: '1001', receiverGroupId: '1001', divisionProfit: '0.1'}
@ -80,8 +83,8 @@ public class PayOrderDivisionMQ extends AbstractMQ {
}
/** 【!重要配置项!】 构造MQModel , 一般用于发送MQ时 **/
public static PayOrderDivisionMQ build(String payOrderId, List<CustomerDivisionReceiver> receiverList){
return new PayOrderDivisionMQ(new MsgPayload(payOrderId, receiverList));
public static PayOrderDivisionMQ build(String payOrderId, Byte useSysAutoDivisionReceivers, List<CustomerDivisionReceiver> receiverList){
return new PayOrderDivisionMQ(new MsgPayload(payOrderId, useSysAutoDivisionReceivers, receiverList));
}
/** 解析MQ消息 一般用于接收MQ消息时 **/

View File

@ -125,7 +125,7 @@ public class AmountUtil {
* 计算百分比类型的各种费用值 订单金额 * 真实费率 结果四舍五入并保留0位小数
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/20 14:53
* @param amount 订单金额 保持与数据库的格式一致 单位
* @param rate 费率 保持与数据库的格式一致 真实费率值如费率为0.55%则传入 0.0055
@ -138,7 +138,7 @@ public class AmountUtil {
* 计算百分比类型的各种费用值 订单金额 * 真实费率 结果四舍五入并保留0位小数
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/20 14:53
* @param amount 订单金额 保持与数据库的格式一致 单位
* @param rate 费率 保持与数据库的格式一致 真实费率值如费率为0.55%则传入 0.0055

View File

@ -36,7 +36,7 @@ import org.springframework.web.bind.annotation.RestController;
* 转账订单api
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/13 10:52
*/
@RestController

View File

@ -36,7 +36,7 @@ import org.springframework.web.bind.annotation.RestController;
* 转账订单api
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/13 10:52
*/
@RestController

View File

@ -29,7 +29,7 @@ import java.util.List;
* 分账接口
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/22 08:59
*/
public interface IDivisionService {

View File

@ -24,7 +24,7 @@ import com.jeequan.jeepay.pay.rqrs.transfer.TransferOrderRQ;
* 转账接口
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/11 13:59
*/
public interface ITransferService {

View File

@ -43,7 +43,7 @@ import java.util.List;
* 分账接口 支付宝官方
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/22 09:05
*/
@Slf4j
@ -63,7 +63,6 @@ public class AlipayDivisionService implements IDivisionService {
@Override
public ChannelRetMsg bind(MchDivisionReceiver mchDivisionReceiver, MchAppConfigContext mchAppConfigContext) {
try {
AlipayTradeRoyaltyRelationBindRequest request = new AlipayTradeRoyaltyRelationBindRequest();
AlipayTradeRoyaltyRelationBindModel model = new AlipayTradeRoyaltyRelationBindModel();
@ -114,6 +113,10 @@ public class AlipayDivisionService implements IDivisionService {
try {
if(recordList.isEmpty()){ // 当无分账用户时 支付宝不允许发起分账请求 支付宝没有完结接口直接响应成功即可
return ChannelRetMsg.confirmSuccess(null);
}
AlipayTradeOrderSettleRequest request = new AlipayTradeOrderSettleRequest();
AlipayTradeOrderSettleModel model = new AlipayTradeOrderSettleModel();
request.setBizModel(model);
@ -150,7 +153,7 @@ public class AlipayDivisionService implements IDivisionService {
}
if(reqReceiverList.isEmpty()){ // 当无分账用户时 支付宝不允许发起分账请求 支付宝没有完结解决直接响应成功即可
if(reqReceiverList.isEmpty()){ // 当无分账用户时 支付宝不允许发起分账请求 支付宝没有完结接口直接响应成功即可
return ChannelRetMsg.confirmSuccess(null);
}

View File

@ -34,7 +34,7 @@ import org.springframework.stereotype.Service;
* 转账接口 支付宝官方
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/11 14:05
*/
@Slf4j

View File

@ -38,7 +38,7 @@ import java.util.List;
* 分账接口 微信官方
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/22 09:05
*/
@Slf4j

View File

@ -34,7 +34,7 @@ import org.springframework.stereotype.Service;
* 转账接口 微信官方
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/11 14:05
*/
@Slf4j

View File

@ -46,7 +46,7 @@ import java.util.Date;
* 分账账号绑定
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/25 9:07
*/
@Slf4j

View File

@ -0,0 +1,192 @@
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jeequan.jeepay.pay.ctrl.division;
import com.alibaba.fastjson.JSON;
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchDivisionReceiver;
import com.jeequan.jeepay.core.entity.MchDivisionReceiverGroup;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.model.ApiRes;
import com.jeequan.jeepay.pay.ctrl.ApiController;
import com.jeequan.jeepay.pay.model.MchAppConfigContext;
import com.jeequan.jeepay.pay.rqrs.division.PayOrderDivisionExecRQ;
import com.jeequan.jeepay.pay.rqrs.division.PayOrderDivisionExecRS;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.pay.service.PayOrderDivisionProcessService;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverGroupService;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 发起分账请求
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/8/27 8:01
*/
@Slf4j
@RestController
public class PayOrderDivisionExecController extends ApiController {
@Autowired private ConfigContextService configContextService;
@Autowired private PayOrderService payOrderService;
@Autowired private MchDivisionReceiverService mchDivisionReceiverService;
@Autowired private MchDivisionReceiverGroupService mchDivisionReceiverGroupService;
@Autowired private PayOrderDivisionProcessService payOrderDivisionProcessService;
/** 分账执行 **/
@PostMapping("/api/division/exec")
public ApiRes exec(){
//获取参数 & 验签
PayOrderDivisionExecRQ bizRQ = getRQByWithMchSign(PayOrderDivisionExecRQ.class);
try {
if(StringUtils.isAllEmpty(bizRQ.getMchOrderNo(), bizRQ.getPayOrderId())){
throw new BizException("mchOrderNo 和 payOrderId不能同时为空");
}
PayOrder payOrder = payOrderService.queryMchOrder(bizRQ.getMchNo(), bizRQ.getPayOrderId(), bizRQ.getMchOrderNo());
if(payOrder == null){
throw new BizException("订单不存在");
}
if(payOrder.getState() != PayOrder.STATE_SUCCESS || payOrder.getDivisionState() != PayOrder.DIVISION_STATE_UNHAPPEN || payOrder.getDivisionMode() != PayOrder.DIVISION_MODE_MANUAL){
throw new BizException("当前订单状态不支持分账");
}
List<PayOrderDivisionMQ.CustomerDivisionReceiver> receiverList = null;
//不使用默认分组 需要转换每个账号信息
if(bizRQ.getUseSysAutoDivisionReceivers() != CS.YES && !StringUtils.isEmpty(bizRQ.getReceivers())){
receiverList = JSON.parseArray(bizRQ.getReceivers(), PayOrderDivisionMQ.CustomerDivisionReceiver.class);
}
// 验证账号是否合法
this.checkReceiverList(receiverList, payOrder.getIfCode(), bizRQ.getMchNo(), bizRQ.getAppId());
// 商户配置信息
MchAppConfigContext mchAppConfigContext = configContextService.getMchAppConfigContext(bizRQ.getMchNo(), bizRQ.getAppId());
if(mchAppConfigContext == null){
throw new BizException("获取商户应用信息失败");
}
//处理分账请求
ChannelRetMsg channelRetMsg = payOrderDivisionProcessService.processPayOrderDivision(bizRQ.getPayOrderId(), bizRQ.getUseSysAutoDivisionReceivers(), receiverList);
PayOrderDivisionExecRS bizRS = new PayOrderDivisionExecRS();
bizRS.setState(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS ? PayOrderDivisionRecord.STATE_SUCCESS : PayOrderDivisionRecord.STATE_FAIL);
bizRS.setChannelBatchOrderId(channelRetMsg.getChannelOrderId());
bizRS.setErrCode(channelRetMsg.getChannelErrCode());
bizRS.setErrMsg(channelRetMsg.getChannelErrMsg());
return ApiRes.okWithSign(bizRS, mchAppConfigContext.getMchApp().getAppSecret());
} catch (BizException e) {
return ApiRes.customFail(e.getMessage());
} catch (Exception e) {
log.error("系统异常:{}", e);
return ApiRes.customFail("系统异常");
}
}
/** 检验账号是否合法 **/
private void checkReceiverList(List<PayOrderDivisionMQ.CustomerDivisionReceiver> receiverList, String ifCode, String mchNo, String appId){
if(receiverList == null || receiverList.isEmpty()){
return ;
}
Set<Long> receiverIdSet = new HashSet<>();
Set<Long> receiverGroupIdSet = new HashSet<>();
for (PayOrderDivisionMQ.CustomerDivisionReceiver receiver : receiverList) {
if(receiver.getReceiverId() != null){
receiverIdSet.add(receiver.getReceiverId());
}
if(receiver.getReceiverGroupId() != null){
receiverGroupIdSet.add(receiver.getReceiverGroupId());
}
if(receiver.getReceiverId() == null && receiver.getReceiverGroupId() == null){
throw new BizException("分账用户组: receiverId 和 与receiverGroupId 必填一项");
}
if(receiver.getDivisionProfit() != null){
if(receiver.getDivisionProfit().compareTo(BigDecimal.ZERO) < 0){
throw new BizException("分账用户receiverId=["+receiver.getReceiverId() == null ? "": receiver.getReceiverId()+"]," +
"receiverGroupId=["+receiver.getReceiverGroupId() == null ? "": receiver.getReceiverGroupId()+"] 分账比例不得小于0%");
}
if(receiver.getDivisionProfit().compareTo(BigDecimal.ONE) > 0){
throw new BizException("分账用户receiverId=["+receiver.getReceiverId() == null ? "": receiver.getReceiverId()+"]," +
"receiverGroupId=["+receiver.getReceiverGroupId() == null ? "": receiver.getReceiverGroupId()+"] 分账比例不得高于100%");
}
}
}
if(!receiverIdSet.isEmpty()){
int receiverCount = mchDivisionReceiverService.count(MchDivisionReceiver.gw()
.in(MchDivisionReceiver::getReceiverId, receiverIdSet)
.eq(MchDivisionReceiver::getMchNo, mchNo)
.eq(MchDivisionReceiver::getAppId, appId)
.eq(MchDivisionReceiver::getIfCode, ifCode)
.eq(MchDivisionReceiver::getState, CS.YES)
);
if(receiverCount != receiverIdSet.size()){
throw new BizException("分账[用户]中包含不存在或渠道不可用账号,请更改");
}
}
if(!receiverGroupIdSet.isEmpty()){
int receiverGroupCount = mchDivisionReceiverGroupService.count(MchDivisionReceiverGroup.gw()
.in(MchDivisionReceiverGroup::getReceiverGroupId, receiverGroupIdSet)
.eq(MchDivisionReceiverGroup::getMchNo, mchNo)
);
if(receiverGroupCount != receiverGroupIdSet.size()){
throw new BizException("分账[账号组]中包含不存在或不可用组,请更改");
}
}
}
}

View File

@ -46,7 +46,7 @@ import java.util.Date;
* 转账接口
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/11 11:07
*/
@Slf4j

View File

@ -15,38 +15,12 @@
*/
package com.jeequan.jeepay.pay.mq;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchDivisionReceiver;
import com.jeequan.jeepay.core.entity.MchDivisionReceiverGroup;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.core.utils.JeepayKit;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.pay.channel.IDivisionService;
import com.jeequan.jeepay.pay.channel.ITransferService;
import com.jeequan.jeepay.pay.model.MchAppConfigContext;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.pay.service.ConfigContextService;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverGroupService;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverService;
import com.jeequan.jeepay.service.impl.PayOrderDivisionRecordService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import com.jeequan.jeepay.pay.service.PayOrderDivisionProcessService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 接收MQ消息
* 业务 支付订单分账处理逻辑
@ -58,232 +32,18 @@ import java.util.List;
@Component
public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceiver {
@Autowired
private PayOrderService payOrderService;
@Autowired
private MchDivisionReceiverService mchDivisionReceiverService;
@Autowired
private MchDivisionReceiverGroupService mchDivisionReceiverGroupService;
@Autowired
private PayOrderDivisionRecordService payOrderDivisionRecordService;
@Autowired
private ConfigContextService configContextService;
@Autowired private PayOrderDivisionProcessService payOrderDivisionProcessService;
@Override
public void receive(PayOrderDivisionMQ.MsgPayload payload) {
try {
log.info("接收订单分账通知MQ, msg={}", payload.toString());
String logPrefix = "订单["+payload.getPayOrderId()+"]执行分账";
//查询订单信息
PayOrder payOrder = payOrderService.getById(payload.getPayOrderId());
if(payOrder == null){
log.error("{},订单不存在", logPrefix);
return ;
}
if(payOrder.getState() != PayOrder.STATE_SUCCESS || payOrder.getDivisionState() != PayOrder.DIVISION_STATE_WAIT_TASK){
log.error("{}, 订单状态或分账状态不正确", logPrefix);
return ;
}
//更新订单为 分账任务处理中
boolean updPayOrder = payOrderService.update(new LambdaUpdateWrapper<PayOrder>()
.set(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_ING)
.eq(PayOrder::getPayOrderId, payload.getPayOrderId())
.eq(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_WAIT_TASK));
if(!updPayOrder){
log.error("{}, 更新支付订单为分账处理中异常!", logPrefix);
return ;
}
// 查询&过滤 所有的分账接收对象
List<MchDivisionReceiver> allReceiver = this.queryReceiver(payOrder, payload.getReceiverList());
//得到全部分账比例 (所有待分账账号的分账比例总和)
BigDecimal allDivisionProfit = BigDecimal.ZERO;
for (MchDivisionReceiver receiver : allReceiver) {
allDivisionProfit = allDivisionProfit.add(receiver.getDivisionProfit());
}
//计算分账金额 = 商家实际入账金额
Long payOrderDivisionAmount = payOrderService.calMchIncomeAmount(payOrder);
//剩余待分账金额 (用作最后一个分账账号的 计算 避免出现分账金额超出最大) [结果向下取整 避免出现金额溢出的情况 ]
Long subDivisionAmount = AmountUtil.calPercentageFee(payOrderDivisionAmount, allDivisionProfit, BigDecimal.ROUND_FLOOR);
List<PayOrderDivisionRecord> recordList = new ArrayList<>();
//计算订单分账金额, 并插入到记录表
String batchOrderId = SeqKit.genDivisionBatchId();
for (MchDivisionReceiver receiver : allReceiver) {
PayOrderDivisionRecord record = genRecord(batchOrderId, payOrder, receiver, payOrderDivisionAmount, subDivisionAmount);
//剩余金额
subDivisionAmount = subDivisionAmount - record.getCalDivisionAmount();
//入库保存
payOrderDivisionRecordService.save(record);
recordList.add(record);
}
try{
//调用渠道侧分账接口
IDivisionService divisionService = SpringBeansUtil.getBean(payOrder.getIfCode() + "DivisionService", IDivisionService.class);
if(divisionService == null){
throw new BizException("通道无此分账接口");
}
ChannelRetMsg channelRetMsg = divisionService.singleDivision(payOrder, recordList, configContextService.getMchAppConfigContext(payOrder.getMchNo(), payOrder.getAppId()));
// 确认分账成功
if(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) {
//分账成功
payOrderDivisionRecordService.updateRecordSuccessOrFail(recordList, PayOrderDivisionRecord.STATE_SUCCESS,
channelRetMsg.getChannelOrderId(), channelRetMsg.getChannelOriginResponse());
}else{
//分账失败
payOrderDivisionRecordService.updateRecordSuccessOrFail(recordList, PayOrderDivisionRecord.STATE_FAIL,
channelRetMsg.getChannelOrderId(), channelRetMsg.getChannelErrMsg());
}
} catch (BizException e) {
log.error("{}, 调用分账接口异常, {}", logPrefix, e.getMessage());
} catch (Exception e) {
log.error("{}, 调用分账接口异常", logPrefix, e);
payOrderDivisionRecordService.updateRecordSuccessOrFail(recordList, PayOrderDivisionRecord.STATE_FAIL,
null, "系统异常:" + e.getMessage());
}
//更新 支付订单主表状态 分账任务已结束
payOrderService.update(new LambdaUpdateWrapper<PayOrder>()
.set(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_FINISH)
.set(PayOrder::getDivisionLastTime, new Date())
.eq(PayOrder::getPayOrderId, payload.getPayOrderId())
.eq(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_ING)
);
payOrderDivisionProcessService.processPayOrderDivision(payload.getPayOrderId(), payload.getUseSysAutoDivisionReceivers(), payload.getReceiverList());
}catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/** 生成对象信息 **/
private PayOrderDivisionRecord genRecord(String batchOrderId, PayOrder payOrder, MchDivisionReceiver receiver,
Long payOrderDivisionAmount, Long subDivisionAmount){
PayOrderDivisionRecord record = new PayOrderDivisionRecord();
record.setMchNo(payOrder.getMchNo());
record.setIsvNo(payOrder.getIsvNo());
record.setAppId(payOrder.getAppId());
record.setMchName(payOrder.getMchName());
record.setMchType(payOrder.getMchType());
record.setIfCode(payOrder.getIfCode());
record.setPayOrderId(payOrder.getPayOrderId());
record.setPayOrderChannelOrderNo(payOrder.getChannelOrderNo()); //支付订单渠道订单号
record.setPayOrderAmount(payOrder.getAmount()); //订单金额
record.setPayOrderDivisionAmount(payOrderDivisionAmount); // 订单计算分账金额
record.setBatchOrderId(batchOrderId); //系统分账批次号
record.setState(PayOrderDivisionRecord.STATE_WAIT); //状态: 待分账
record.setReceiverId(receiver.getReceiverId());
record.setReceiverGroupId(receiver.getReceiverGroupId());
record.setReceiverAlias(receiver.getReceiverAlias());
record.setAccType(receiver.getAccType());
record.setAccNo(receiver.getAccNo());
record.setAccName(receiver.getAccName());
record.setRelationType(receiver.getRelationType());
record.setRelationTypeName(receiver.getRelationTypeName());
record.setDivisionProfit(receiver.getDivisionProfit());
if( subDivisionAmount <= 0 ) {
record.setCalDivisionAmount(0L);
}else{
//计算的分账金额
record.setCalDivisionAmount(AmountUtil.calPercentageFee(record.getPayOrderDivisionAmount(), record.getDivisionProfit()));
if(record.getCalDivisionAmount() > subDivisionAmount){ // 分账金额超过剩余总金额时 将按照剩余金额进行分账
record.setCalDivisionAmount(subDivisionAmount);
}
}
return record;
}
private List<MchDivisionReceiver> queryReceiver(PayOrder payOrder, List<PayOrderDivisionMQ.CustomerDivisionReceiver> customerDivisionReceiverList){
// 查询全部分账列表
LambdaQueryWrapper<MchDivisionReceiver> queryWrapper = MchDivisionReceiver.gw();
queryWrapper.eq(MchDivisionReceiver::getMchNo, payOrder.getMchNo()); //mchNo
queryWrapper.eq(MchDivisionReceiver::getAppId, payOrder.getAppId()); //appId
queryWrapper.eq(MchDivisionReceiver::getIfCode, payOrder.getIfCode()); //ifCode
queryWrapper.eq(MchDivisionReceiver::getState, CS.PUB_USABLE); // 可用状态的账号
// 未设置接收者账号信息 需要查询 自动分账组的账号
if(customerDivisionReceiverList == null){
List<MchDivisionReceiverGroup> groups = mchDivisionReceiverGroupService.list(
MchDivisionReceiverGroup.gw().eq(MchDivisionReceiverGroup::getMchNo, payOrder.getMchNo())
.eq(MchDivisionReceiverGroup::getAutoDivisionFlag, CS.YES));
if(groups.isEmpty()){
return new ArrayList<>();
}
queryWrapper.eq(MchDivisionReceiver::getReceiverGroupId, groups.get(0).getReceiverGroupId());
}
//全部分账账号
List<MchDivisionReceiver> allMchReceiver = mchDivisionReceiverService.list(queryWrapper);
if(allMchReceiver.isEmpty()){
return allMchReceiver;
}
// 自定义列表未定义
if(customerDivisionReceiverList == null){
return allMchReceiver;
}
//参数有定义但是没有任何值
if(customerDivisionReceiverList.isEmpty()){
return new ArrayList<>();
}
// 过滤账号
List<MchDivisionReceiver> filterMchReceiver = new ArrayList<>();
for (MchDivisionReceiver mchDivisionReceiver : allMchReceiver) {
for (PayOrderDivisionMQ.CustomerDivisionReceiver customerDivisionReceiver : customerDivisionReceiverList) {
// 查询匹配相同的项目
if( mchDivisionReceiver.getReceiverId().equals(customerDivisionReceiver.getReceiverId()) ||
mchDivisionReceiver.getReceiverGroupId().equals(customerDivisionReceiver.getReceiverGroupId())
){
// 重新对分账比例赋值
if(customerDivisionReceiver.getDivisionProfit() != null){
mchDivisionReceiver.setDivisionProfit(customerDivisionReceiver.getDivisionProfit());
}
filterMchReceiver.add(mchDivisionReceiver);
}
}
}
return filterMchReceiver;
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jeequan.jeepay.pay.rqrs.division;
import com.jeequan.jeepay.pay.rqrs.AbstractMchAppRQ;
import lombok.Data;
import javax.validation.constraints.NotNull;
/*
* 发起订单分账 请求参数
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/8/26 17:21
*/
@Data
public class PayOrderDivisionExecRQ extends AbstractMchAppRQ {
/** 商户订单号 **/
private String mchOrderNo;
/** 支付系统订单号 **/
private String payOrderId;
/**
* 是否使用系统配置的自动分账组 0- 1-
**/
@NotNull(message = "是否使用系统配置的自动分账组不能为空")
private Byte useSysAutoDivisionReceivers;
/** 接收者账号列表JSONArray 转换为字符串类型
* 仅当useSysAutoDivisionReceivers=0 时有效
*
* 参考
*
* 方式1 按账号纬度
* [{
* receiverId: 800001,
* divisionProfit: 0.1 (若不填入则使用系统默认配置值)
* }]
*
* 方式2 按组纬度
* [{
* receiverGroupId: 100001, (该组所有 当前订单的渠道账号并且可用状态的全部参与分账)
* divisionProfit: 0.1 (每个账号的分账比例 若不填入则使用系统默认配置值 建议不填写)
* }]
*
* **/
private String receivers;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jeequan.jeepay.pay.rqrs.division;
import com.jeequan.jeepay.pay.rqrs.AbstractRS;
import lombok.Data;
/**
* 发起订单分账 响应参数
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/8/26 17:20
*/
@Data
public class PayOrderDivisionExecRS extends AbstractRS {
/**
* 分账状态 1-分账成功, 2-分账失败
*/
private Byte state;
/**
* 上游分账批次号
*/
private String channelBatchOrderId;
/**
* 支付渠道错误码
*/
private String errCode;
/**
* 支付渠道错误信息
*/
private String errMsg;
}

View File

@ -0,0 +1,292 @@
/*
* Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jeequan.jeepay.pay.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.MchDivisionReceiver;
import com.jeequan.jeepay.core.entity.MchDivisionReceiverGroup;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord;
import com.jeequan.jeepay.core.exception.BizException;
import com.jeequan.jeepay.core.utils.AmountUtil;
import com.jeequan.jeepay.core.utils.SeqKit;
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
import com.jeequan.jeepay.pay.channel.IDivisionService;
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverGroupService;
import com.jeequan.jeepay.service.impl.MchDivisionReceiverService;
import com.jeequan.jeepay.service.impl.PayOrderDivisionRecordService;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 业务 支付订单分账处理逻辑
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/8/27 9:43
*/
@Slf4j
@Component
public class PayOrderDivisionProcessService {
@Autowired
private PayOrderService payOrderService;
@Autowired
private MchDivisionReceiverService mchDivisionReceiverService;
@Autowired
private MchDivisionReceiverGroupService mchDivisionReceiverGroupService;
@Autowired
private PayOrderDivisionRecordService payOrderDivisionRecordService;
@Autowired
private ConfigContextService configContextService;
/***
* 处理分账
* 1. 向外抛异常 系统检查没有通过 / 系统级别异常
* 2 若正常调起接口将返回渠道侧响应结果
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/8/27 9:44
*/
public ChannelRetMsg processPayOrderDivision(String payOrderId, Byte useSysAutoDivisionReceivers, List<PayOrderDivisionMQ.CustomerDivisionReceiver> receiverList) {
String logPrefix = "订单["+payOrderId+"]执行分账";
//查询订单信息
PayOrder payOrder = payOrderService.getById(payOrderId);
if(payOrder == null){
log.error("{},订单不存在", logPrefix);
throw new BizException("订单不存在");
}
// 订单不是成功状态 || 分账状态不正确
if(payOrder.getState() != PayOrder.STATE_SUCCESS ||
(
payOrder.getDivisionState() != PayOrder.DIVISION_STATE_WAIT_TASK
&& payOrder.getDivisionState() != PayOrder.DIVISION_STATE_UNHAPPEN
)){
log.error("{}, 订单状态或分账状态不正确", logPrefix);
throw new BizException("订单状态或分账状态不正确");
}
//更新订单为 分账任务处理中
boolean updPayOrder = payOrderService.update(new LambdaUpdateWrapper<PayOrder>()
.set(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_ING)
.eq(PayOrder::getPayOrderId, payOrderId)
.eq(PayOrder::getDivisionState, payOrder.getDivisionState()));
if(!updPayOrder){
log.error("{}, 更新支付订单为分账处理中异常!", logPrefix);
throw new BizException("更新支付订单为分账处理中异常");
}
// 查询&过滤 所有的分账接收对象
List<MchDivisionReceiver> allReceiver = this.queryReceiver(useSysAutoDivisionReceivers, payOrder, receiverList);
//得到全部分账比例 (所有待分账账号的分账比例总和)
BigDecimal allDivisionProfit = BigDecimal.ZERO;
for (MchDivisionReceiver receiver : allReceiver) {
allDivisionProfit = allDivisionProfit.add(receiver.getDivisionProfit());
}
//计算分账金额 = 商家实际入账金额
Long payOrderDivisionAmount = payOrderService.calMchIncomeAmount(payOrder);
//剩余待分账金额 (用作最后一个分账账号的 计算 避免出现分账金额超出最大) [结果向下取整 避免出现金额溢出的情况 ]
Long subDivisionAmount = AmountUtil.calPercentageFee(payOrderDivisionAmount, allDivisionProfit, BigDecimal.ROUND_FLOOR);
List<PayOrderDivisionRecord> recordList = new ArrayList<>();
//计算订单分账金额, 并插入到记录表
String batchOrderId = SeqKit.genDivisionBatchId();
for (MchDivisionReceiver receiver : allReceiver) {
PayOrderDivisionRecord record = genRecord(batchOrderId, payOrder, receiver, payOrderDivisionAmount, subDivisionAmount);
//剩余金额
subDivisionAmount = subDivisionAmount - record.getCalDivisionAmount();
//入库保存
payOrderDivisionRecordService.save(record);
recordList.add(record);
}
ChannelRetMsg channelRetMsg = null;
try{
//调用渠道侧分账接口
IDivisionService divisionService = SpringBeansUtil.getBean(payOrder.getIfCode() + "DivisionService", IDivisionService.class);
if(divisionService == null){
throw new BizException("通道无此分账接口");
}
channelRetMsg = divisionService.singleDivision(payOrder, recordList, configContextService.getMchAppConfigContext(payOrder.getMchNo(), payOrder.getAppId()));
// 确认分账成功
if(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) {
//分账成功
payOrderDivisionRecordService.updateRecordSuccessOrFail(recordList, PayOrderDivisionRecord.STATE_SUCCESS,
channelRetMsg.getChannelOrderId(), channelRetMsg.getChannelOriginResponse());
}else{
//分账失败
payOrderDivisionRecordService.updateRecordSuccessOrFail(recordList, PayOrderDivisionRecord.STATE_FAIL,
channelRetMsg.getChannelOrderId(), channelRetMsg.getChannelErrMsg());
}
} catch (Exception e) {
log.error("{}, 调用分账接口异常", logPrefix, e);
payOrderDivisionRecordService.updateRecordSuccessOrFail(recordList, PayOrderDivisionRecord.STATE_FAIL,
null, "系统异常:" + e.getMessage());
channelRetMsg = ChannelRetMsg.confirmFail(null, null, e.getMessage());
}
//更新 支付订单主表状态 分账任务已结束
payOrderService.update(new LambdaUpdateWrapper<PayOrder>()
.set(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_FINISH)
.set(PayOrder::getDivisionLastTime, new Date())
.eq(PayOrder::getPayOrderId, payOrderId)
.eq(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_ING)
);
return channelRetMsg;
}
/** 生成对象信息 **/
private PayOrderDivisionRecord genRecord(String batchOrderId, PayOrder payOrder, MchDivisionReceiver receiver,
Long payOrderDivisionAmount, Long subDivisionAmount){
PayOrderDivisionRecord record = new PayOrderDivisionRecord();
record.setMchNo(payOrder.getMchNo());
record.setIsvNo(payOrder.getIsvNo());
record.setAppId(payOrder.getAppId());
record.setMchName(payOrder.getMchName());
record.setMchType(payOrder.getMchType());
record.setIfCode(payOrder.getIfCode());
record.setPayOrderId(payOrder.getPayOrderId());
record.setPayOrderChannelOrderNo(payOrder.getChannelOrderNo()); //支付订单渠道订单号
record.setPayOrderAmount(payOrder.getAmount()); //订单金额
record.setPayOrderDivisionAmount(payOrderDivisionAmount); // 订单计算分账金额
record.setBatchOrderId(batchOrderId); //系统分账批次号
record.setState(PayOrderDivisionRecord.STATE_WAIT); //状态: 待分账
record.setReceiverId(receiver.getReceiverId());
record.setReceiverGroupId(receiver.getReceiverGroupId());
record.setReceiverAlias(receiver.getReceiverAlias());
record.setAccType(receiver.getAccType());
record.setAccNo(receiver.getAccNo());
record.setAccName(receiver.getAccName());
record.setRelationType(receiver.getRelationType());
record.setRelationTypeName(receiver.getRelationTypeName());
record.setDivisionProfit(receiver.getDivisionProfit());
if( subDivisionAmount <= 0 ) {
record.setCalDivisionAmount(0L);
}else{
//计算的分账金额
record.setCalDivisionAmount(AmountUtil.calPercentageFee(record.getPayOrderDivisionAmount(), record.getDivisionProfit()));
if(record.getCalDivisionAmount() > subDivisionAmount){ // 分账金额超过剩余总金额时 将按照剩余金额进行分账
record.setCalDivisionAmount(subDivisionAmount);
}
}
return record;
}
private List<MchDivisionReceiver> queryReceiver(Byte useSysAutoDivisionReceivers, PayOrder payOrder, List<PayOrderDivisionMQ.CustomerDivisionReceiver> customerDivisionReceiverList){
// 查询全部分账列表
LambdaQueryWrapper<MchDivisionReceiver> queryWrapper = MchDivisionReceiver.gw();
queryWrapper.eq(MchDivisionReceiver::getMchNo, payOrder.getMchNo()); //mchNo
queryWrapper.eq(MchDivisionReceiver::getAppId, payOrder.getAppId()); //appId
queryWrapper.eq(MchDivisionReceiver::getIfCode, payOrder.getIfCode()); //ifCode
queryWrapper.eq(MchDivisionReceiver::getState, CS.PUB_USABLE); // 可用状态的账号
// 自动分账组的账号
if(useSysAutoDivisionReceivers == CS.YES) {
List<MchDivisionReceiverGroup> groups = mchDivisionReceiverGroupService.list(
MchDivisionReceiverGroup.gw().eq(MchDivisionReceiverGroup::getMchNo, payOrder.getMchNo())
.eq(MchDivisionReceiverGroup::getAutoDivisionFlag, CS.YES));
if(groups.isEmpty()){
return new ArrayList<>();
}
queryWrapper.eq(MchDivisionReceiver::getReceiverGroupId, groups.get(0).getReceiverGroupId());
}
//全部分账账号
List<MchDivisionReceiver> allMchReceiver = mchDivisionReceiverService.list(queryWrapper);
if(allMchReceiver.isEmpty()){
return allMchReceiver;
}
//自动分账组
if(useSysAutoDivisionReceivers == CS.YES){
return allMchReceiver;
}
//以下为 自定义列表
// 自定义列表未定义
if(customerDivisionReceiverList == null || customerDivisionReceiverList.isEmpty()){
return new ArrayList<>();
}
// 过滤账号
List<MchDivisionReceiver> filterMchReceiver = new ArrayList<>();
for (MchDivisionReceiver mchDivisionReceiver : allMchReceiver) {
for (PayOrderDivisionMQ.CustomerDivisionReceiver customerDivisionReceiver : customerDivisionReceiverList) {
// 查询匹配相同的项目
if( mchDivisionReceiver.getReceiverId().equals(customerDivisionReceiver.getReceiverId()) ||
mchDivisionReceiver.getReceiverGroupId().equals(customerDivisionReceiver.getReceiverGroupId())
){
// 重新对分账比例赋值
if(customerDivisionReceiver.getDivisionProfit() != null){
mchDivisionReceiver.setDivisionProfit(customerDivisionReceiver.getDivisionProfit());
}
filterMchReceiver.add(mchDivisionReceiver);
}
}
}
return filterMchReceiver;
}
}

View File

@ -18,6 +18,7 @@ package com.jeequan.jeepay.pay.service;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
import com.jeequan.jeepay.components.mq.vender.IMQSender;
import com.jeequan.jeepay.core.constants.CS;
import com.jeequan.jeepay.core.entity.PayOrder;
import com.jeequan.jeepay.service.impl.PayOrderService;
import lombok.extern.slf4j.Slf4j;
@ -28,7 +29,7 @@ import org.springframework.stereotype.Service;
* 订单处理通用逻辑
*
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/22 16:50
*/
@Service
@ -75,7 +76,7 @@ public class PayOrderProcessService {
if(updDivisionState){
//推送到分账MQ
mqSender.send(PayOrderDivisionMQ.build(payOrder.getPayOrderId(), null), 60); //1分钟后执行
mqSender.send(PayOrderDivisionMQ.build(payOrder.getPayOrderId(), CS.YES,null), 60); //1分钟后执行
}
} catch (Exception e) {

View File

@ -362,7 +362,7 @@ public class PayOrderService extends ServiceImpl<PayOrderMapper, PayOrder> {
* 计算支付订单商家入账金额
* 商家订单入账金额 支付金额 - 手续费 - 退款金额 - 总分账金额
* @author terrfly
* @site https://www.jeepay.vip
* @site https://www.jeequan.com
* @date 2021/8/26 16:39
*/
public Long calMchIncomeAmount(PayOrder dbPayOrder){