Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
jinli gu
Jeepay
Commits
ad2afc15
Commit
ad2afc15
authored
Aug 22, 2021
by
terrfly
Browse files
分账第一卷: 分账表结构设计; 分账MQ, 入库操作。
parent
07212404
Changes
31
Hide whitespace changes
Inline
Side-by-side
docs/sql/init.sql
View file @
ad2afc15
...
...
@@ -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
↑↑↑↑↑↑↑↑↑↑
#####
...
...
docs/sql/patch.sql
View file @
ad2afc15
...
...
@@ -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 分账的两张表
##
-- ++++ ++++
jeepay-components/jeepay-components-mq/src/main/java/com/jeequan/jeepay/components/mq/model/PayOrderDivisionMQ.java
0 → 100644
View file @
ad2afc15
/*
* 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
;
}
}
jeepay-components/jeepay-components-mq/src/main/java/com/jeequan/jeepay/components/mq/vender/activemq/receive/PayOrderDivisionActiveMQReceiver.java
0 → 100644
View file @
ad2afc15
/*
* 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
));
}
}
jeepay-components/jeepay-components-mq/src/main/java/com/jeequan/jeepay/components/mq/vender/rabbitmq/receive/PayOrderDivisionRabbitMQReceiver.java
0 → 100644
View file @
ad2afc15
/*
* 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
));
}
}
jeepay-components/jeepay-components-mq/src/main/java/com/jeequan/jeepay/components/mq/vender/rocketmq/receive/PayOrderDivisionRocketMQReceiver.java
0 → 100644
View file @
ad2afc15
/*
* 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
);
}
}
jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/MchDivisionReceiver.java
0 → 100644
View file @
ad2afc15
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
;
}
jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/PayOrder.java
View file @
ad2afc15
...
...
@@ -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
division
Flag
;
private
Byte
division
Mode
;
/**
*
预计分账发起时间
*
订单分账状态: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
;
}
jeepay-core/src/main/java/com/jeequan/jeepay/core/entity/PayOrderDivisionRecord.java
0 → 100644
View file @
ad2afc15
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
;
}
jeepay-core/src/main/java/com/jeequan/jeepay/core/utils/AmountUtil.java
View file @
ad2afc15
...
...
@@ -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
();
}
}
jeepay-core/src/main/java/com/jeequan/jeepay/core/utils/SeqKit.java
View file @
ad2afc15
...
...
@@ -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
);
}
}
jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IChannelNoticeService.java
View file @
ad2afc15
...
...
@@ -38,7 +38,7 @@ public interface IChannelNoticeService {
DO_NOTIFY
//异步回调
}
/* 获取到接口code **/
/*
*
获取到接口code **/
String
getIfCode
();
/** 解析参数: 订单号 和 请求参数
...
...
jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IChannelUserService.java
View file @
ad2afc15
...
...
@@ -25,7 +25,7 @@ import com.jeequan.jeepay.pay.model.MchAppConfigContext;
*/
public
interface
IChannelUserService
{
/* 获取到接口code **/
/*
*
获取到接口code **/
String
getIfCode
();
/** 获取重定向地址 **/
...
...
jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IDivisionService.java
0 → 100644
View file @
ad2afc15
/*
* 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
);
}
jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IPaymentService.java
View file @
ad2afc15
...
...
@@ -29,7 +29,7 @@ import com.jeequan.jeepay.pay.model.MchAppConfigContext;
*/
public
interface
IPaymentService
{
/* 获取到接口code **/
/*
*
获取到接口code **/
String
getIfCode
();
/** 是否支持该支付方式 */
...
...
jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IRefundService.java
View file @
ad2afc15
...
...
@@ -30,7 +30,7 @@ import com.jeequan.jeepay.pay.rqrs.refund.RefundOrderRQ;
*/
public
interface
IRefundService
{
/* 获取到接口code **/
/*
*
获取到接口code **/
String
getIfCode
();
/** 前置检查如参数等信息是否符合要求, 返回错误信息或直接抛出异常即可 */
...
...
jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/wxpay/WxpayDivisionService.java
0 → 100644
View file @
ad2afc15
/*
* 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
;
}
}
jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java
View file @
ad2afc15
...
...
@@ -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
Pay
MchNotifyService
payMchNotify
Service
;
@Autowired
private
Pay
OrderProcessService
payOrderProcess
Service
;
@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
,
wayCod
e
);
IPaymentService
paymentService
=
checkMchWayCodeAndGetService
(
mchAppConfigContext
,
mchPayPassag
e
);
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
(
w
ayCode
)){
if
(!
paymentService
.
isSupport
(
mchPayPassage
.
getW
ayCode
()
)){
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
(
"更新订单异常!"
);
}
...
...
jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/ChannelNoticeController.java
View file @
ad2afc15
...
...
@@ -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
());
...
...
jeepay-payment/src/main/java/com/jeequan/jeepay/pay/mq/PayOrderDivisionMQReceiver.java
0 → 100644
View file @
ad2afc15
/*
* 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
;
}
}
Prev
1
2
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment