分账第一卷: 分账表结构设计; 分账MQ, 入库操作。
This commit is contained in:
parent
07212404d5
commit
ad2afc150b
|
|
@ -259,6 +259,9 @@ CREATE TABLE `t_pay_order` (
|
|||
`if_code` VARCHAR(20) COMMENT '支付接口代码',
|
||||
`way_code` VARCHAR(20) NOT NULL COMMENT '支付方式代码',
|
||||
`amount` BIGINT(20) NOT NULL COMMENT '支付金额,单位分',
|
||||
`mch_fee_rate` decimal(20,6) NOT NULL COMMENT '商户手续费费率快照',
|
||||
`mch_fee_amount` BIGINT(20) NOT NULL COMMENT '商户手续费,单位分',
|
||||
`mch_income_amount` BIGINT(20) NOT NULL COMMENT '商户入账金额(支付金额-手续费),单位分',
|
||||
`currency` VARCHAR(3) NOT NULL DEFAULT 'cny' COMMENT '三位货币代码,人民币:cny',
|
||||
`state` TINYINT(6) NOT NULL DEFAULT '0' COMMENT '支付状态: 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭',
|
||||
`notify_state` TINYINT(6) NOT NULL DEFAULT '0' COMMENT '向下游回调状态, 0-未发送, 1-已发送',
|
||||
|
|
@ -271,8 +274,9 @@ CREATE TABLE `t_pay_order` (
|
|||
`refund_state` TINYINT(6) NOT NULL DEFAULT '0' COMMENT '退款状态: 0-未发生实际退款, 1-部分退款, 2-全额退款',
|
||||
`refund_times` INT NOT NULL DEFAULT 0 COMMENT '退款次数',
|
||||
`refund_amount` BIGINT(20) NOT NULL DEFAULT 0 COMMENT '退款总金额,单位分',
|
||||
`division_flag` TINYINT(6) DEFAULT 0 COMMENT '订单分账标志:0-否 1-是',
|
||||
`division_time` DATETIME COMMENT '预计分账发起时间',
|
||||
`division_mode` TINYINT(6) DEFAULT 0 COMMENT '订单分账模式:0-该笔订单不允许分账, 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额)',
|
||||
`division_state` TINYINT(6) DEFAULT 0 COMMENT '订单分账状态:0-未发生分账, 1-等待分账任务处理, 2-分账处理中, 3-分账任务已结束(不体现状态)',
|
||||
`division_last_time` DATETIME COMMENT '最新分账时间',
|
||||
`err_code` VARCHAR(128) DEFAULT NULL COMMENT '渠道支付错误码',
|
||||
`err_msg` VARCHAR(256) DEFAULT NULL COMMENT '渠道支付错误描述',
|
||||
`ext_param` VARCHAR(128) DEFAULT NULL COMMENT '商户扩展参数',
|
||||
|
|
@ -400,6 +404,65 @@ CREATE TABLE `t_transfer_order` (
|
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='转账订单表';
|
||||
|
||||
|
||||
-- 商户分账接收者账号绑定关系表
|
||||
DROP TABLE IF EXISTS `t_mch_division_receiver`;
|
||||
CREATE TABLE `t_mch_division_receiver` (
|
||||
`receiver_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '分账接收者ID',
|
||||
`receiver_group_id` BIGINT(20) NOT NULL COMMENT '多渠道组合ID(便于商户接口使用)',
|
||||
`receiver_name` VARCHAR(64) NOT NULL COMMENT '接收者账号别名',
|
||||
`mch_no` VARCHAR(64) NOT NULL COMMENT '商户号',
|
||||
`isv_no` VARCHAR(64) COMMENT '服务商号',
|
||||
`app_id` VARCHAR(64) NOT NULL COMMENT '应用ID',
|
||||
`if_code` VARCHAR(20) NOT NULL COMMENT '支付接口代码',
|
||||
`acc_type` TINYINT(6) NOT NULL COMMENT '分账接收账号类型: 0-个人(对私) 1-商户(对公)',
|
||||
`acc_no` VARCHAR(50) NOT NULL COMMENT '分账接收账号',
|
||||
`acc_name` VARCHAR(30) NOT NULL DEFAULT '' COMMENT '分账接收账号名称',
|
||||
`relation_type` VARCHAR(30) NOT NULL COMMENT '分账关系类型(参考微信), 如: SERVICE_PROVIDER 服务商等',
|
||||
`relation_type_name` VARCHAR(30) NOT NULL COMMENT '当选择自定义时,需要录入该字段。 否则为对应的名称',
|
||||
`division_profit` DECIMAL(20,6) COMMENT '分账比例',
|
||||
`state` TINYINT(6) NOT NULL COMMENT '分账状态(本系统状态,并不调用上游关联关系): 1-正常分账, 0-暂停分账',
|
||||
`channel_bind_state` TINYINT(6) NOT NULL COMMENT '上游绑定状态: 1-绑定成功, 2-绑定异常',
|
||||
`channel_bind_result` TEXT COMMENT '上游绑定返回信息,一般用作查询绑定异常时的记录',
|
||||
`channel_ext_info` TEXT COMMENT '渠道特殊信息',
|
||||
`bind_success_time` DATETIME DEFAULT NULL COMMENT '绑定成功时间',
|
||||
`created_at` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新时间',
|
||||
PRIMARY KEY (`receiver_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COMMENT='商户分账接收者账号绑定关系表';
|
||||
|
||||
-- 分账记录表
|
||||
DROP TABLE IF EXISTS `t_pay_order_division_record`;
|
||||
CREATE TABLE `t_pay_order_division_record` (
|
||||
`record_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分账记录ID',
|
||||
`mch_no` VARCHAR(64) NOT NULL COMMENT '商户号',
|
||||
`isv_no` VARCHAR(64) COMMENT '服务商号',
|
||||
`app_id` VARCHAR(64) NOT NULL COMMENT '应用ID',
|
||||
`mch_name` VARCHAR(30) NOT NULL COMMENT '商户名称',
|
||||
`mch_type` TINYINT(6) NOT NULL COMMENT '类型: 1-普通商户, 2-特约商户(服务商模式)',
|
||||
`if_code` VARCHAR(20) NOT NULL COMMENT '支付接口代码',
|
||||
`pay_order_id` VARCHAR(30) NOT NULL COMMENT '系统支付订单号',
|
||||
`pay_order_channel_order_no` VARCHAR(64) COMMENT '支付订单渠道支付订单号',
|
||||
`pay_order_amount` BIGINT(20) NOT NULL COMMENT '订单金额,单位分',
|
||||
`pay_order_division_amount` BIGINT(20) NOT NULL COMMENT '订单实际分账金额, 单位:分(订单金额 - 商户手续费 - 已退款金额)',
|
||||
`batch_order_id` VARCHAR(30) NOT NULL COMMENT '系统分账批次号',
|
||||
`channel_batch_order_id` VARCHAR(64) COMMENT '上游分账批次号',
|
||||
`state` TINYINT(6) NOT NULL COMMENT '状态: 0-待分账 1-分账成功, 2-分账失败',
|
||||
`channel_resp_result` TEXT COMMENT '上游返回数据包',
|
||||
`receiver_id` BIGINT(20) NOT NULL COMMENT '账号快照》 分账接收者ID',
|
||||
`receiver_group_id` BIGINT(20) NOT NULL COMMENT '账号快照》 多渠道组合ID(便于商户存储)',
|
||||
`acc_type` TINYINT(6) NOT NULL COMMENT '账号快照》 分账接收账号类型: 0-个人 1-商户',
|
||||
`acc_no` VARCHAR(50) NOT NULL COMMENT '账号快照》 分账接收账号',
|
||||
`acc_name` VARCHAR(30) NOT NULL DEFAULT '' COMMENT '账号快照》 分账接收账号名称',
|
||||
`relation_type` VARCHAR(30) NOT NULL COMMENT '账号快照》 分账关系类型(参考微信), 如: SERVICE_PROVIDER 服务商等',
|
||||
`relation_type_name` VARCHAR(30) NOT NULL COMMENT '账号快照》 当选择自定义时,需要录入该字段。 否则为对应的名称',
|
||||
`division_profit` DECIMAL(20,6) NOT NULL COMMENT '账号快照》 配置的实际分账比例',
|
||||
`cal_division_amount` BIGINT(20) NOT NULL COMMENT '计算该接收方的分账金额,单位分',
|
||||
`created_at` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
|
||||
`updated_at` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新时间',
|
||||
PRIMARY KEY (`record_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COMMENT='分账记录表';
|
||||
|
||||
|
||||
|
||||
##### ↑↑↑↑↑↑↑↑↑↑ 表结构DDL ↑↑↑↑↑↑↑↑↑↑ #####
|
||||
|
||||
|
|
|
|||
|
|
@ -101,5 +101,25 @@ insert into t_sys_entitlement values('ENT_MCH_TRANSFER_DO', '按钮:发起转
|
|||
insert into t_sys_entitlement values('ENT_PAY_ORDER_SEARCH_PAY_WAY', '筛选项:支付方式', 'no-icon', '', '', 'PB', 0, 1, 'ENT_PAY_ORDER', '0', 'MGR', now(), now());
|
||||
insert into t_sys_entitlement values('ENT_PAY_ORDER_SEARCH_PAY_WAY', '筛选项:支付方式', 'no-icon', '', '', 'PB', 0, 1, 'ENT_PAY_ORDER', '0', 'MCH', now(), now());
|
||||
|
||||
|
||||
-- 插入表结构,并插入默认数据(默认费率 0)
|
||||
alter table `t_pay_order` add column `mch_fee_rate` decimal(20,6) NOT NULL COMMENT '商户手续费费率快照' after `amount`;
|
||||
alter table `t_pay_order` add column `mch_fee_amount` BIGINT(20) NOT NULL COMMENT '商户手续费,单位分' after `mch_fee_rate`;
|
||||
alter table `t_pay_order` add column `mch_income_amount` BIGINT(20) NOT NULL COMMENT '商户入账金额(支付金额-手续费),单位分' after `mch_fee_amount`;
|
||||
update `t_pay_order` set mch_fee_rate = 0;
|
||||
update `t_pay_order` set mch_fee_amount = 0;
|
||||
update `t_pay_order` set mch_income_amount = amount - mch_fee_amount;
|
||||
|
||||
alter table `t_pay_order` drop column `division_flag`;
|
||||
alter table `t_pay_order` drop column `division_time`;
|
||||
|
||||
alter table `t_pay_order` add column `division_mode` TINYINT(6) DEFAULT 0 COMMENT '订单分账模式:0-该笔订单不允许分账, 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额)' after `refund_amount`;
|
||||
alter table `t_pay_order` add column `division_state` TINYINT(6) DEFAULT 0 COMMENT '订单分账状态:0-未发生分账, 1-等待分账任务处理, 2-分账处理中, 3-分账任务已结束(不体现状态)' after `division_mode`;
|
||||
alter table `t_pay_order` add column `division_last_time` DATETIME COMMENT '最新分账时间' after `division_state`;
|
||||
|
||||
-- TODO 分账的两张表
|
||||
|
||||
|
||||
|
||||
## -- ++++ ++++
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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.components.mq.model;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.jeequan.jeepay.components.mq.constant.MQSendTypeEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* 定义MQ消息格式
|
||||
* 业务场景: [ 支付订单的订单分账消息 ]
|
||||
*
|
||||
* @author terrfly
|
||||
* @site https://www.jeequan.com
|
||||
* @date 2021/8/22 11:25
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayOrderDivisionMQ extends AbstractMQ {
|
||||
|
||||
/** 【!重要配置项!】 定义MQ名称 **/
|
||||
public static final String MQ_NAME = "QUEUE_PAY_ORDER_DIVISION";
|
||||
|
||||
/** 内置msg 消息体定义 **/
|
||||
private MsgPayload payload;
|
||||
|
||||
/** 【!重要配置项!】 定义Msg消息载体 **/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class MsgPayload {
|
||||
|
||||
/** 支付订单号 **/
|
||||
private String payOrderId;
|
||||
|
||||
/**
|
||||
* 分账接受者列表, 字段值为空表示系统默认配置项。
|
||||
* 格式:{receiverId: '1001', receiverGroupId: '1001', divisionProfit: '0.1'}
|
||||
* divisionProfit: 空表示使用系统默认比例。
|
||||
* **/
|
||||
private List<CustomerDivisionReceiver> receiverList;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMQName() {
|
||||
return MQ_NAME;
|
||||
}
|
||||
|
||||
/** 【!重要配置项!】 **/
|
||||
@Override
|
||||
public MQSendTypeEnum getMQType(){
|
||||
return MQSendTypeEnum.QUEUE; // QUEUE - 点对点 、 BROADCAST - 广播模式
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMessage() {
|
||||
return JSONObject.toJSONString(payload);
|
||||
}
|
||||
|
||||
/** 【!重要配置项!】 构造MQModel , 一般用于发送MQ时 **/
|
||||
public static PayOrderDivisionMQ build(String payOrderId, List<CustomerDivisionReceiver> receiverList){
|
||||
return new PayOrderDivisionMQ(new MsgPayload(payOrderId, receiverList));
|
||||
}
|
||||
|
||||
/** 解析MQ消息, 一般用于接收MQ消息时 **/
|
||||
public static MsgPayload parse(String msg){
|
||||
return JSON.parseObject(msg, MsgPayload.class);
|
||||
}
|
||||
|
||||
/** 定义 IMQReceiver 接口: 项目实现该接口则可接收到对应的业务消息 **/
|
||||
public interface IMQReceiver{
|
||||
void receive(MsgPayload payload);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** 自定义定义接收账号定义信息 **/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public static class CustomerDivisionReceiver {
|
||||
|
||||
/**
|
||||
* 分账接收者ID (与receiverGroupId 二选一)
|
||||
*/
|
||||
private Long receiverId;
|
||||
|
||||
/**
|
||||
* 多渠道组合ID(便于商户接口使用) (与 receiverId 二选一)
|
||||
*/
|
||||
private Long receiverGroupId;
|
||||
|
||||
/**
|
||||
* 分账比例 (可以为空, 为空表示使用系统默认值)
|
||||
*/
|
||||
private BigDecimal divisionProfit;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.components.mq.vender.activemq.receive;
|
||||
|
||||
import com.jeequan.jeepay.components.mq.constant.MQVenderCS;
|
||||
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
|
||||
import com.jeequan.jeepay.components.mq.vender.IMQMsgReceiver;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.jms.annotation.JmsListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* activeMQ 消息接收器:仅在vender=activeMQ时 && 项目实现IMQReceiver接口时 进行实例化
|
||||
* 业务: 支付订单分账通知
|
||||
*
|
||||
* @author terrfly
|
||||
* @site https://www.jeequan.com
|
||||
* @date 2021/8/22 16:43
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(name = MQVenderCS.YML_VENDER_KEY, havingValue = MQVenderCS.ACTIVE_MQ)
|
||||
@ConditionalOnBean(PayOrderDivisionMQ.IMQReceiver.class)
|
||||
public class PayOrderDivisionActiveMQReceiver implements IMQMsgReceiver {
|
||||
|
||||
@Autowired
|
||||
private PayOrderDivisionMQ.IMQReceiver mqReceiver;
|
||||
|
||||
/** 接收 【 queue 】 类型的消息 **/
|
||||
@Override
|
||||
@JmsListener(destination = PayOrderDivisionMQ.MQ_NAME)
|
||||
public void receiveMsg(String msg){
|
||||
mqReceiver.receive(PayOrderDivisionMQ.parse(msg));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.components.mq.vender.rabbitmq.receive;
|
||||
|
||||
import com.jeequan.jeepay.components.mq.constant.MQVenderCS;
|
||||
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
|
||||
import com.jeequan.jeepay.components.mq.vender.IMQMsgReceiver;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* rabbitMQ消息接收器:仅在vender=rabbitMQ时 && 项目实现IMQReceiver接口时 进行实例化
|
||||
* 业务: 支付订单分账通知
|
||||
*
|
||||
* @author terrfly
|
||||
* @site https://www.jeequan.com
|
||||
* @date 2021/8/22 16:43
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(name = MQVenderCS.YML_VENDER_KEY, havingValue = MQVenderCS.RABBIT_MQ)
|
||||
@ConditionalOnBean(PayOrderDivisionMQ.IMQReceiver.class)
|
||||
public class PayOrderDivisionRabbitMQReceiver implements IMQMsgReceiver {
|
||||
|
||||
@Autowired
|
||||
private PayOrderDivisionMQ.IMQReceiver mqReceiver;
|
||||
|
||||
/** 接收 【 queue 】 类型的消息 **/
|
||||
@Override
|
||||
@RabbitListener(queues = PayOrderDivisionMQ.MQ_NAME)
|
||||
public void receiveMsg(String msg){
|
||||
mqReceiver.receive(PayOrderDivisionMQ.parse(msg));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.components.mq.vender.rocketmq.receive;
|
||||
|
||||
import com.jeequan.jeepay.components.mq.constant.MQVenderCS;
|
||||
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
|
||||
import com.jeequan.jeepay.components.mq.vender.IMQMsgReceiver;
|
||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* rocketMQ消息接收器:仅在vender=rocketMQ时 && 项目实现IMQReceiver接口时 进行实例化
|
||||
* 业务: 支付订单分账通知
|
||||
*
|
||||
* @author terrfly
|
||||
* @site https://www.jeequan.com
|
||||
* @date 2021/8/22 16:43
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(name = MQVenderCS.YML_VENDER_KEY, havingValue = MQVenderCS.ROCKET_MQ)
|
||||
@ConditionalOnBean(PayOrderDivisionMQ.IMQReceiver.class)
|
||||
@RocketMQMessageListener(topic = PayOrderDivisionMQ.MQ_NAME, consumerGroup = PayOrderDivisionMQ.MQ_NAME)
|
||||
public class PayOrderDivisionRocketMQReceiver implements IMQMsgReceiver, RocketMQListener<String> {
|
||||
|
||||
@Autowired
|
||||
private PayOrderDivisionMQ.IMQReceiver mqReceiver;
|
||||
|
||||
/** 接收 【 queue 】 类型的消息 **/
|
||||
@Override
|
||||
public void receiveMsg(String msg){
|
||||
mqReceiver.receive(PayOrderDivisionMQ.parse(msg));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
this.receiveMsg(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
package com.jeequan.jeepay.core.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 商户分账接收者账号绑定关系表
|
||||
* </p>
|
||||
*
|
||||
* @author [mybatis plus generator]
|
||||
* @since 2021-08-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("t_mch_division_receiver")
|
||||
public class MchDivisionReceiver implements Serializable {
|
||||
|
||||
private static final long serialVersionUID=1L;
|
||||
|
||||
public static final byte STATE_WAIT = 0; // 待分账
|
||||
public static final byte STATE_SUCCESS = 1; // 分账成功
|
||||
public static final byte STATE_FAIL = 2; // 分账失败
|
||||
|
||||
|
||||
//gw
|
||||
public static final LambdaQueryWrapper<MchDivisionReceiver> gw(){
|
||||
return new LambdaQueryWrapper<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分账接收者ID
|
||||
*/
|
||||
@TableId(value = "receiver_id", type = IdType.AUTO)
|
||||
private Long receiverId;
|
||||
|
||||
/**
|
||||
* 多渠道组合ID(便于商户接口使用)
|
||||
*/
|
||||
private Long receiverGroupId;
|
||||
|
||||
/**
|
||||
* 接收者账号别名
|
||||
*/
|
||||
private String receiverName;
|
||||
|
||||
/**
|
||||
* 商户号
|
||||
*/
|
||||
private String mchNo;
|
||||
|
||||
/**
|
||||
* 服务商号
|
||||
*/
|
||||
private String isvNo;
|
||||
|
||||
/**
|
||||
* 应用ID
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 支付接口代码
|
||||
*/
|
||||
private String ifCode;
|
||||
|
||||
/**
|
||||
* 分账接收账号类型: 0-个人(对私) 1-商户(对公)
|
||||
*/
|
||||
private Byte accType;
|
||||
|
||||
/**
|
||||
* 分账接收账号
|
||||
*/
|
||||
private String accNo;
|
||||
|
||||
/**
|
||||
* 分账接收账号名称
|
||||
*/
|
||||
private String accName;
|
||||
|
||||
/**
|
||||
* 分账关系类型(参考微信), 如: SERVICE_PROVIDER 服务商等
|
||||
*/
|
||||
private String relationType;
|
||||
|
||||
/**
|
||||
* 当选择自定义时,需要录入该字段。 否则为对应的名称
|
||||
*/
|
||||
private String relationTypeName;
|
||||
|
||||
/**
|
||||
* 分账比例
|
||||
*/
|
||||
private BigDecimal divisionProfit;
|
||||
|
||||
/**
|
||||
* 分账状态(本系统状态,并不调用上游关联关系): 1-正常分账, 0-暂停分账
|
||||
*/
|
||||
private Byte state;
|
||||
|
||||
/**
|
||||
* 上游绑定状态: 1-绑定成功, 2-绑定异常
|
||||
*/
|
||||
private Byte channelBindState;
|
||||
|
||||
/**
|
||||
* 上游绑定返回信息,一般用作查询绑定异常时的记录
|
||||
*/
|
||||
private String channelBindResult;
|
||||
|
||||
/**
|
||||
* 渠道特殊信息
|
||||
*/
|
||||
private String channelExtInfo;
|
||||
|
||||
/**
|
||||
* 绑定成功时间
|
||||
*/
|
||||
private Date bindSuccessTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updatedAt;
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import lombok.EqualsAndHashCode;
|
|||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
|
|
@ -59,6 +60,17 @@ public class PayOrder extends BaseModel implements Serializable {
|
|||
public static final byte REFUND_STATE_SUB = 1; //部分退款
|
||||
public static final byte REFUND_STATE_ALL = 2; //全额退款
|
||||
|
||||
|
||||
public static final byte DIVISION_MODE_FORBID = 0; //该笔订单不允许分账
|
||||
public static final byte DIVISION_MODE_AUTO = 1; //支付成功按配置自动完成分账
|
||||
public static final byte DIVISION_MODE_MANUAL = 2; //商户手动分账(解冻商户金额)
|
||||
|
||||
public static final byte DIVISION_STATE_UNHAPPEN = 0; //未发生分账
|
||||
public static final byte DIVISION_STATE_WAIT_TASK = 1; //等待分账任务处理
|
||||
public static final byte DIVISION_STATE_ING = 2; //分账处理中
|
||||
public static final byte DIVISION_STATE_FINISH = 3; //分账任务已结束(不体现状态)
|
||||
|
||||
|
||||
/**
|
||||
* 支付订单号
|
||||
*/
|
||||
|
|
@ -110,6 +122,21 @@ public class PayOrder extends BaseModel implements Serializable {
|
|||
*/
|
||||
private Long amount;
|
||||
|
||||
/**
|
||||
* 商户手续费费率快照
|
||||
*/
|
||||
private BigDecimal mchFeeRate;
|
||||
|
||||
/**
|
||||
* 商户手续费,单位分
|
||||
*/
|
||||
private Long mchFeeAmount;
|
||||
|
||||
/**
|
||||
* 商户入账金额(支付金额-手续费),单位分
|
||||
*/
|
||||
private Long mchIncomeAmount;
|
||||
|
||||
/**
|
||||
* 三位货币代码,人民币:cny
|
||||
*/
|
||||
|
|
@ -171,14 +198,19 @@ public class PayOrder extends BaseModel implements Serializable {
|
|||
private Long refundAmount;
|
||||
|
||||
/**
|
||||
* 订单分账标志:0-否 1-是
|
||||
* 订单分账模式:0-该笔订单不允许分账, 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额)
|
||||
*/
|
||||
private Byte divisionFlag;
|
||||
private Byte divisionMode;
|
||||
|
||||
/**
|
||||
* 预计分账发起时间
|
||||
* 订单分账状态:0-未发生分账, 1-等待分账任务处理, 2-分账成功, 3-分账失败
|
||||
*/
|
||||
private Date divisionTime;
|
||||
private Byte divisionState;
|
||||
|
||||
/**
|
||||
* 最新分账时间
|
||||
*/
|
||||
private Date divisionLastTime;
|
||||
|
||||
/**
|
||||
* 渠道支付错误码
|
||||
|
|
@ -225,5 +257,4 @@ public class PayOrder extends BaseModel implements Serializable {
|
|||
*/
|
||||
private Date updatedAt;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
package com.jeequan.jeepay.core.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 分账记录表
|
||||
* </p>
|
||||
*
|
||||
* @author [mybatis plus generator]
|
||||
* @since 2021-08-19
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("t_pay_order_division_record")
|
||||
public class PayOrderDivisionRecord implements Serializable {
|
||||
|
||||
private static final long serialVersionUID=1L;
|
||||
|
||||
/**
|
||||
* 分账记录ID
|
||||
*/
|
||||
@TableId(value = "record_id", type = IdType.AUTO)
|
||||
private Long recordId;
|
||||
|
||||
/**
|
||||
* 商户号
|
||||
*/
|
||||
private String mchNo;
|
||||
|
||||
/**
|
||||
* 服务商号
|
||||
*/
|
||||
private String isvNo;
|
||||
|
||||
/**
|
||||
* 应用ID
|
||||
*/
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 商户名称
|
||||
*/
|
||||
private String mchName;
|
||||
|
||||
/**
|
||||
* 类型: 1-普通商户, 2-特约商户(服务商模式)
|
||||
*/
|
||||
private Byte mchType;
|
||||
|
||||
/**
|
||||
* 支付接口代码
|
||||
*/
|
||||
private String ifCode;
|
||||
|
||||
/**
|
||||
* 系统支付订单号
|
||||
*/
|
||||
private String payOrderId;
|
||||
|
||||
/**
|
||||
* 支付订单渠道支付订单号
|
||||
*/
|
||||
private String payOrderChannelOrderNo;
|
||||
|
||||
/**
|
||||
* 订单金额,单位分
|
||||
*/
|
||||
private Long payOrderAmount;
|
||||
|
||||
/**
|
||||
* 订单实际分账金额, 单位:分(订单金额 - 商户手续费 - 已退款金额)
|
||||
*/
|
||||
private Long payOrderDivisionAmount;
|
||||
|
||||
/**
|
||||
* 系统分账批次号
|
||||
*/
|
||||
private String batchOrderId;
|
||||
|
||||
/**
|
||||
* 上游分账批次号
|
||||
*/
|
||||
private String channelBatchOrderId;
|
||||
|
||||
/**
|
||||
* 状态: 0-待分账 1-分账成功, 2-分账失败
|
||||
*/
|
||||
private Byte state;
|
||||
|
||||
/**
|
||||
* 上游返回数据包
|
||||
*/
|
||||
private String channelRespResult;
|
||||
|
||||
/**
|
||||
* 账号快照》 分账接收者ID
|
||||
*/
|
||||
private Long receiverId;
|
||||
|
||||
/**
|
||||
* 账号快照》 多渠道组合ID(便于商户存储)
|
||||
*/
|
||||
private Long receiverGroupId;
|
||||
|
||||
/**
|
||||
* 账号快照》 分账接收账号类型: 0-个人 1-商户
|
||||
*/
|
||||
private Byte accType;
|
||||
|
||||
/**
|
||||
* 账号快照》 分账接收账号
|
||||
*/
|
||||
private String accNo;
|
||||
|
||||
/**
|
||||
* 账号快照》 分账接收账号名称
|
||||
*/
|
||||
private String accName;
|
||||
|
||||
/**
|
||||
* 账号快照》 分账关系类型(参考微信), 如: SERVICE_PROVIDER 服务商等
|
||||
*/
|
||||
private String relationType;
|
||||
|
||||
/**
|
||||
* 账号快照》 当选择自定义时,需要录入该字段。 否则为对应的名称
|
||||
*/
|
||||
private String relationTypeName;
|
||||
|
||||
/**
|
||||
* 账号快照》 配置的实际分账比例
|
||||
*/
|
||||
private BigDecimal divisionProfit;
|
||||
|
||||
/**
|
||||
* 计算该接收方的分账金额,单位分
|
||||
*/
|
||||
private Long calDivisionAmount;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createdAt;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updatedAt;
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -120,4 +120,19 @@ public class AmountUtil {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 计算百分比类型的各种费用值 (订单金额 * 真实费率 结果四舍五入并保留0位小数 )
|
||||
*
|
||||
* @author terrfly
|
||||
* @site https://www.jeepay.vip
|
||||
* @date 2021/8/20 14:53
|
||||
* @param amount 订单金额 (保持与数据库的格式一致 ,单位:分)
|
||||
* @param rate 费率 (保持与数据库的格式一致 ,真实费率值,如费率为0.55%,则传入 0.0055)
|
||||
*/
|
||||
public static Long calPercentageFee(Long amount, BigDecimal rate){
|
||||
//费率乘以订单金额 结果四舍五入并保留0位小数
|
||||
return new BigDecimal(amount).multiply(rate).setScale(0, BigDecimal.ROUND_HALF_UP).longValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,10 +35,13 @@ public class SeqKit {
|
|||
private static final AtomicLong REFUND_ORDER_SEQ = new AtomicLong(0L);
|
||||
private static final AtomicLong MHO_ORDER_SEQ = new AtomicLong(0L);
|
||||
private static final AtomicLong TRANSFER_ID_SEQ = new AtomicLong(0L);
|
||||
private static final AtomicLong DIVISION_BATCH_ID_SEQ = new AtomicLong(0L);
|
||||
|
||||
private static final String PAY_ORDER_SEQ_PREFIX = "P";
|
||||
private static final String REFUND_ORDER_SEQ_PREFIX = "R";
|
||||
private static final String MHO_ORDER_SEQ_PREFIX = "M";
|
||||
private static final String TRANSFER_ID_SEQ_PREFIX = "T";
|
||||
private static final String DIVISION_BATCH_ID_SEQ_PREFIX = "D";
|
||||
|
||||
/** 生成支付订单号 **/
|
||||
public static String genPayOrderId() {
|
||||
|
|
@ -69,4 +72,11 @@ public class SeqKit {
|
|||
(int) TRANSFER_ID_SEQ.getAndIncrement() % 10000);
|
||||
}
|
||||
|
||||
/** 模拟生成分账批次号 **/
|
||||
public static String genDivisionBatchId() {
|
||||
return String.format("%s%s%04d", DIVISION_BATCH_ID_SEQ_PREFIX,
|
||||
DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN),
|
||||
(int) DIVISION_BATCH_ID_SEQ.getAndIncrement() % 10000);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public interface IChannelNoticeService {
|
|||
DO_NOTIFY //异步回调
|
||||
}
|
||||
|
||||
/* 获取到接口code **/
|
||||
/** 获取到接口code **/
|
||||
String getIfCode();
|
||||
|
||||
/** 解析参数: 订单号 和 请求参数
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.jeequan.jeepay.pay.model.MchAppConfigContext;
|
|||
*/
|
||||
public interface IChannelUserService {
|
||||
|
||||
/* 获取到接口code **/
|
||||
/** 获取到接口code **/
|
||||
String getIfCode();
|
||||
|
||||
/** 获取重定向地址 **/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.channel;
|
||||
|
||||
import com.jeequan.jeepay.core.entity.MchDivisionReceiver;
|
||||
import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord;
|
||||
import com.jeequan.jeepay.core.entity.TransferOrder;
|
||||
import com.jeequan.jeepay.pay.model.MchAppConfigContext;
|
||||
import com.jeequan.jeepay.pay.rqrs.msg.ChannelRetMsg;
|
||||
import com.jeequan.jeepay.pay.rqrs.transfer.TransferOrderRQ;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分账接口
|
||||
*
|
||||
* @author terrfly
|
||||
* @site https://www.jeepay.vip
|
||||
* @date 2021/8/22 08:59
|
||||
*/
|
||||
public interface IDivisionService {
|
||||
|
||||
/** 获取到接口code **/
|
||||
String getIfCode();
|
||||
|
||||
/** 是否支持该分账 */
|
||||
boolean isSupport();
|
||||
|
||||
/** 绑定关系 **/
|
||||
boolean bind(MchDivisionReceiver mchDivisionReceiver, MchAppConfigContext mchAppConfigContext);
|
||||
|
||||
/** 单次分账 (无需调用完结接口,或自动解冻商户资金) **/
|
||||
boolean singleDivision(List<PayOrderDivisionRecord> recordList, MchAppConfigContext mchAppConfigContext);
|
||||
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ import com.jeequan.jeepay.pay.model.MchAppConfigContext;
|
|||
*/
|
||||
public interface IPaymentService {
|
||||
|
||||
/* 获取到接口code **/
|
||||
/** 获取到接口code **/
|
||||
String getIfCode();
|
||||
|
||||
/** 是否支持该支付方式 */
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import com.jeequan.jeepay.pay.rqrs.refund.RefundOrderRQ;
|
|||
*/
|
||||
public interface IRefundService {
|
||||
|
||||
/* 获取到接口code **/
|
||||
/** 获取到接口code **/
|
||||
String getIfCode();
|
||||
|
||||
/** 前置检查如参数等信息是否符合要求, 返回错误信息或直接抛出异常即可 */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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.channel.wxpay;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingReceiverRequest;
|
||||
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingReceiverResult;
|
||||
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingRequest;
|
||||
import com.github.binarywang.wxpay.bean.profitsharing.ProfitSharingResult;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.jeequan.jeepay.core.constants.CS;
|
||||
import com.jeequan.jeepay.core.entity.MchDivisionReceiver;
|
||||
import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord;
|
||||
import com.jeequan.jeepay.pay.channel.IDivisionService;
|
||||
import com.jeequan.jeepay.pay.model.MchAppConfigContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分账接口: 微信官方
|
||||
*
|
||||
* @author terrfly
|
||||
* @site https://www.jeepay.vip
|
||||
* @date 2021/8/22 09:05
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WxpayDivisionService implements IDivisionService {
|
||||
|
||||
@Override
|
||||
public String getIfCode() {
|
||||
return CS.IF_CODE.WXPAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupport() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean bind(MchDivisionReceiver mchDivisionReceiver, MchAppConfigContext mchAppConfigContext) {
|
||||
|
||||
try {
|
||||
|
||||
ProfitSharingReceiverRequest request = new ProfitSharingReceiverRequest();
|
||||
|
||||
JSONObject receiverJSON = new JSONObject();
|
||||
|
||||
// 0-个人, 1-商户 (目前仅支持服务商appI获取个人openId, 即: PERSONAL_OPENID, 不支持 PERSONAL_SUB_OPENID )
|
||||
receiverJSON.put("type", mchDivisionReceiver.getAccType() == 0 ? "PERSONAL_OPENID" : "MERCHANT_ID");
|
||||
receiverJSON.put("account", mchDivisionReceiver.getAccNo());
|
||||
receiverJSON.put("name", mchDivisionReceiver.getAccName());
|
||||
receiverJSON.put("relation_type", mchDivisionReceiver.getRelationType());
|
||||
receiverJSON.put("custom_relation", mchDivisionReceiver.getRelationTypeName());
|
||||
request.setReceiver(receiverJSON.toJSONString());
|
||||
|
||||
ProfitSharingReceiverResult profitSharingReceiverResult =
|
||||
mchAppConfigContext.getWxServiceWrapper().getWxPayService().getProfitSharingService().addReceiver(request);
|
||||
|
||||
} catch (WxPayException wxPayException) {
|
||||
wxPayException.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean singleDivision(List<PayOrderDivisionRecord> recordList, MchAppConfigContext mchAppConfigContext) {
|
||||
|
||||
try {
|
||||
|
||||
if(true || recordList.isEmpty()){
|
||||
return true;
|
||||
}
|
||||
|
||||
ProfitSharingRequest request = new ProfitSharingRequest();
|
||||
request.setTransactionId(recordList.get(0).getPayOrderChannelOrderNo());
|
||||
request.setOutOrderNo(recordList.get(0).getBatchOrderId());
|
||||
|
||||
JSONArray receiverJSONArray = new JSONArray();
|
||||
|
||||
for (PayOrderDivisionRecord record : recordList) {
|
||||
JSONObject receiverJSON = new JSONObject();
|
||||
// 0-个人, 1-商户 (目前仅支持服务商appI获取个人openId, 即: PERSONAL_OPENID, 不支持 PERSONAL_SUB_OPENID )
|
||||
receiverJSON.put("type", record.getAccType() == 0 ? "PERSONAL_OPENID" : "MERCHANT_ID");
|
||||
receiverJSON.put("account", record.getAccNo());
|
||||
receiverJSON.put("amount", record.getCalDivisionAmount());
|
||||
receiverJSON.put("description", record.getPayOrderId() + "分账");
|
||||
receiverJSONArray.add(receiverJSON);
|
||||
}
|
||||
|
||||
request.setReceivers(receiverJSONArray.toJSONString());
|
||||
|
||||
ProfitSharingResult profitSharingResult = mchAppConfigContext.getWxServiceWrapper().getWxPayService().getProfitSharingService().profitSharing(request);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@
|
|||
package com.jeequan.jeepay.pay.ctrl.payorder;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
|
||||
import com.jeequan.jeepay.components.mq.model.PayOrderReissueMQ;
|
||||
import com.jeequan.jeepay.components.mq.vender.IMQSender;
|
||||
import com.jeequan.jeepay.core.constants.CS;
|
||||
|
|
@ -25,9 +27,7 @@ import com.jeequan.jeepay.core.entity.MchPayPassage;
|
|||
import com.jeequan.jeepay.core.entity.PayOrder;
|
||||
import com.jeequan.jeepay.core.exception.BizException;
|
||||
import com.jeequan.jeepay.core.model.ApiRes;
|
||||
import com.jeequan.jeepay.core.utils.SeqKit;
|
||||
import com.jeequan.jeepay.core.utils.SpringBeansUtil;
|
||||
import com.jeequan.jeepay.core.utils.StringKit;
|
||||
import com.jeequan.jeepay.core.utils.*;
|
||||
import com.jeequan.jeepay.pay.channel.IPaymentService;
|
||||
import com.jeequan.jeepay.pay.ctrl.ApiController;
|
||||
import com.jeequan.jeepay.pay.exception.ChannelException;
|
||||
|
|
@ -40,13 +40,16 @@ import com.jeequan.jeepay.pay.rqrs.payorder.payway.QrCashierOrderRQ;
|
|||
import com.jeequan.jeepay.pay.rqrs.payorder.payway.QrCashierOrderRS;
|
||||
import com.jeequan.jeepay.pay.service.ConfigContextService;
|
||||
import com.jeequan.jeepay.pay.service.PayMchNotifyService;
|
||||
import com.jeequan.jeepay.pay.service.PayOrderProcessService;
|
||||
import com.jeequan.jeepay.service.impl.MchPayPassageService;
|
||||
import com.jeequan.jeepay.service.impl.PayOrderService;
|
||||
import com.jeequan.jeepay.service.impl.SysConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/*
|
||||
|
|
@ -62,7 +65,7 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
@Autowired private MchPayPassageService mchPayPassageService;
|
||||
@Autowired private PayOrderService payOrderService;
|
||||
@Autowired private ConfigContextService configContextService;
|
||||
@Autowired private PayMchNotifyService payMchNotifyService;
|
||||
@Autowired private PayOrderProcessService payOrderProcessService;
|
||||
@Autowired private SysConfigService sysConfigService;
|
||||
@Autowired private IMQSender mqSender;
|
||||
|
||||
|
|
@ -103,6 +106,7 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
bizRQ.setChannelExtra(payOrder.getChannelExtra());
|
||||
bizRQ.setChannelUser(payOrder.getChannelUser());
|
||||
bizRQ.setExtParam(payOrder.getExtParam());
|
||||
bizRQ.setDivisionMode(payOrder.getDivisionMode());
|
||||
}
|
||||
|
||||
String mchNo = bizRQ.getMchNo();
|
||||
|
|
@ -133,7 +137,7 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
if(isNewOrder && CS.PAY_WAY_CODE.QR_CASHIER.equals(wayCode)){
|
||||
|
||||
//生成订单
|
||||
payOrder = genPayOrder(bizRQ, mchInfo, mchApp, null);
|
||||
payOrder = genPayOrder(bizRQ, mchInfo, mchApp, null, null);
|
||||
String payOrderId = payOrder.getPayOrderId();
|
||||
//订单入库 订单状态: 生成状态 此时没有和任何上游渠道产生交互。
|
||||
payOrderService.save(payOrder);
|
||||
|
|
@ -152,13 +156,19 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
return packageApiResByPayOrder(bizRQ, qrCashierOrderRS, payOrder);
|
||||
}
|
||||
|
||||
// 根据支付方式, 查询出 该商户 可用的支付接口
|
||||
MchPayPassage mchPayPassage = mchPayPassageService.findMchPayPassage(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), wayCode);
|
||||
if(mchPayPassage == null){
|
||||
throw new BizException("商户应用不支持该支付方式");
|
||||
}
|
||||
|
||||
//获取支付接口
|
||||
IPaymentService paymentService = checkMchWayCodeAndGetService(mchAppConfigContext, wayCode);
|
||||
IPaymentService paymentService = checkMchWayCodeAndGetService(mchAppConfigContext, mchPayPassage);
|
||||
String ifCode = paymentService.getIfCode();
|
||||
|
||||
//生成订单
|
||||
if(isNewOrder){
|
||||
payOrder = genPayOrder(bizRQ, mchInfo, mchApp, ifCode);
|
||||
payOrder = genPayOrder(bizRQ, mchInfo, mchApp, ifCode, mchPayPassage);
|
||||
}else{
|
||||
payOrder.setIfCode(ifCode);
|
||||
}
|
||||
|
|
@ -203,7 +213,7 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
}
|
||||
}
|
||||
|
||||
private PayOrder genPayOrder(UnifiedOrderRQ rq, MchInfo mchInfo, MchApp mchApp, String ifCode){
|
||||
private PayOrder genPayOrder(UnifiedOrderRQ rq, MchInfo mchInfo, MchApp mchApp, String ifCode, MchPayPassage mchPayPassage){
|
||||
|
||||
PayOrder payOrder = new PayOrder();
|
||||
payOrder.setPayOrderId(SeqKit.genPayOrderId()); //生成订单ID
|
||||
|
|
@ -216,6 +226,16 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
payOrder.setIfCode(ifCode); //接口代码
|
||||
payOrder.setWayCode(rq.getWayCode()); //支付方式
|
||||
payOrder.setAmount(rq.getAmount()); //订单金额
|
||||
|
||||
if(mchPayPassage != null){
|
||||
payOrder.setMchFeeRate(mchPayPassage.getRate()); //商户手续费费率快照
|
||||
}else{
|
||||
payOrder.setMchFeeRate(BigDecimal.ZERO); //预下单模式, 按照0计算入库, 后续进行更新
|
||||
}
|
||||
|
||||
payOrder.setMchFeeAmount(AmountUtil.calPercentageFee(payOrder.getAmount(), payOrder.getMchFeeRate())); //商户手续费,单位分
|
||||
payOrder.setMchIncomeAmount(payOrder.getAmount() - payOrder.getMchFeeAmount()); //商户入账金额(支付金额-手续费),单位分
|
||||
|
||||
payOrder.setCurrency(rq.getCurrency()); //币种
|
||||
payOrder.setState(PayOrder.STATE_INIT); //订单状态, 默认订单生成状态
|
||||
payOrder.setClientIp(StringUtils.defaultIfEmpty(rq.getClientIp(), getClientIp())); //客户端IP
|
||||
|
|
@ -223,11 +243,13 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
payOrder.setBody(rq.getBody()); //商品描述信息
|
||||
// payOrder.setChannelExtra(rq.getChannelExtra()); //特殊渠道发起的附件额外参数, 是否应该删除该字段了?? 比如authCode不应该记录, 只是在传输阶段存在的吧? 之前的为了在payOrder对象需要传参。
|
||||
payOrder.setChannelUser(rq.getChannelUser()); //渠道用户标志
|
||||
payOrder.setDivisionFlag(CS.NO); //分账标志, 默认为: 0-否
|
||||
payOrder.setExtParam(rq.getExtParam()); //商户扩展参数
|
||||
payOrder.setNotifyUrl(rq.getNotifyUrl()); //异步通知地址
|
||||
payOrder.setReturnUrl(rq.getReturnUrl()); //页面跳转地址
|
||||
|
||||
// 分账模式
|
||||
payOrder.setDivisionMode(ObjectUtils.defaultIfNull(rq.getDivisionMode(), PayOrder.DIVISION_MODE_FORBID));
|
||||
|
||||
Date nowDate = new Date();
|
||||
|
||||
//订单过期时间 单位: 秒
|
||||
|
|
@ -246,13 +268,7 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
* 校验: 商户的支付方式是否可用
|
||||
* 返回: 支付接口
|
||||
* **/
|
||||
private IPaymentService checkMchWayCodeAndGetService(MchAppConfigContext mchAppConfigContext, String wayCode){
|
||||
|
||||
// 根据支付方式, 查询出 该商户 可用的支付接口
|
||||
MchPayPassage mchPayPassage = mchPayPassageService.findMchPayPassage(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), wayCode);
|
||||
if(mchPayPassage == null){
|
||||
throw new BizException("商户应用不支持该支付方式");
|
||||
}
|
||||
private IPaymentService checkMchWayCodeAndGetService(MchAppConfigContext mchAppConfigContext, MchPayPassage mchPayPassage){
|
||||
|
||||
// 接口代码
|
||||
String ifCode = mchPayPassage.getIfCode();
|
||||
|
|
@ -261,7 +277,7 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
throw new BizException("无此支付通道接口");
|
||||
}
|
||||
|
||||
if(!paymentService.isSupport(wayCode)){
|
||||
if(!paymentService.isSupport(mchPayPassage.getWayCode())){
|
||||
throw new BizException("接口不支持该支付方式");
|
||||
}
|
||||
|
||||
|
|
@ -306,7 +322,9 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
if(ChannelRetMsg.ChannelState.CONFIRM_SUCCESS == channelRetMsg.getChannelState()) {
|
||||
|
||||
this.updateInitOrderStateThrowException(PayOrder.STATE_SUCCESS, payOrder, channelRetMsg);
|
||||
payMchNotifyService.payOrderNotify(payOrder);
|
||||
|
||||
//订单支付成功,其他业务逻辑
|
||||
payOrderProcessService.confirmSuccess(payOrder);
|
||||
|
||||
//明确失败
|
||||
}else if(ChannelRetMsg.ChannelState.CONFIRM_FAIL == channelRetMsg.getChannelState()) {
|
||||
|
|
@ -346,7 +364,7 @@ public abstract class AbstractPayOrderController extends ApiController {
|
|||
payOrder.setErrMsg(channelRetMsg.getChannelErrMsg());
|
||||
|
||||
|
||||
boolean isSuccess = payOrderService.updateInit2Ing(payOrder.getPayOrderId(), payOrder.getIfCode(), payOrder.getWayCode());
|
||||
boolean isSuccess = payOrderService.updateInit2Ing(payOrder.getPayOrderId(), payOrder);
|
||||
if(!isSuccess){
|
||||
throw new BizException("更新订单异常!");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ 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.pay.service.PayMchNotifyService;
|
||||
import com.jeequan.jeepay.pay.service.PayOrderProcessService;
|
||||
import com.jeequan.jeepay.service.impl.PayOrderService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
|
@ -52,6 +53,7 @@ public class ChannelNoticeController extends AbstractCtrl {
|
|||
@Autowired private PayOrderService payOrderService;
|
||||
@Autowired private ConfigContextService configContextService;
|
||||
@Autowired private PayMchNotifyService payMchNotifyService;
|
||||
@Autowired private PayOrderProcessService payOrderProcessService;
|
||||
|
||||
/** 同步通知入口 **/
|
||||
@RequestMapping(value= {"/api/pay/return/{ifCode}", "/api/pay/return/{ifCode}/{payOrderId}"})
|
||||
|
|
@ -233,10 +235,9 @@ public class ChannelNoticeController extends AbstractCtrl {
|
|||
return payNotifyService.doNotifyOrderStateUpdateFail(request);
|
||||
}
|
||||
|
||||
//订单支付成功 需要MQ通知下游商户
|
||||
//订单支付成功 其他业务逻辑
|
||||
if(notifyResult.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS){
|
||||
payOrder.setState(PayOrder.STATE_SUCCESS);
|
||||
payMchNotifyService.payOrderNotify(payOrder);
|
||||
payOrderProcessService.confirmSuccess(payOrder);
|
||||
}
|
||||
|
||||
log.info("===== {}, 订单通知完成。 payOrderId={}, parseState = {} =====", logPrefix, payOrderId, notifyResult.getChannelState());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
* 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.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.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.service.ConfigContextService;
|
||||
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.List;
|
||||
|
||||
/**
|
||||
* 接收MQ消息
|
||||
* 业务: 支付订单分账处理逻辑
|
||||
* @author terrfly
|
||||
* @site https://www.jeequan.com
|
||||
* @date 2021/8/22 8:23
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PayOrderDivisionMQReceiver implements PayOrderDivisionMQ.IMQReceiver {
|
||||
|
||||
@Autowired
|
||||
private PayOrderService payOrderService;
|
||||
@Autowired
|
||||
private MchDivisionReceiverService mchDivisionReceiverService;
|
||||
@Autowired
|
||||
private PayOrderDivisionRecordService payOrderDivisionRecordService;
|
||||
@Autowired
|
||||
private ConfigContextService configContextService;
|
||||
|
||||
@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 = queryReceiver(payOrder, payload.getReceiverList());
|
||||
|
||||
//得到全部分账比例 (所有待分账账号的分账比例总和)
|
||||
BigDecimal allDivisionProfit = BigDecimal.ZERO;
|
||||
for (MchDivisionReceiver receiver : allReceiver) {
|
||||
allDivisionProfit = allDivisionProfit.add(receiver.getDivisionProfit());
|
||||
}
|
||||
|
||||
//剩余待分账金额 (用作最后一个分账账号的 计算, 避免出现分账金额超出最大)
|
||||
Long subDivisionAmount = AmountUtil.calPercentageFee(payOrder.getMchIncomeAmount(), allDivisionProfit);
|
||||
|
||||
List<PayOrderDivisionRecord> recordList = new ArrayList<>();
|
||||
|
||||
//计算订单分账金额, 并插入到记录表
|
||||
|
||||
String batchOrderId = SeqKit.genDivisionBatchId();
|
||||
|
||||
for (MchDivisionReceiver receiver : allReceiver) {
|
||||
|
||||
PayOrderDivisionRecord record = genRecord(batchOrderId, payOrder, receiver, 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("通道无此分账接口");
|
||||
}
|
||||
|
||||
divisionService.singleDivision(recordList, configContextService.getMchAppConfigContext(payOrder.getMchNo(), payOrder.getAppId()));
|
||||
|
||||
if(true) {
|
||||
|
||||
//分账成功
|
||||
|
||||
}else{
|
||||
//分账失败
|
||||
}
|
||||
|
||||
} catch (BizException e) {
|
||||
log.error("{}, 调用分账接口异常, {}", logPrefix, e.getMessage());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 调用分账接口异常", logPrefix, e);
|
||||
|
||||
//分账失败
|
||||
|
||||
}
|
||||
|
||||
//更新 支付订单主表状态 分账任务已结束。
|
||||
payOrderService.update(new LambdaUpdateWrapper<PayOrder>()
|
||||
.set(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_FINISH)
|
||||
.eq(PayOrder::getPayOrderId, payload.getPayOrderId())
|
||||
.eq(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_ING)
|
||||
);
|
||||
|
||||
}catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 生成对象信息 **/
|
||||
private PayOrderDivisionRecord genRecord(String batchOrderId, PayOrder payOrder, MchDivisionReceiver receiver, 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(payOrder.getMchIncomeAmount()); // 订单实际分账金额, 单位:分(订单金额 - 商户手续费 - 已退款金额) //TODO 实际计算金额
|
||||
record.setBatchOrderId(batchOrderId); //系统分账批次号
|
||||
record.setState(MchDivisionReceiver.STATE_WAIT); //状态: 待分账
|
||||
record.setReceiverId(receiver.getReceiverId());
|
||||
record.setReceiverGroupId(receiver.getReceiverGroupId());
|
||||
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()));
|
||||
}
|
||||
|
||||
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); // 可用状态的账号
|
||||
|
||||
//全部分账账号
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import com.jeequan.jeepay.pay.rqrs.AbstractMchAppRQ;
|
|||
import com.jeequan.jeepay.pay.rqrs.payorder.payway.*;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
|
|
@ -84,6 +85,9 @@ public class UnifiedOrderRQ extends AbstractMchAppRQ {
|
|||
/** 商户扩展参数 **/
|
||||
private String extParam;
|
||||
|
||||
/** 分账模式: 0-该笔订单不允许分账, 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额) **/
|
||||
@Range(min = 0, max = 2, message = "分账模式设置值有误")
|
||||
private Byte divisionMode;
|
||||
|
||||
/** 返回真实的bizRQ **/
|
||||
public UnifiedOrderRQ buildBizRQ(){
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ public class ChannelOrderReissueService {
|
|||
@Autowired private ConfigContextService configContextService;
|
||||
@Autowired private PayOrderService payOrderService;
|
||||
@Autowired private RefundOrderService refundOrderService;
|
||||
@Autowired private PayOrderProcessService payOrderProcessService;
|
||||
@Autowired private PayMchNotifyService payMchNotifyService;
|
||||
|
||||
|
||||
|
|
@ -78,11 +79,8 @@ public class ChannelOrderReissueService {
|
|||
if(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_SUCCESS) {
|
||||
if (payOrderService.updateIng2Success(payOrderId, channelRetMsg.getChannelOrderId())) {
|
||||
|
||||
// 通知商户系统
|
||||
if(StringUtils.isNotEmpty(payOrder.getNotifyUrl())){
|
||||
payMchNotifyService.payOrderNotify(payOrderService.getById(payOrderId));
|
||||
}
|
||||
|
||||
//订单支付成功,其他业务逻辑
|
||||
payOrderProcessService.confirmSuccess(payOrder);
|
||||
}
|
||||
}else if(channelRetMsg.getChannelState() == ChannelRetMsg.ChannelState.CONFIRM_FAIL){ //确认失败
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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.update.LambdaUpdateWrapper;
|
||||
import com.jeequan.jeepay.components.mq.model.PayOrderDivisionMQ;
|
||||
import com.jeequan.jeepay.components.mq.vender.IMQSender;
|
||||
import com.jeequan.jeepay.core.entity.PayOrder;
|
||||
import com.jeequan.jeepay.service.impl.PayOrderService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/***
|
||||
* 订单处理通用逻辑
|
||||
*
|
||||
* @author terrfly
|
||||
* @site https://www.jeepay.vip
|
||||
* @date 2021/8/22 16:50
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class PayOrderProcessService {
|
||||
|
||||
|
||||
@Autowired private PayOrderService payOrderService;
|
||||
@Autowired private PayMchNotifyService payMchNotifyService;
|
||||
@Autowired private IMQSender mqSender;
|
||||
|
||||
/** 明确成功的处理逻辑(除更新订单其他业务) **/
|
||||
public void confirmSuccess(PayOrder payOrder){
|
||||
|
||||
//设置订单状态
|
||||
payOrder.setState(PayOrder.STATE_SUCCESS);
|
||||
|
||||
//自动分账 处理逻辑, 不影响主订单任务
|
||||
this.updatePayOrderAutoDivision(payOrder);
|
||||
|
||||
//发送商户通知
|
||||
payMchNotifyService.payOrderNotify(payOrder);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 更新订单自动分账业务 **/
|
||||
private void updatePayOrderAutoDivision(PayOrder payOrder){
|
||||
|
||||
try {
|
||||
|
||||
//默认不分账 || 其他非【自动分账】逻辑时, 不处理
|
||||
if(payOrder == null || payOrder.getDivisionMode() == null || payOrder.getDivisionMode() != PayOrder.DIVISION_MODE_AUTO){
|
||||
return ;
|
||||
}
|
||||
|
||||
//更新订单表分账状态为: 等待分账任务处理
|
||||
boolean updDivisionState = payOrderService.update(new LambdaUpdateWrapper<PayOrder>()
|
||||
.set(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_WAIT_TASK)
|
||||
.eq(PayOrder::getPayOrderId, payOrder.getPayOrderId())
|
||||
.eq(PayOrder::getDivisionState, PayOrder.DIVISION_STATE_UNHAPPEN)
|
||||
);
|
||||
|
||||
if(updDivisionState){
|
||||
//推送到分账MQ
|
||||
mqSender.send(PayOrderDivisionMQ.build(payOrder.getPayOrderId(), null), 1); //1分钟后执行
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("订单[{}]自动分账逻辑异常:", payOrder.getPayOrderId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.jeequan.jeepay.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.jeequan.jeepay.core.entity.MchDivisionReceiver;
|
||||
import com.jeequan.jeepay.service.mapper.MchDivisionReceiverMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 商户分账接收者账号绑定关系表 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author [mybatis plus generator]
|
||||
* @since 2021-08-19
|
||||
*/
|
||||
@Service
|
||||
public class MchDivisionReceiverService extends ServiceImpl<MchDivisionReceiverMapper, MchDivisionReceiver> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.jeequan.jeepay.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord;
|
||||
import com.jeequan.jeepay.service.mapper.PayOrderDivisionRecordMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 分账记录表 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author [mybatis plus generator]
|
||||
* @since 2021-08-19
|
||||
*/
|
||||
@Service
|
||||
public class PayOrderDivisionRecordService extends ServiceImpl<PayOrderDivisionRecordMapper, PayOrderDivisionRecord> {
|
||||
|
||||
}
|
||||
|
|
@ -55,12 +55,17 @@ public class PayOrderService extends ServiceImpl<PayOrderMapper, PayOrder> {
|
|||
@Autowired private PayWayMapper payWayMapper;
|
||||
|
||||
/** 更新订单状态 【订单生成】 --》 【支付中】 **/
|
||||
public boolean updateInit2Ing(String payOrderId, String ifCode, String wayCode){
|
||||
public boolean updateInit2Ing(String payOrderId, PayOrder payOrder){
|
||||
|
||||
PayOrder updateRecord = new PayOrder();
|
||||
updateRecord.setState(PayOrder.STATE_ING);
|
||||
updateRecord.setIfCode(ifCode);
|
||||
updateRecord.setWayCode(wayCode);
|
||||
|
||||
//同时更新, 未确定 --》 已确定的其他信息。 如支付接口的确认、 费率的计算。
|
||||
updateRecord.setIfCode(payOrder.getIfCode());
|
||||
updateRecord.setWayCode(payOrder.getWayCode());
|
||||
updateRecord.setMchFeeRate(payOrder.getMchFeeRate());
|
||||
updateRecord.setMchFeeAmount(payOrder.getMchFeeAmount());
|
||||
updateRecord.setMchIncomeAmount(payOrder.getMchIncomeAmount());
|
||||
|
||||
return update(updateRecord, new LambdaUpdateWrapper<PayOrder>()
|
||||
.eq(PayOrder::getPayOrderId, payOrderId).eq(PayOrder::getState, PayOrder.STATE_INIT));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.jeequan.jeepay.service.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.jeequan.jeepay.core.entity.MchDivisionReceiver;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 商户分账接收者账号绑定关系表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author [mybatis plus generator]
|
||||
* @since 2021-08-19
|
||||
*/
|
||||
public interface MchDivisionReceiverMapper extends BaseMapper<MchDivisionReceiver> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.jeequan.jeepay.service.mapper.MchDivisionReceiverMapper">
|
||||
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="com.jeequan.jeepay.core.entity.MchDivisionReceiver">
|
||||
<id column="receiver_id" property="receiverId" />
|
||||
<result column="receiver_group_id" property="receiverGroupId" />
|
||||
<result column="receiver_name" property="receiverName" />
|
||||
<result column="mch_no" property="mchNo" />
|
||||
<result column="isv_no" property="isvNo" />
|
||||
<result column="app_id" property="appId" />
|
||||
<result column="if_code" property="ifCode" />
|
||||
<result column="acc_type" property="accType" />
|
||||
<result column="acc_no" property="accNo" />
|
||||
<result column="acc_name" property="accName" />
|
||||
<result column="relation_type" property="relationType" />
|
||||
<result column="relation_type_name" property="relationTypeName" />
|
||||
<result column="division_profit" property="divisionProfit" />
|
||||
<result column="state" property="state" />
|
||||
<result column="channel_bind_state" property="channelBindState" />
|
||||
<result column="channel_bind_result" property="channelBindResult" />
|
||||
<result column="channel_ext_info" property="channelExtInfo" />
|
||||
<result column="bind_success_time" property="bindSuccessTime" />
|
||||
<result column="created_at" property="createdAt" />
|
||||
<result column="updated_at" property="updatedAt" />
|
||||
</resultMap>
|
||||
|
||||
</mapper>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.jeequan.jeepay.service.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.jeequan.jeepay.core.entity.PayOrderDivisionRecord;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 分账记录表 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author [mybatis plus generator]
|
||||
* @since 2021-08-19
|
||||
*/
|
||||
public interface PayOrderDivisionRecordMapper extends BaseMapper<PayOrderDivisionRecord> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.jeequan.jeepay.service.mapper.PayOrderDivisionRecordMapper">
|
||||
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="com.jeequan.jeepay.core.entity.PayOrderDivisionRecord">
|
||||
<id column="record_id" property="recordId" />
|
||||
<result column="mch_no" property="mchNo" />
|
||||
<result column="isv_no" property="isvNo" />
|
||||
<result column="app_id" property="appId" />
|
||||
<result column="mch_name" property="mchName" />
|
||||
<result column="mch_type" property="mchType" />
|
||||
<result column="if_code" property="ifCode" />
|
||||
<result column="pay_order_id" property="payOrderId" />
|
||||
<result column="pay_order_channel_order_no" property="payOrderChannelOrderNo" />
|
||||
<result column="pay_order_amount" property="payOrderAmount" />
|
||||
<result column="pay_order_division_amount" property="payOrderDivisionAmount" />
|
||||
<result column="batch_order_id" property="batchOrderId" />
|
||||
<result column="channel_batch_order_id" property="channelBatchOrderId" />
|
||||
<result column="state" property="state" />
|
||||
<result column="channel_resp_result" property="channelRespResult" />
|
||||
<result column="receiver_id" property="receiverId" />
|
||||
<result column="receiver_group_id" property="receiverGroupId" />
|
||||
<result column="acc_type" property="accType" />
|
||||
<result column="acc_no" property="accNo" />
|
||||
<result column="acc_name" property="accName" />
|
||||
<result column="relation_type" property="relationType" />
|
||||
<result column="relation_type_name" property="relationTypeName" />
|
||||
<result column="division_profit" property="divisionProfit" />
|
||||
<result column="cal_division_amount" property="calDivisionAmount" />
|
||||
<result column="created_at" property="createdAt" />
|
||||
<result column="updated_at" property="updatedAt" />
|
||||
</resultMap>
|
||||
|
||||
</mapper>
|
||||
|
|
@ -14,6 +14,9 @@
|
|||
<result column="if_code" property="ifCode" />
|
||||
<result column="way_code" property="wayCode" />
|
||||
<result column="amount" property="amount" />
|
||||
<result column="mch_fee_rate" property="mchFeeRate" />
|
||||
<result column="mch_fee_amount" property="mchFeeAmount" />
|
||||
<result column="mch_income_amount" property="mchIncomeAmount" />
|
||||
<result column="currency" property="currency" />
|
||||
<result column="state" property="state" />
|
||||
<result column="notify_state" property="notifyState" />
|
||||
|
|
@ -26,8 +29,9 @@
|
|||
<result column="refund_state" property="refundState" />
|
||||
<result column="refund_times" property="refundTimes" />
|
||||
<result column="refund_amount" property="refundAmount" />
|
||||
<result column="division_flag" property="divisionFlag" />
|
||||
<result column="division_time" property="divisionTime" />
|
||||
<result column="division_mode" property="divisionMode" />
|
||||
<result column="division_state" property="divisionState" />
|
||||
<result column="division_last_time" property="divisionLastTime" />
|
||||
<result column="err_code" property="errCode" />
|
||||
<result column="err_msg" property="errMsg" />
|
||||
<result column="ext_param" property="extParam" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue